import type { Logger, TokenUsage } from "@skaile/workspaces/types"; import { type AgentConfig, AgentDriver, type AgentError } from "../types.js"; /** * Shared base for one-shot **subprocess CLI** agent backends (Gemini, Qwen, * OpenCode, Goose, Continue). * * These backends are neither persistent JSON-RPC subprocesses (`omp`) nor * in-process SDKs (`claude-sdk`, `codex`). In headless mode each is invoked * **once per turn** — `prompt()` spawns the CLI fresh, a `readline` loop maps * newline-delimited JSON on stdout to the unified {@link AgentEvent} stream, and * **process exit marks the end of the turn**. Multi-turn continuity comes from * capturing a backend session id (emitted as `session_info`) and replaying it * via a resume flag on the next spawn. * * Subclasses implement {@link buildArgs}, {@link buildEnv}, and either * {@link handleJson} (streaming JSONL, the default) or {@link handleBuffered} * (`mode = "buffered"`, for CLIs that print a single JSON blob at the end). * * **Terminal-sequence invariant.** Subclasses MUST NOT emit `agent_end`. The * base emits exactly one `result` (synthesizing a success result when the * stream produced none) followed by a single `agent_end` on clean exit, or an * `error` + `agent_end` on crash. This keeps the turn-completion contract * (`prompt()` resolves after `agent_end`) identical across every CLI driver. * * @docLink packages/bridge/drivers#subprocess-cli */ export declare abstract class SubprocessCliDriver extends AgentDriver { /** Per-driver logger, constructed once by the subclass. */ protected abstract readonly log: Logger; /** Default binary name spawned in production (e.g. `"gemini"`). */ protected abstract readonly defaultBin: string; /** Human product name used in error messages / hints (e.g. `"Gemini CLI"`). */ protected abstract readonly productName: string; /** * Test-seam env prefix. `_BRIDGE_BIN` overrides the spawn binary and * `_BRIDGE_PREARGS` (space-delimited) supplies leading argv — used by * the fake-CLI harness to redirect spawn without monkey-patching * `child_process`. Both are ignored in production. */ protected abstract readonly envPrefix: string; /** `"jsonl"` streams events line-by-line; `"buffered"` parses all stdout once on exit. */ protected readonly mode: "jsonl" | "buffered"; protected config: AgentConfig; private proc; private rl; private stderrBuffer; private stdoutBuffer; private started; private prevText; private activeAssistant; private pendingToolResults; private sawResult; private aborting; private turnResolve; private turnReject; protected _sessionId?: string; protected _lastTokens: TokenUsage | null; constructor(config: AgentConfig); get runtimeSessionId(): string | undefined; getModel(): string | undefined; getTokenUsage(): TokenUsage | null; reconfigure(patch: Partial>): void; /** * No-op for one-shot CLIs — there is no long-lived process to spawn until the * first prompt. Kept so the runner's `start()` → `prompt()` ordering works * uniformly; binary-presence failures surface as a `config` error on the * first spawn (see {@link classifyProcessError}). */ start(): Promise; get isRunning(): boolean; /** * Sends a user message to the CLI and resolves after the turn completes * (`agent_end`). Spawns a fresh child per call; rejects if the child exits * non-zero before emitting a result, so the caller never hangs. */ prompt(message: string): Promise; /** Out-of-band abort: terminates the current child. Driver stays usable for the next prompt. */ abort(): Promise; kill(): void; resetSession(): Promise; /** Build the CLI argv (binary excluded) for one turn. `resumeId` is the captured session id, if any. */ protected abstract buildArgs(message: string, resumeId: string | undefined): string[]; /** Build the child-process env (explicit allowlist — see {@link baseEnv}). */ protected abstract buildEnv(): Record; /** Map one parsed stdout JSON object to events via the `push*` helpers. Streaming (`jsonl`) mode only. */ protected handleJson(_json: unknown): void; /** Parse the full stdout blob once on clean exit. `buffered` mode only. */ protected handleBuffered(_raw: string): void; /** Install-failure hint surfaced when the binary is missing. */ protected abstract installHint(): string; /** * Classify a non-zero exit into an {@link AgentError}, or `null` to treat the * exit as success (e.g. a turn-limit code where a result was already * emitted). Default: code `0` / clean signal → success; otherwise `process`. */ protected classifyExit(code: number | null, signal: NodeJS.Signals | null): AgentError | null; /** Prepend `systemPrompt` to the user message — uniform across CLIs lacking a reliable system flag. */ protected composePrompt(message: string): string; protected pushAssistantDelta(fullText: string): void; protected pushAssistantEnd(fullText?: string): void; protected pushToolCall(name: string, input: unknown): void; protected pushToolResult(name: string, data: unknown, isError?: boolean): void; private flushToolResults; protected pushResult(opts: { tokens?: TokenUsage | null; costUsd?: number; summary?: string; subtype?: string; }): void; protected pushError(detail: AgentError, fatal?: boolean): void; protected captureSession(id?: string, file?: string): void; /** Shared keyword classification + per-driver hint copy (mirrors omp/codex). */ protected classifyMessage(message: string): AgentError; /** * Explicit env whitelist forwarded to the child. Omits anything provisioned * by `provision_secrets` so user credentials stay out of the subprocess * environment. `config.env`, when supplied, fully overrides the whitelist. * Always passes through the driver's test-seam vars and `FAKE_CLI_*`. */ protected baseEnv(extraKeys?: string[]): Record; private onStdoutLine; private onExit; private settleResolve; private settleReject; private cleanup; private classifyProcessError; } /** Pull assistant text out of a CLI event, trying the common field names. */ export declare function extractText(json: any): string; /** Map a CLI usage/stats object onto {@link TokenUsage}, trying common field names. */ export declare function extractTokens(json: any): TokenUsage | null; /** Pull a per-turn USD cost from a CLI result object, or `undefined`. */ export declare function extractCost(json: any): number | undefined; /** Map known `apiKeys` provider names onto the env var names provider CLIs expect. */ export declare function mapApiKeysToEnv(apiKeys: Record | undefined): Record; //# sourceMappingURL=_subprocess-cli.d.ts.map