{"version":3,"file":"wire.mjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/wire.ts"],"sourcesContent":["/**\n * The normalized adapter↔wrapper protocol shared by every CLI-harness LLM battery.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/wire\n *\n * @remarks\n * Zero imports by design: this module is the seam between `adapter.ts` (which runs in the ADK\n * process and imports ADK barrels freely) and `wrapper.ts` (which runs as a separate spawned\n * process and must import nothing from `@nhtio/adk/*`). Depending on either side would break the\n * boundary, so this file depends on neither.\n *\n * The wrapper↔adapter protocol itself is deliberately harness-agnostic — `WrapperRunCommand` /\n * `WrapperEvent` name no Claude-Code-specific concept — so a future Codex-CLI or Pi-agent battery\n * can reuse this exact module, writing only its own wrapper.\n */\n\n/** One entry in a `--json-schema`/`--effort`-style `extraArgs` escape hatch. */\nexport interface ClaudeCodeCliExtraArg {\n  /** The exact CLI flag spelling. Restricted to a small, deliberately-chosen allowlist. */\n  flag: '--effort' | '--agent' | '--betas' | '--json-schema' | '--name' | '--prompt-suggestions'\n  /**\n   * The flag's value. Required for every flag except `--prompt-suggestions` (optional, matching\n   * the CLI's own `[value]` bracket syntax). A plain `string` for every flag except `--betas`,\n   * which accepts `string[]` (matching its own `<betas...>` variadic arity). Every individual\n   * value string, in every position, must not start with `-` — this is what makes it structurally\n   * impossible for a value to be interpreted by the CLI's own parser as a separate flag.\n   */\n  value?: string | string[]\n}\n\n/** A bridged ADK tool's JSON-Schema-rendered description, as exposed to the CLI over MCP. */\nexport interface WrapperBridgedTool {\n  /** The tool's raw name — matches `ctx.tools.visible()`, NOT the `mcp__<server>__<name>` permission spelling. */\n  name: string\n  /** Human/model-facing description. */\n  description: string\n  /** Plain JSON-Schema-shaped input schema (never a Zod schema — see Decision F in the design). */\n  inputSchema: Record<string, unknown>\n}\n\n/** Explicit auth credential to forward to the grandchild's environment. Exactly one of the two fields is set. */\nexport interface WrapperAuth {\n  /** Forwarded as `ANTHROPIC_API_KEY`. */\n  apiKey?: string\n  /** Forwarded as `ANTHROPIC_AUTH_TOKEN`. */\n  authToken?: string\n  /** Forwarded as `ANTHROPIC_BASE_URL`. */\n  baseUrl?: string\n}\n\n/**\n * The one command `adapter.ts` sends per dispatch iteration, immediately after the wrapper's\n * `ready` event arrives. Exactly one `run` command is accepted per wrapper process lifetime — the\n * wrapper is spawned fresh per dispatch iteration, so there is no multi-run session.\n */\nexport interface WrapperRunCommand {\n  /** Discriminant for the {@link WrapperCommand} union. */\n  type: 'run'\n  /** The fully-rendered history, as one `-p` positional prompt string. */\n  prompt: string\n  /** Forwarded verbatim to `--append-system-prompt`, when set. */\n  appendSystemPrompt?: string\n  /** The model identifier, forwarded to `--model`. */\n  model?: string\n  /** Working directory for the grandchild, forwarded to `--cwd`-equivalent spawn option. */\n  cwd?: string\n  /** Additional directories to allow tool access to, forwarded to `--add-dir`. */\n  addDir?: string[]\n  /**\n   * The exact MCP-bridged tool names the grandchild is allowed to call, ALREADY filtered by the\n   * adapter to exclude `disallowedTools`. Always sent, never omitted at this layer — the wrapper\n   * itself decides whether to emit `--allowedTools` (omitted entirely when this array is empty,\n   * since the flag is variadic and a bare `--allowedTools` with nothing after it would swallow the\n   * next argv token).\n   */\n  allowedTools: string[]\n  /** Forwarded to `--max-budget-usd`. */\n  maxBudgetUsd?: number\n  /** Forwarded to `--fallback-model` as one comma-joined value, never as separate argv tokens. */\n  fallbackModel?: string[]\n  /** Explicit auth credential(s) for the grandchild's environment. */\n  auth?: WrapperAuth\n  /** Mapped to `DISABLE_TELEMETRY` on the grandchild's env, never a CLI flag. */\n  disableTelemetry?: boolean\n  /** Mapped to `DISABLE_ERROR_REPORTING` on the grandchild's env, never a CLI flag. */\n  disableErrorReporting?: boolean\n  /** Mapped to `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` on the grandchild's env, never a CLI flag. */\n  disableNonessentialTraffic?: boolean\n  /** Mapped to `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT` on the grandchild's env, never a CLI flag. */\n  mcpToolIdleTimeoutMs?: number\n  /** Path to the `claude` binary to spawn. */\n  claudeBin: string\n  /** Forwarded to `--forward-subagent-text` when true. */\n  forwardSubagentText?: boolean\n  /**\n   * Governs how the adapter rendered an ADK tool-result's unsupported `Media` kind (or oversized\n   * inline `SpooledArtifact`) into the outbound `WrapperToolCallResponseCommand` — informational\n   * only, since the adapter has already applied the policy before this command is sent.\n   */\n  unsupportedResultMediaPolicy: string\n  /** The JSON-Schema-rendered subset of `ctx.tools.visible()` the wrapper exposes over MCP, already pre-filtered to exclude `disallowedTools`. */\n  bridgedTools: WrapperBridgedTool[]\n  /** Pre-validated additional argv entries, appended after every constructed flag and before the `--` prompt separator. */\n  extraArgs?: ClaudeCodeCliExtraArg[]\n}\n\n/** One MCP content block a tool-call response may carry. */\nexport type WrapperToolResultContentBlock =\n  | { type: 'text'; text: string }\n  | { type: 'image'; data: string; mimeType: string }\n\n/**\n * The adapter's answer to a `tool_call_request` — a finished `CallToolResult`-shaped payload the\n * wrapper hands straight to the CLI's MCP bridge with no further interpretation.\n */\nexport interface WrapperToolCallResponseCommand {\n  /** Discriminant for the {@link WrapperCommand} union. */\n  type: 'tool_call_response'\n  /** Correlates with the `requestId` on the originating `tool_call_request` event. */\n  requestId: string\n  /** A finished `CallToolResult`-shaped payload, handed straight to the CLI's MCP bridge. */\n  results: {\n    /** MCP content blocks to return for the call. */\n    content: WrapperToolResultContentBlock[]\n    /** Whether the tool call itself failed (as opposed to the wrapper/transport). */\n    isError?: boolean\n  }\n}\n\n/** Graceful-stop advisory sent to the wrapper (e.g. on `ctx.abortSignal` firing). */\nexport interface WrapperShutdownCommand {\n  /** Discriminant for the {@link WrapperCommand} union. */\n  type: 'shutdown'\n}\n\n/** The full adapter→wrapper command union. */\nexport type WrapperCommand =\n  | WrapperRunCommand\n  | WrapperToolCallResponseCommand\n  | WrapperShutdownCommand\n\n// ─── Wrapper → adapter events ──────────────────────────────────────────────\n\n/** The bridge's HTTP listener is bound and the wrapper is about to spawn `claude`. */\nexport interface WrapperReadyEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'ready'\n}\n\n/** Mirrors Claude's own `system/init` stream-json event. */\nexport interface WrapperInitEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'init'\n  /** The model Claude reports it initialized with. */\n  model?: string\n  /** The built-in tool names Claude reports as available (expected empty under `--tools \"\"`). */\n  tools?: string[]\n  /** Any MCP server connection errors Claude reported during its own startup handshake. */\n  mcpServerErrors?: string[]\n  /** The original, unmodified `system/init` stream-json line. */\n  raw?: unknown\n}\n\n/** A streamed chunk of assistant text or reasoning. */\nexport interface WrapperMessageDeltaEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'message_delta'\n  /** Identifier correlating deltas belonging to the same in-progress message. */\n  id: string\n  /** The incremental text chunk. */\n  delta: string\n  /** Set on the final delta for this message id. */\n  isComplete?: boolean\n}\n\n/** A streamed chunk of reasoning/thinking text. */\nexport interface WrapperThoughtDeltaEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'thought_delta'\n  /** Identifier correlating deltas belonging to the same in-progress thought. */\n  id: string\n  /** The incremental text chunk. */\n  delta: string\n  /** Set on the final delta for this thought id. */\n  isComplete?: boolean\n}\n\n/** A real ADK tool the wrapper's MCP bridge is asking the adapter to execute. */\nexport interface WrapperToolCallRequestEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'tool_call_request'\n  /** Correlates with the `requestId` the adapter must echo back on its `tool_call_response`. */\n  requestId: string\n  /** The bridged tool's raw name, matching `ctx.tools.visible()`. */\n  tool: string\n  /** The call arguments Claude supplied, as received from the MCP `CallTool` request. */\n  args: unknown\n}\n\n/** Mirrors Claude's own `system/api_retry` stream-json event. Observability only. */\nexport interface WrapperRetryEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'retry'\n  /** The retry attempt number. */\n  attempt: number\n  /** The maximum number of retries Claude will attempt. */\n  maxRetries?: number\n  /** The delay, in milliseconds, before the next retry. */\n  retryDelayMs?: number\n  /** The HTTP status code that triggered the retry. */\n  errorStatus?: number\n  /** The error message associated with the retry. */\n  error?: string\n}\n\n/** The terminal event for a dispatch iteration. */\nexport interface WrapperResultEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'result'\n  /** The final assistant-facing result text, when present. */\n  resultText?: string\n  /** Claude's own session identifier for this turn. */\n  sessionId?: string\n  /** Total cost, in USD, Claude reports for this turn. */\n  totalCostUsd?: number\n  /** Token/usage accounting Claude reports for this turn. */\n  usage?: Record<string, unknown>\n  /** Whether this turn ended in an error (e.g. `--max-turns`/`--max-budget-usd` exhaustion). */\n  isError: boolean\n  /** Claude's machine-readable reason the turn stopped. */\n  subtype?: string\n  /** Claude's own stated reason the turn stopped. */\n  stopReason?: string\n  /** The original, unmodified terminal `result` stream-json line. */\n  raw?: unknown\n}\n\n/** A wrapper-level failure (spawn error, unexpected exit, MCP bridge startup failure). */\nexport interface WrapperErrorEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'error'\n  /** Human-readable summary of the failure. */\n  message: string\n  /** Additional detail, when available (e.g. the underlying error's message). */\n  detail?: string\n}\n\n/** A generic diagnostic passthrough, never fatal. */\nexport interface WrapperLogEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'log'\n  /** Severity of the diagnostic. */\n  level: 'trace' | 'debug' | 'info' | 'warn' | 'error'\n  /** A short machine-readable category for the diagnostic (e.g. `'malformed-stream-json'`). */\n  kind: string\n  /** Human-readable message. */\n  message: string\n  /** Additional structured detail, when available. */\n  payload?: unknown\n}\n\n/**\n * The wrapper's bridge HTTP listener and `claude` grandchild have both been torn down and the\n * wrapper is about to exit.\n */\nexport interface WrapperShutdownCompleteEvent {\n  /** Discriminant for the {@link WrapperEvent} union. */\n  type: 'shutdown_complete'\n}\n\n/** The full wrapper→adapter event union. */\nexport type WrapperEvent =\n  | WrapperReadyEvent\n  | WrapperInitEvent\n  | WrapperMessageDeltaEvent\n  | WrapperThoughtDeltaEvent\n  | WrapperToolCallRequestEvent\n  | WrapperRetryEvent\n  | WrapperResultEvent\n  | WrapperErrorEvent\n  | WrapperLogEvent\n  | WrapperShutdownCompleteEvent\n\n// ─── Encoding ───────────────────────────────────────────────────────────────\n\n/** Encode a `WrapperCommand` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperCommand = (command: WrapperCommand): string =>\n  `${JSON.stringify(command)}\\n`\n\n/** Encode a `WrapperEvent` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperEvent = (event: WrapperEvent): string => `${JSON.stringify(event)}\\n`\n\n// ─── Byte-oriented NDJSON line framing ─────────────────────────────────────\n\nconst LF = 0x0a\nconst CR = 0x0d\n\n/**\n * Create an incremental, byte-oriented NDJSON line reader. Generalizes\n * `local_diffusion/protocol.ts`'s `createFrameReader` discipline (bounded memory via a hard\n * `maxLineBytes` cap enforced while consuming, fatal-UTF8-decode, malformed-line-is-non-fatal) for\n * pure JSON-per-line framing with no tag-prefixed grammar. `onLine` receives each decoded line and\n * returns the parsed value, or `undefined` for a line that failed to parse — the reader itself\n * never throws and never classifies content, it only frames bytes into lines.\n *\n * @throws RangeError if `maxLineBytes` is provided but is not a positive, finite, safe integer.\n */\nexport const createNdjsonLineReader = <T>(\n  onLine: (raw: string) => T | undefined,\n  opts?: { maxLineBytes?: number }\n): { push(chunk: Uint8Array): void; end(): void } => {\n  if (\n    opts?.maxLineBytes !== undefined &&\n    (!Number.isSafeInteger(opts.maxLineBytes) || opts.maxLineBytes < 1)\n  ) {\n    throw new RangeError(\n      `maxLineBytes must be a positive safe integer, received ${String(opts.maxLineBytes)}`\n    )\n  }\n  const cap = opts?.maxLineBytes ?? 1_048_576\n  const segments: Uint8Array[] = []\n  let pending = 0\n  let discarding = false\n  let ended = false\n\n  const resetLine = (): void => {\n    segments.length = 0\n    pending = 0\n  }\n\n  const assemble = (chunk: Uint8Array, start: number, end: number): Uint8Array => {\n    const tail = end - start\n    if (segments.length === 0) return chunk.subarray(start, end)\n    const line = new Uint8Array(pending + tail)\n    let at = 0\n    for (const seg of segments) {\n      line.set(seg, at)\n      at += seg.length\n    }\n    if (tail > 0) line.set(chunk.subarray(start, end), at)\n    return line\n  }\n\n  const decodeLine = (bytes: Uint8Array): void => {\n    let end = bytes.length\n    if (end > 0 && bytes[end - 1] === CR) end -= 1\n    if (end === 0) return\n    const slice = bytes.subarray(0, end)\n    let text: string\n    try {\n      text = new TextDecoder('utf-8', { fatal: true }).decode(slice)\n    } catch {\n      // Invalid UTF-8 — not a fatal condition for the reader; the caller cannot parse it either,\n      // so treat it exactly like a line onLine failed to parse (return undefined, no callback).\n      return\n    }\n    onLine(text)\n  }\n\n  const consume = (chunk: Uint8Array): void => {\n    let pos = 0\n    if (discarding) {\n      const nl = chunk.indexOf(LF, pos)\n      if (nl === -1) return\n      discarding = false\n      pos = nl + 1\n    }\n    while (pos < chunk.length) {\n      const nl = chunk.indexOf(LF, pos)\n      if (nl === -1) {\n        if (pending + (chunk.length - pos) > cap) {\n          resetLine()\n          discarding = true\n        } else if (chunk.length > pos) {\n          const seg = chunk.subarray(pos, chunk.length)\n          segments.push(seg)\n          pending += seg.length\n        }\n        return\n      }\n      if (pending + (nl - pos) > cap) {\n        resetLine()\n      } else {\n        const line = assemble(chunk, pos, nl)\n        resetLine()\n        decodeLine(line)\n      }\n      pos = nl + 1\n    }\n  }\n\n  return {\n    push(chunk) {\n      if (!ended) consume(chunk)\n    },\n    end() {\n      if (ended) return\n      ended = true\n      resetLine()\n      discarding = false\n    },\n  }\n}\n"],"mappings":";;AA8RA,IAAa,wBAAwB,YACnC,GAAG,KAAK,UAAU,OAAO,EAAE;;AAG7B,IAAa,sBAAsB,UAAgC,GAAG,KAAK,UAAU,KAAK,EAAE;AAI5F,IAAM,KAAK;AACX,IAAM,KAAK;;;;;;;;;;;AAYX,IAAa,0BACX,QACA,SACmD;CACnD,IACE,MAAM,iBAAiB,KAAA,MACtB,CAAC,OAAO,cAAc,KAAK,YAAY,KAAK,KAAK,eAAe,IAEjE,MAAM,IAAI,WACR,0DAA0D,OAAO,KAAK,YAAY,GACpF;CAEF,MAAM,MAAM,MAAM,gBAAgB;CAClC,MAAM,WAAyB,CAAC;CAChC,IAAI,UAAU;CACd,IAAI,aAAa;CACjB,IAAI,QAAQ;CAEZ,MAAM,kBAAwB;EAC5B,SAAS,SAAS;EAClB,UAAU;CACZ;CAEA,MAAM,YAAY,OAAmB,OAAe,QAA4B;EAC9E,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,WAAW,GAAG,OAAO,MAAM,SAAS,OAAO,GAAG;EAC3D,MAAM,OAAO,IAAI,WAAW,UAAU,IAAI;EAC1C,IAAI,KAAK;EACT,KAAK,MAAM,OAAO,UAAU;GAC1B,KAAK,IAAI,KAAK,EAAE;GAChB,MAAM,IAAI;EACZ;EACA,IAAI,OAAO,GAAG,KAAK,IAAI,MAAM,SAAS,OAAO,GAAG,GAAG,EAAE;EACrD,OAAO;CACT;CAEA,MAAM,cAAc,UAA4B;EAC9C,IAAI,MAAM,MAAM;EAChB,IAAI,MAAM,KAAK,MAAM,MAAM,OAAO,IAAI,OAAO;EAC7C,IAAI,QAAQ,GAAG;EACf,MAAM,QAAQ,MAAM,SAAS,GAAG,GAAG;EACnC,IAAI;EACJ,IAAI;GACF,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;EAC/D,QAAQ;GAGN;EACF;EACA,OAAO,IAAI;CACb;CAEA,MAAM,WAAW,UAA4B;EAC3C,IAAI,MAAM;EACV,IAAI,YAAY;GACd,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;GACf,aAAa;GACb,MAAM,KAAK;EACb;EACA,OAAO,MAAM,MAAM,QAAQ;GACzB,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;IACb,IAAI,WAAW,MAAM,SAAS,OAAO,KAAK;KACxC,UAAU;KACV,aAAa;IACf,OAAO,IAAI,MAAM,SAAS,KAAK;KAC7B,MAAM,MAAM,MAAM,SAAS,KAAK,MAAM,MAAM;KAC5C,SAAS,KAAK,GAAG;KACjB,WAAW,IAAI;IACjB;IACA;GACF;GACA,IAAI,WAAW,KAAK,OAAO,KACzB,UAAU;QACL;IACL,MAAM,OAAO,SAAS,OAAO,KAAK,EAAE;IACpC,UAAU;IACV,WAAW,IAAI;GACjB;GACA,MAAM,KAAK;EACb;CACF;CAEA,OAAO;EACL,KAAK,OAAO;GACV,IAAI,CAAC,OAAO,QAAQ,KAAK;EAC3B;EACA,MAAM;GACJ,IAAI,OAAO;GACX,QAAQ;GACR,UAAU;GACV,aAAa;EACf;CACF;AACF"}