/** * CopilotAgentRunner — AgentRunner impl backed by the GitHub Copilot CLI * (`copilot`, https://docs.github.com/en/copilot/reference/copilot-cli-reference). * * Architectural pattern: SAME as the other subprocess runners — spawn the * CLI, parse its output, normalize to AgentMessage. * * Auth: GitHub's own device/browser login flow (`copilot` handles this * itself on first run). No env vars set here. * * Streaming / liveness — WHY INCREMENTAL (#204): * This runner originally buffered ALL of copilot's stdout until the * subprocess exited before parsing anything, so any turn longer than * session.ts's 4-minute silent-stream watchdog (SILENT_SESSION_MS) yielded * zero messages in time — abort, retry, kill, circuit breaker. Same bug * class the kimi/antigravity/codex runners had (#204 audit). This runner * now parses stdout LINE BY LINE as data arrives: a liveness `tool_use` * message is yielded the moment the subprocess spawns (deterministically * winning the watchdog's first-pull race regardless of copilot's latency), * and each assistant-shaped NDJSON event is yielded as it lands rather * than accumulated until process close. Any tool-activity-shaped event * (see handleLine's `kind.startsWith('tool')` check) is forwarded as a * `tool_use` liveness message too — a wrong guess there only costs a * missed heartbeat, unlike assistant text, which still fails closed (no * text extracted) on an unrecognized shape per the note below. * * Subprocess protocol — per public docs, NOT byte-verified against a running * binary: * - Invocation: `copilot -p "" --output-format json -s * --allow-all-tools --no-ask-user [--model=]`. `-s` * (silent) suppresses the stats/decoration footer that otherwise mixes * into stdout (documented GitHub issue: without `-s`, response text can * be absent from plain stdout entirely — `--output-format json` + `-s` * is the documented workaround). `--no-ask-user` disables the CLI's * ask_user tool so a headless run can never block waiting on a question * with no human to answer it — confirmed by cross-checking a second, * independent public agentic-CLI wrapper's provider table, which lists * the identical `-s --allow-all-tools --no-ask-user` combination for * Copilot's non-interactive mode; this runner was missing `--no-ask-user` * until that cross-check. * - Output: NDJSON; assistant text arrives on `assistant.message`-shaped * events. The exact full field list isn't published, so handleLine * tolerates a couple of plausible shapes (an explicit `type`/`kind` of * 'assistant.message' or 'assistant' carrying `content`/`text`) and * fails closed (no text extracted) on anything else, rather than * guessing wrong and emitting garbage. UPDATE (#181, byte-verified * against copilot 1.0.83): the REAL shape nests the payload one level * down — `{"type":"assistant.message","data":{"content":"...", ...}}` — * which none of the guessed shapes above matched, so this runner * previously extracted NO assistant text at all. handleLine now reads * `data.content`/`data.text` first; the guessed shapes are kept as * fallbacks rather than removed (they cost nothing and other copilot * versions may differ). * - Session/resume: the closing `{"type":"result","sessionId":...}` line * names the session (verified live, copilot 1.0.88); later invocations * pass `--resume=` and send only the new prompt, and * AgentRunArgs.resume seeds it. * - Native tools: `tool.execution_start` {toolCallId, toolName, * arguments} and `tool.execution_complete` {toolCallId, success, * result.content} (verified live, 1.0.88) become matched * tool_use/tool_result pairs. * - Token usage (#181) — RESOLVED, byte-verified against copilot 1.0.83 on * 2026-09-18. Copilot CLI has a first-class `--usage-output-file ` * flag ("Write final usage statistics as JSON to the specified file"). * This runner passes a per-turn temp path and reads it back after the * subprocess exits (see parseCopilotUsage). Captured shape: * { "modelMetrics": { "": { "usage": { * "inputTokens": 30522, "outputTokens": 47, * "cacheReadTokens": 15224, "cacheWriteTokens": 15292, * "reasoningTokens": 9 } } }, ... } * `inputTokens` is TOTAL prompt tokens for the whole invocation (the * identity inputTokens === tokenDetails.input + cache_read + cache_write * held exactly on both samples) and the counts are cumulative across * every model call, not last-call-only. Deliberately NOT reported, * because copilot does not report them: `cost_usd` (copilot meters in AI * credits / premium requests, and the credit→USD rate is plan-dependent * — inventing one would poison policy.overBudgetUsd) and * `reasoningTokens` (present, but nothing states whether outputTokens * already includes it, so adding it risks double-counting). * Rejected alternatives, both inspected: stdout's final `result` event * has no token counts and its `session.usage_checkpoint` events carry * prompt/cache but no output tokens; the OTel file export * (`COPILOT_OTEL_FILE_EXPORTER_PATH`) would work but needs a histogram * parser for what this flag hands over as plain JSON. Older copilot * builds reject the flag at argv-parse time (before any spend) — * turnError makes that an explicit "update copilot" failure rather than * falling back to 0, which is the silent free lunch this issue is about. * * Org tools — FENCE PROTOCOL: same approach as the other subprocess runners. */ import type { AgentMessage, AgentRunArgs, AgentRunner } from './agent-runner.js'; export { parseCopilotEvents, parseCopilotUsage } from './copilot-runner-parse.js'; export type { CopilotStreamEvent, CopilotUsage } from './copilot-runner-types.js'; export declare class CopilotAgentRunner implements AgentRunner { private copilotBin?; constructor(copilotBin?: string | undefined); run(args: AgentRunArgs): AsyncIterable; } //# sourceMappingURL=copilot-runner.d.ts.map