105 lines
3.3 KiB
Python
105 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""VS Code/GitHub Copilot adapter for the CASAN-owned `@casan` participant.
|
|
|
|
The VS Code extension owns the request and native model call, while this thin
|
|
adapter maps participant lifecycle messages to the shared Plan-20 bridge. It
|
|
does not call a model and it does not claim to intercept built-in Copilot chat.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
_ADAPTER_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
_BRIDGE_DIR = os.path.abspath(os.path.join(_ADAPTER_DIR, "..", "..", "scripts", "python"))
|
|
if _BRIDGE_DIR not in sys.path:
|
|
sys.path.insert(0, _BRIDGE_DIR)
|
|
|
|
import agentic_bridge as bridge # noqa: E402
|
|
|
|
ADAPTER_VERSION = "20.2.0-vscode"
|
|
|
|
|
|
def emit(value, exit_code=0):
|
|
sys.stdout.write(json.dumps(value, ensure_ascii=False) + "\n")
|
|
return exit_code
|
|
|
|
|
|
def handle_begin(payload):
|
|
response = bridge.op_begin({
|
|
"op": "begin",
|
|
"client": "vscode",
|
|
"client_version": payload.get("client_version"),
|
|
"adapter_version": ADAPTER_VERSION,
|
|
"project": payload.get("project") or payload.get("cwd"),
|
|
"session": payload.get("session_id"),
|
|
"turn": payload.get("turn_id"),
|
|
"prompt": payload.get("prompt", ""),
|
|
# The explicit @casan participant owns the complete model lifecycle.
|
|
"integration_mode": "casan_owned",
|
|
})
|
|
return emit(response, 2 if response.get("decision") == "block" else 0)
|
|
|
|
|
|
def handle_finalize(payload):
|
|
response = bridge.op_finalize({
|
|
"op": "finalize",
|
|
"admission_id": payload.get("admission_id", ""),
|
|
"stop_reason": payload.get("stop_reason") or "completed",
|
|
"assistant_summary": payload.get("assistant_summary"),
|
|
})
|
|
return emit(response)
|
|
|
|
|
|
def handle_abort(payload):
|
|
response = bridge.op_abort({
|
|
"op": "abort",
|
|
"admission_id": payload.get("admission_id", ""),
|
|
"reason": payload.get("reason") or "vscode_participant_aborted",
|
|
})
|
|
return emit(response)
|
|
|
|
|
|
def handle_telemetry(payload):
|
|
response = bridge.op_telemetry({
|
|
"op": "telemetry",
|
|
"admission_id": payload.get("admission_id", ""),
|
|
"model": payload.get("model"),
|
|
"runtime_ms": payload.get("runtime_ms"),
|
|
"input_tokens": payload.get("input_tokens"),
|
|
"output_tokens": payload.get("output_tokens"),
|
|
"cost_amount": payload.get("cost_amount"),
|
|
"cost_currency": payload.get("cost_currency"),
|
|
"cost_source": payload.get("cost_source"),
|
|
})
|
|
return emit(response)
|
|
|
|
|
|
HANDLERS = {
|
|
"Begin": handle_begin,
|
|
"Finalize": handle_finalize,
|
|
"Abort": handle_abort,
|
|
"Telemetry": handle_telemetry,
|
|
}
|
|
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description="CASAN VS Code participant adapter")
|
|
parser.add_argument("--event", required=True, choices=sorted(HANDLERS))
|
|
args = parser.parse_args(argv)
|
|
try:
|
|
payload = json.loads(sys.stdin.read() or "{}")
|
|
except ValueError:
|
|
return emit({"decision": "block", "reason": "invalid_json"}, 2)
|
|
try:
|
|
return HANDLERS[args.event](payload)
|
|
except Exception as exc: # noqa: BLE001 - adapter boundary
|
|
return emit({"decision": "block", "reason": "adapter_error:%s" % exc}, 2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|