/** * OpencodeAgentRunner — AgentRunner impl backed by the opencode server. * * Architectural difference from ClaudeAgentRunner: * - Claude's `query()` runs the whole agent loop IN-PROCESS (tools execute * inside the same Node process as the daemon). That's why ClaudeAgentRunner * can register org tools (org_send, ask_human, …) directly via * createSdkMcpServer. * - opencode runs the agent loop in its OWN server process. The SDK is a * remote client: create a session, kick off a prompt, watch a shared * event stream for what happens. * * Streaming — LIVE-VERIFIED against a real opencode server (v1.18.30, * @opencode-ai/sdk 1.18.15), not just the installed .d.ts, which turned out * to be incomplete for this specific need: * - `client.session.promptAsync({ path, body })` is fire-and-forget — * `POST /session/{id}/prompt_async` returns 204 with no body. Unlike the * blocking `session.prompt()` this runner used before, there is no * response to read text or usage from; everything comes from the event * stream below. * - `client.event.subscribe()` resolves `{ stream: AsyncGenerator }` * (SSE). The two events this runner needs: * `message.part.delta` — `{sessionID, messageID, partID, field, delta}` * — the REAL per-token/per-chunk text event. This is NOT in the * installed SDK's own `Event` union type at all (only * `message.part.updated` is, whose own `delta?: string` field was * observed to always be `undefined` live — the .d.ts is stale * relative to the server's actual wire format). Confirmed live: a * real generation emits dozens of these per part. * `message.updated` — `{info: AssistantMessage}` — fires repeatedly as * a message evolves; `info.time.completed` being set (confirmed live * on both a normal completion and a provider error) is the reliable * "this assistant message is fully done" signal, at which point * `info.tokens`/`info.cost` are the final values for the round. * `message.part.updated`'s own full `part.text` snapshot (fired once * when a part is created, empty, and again as it's completed) is kept as * a checkpoint but the incremental visible stream is driven by * `message.part.delta` — same decouple-and-diff shape as * antigravity-runner.ts/qwen-rpc-runner.ts: `computeSafeChunk` picks out * what's fence-safe to reveal from the accumulating per-part raw text, * diffed against how much of that part has already been shown; the * final reconciliation at round end reveals anything the incremental * path hasn't (normally nothing). * - A message's `reasoning`-type parts also emit `message.part.delta` * events with `field:"text"` — excluded from visible output the same * way Claude's thinking blocks are, by checking the owning part's own * `type` (tracked from `message.part.updated`, which always precedes * that part's first delta). * - Because prompt() no longer blocks, the previous undici headers-timeout * workaround is less critical for the prompt call itself (which now * returns near-instantly), but the SSE connection is still a long-lived * body stream for the whole role's lifetime — the same generous * dispatcher (headersTimeout + bodyTimeout:0) is kept for it. * * Org tools (org_send, knowledge_search, …) travel over the shared FENCE * PROTOCOL (tool-fence.ts): rendered into the system prompt, parsed out of * the assistant text, executed in-process, results fed back into the same * session — same mechanism as KimiCodeAgentRunner. `parseToolCalls` needs * each text part's RAW (fence-intact) text, so that — not the fence-stripped * visible text — is what's collected per part for it, exactly as the * previous blocking-call implementation did. * * Coder mode (plan MM3, contract §4/§5): * - opencode's agent loop writes one assistant message PER STEP (a step * that calls a native tool completes with `finish: "tool-calls"` and * the next step is a new message), so a round collects every assistant * message in the session and ends on the first one that completes with * any other finish — or on `session.idle` once everything seen is done. * - `type:"tool"` parts become matched tool_use/tool_result messages * (opencode-runner-tools.ts), gated like the Claude runner's. * - Full access: permission override + `always` replies + process-group * spawn (opencode-runner-server.ts). Effort → the model's variant. * - settingSources: nothing to un-isolate — the served instance always * loads the user's opencode config and the project's opencode.json, * AGENTS.md and MCP servers; full mode overrides only `permission`. * * Non-disturbance guarantees: * - The SDK is imported dynamically; the package has no hard dependency on * @opencode-ai/sdk. Selected only via MONOMIND_RUNTIME=opencode; without * it the Claude path is byte-for-byte unchanged and run() rejects with a * clear actionable error instead of crashing at import time. */ import { type AgentMessage, type AgentRunArgs, type AgentRunner } from './agent-runner.js'; export declare class OpencodeAgentRunner implements AgentRunner { private opencodeUrl?; constructor(opencodeUrl?: string | undefined); run(args: AgentRunArgs): AsyncIterable; } //# sourceMappingURL=opencode-runner.d.ts.map