zidane 6.2.36 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +16 -5
  2. package/dist/{acp-DZNCQaTQ.js → acp-BCg03Fqc.js} +6 -6
  3. package/dist/{acp-DZNCQaTQ.js.map → acp-BCg03Fqc.js.map} +1 -1
  4. package/dist/acp-cli.js +7 -7
  5. package/dist/acp.d.ts +2 -2
  6. package/dist/acp.js +1 -1
  7. package/dist/{agent-CrPwqpLI.js → agent-DO8s7bDf.js} +168 -23
  8. package/dist/agent-DO8s7bDf.js.map +1 -0
  9. package/dist/agent.d.ts +2 -2
  10. package/dist/agent.js +4 -4
  11. package/dist/{anthropic-asnZVFS_.js → anthropic-B3hj9OMm.js} +32 -13
  12. package/dist/anthropic-B3hj9OMm.js.map +1 -0
  13. package/dist/{auth-C5YTniaq.js → auth-kqdSYLMU.js} +4 -4
  14. package/dist/{auth-C5YTniaq.js.map → auth-kqdSYLMU.js.map} +1 -1
  15. package/dist/{cache-telemetry-BhTryxFT.js → cache-telemetry-DzyfqSds.js} +87 -5
  16. package/dist/cache-telemetry-DzyfqSds.js.map +1 -0
  17. package/dist/chat/pure.d.ts +3 -3
  18. package/dist/chat.d.ts +42 -4
  19. package/dist/chat.d.ts.map +1 -1
  20. package/dist/chat.js +31 -3
  21. package/dist/chat.js.map +1 -1
  22. package/dist/context-transport-CTLvmkj8.js +84 -0
  23. package/dist/context-transport-CTLvmkj8.js.map +1 -0
  24. package/dist/contexts/daytona.d.ts +33 -4
  25. package/dist/contexts/daytona.d.ts.map +1 -1
  26. package/dist/contexts/daytona.js +136 -0
  27. package/dist/contexts/daytona.js.map +1 -1
  28. package/dist/contexts/docker.d.ts +1 -1
  29. package/dist/contexts/docker.d.ts.map +1 -1
  30. package/dist/contexts/docker.js +85 -2
  31. package/dist/contexts/docker.js.map +1 -1
  32. package/dist/contexts/e2b.d.ts +34 -4
  33. package/dist/contexts/e2b.d.ts.map +1 -1
  34. package/dist/contexts/e2b.js +92 -0
  35. package/dist/contexts/e2b.js.map +1 -1
  36. package/dist/contexts/sandbox.d.ts +1 -1
  37. package/dist/contexts/sandbox.js +4 -2
  38. package/dist/contexts/sandbox.js.map +1 -1
  39. package/dist/{contexts-o8y5B3Tm.js → contexts-DbMuUPjS.js} +92 -5
  40. package/dist/contexts-DbMuUPjS.js.map +1 -0
  41. package/dist/contexts.d.ts +4 -4
  42. package/dist/contexts.js +1 -1
  43. package/dist/eval.d.ts +1 -1
  44. package/dist/eval.js +3 -3
  45. package/dist/{exec-file-size-PXkr-MGA.js → exec-file-size-BRpoB6Io.js} +3 -32
  46. package/dist/exec-file-size-BRpoB6Io.js.map +1 -0
  47. package/dist/extensions.d.ts +1 -1
  48. package/dist/extensions.js +1 -1
  49. package/dist/headless.d.ts +1 -1
  50. package/dist/headless.js +4 -4
  51. package/dist/{index-DsPQbyUn.d.ts → index-B9jSo84a.d.ts} +2 -2
  52. package/dist/index-B9jSo84a.d.ts.map +1 -0
  53. package/dist/{index-CohO0oaX.d.ts → index-Bht3PKx6.d.ts} +5588 -5202
  54. package/dist/index-Bht3PKx6.d.ts.map +1 -0
  55. package/dist/index.d.ts +6 -6
  56. package/dist/index.js +12 -12
  57. package/dist/{login-CWwu9Sex.js → login-DRy_-rQh.js} +2 -2
  58. package/dist/{login-CWwu9Sex.js.map → login-DRy_-rQh.js.map} +1 -1
  59. package/dist/{mcp-Ct5tF4oj.js → mcp-DHKk_tLV.js} +77 -34
  60. package/dist/mcp-DHKk_tLV.js.map +1 -0
  61. package/dist/mcp.d.ts +2 -2
  62. package/dist/mcp.js +2 -2
  63. package/dist/{messages-Cpb4g_KV.js → messages-DHzFOdx_.js} +2 -2
  64. package/dist/{messages-Cpb4g_KV.js.map → messages-DHzFOdx_.js.map} +1 -1
  65. package/dist/{openai-compat-C77gOaHh.js → openai-compat-OI68avOl.js} +59 -16
  66. package/dist/openai-compat-OI68avOl.js.map +1 -0
  67. package/dist/output/stream-json.d.ts +1 -1
  68. package/dist/output/terminal.d.ts +1 -1
  69. package/dist/presets.d.ts +1 -1
  70. package/dist/presets.js +1 -1
  71. package/dist/prompt.d.ts +1 -1
  72. package/dist/prompt.js +14 -0
  73. package/dist/prompt.js.map +1 -1
  74. package/dist/providers/anthropic.d.ts +2 -2
  75. package/dist/providers/anthropic.js +2 -2
  76. package/dist/providers/arcee.d.ts +1 -1
  77. package/dist/providers/arcee.js +1 -1
  78. package/dist/providers/baseten.d.ts +1 -1
  79. package/dist/providers/baseten.js +1 -1
  80. package/dist/providers/cerebras.d.ts +1 -1
  81. package/dist/providers/cerebras.js +1 -1
  82. package/dist/providers/local.d.ts +1 -1
  83. package/dist/providers/local.js +1 -1
  84. package/dist/providers/openai-compat.d.ts +1 -1
  85. package/dist/providers/openai-compat.js +1 -1
  86. package/dist/providers/openai.d.ts +1 -1
  87. package/dist/providers/openai.js +2 -2
  88. package/dist/providers/openrouter.d.ts +1 -1
  89. package/dist/providers/openrouter.js +1 -1
  90. package/dist/providers/xai.d.ts +1 -1
  91. package/dist/providers/xai.js +1 -1
  92. package/dist/{providers-DnHPW-dL.js → providers-y_l5VkAZ.js} +3 -3
  93. package/dist/{providers-DnHPW-dL.js.map → providers-y_l5VkAZ.js.map} +1 -1
  94. package/dist/providers.d.ts +1 -1
  95. package/dist/providers.js +4 -4
  96. package/dist/{read-state-rDmfeLmz.js → read-state-CNyeMG7W.js} +7 -4
  97. package/dist/read-state-CNyeMG7W.js.map +1 -0
  98. package/dist/restate.d.ts +2 -2
  99. package/dist/restate.js +2 -2
  100. package/dist/restate.js.map +1 -1
  101. package/dist/{sandbox-CgjG2VvQ.d.ts → sandbox-DqwpKX18.d.ts} +14 -2
  102. package/dist/{sandbox-CgjG2VvQ.d.ts.map → sandbox-DqwpKX18.d.ts.map} +1 -1
  103. package/dist/session/sqlite.d.ts +1 -1
  104. package/dist/{session-DpDBnktP.js → session-DdT2xT_d.js} +2 -2
  105. package/dist/{session-DpDBnktP.js.map → session-DdT2xT_d.js.map} +1 -1
  106. package/dist/session.d.ts +1 -1
  107. package/dist/session.js +2 -2
  108. package/dist/shell-quote-BmnhZmdM.js +33 -0
  109. package/dist/shell-quote-BmnhZmdM.js.map +1 -0
  110. package/dist/skills.d.ts +1 -1
  111. package/dist/{stdio-loader-Ce68wUmM.js → stdio-DU3ljLqy.js} +8 -215
  112. package/dist/stdio-DU3ljLqy.js.map +1 -0
  113. package/dist/stdio-loader-9PnQWshg.js +217 -0
  114. package/dist/stdio-loader-9PnQWshg.js.map +1 -0
  115. package/dist/stream-queue-Cd9OJxl7.js +71 -0
  116. package/dist/stream-queue-Cd9OJxl7.js.map +1 -0
  117. package/dist/{system-prompt-BlsdjcUi.d.ts → system-prompt-MNVHatSD.d.ts} +15 -1
  118. package/dist/system-prompt-MNVHatSD.d.ts.map +1 -0
  119. package/dist/{tool-formatters-bZYz74qU.d.ts → tool-formatters-B0ZXWJTe.d.ts} +2 -2
  120. package/dist/{tool-formatters-bZYz74qU.d.ts.map → tool-formatters-B0ZXWJTe.d.ts.map} +1 -1
  121. package/dist/tools/fetch-url.d.ts +1 -1
  122. package/dist/tools/web-search.d.ts +1 -1
  123. package/dist/tools.d.ts +1 -1
  124. package/dist/tools.js +2 -2
  125. package/dist/{transcript-anchors-ZZYwCTjo.js → transcript-anchors-ChQH1fMm.js} +18 -8
  126. package/dist/transcript-anchors-ChQH1fMm.js.map +1 -0
  127. package/dist/{transcript-anchors-EBchHUMo.d.ts → transcript-anchors-yt0sGL0w.d.ts} +2 -2
  128. package/dist/{transcript-anchors-EBchHUMo.d.ts.map → transcript-anchors-yt0sGL0w.d.ts.map} +1 -1
  129. package/dist/tui.d.ts +3 -3
  130. package/dist/tui.d.ts.map +1 -1
  131. package/dist/tui.js +59 -7
  132. package/dist/tui.js.map +1 -1
  133. package/dist/{turn-operations-DygcPreU.d.ts → turn-operations-XlnBT7p9.d.ts} +3 -3
  134. package/dist/{turn-operations-DygcPreU.d.ts.map → turn-operations-XlnBT7p9.d.ts.map} +1 -1
  135. package/dist/types-BIarq1qE.js.map +1 -1
  136. package/dist/{types-BDccavwj.d.ts → types-DdZMQxfT.d.ts} +117 -2
  137. package/dist/types-DdZMQxfT.d.ts.map +1 -0
  138. package/dist/types.d.ts +3 -3
  139. package/dist/{xai-B2hIs5We.js → xai-DxdI27ds.js} +2 -2
  140. package/dist/{xai-B2hIs5We.js.map → xai-DxdI27ds.js.map} +1 -1
  141. package/docs/ARCHITECTURE.md +62 -4
  142. package/docs/CHAT.md +11 -4
  143. package/docs/RESTATE.md +1 -1
  144. package/docs/SKILL.md +21 -1
  145. package/docs/TUI.md +2 -0
  146. package/package.json +1 -1
  147. package/dist/agent-CrPwqpLI.js.map +0 -1
  148. package/dist/anthropic-asnZVFS_.js.map +0 -1
  149. package/dist/cache-telemetry-BhTryxFT.js.map +0 -1
  150. package/dist/contexts-o8y5B3Tm.js.map +0 -1
  151. package/dist/exec-file-size-PXkr-MGA.js.map +0 -1
  152. package/dist/index-CohO0oaX.d.ts.map +0 -1
  153. package/dist/index-DsPQbyUn.d.ts.map +0 -1
  154. package/dist/mcp-Ct5tF4oj.js.map +0 -1
  155. package/dist/openai-compat-C77gOaHh.js.map +0 -1
  156. package/dist/read-state-rDmfeLmz.js.map +0 -1
  157. package/dist/stdio-loader-Ce68wUmM.js.map +0 -1
  158. package/dist/system-prompt-BlsdjcUi.d.ts.map +0 -1
  159. package/dist/transcript-anchors-ZZYwCTjo.js.map +0 -1
  160. package/dist/types-BDccavwj.d.ts.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"anthropic-asnZVFS_.js","names":[],"sources":["../src/chat/anthropic-models.ts","../src/providers/anthropic.ts"],"sourcesContent":["/**\n * Anthropic models not (yet) bundled in `@earendil-works/pi-ai`'s registry.\n *\n * pi-ai ships a static price/metadata registry that lags new Anthropic\n * releases by a dep bump. Until the bump lands, list freshly-shipped models\n * here so they show in the picker, expose the reasoning knob, and get an\n * accurate cost estimate. Entries are merged ahead of pi-ai's list in\n * {@link modelsForDescriptor} and de-duped by id, so once pi-ai catches up\n * the bundled entry simply wins.\n */\n\nimport type { ModelInfo, ModelOption } from './providers'\n\n/**\n * Anthropic \"fast mode\" — `speed: \"fast\"` on the Messages API. ~2.5× output\n * tokens/sec at a per-model price multiplier. Supported on Opus 4.6 / 4.7 /\n * 4.8 / 5 only; the API errors if sent on any other model, so it's attached\n * just to those entries (see {@link FAST_MODE_OPTIONS}).\n *\n * The multiplier scales ALL rate components (input, output, and both cache\n * rates) uniformly: per Anthropic, \"prompt caching multipliers apply on top of\n * fast mode pricing\" across the full context window. Opus 5 and 4.8 are 2×\n * ($5/$25 → $10/$50); Opus 4.6 / 4.7 are a steeper 6× ($5/$25 → $30/$150).\n * See https://platform.claude.com/docs/en/build-with-claude/fast-mode.\n */\nexport const FAST_MODE_OPTION: ModelOption = {\n id: 'fast',\n label: 'Fast mode',\n description: '~2.5× faster output at premium pricing',\n costMultiplier: { input: 2, output: 2, cacheRead: 2, cacheWrite: 2 },\n}\n\nconst FAST_MODE_OPTION_LEGACY: ModelOption = {\n ...FAST_MODE_OPTION,\n description: '~2.5× faster output at 6× pricing',\n costMultiplier: { input: 6, output: 6, cacheRead: 6, cacheWrite: 6 },\n}\n\n/** Model ids that accept `speed: \"fast\"`, mapped to their fast-mode option. */\nexport const FAST_MODE_OPTIONS: Readonly<Record<string, ModelOption>> = {\n 'claude-opus-5': FAST_MODE_OPTION,\n 'claude-opus-4-8': FAST_MODE_OPTION,\n 'claude-opus-4-7': FAST_MODE_OPTION_LEGACY,\n 'claude-opus-4-6': FAST_MODE_OPTION_LEGACY,\n}\n\n/**\n * The price multiplier to apply to a model's base rates when fast mode is\n * active, or `undefined` when the model has no fast-mode pricing (i.e. doesn't\n * support it). Mirrors {@link FAST_MODE_OPTIONS} — kept as a separate accessor\n * so the cost layer can stay decoupled from the picker-facing option objects.\n */\nexport function fastModeCostMultiplier(modelId: string): ModelOption['costMultiplier'] {\n return FAST_MODE_OPTIONS[modelId]?.costMultiplier\n}\n\n/**\n * Claude Opus 5 — GA 2026-07-24. The announcement confirms $5/$25 per MTok\n * (same as Opus 4.8) and fast mode at 2×; the 1M context, 128k output, and\n * cache rates are ASSUMED from Opus 4.8 — verify against the model docs once\n * they're up. See https://www.anthropic.com/news/claude-opus-5.\n *\n * Claude Fable 5 — GA 2026-06-09. Anthropic's most capable widely released\n * model: $10/$50 per MTok, 1M context, 128k output, adaptive thinking\n * (always on), vision. No fast-mode support (Opus 4.6/4.7/4.8/5 only).\n *\n * Claude Opus 4.8 — shipped 2026-05-28. Same price/context/output as Opus 4.7\n * ($5/$25 per MTok, 1M context, 128k output), adaptive thinking, vision.\n * See https://www.anthropic.com/news/claude-opus-4-8.\n *\n * Claude Mythos 5 is deliberately absent: it's invitation-only (Project\n * Glasswing), so it would be a dead picker entry for virtually all users.\n *\n * See https://platform.claude.com/docs/en/about-claude/models/overview.\n */\nexport const ANTHROPIC_EXTRA_MODELS: readonly ModelInfo[] = [\n {\n id: 'claude-opus-5',\n name: 'Claude Opus 5',\n reasoning: true,\n input: ['text', 'image'],\n cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },\n contextWindow: 1_000_000,\n maxTokens: 128_000,\n provider: 'anthropic',\n options: [FAST_MODE_OPTION],\n },\n {\n id: 'claude-fable-5',\n name: 'Claude Fable 5',\n reasoning: true,\n input: ['text', 'image'],\n cost: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },\n contextWindow: 1_000_000,\n maxTokens: 128_000,\n provider: 'anthropic',\n },\n {\n id: 'claude-opus-4-8',\n name: 'Claude Opus 4.8',\n reasoning: true,\n input: ['text', 'image'],\n cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },\n contextWindow: 1_000_000,\n maxTokens: 128_000,\n provider: 'anthropic',\n options: [FAST_MODE_OPTION],\n },\n]\n","// Type-only imports — erased at build time. Safe when the peer dep is not installed\n// at consume time; the types only need to resolve during Zidane's own build\n// (where `@anthropic-ai/sdk` is present as a devDependency).\nimport type Anthropic from '@anthropic-ai/sdk'\nimport type { Model } from '@anthropic-ai/sdk/resources'\nimport type { Provider, ProviderCapabilities, StreamCallbacks, ToolResult, ToolSpec, TurnResult } from '.'\nimport type { ClassifiedError } from '../errors'\nimport type { PromptPart, SessionContentBlock, SessionMessage, ThinkingLevel, TurnFinishReason, TurnUsage } from '../types'\nimport type { OAuthParams } from './oauth'\nimport { fastModeCostMultiplier } from '../chat/anthropic-models'\nimport { classifyErrorPrelude, errorMessage, isRetryableHttpStatus, matchesContextExceeded, matchesToolPairingError } from '../errors'\nimport { lazyAsync } from '../lazy'\nimport { unsupportedMediaError } from '../prompt'\nimport { fromAnthropic, toAnthropic } from '../session/messages'\nimport { renderSystemForWire, splitSystemPrompt } from '../system-prompt'\nimport { fillEstimatedCost } from './cost'\nimport { extractRuntimeCredentials, readOAuthCredentials, resolveOAuthApiKey } from './oauth'\nimport { sanitizeToolSpecs } from './schema-sanitize'\n\ntype AnthropicCtor = typeof Anthropic\ntype AnthropicInstance = InstanceType<AnthropicCtor>\n\n// Lazy-loaded SDK class. `@anthropic-ai/sdk` is an OPTIONAL peer dependency: the\n// module is imported only when the anthropic provider opens a connection.\nconst loadAnthropicSdk = lazyAsync(async (): Promise<AnthropicCtor> => {\n try {\n const mod = await import('@anthropic-ai/sdk')\n return mod.default as unknown as AnthropicCtor\n }\n catch (err) {\n throw new Error(\n 'The `anthropic` provider requires the `@anthropic-ai/sdk` package, which is an optional peer dependency. '\n + 'Install it with your package manager (e.g. `bun add @anthropic-ai/sdk`).',\n err instanceof Error ? { cause: err } : undefined,\n )\n }\n})\n\n/**\n * Server-side context-management config — the body of `context_management` on\n * the Messages API. Typed loosely (Record-of-unknown) so we don't pin a specific\n * SDK schema version: the v0.90 SDK does not yet type this field, but the wire\n * format is stable behind the `context-management-2025-06-27` beta.\n *\n * See: https://docs.anthropic.com/en/docs/build-with-claude/context-management\n */\nexport interface AnthropicContextManagement {\n edits?: Array<Record<string, unknown>>\n [key: string]: unknown\n}\n\nexport interface AnthropicParams extends OAuthParams {\n defaultModel?: string\n /**\n * Optional override for the Anthropic SDK base URL. Honored end-to-end — headers and\n * routing pass through to the forwarded host. Useful for proxies (e.g. corporate\n * gateways, internal router).\n */\n baseURL?: string\n /**\n * Additional `anthropic-beta` flags to opt into. Merged with the OAuth-path\n * defaults (`claude-code-20250219`, `oauth-2025-04-20`); duplicates are\n * de-duped. Examples:\n *\n * - `'context-management-2025-06-27'` — server-side context compaction\n * (token-accurate; pair with {@link AnthropicParams.contextManagement}).\n * - `'token-efficient-tools-2026-03-28'` — terser tool_use wire format.\n * - `'interleaved-thinking-2025-05-14'` — think between tool calls within\n * one turn.\n * - `'redact-thinking-2026-02-12'` — replace large thinking blocks with\n * stubs server-side.\n * - `'prompt-caching-scope-2026-01-05'` — extended prompt-cache scope.\n *\n * Honored on both the OAuth and API-key paths.\n */\n extraBetas?: readonly string[]\n /**\n * Server-side context-management directive. Sent on the request body as\n * `context_management`. Requires the `context-management-2025-06-27` beta —\n * add it to {@link AnthropicParams.extraBetas}.\n *\n * Typed loosely so future Anthropic schema additions land without an SDK\n * bump. A typical compaction edit:\n *\n * ```ts\n * contextManagement: {\n * edits: [{\n * type: 'clear_tool_uses_20250919',\n * trigger: { type: 'input_tokens', value: 180_000 },\n * clear_at_least: { type: 'input_tokens', value: 140_000 },\n * clear_tool_inputs: ['Read', 'Bash', 'Grep'],\n * }],\n * }\n * ```\n */\n contextManagement?: AnthropicContextManagement\n /**\n * Generic pass-through for fields on the Messages API request body that the\n * SDK does not yet type. Spread into the request before the typed fields,\n * so explicit options ({@link AnthropicParams.contextManagement} and the\n * built-in fields like `model` / `tools` / `messages`) win on collision.\n *\n * Forward-compat escape hatch for new Anthropic betas — when a future flag\n * ships before zidane has a dedicated typed knob, set it here without\n * waiting on a release. Most fields will still need the matching beta in\n * {@link AnthropicParams.extraBetas}.\n */\n extraBodyParams?: Record<string, unknown>\n /**\n * Extra HTTP headers sent on every request, merged into the SDK's\n * `defaultHeaders`. Honored on both the OAuth and API-key paths.\n *\n * Use for routing/attribution headers a proxy or gateway in front of the\n * `baseURL` consumes (e.g. a target-provider selector). Reserved zidane\n * headers (`anthropic-beta`, `user-agent`, `x-app`, and the OAuth browser\n * flag) take precedence on collision.\n *\n * May be a **function** resolved per request (awaited) instead of a static\n * map — the seam for gateways minting short-lived/signed auth tokens that a\n * long, suspended, or replayed durable run outlives. The resolver runs on\n * every `stream()` / `countTokens()` call, so it can return a freshly-minted\n * token each turn (e.g. `{ authorization: \\`Bearer ${await mintWorkToken()}\\` }`)\n * without rebuilding the provider.\n */\n extraHeaders?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>)\n /**\n * How the resolved credential reaches the wire.\n *\n * - `'auto'` (default) — bearer iff the token is an Anthropic OAuth access\n * token (`sk-ant-oat…`), which also turns on the **Claude Code persona**\n * (`claude-code-*` betas, `x-app: cli`, the canonical Claude Code system\n * block). Otherwise `x-api-key`.\n * - `'bearer'` — always `Authorization: Bearer <token>`, WITHOUT the Claude\n * Code persona. This is the mode for an Anthropic-Messages-compatible\n * gateway behind {@link AnthropicParams.baseURL} that authenticates with\n * its own bearer token (Bolt's `bex_…`, an internal router, …).\n * - `'api-key'` — always `x-api-key`, no persona. Forces a proxy that\n * expects Anthropic's native header even when the token happens to look\n * like an OAuth one.\n *\n * Only `'auto'` can enable the persona: an explicit mode means \"this is not\n * Anthropic's own OAuth\", and sending Claude Code's betas / system block to\n * a third-party gateway is at best noise and at worst a 400.\n */\n authMode?: 'auto' | 'bearer' | 'api-key'\n /**\n * Override advertised capabilities (merged over the Anthropic defaults).\n * For gateways behind {@link AnthropicParams.baseURL} that don't implement\n * a native affordance — e.g. `{ nativeWebSearch: undefined }` to fall back\n * to the client-side `web_search` tool when the proxy rejects Anthropic's\n * `web_search_20250305` server tool.\n */\n capabilities?: Partial<ProviderCapabilities>\n /**\n * Override the credential identity used to resolve (and lazily refresh) the\n * token on every call. Defaults to Anthropic's own\n * (`providerId: 'anthropic'`, env `ANTHROPIC_API_KEY`).\n *\n * Set this when {@link AnthropicParams.baseURL} points at a gateway that\n * issues its own OAuth credentials under a different key in\n * `credentials.json`. The whole shared machinery — refresh-on-expiry,\n * cross-process lock, atomic persist, the `onOAuthRefresh` callback — then\n * applies to that provider instead, with no reimplementation. Pair with\n * `authMode: 'bearer'` for a bearer-token gateway.\n *\n * `providerId` must be registered in pi-ai's OAuth registry\n * (`registerOAuthProvider`) for refresh to resolve.\n */\n credentialSource?: {\n /** OAuth provider id — also the credentials-file key. */\n providerId: string\n /** Env var consulted before the credentials file. Omit for OAuth-only. */\n envKey?: string\n /** Message thrown when no credential is configured. */\n missingError?: string\n /** Message thrown when refresh fails, given the underlying reason. */\n refreshError?: (reason: string) => string\n }\n}\n\n/** Beta flags sent unconditionally on the OAuth path (Claude Code parity). */\nconst OAUTH_DEFAULT_BETAS = ['claude-code-20250219', 'oauth-2025-04-20'] as const\n\n/**\n * Beta flag that enables the `speed: \"fast\"` request field (fast mode — up to\n * 2.5× output tokens/sec at premium pricing; supported models are enumerated\n * in `FAST_MODE_OPTIONS` in chat/anthropic-models.ts). Sent as an\n * `anthropic-beta` header only on requests that opt into fast mode; absent\n * otherwise so standard requests keep a byte-stable header for prompt caching.\n *\n * See: https://platform.claude.com/docs/en/build-with-claude/fast-mode\n */\nconst FAST_MODE_BETA = 'fast-mode-2026-02-01'\nconst API_KEY_RESERVED_EXTRA_HEADER_NAMES = new Set(['anthropic-beta'])\nconst OAUTH_RESERVED_EXTRA_HEADER_NAMES = new Set([\n 'anthropic-beta',\n 'anthropic-dangerous-direct-browser-access',\n 'user-agent',\n 'x-app',\n])\n\nfunction filterReservedExtraHeaders(\n extraHeaders: Record<string, string> | undefined,\n reservedNames: ReadonlySet<string>,\n): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [name, value] of Object.entries(extraHeaders ?? {})) {\n if (!reservedNames.has(name.toLowerCase()))\n out[name] = value\n }\n return out\n}\n\n/**\n * Build the `anthropic-beta` header value — OAuth defaults plus caller-supplied\n * extras, de-duped while preserving order. Returns `undefined` when no betas\n * apply (non-OAuth, no extras).\n */\nexport function resolveAnthropicBetas(\n isOAuth: boolean,\n extraBetas?: readonly string[],\n): string | undefined {\n const seen = new Set<string>()\n const out: string[] = []\n if (isOAuth) {\n for (const b of OAUTH_DEFAULT_BETAS) {\n if (!seen.has(b)) { seen.add(b); out.push(b) }\n }\n }\n if (extraBetas) {\n for (const b of extraBetas) {\n if (typeof b === 'string' && b.length > 0 && !seen.has(b)) {\n seen.add(b)\n out.push(b)\n }\n }\n }\n return out.length > 0 ? out.join(',') : undefined\n}\n\n/**\n * Credential-resolution identity for {@link resolveOAuthApiKey}. Anthropic's\n * own unless the caller pointed the provider at a gateway with its own\n * credentials via {@link AnthropicParams.credentialSource}.\n */\nexport function credentialIdentity(anthropicParams?: AnthropicParams): {\n providerId: string\n envKey?: string\n missingError: string\n refreshError: (reason: string) => string\n} {\n const source = anthropicParams?.credentialSource\n if (!source) {\n return {\n providerId: 'anthropic',\n envKey: 'ANTHROPIC_API_KEY',\n missingError: 'No API key found. Run `bun run auth` first.',\n refreshError: reason => `Anthropic OAuth token refresh failed. Run \\`bun run auth --anthropic\\` again. ${reason}`,\n }\n }\n return {\n providerId: source.providerId,\n envKey: source.envKey,\n missingError: source.missingError ?? `No credentials found for \"${source.providerId}\". Sign in first.`,\n refreshError: source.refreshError ?? (reason => `OAuth token refresh failed for \"${source.providerId}\". Sign in again. ${reason}`),\n }\n}\n\n/**\n * Whether the wire auth is a bearer token, and whether the Claude Code\n * persona (betas + `x-app` headers + system-prompt folding) applies. Only the\n * default `'auto'` mode infers both from the token shape — see\n * {@link AnthropicParams.authMode}.\n */\nexport function resolveAnthropicAuthShape(\n apiKey: string,\n authMode: AnthropicParams['authMode'],\n): { bearer: boolean, claudeCode: boolean } {\n if (authMode === 'bearer')\n return { bearer: true, claudeCode: false }\n if (authMode === 'api-key')\n return { bearer: false, claudeCode: false }\n const isOAuth = apiKey.includes('sk-ant-oat')\n return { bearer: isOAuth, claudeCode: isOAuth }\n}\n\nfunction getConfiguredApiKey(anthropicParams?: AnthropicParams): string {\n if (anthropicParams?.apiKey)\n return anthropicParams.apiKey\n\n if (anthropicParams?.access)\n return anthropicParams.access\n\n // Gateway credentials live under their own key — resolve the factory-time\n // snapshot from there and never fall back to Anthropic's own key/creds\n // (a `bex_` gateway must not silently stream on the user's Anthropic key).\n // An empty snapshot is fine: the real error surfaces per call from\n // `resolveOAuthApiKey` with this source's `missingError`.\n const source = anthropicParams?.credentialSource\n if (source) {\n const fromEnv = source.envKey ? process.env[source.envKey] : undefined\n if (fromEnv)\n return fromEnv\n return readOAuthCredentials()[source.providerId]?.access ?? ''\n }\n\n if (process.env.ANTHROPIC_API_KEY)\n return process.env.ANTHROPIC_API_KEY\n\n // `readOAuthCredentials` tolerates a missing or corrupted file and returns `{}`\n // in both cases, so stale/half-written creds never crash agent construction.\n const access = readOAuthCredentials().anthropic?.access\n if (typeof access === 'string' && access.length > 0)\n return access\n\n throw new Error('No API key found. Run `bun run auth` first.')\n}\n\n/**\n * Resolve {@link AnthropicParams.extraHeaders} to a concrete map. A function\n * form is invoked (and awaited) per call so gateway auth tokens can refresh\n * each turn; a static map passes through.\n */\nasync function resolveExtraHeaders(\n value: AnthropicParams['extraHeaders'],\n): Promise<Record<string, string> | undefined> {\n return typeof value === 'function' ? value() : value\n}\n\n/**\n * SDK constructor options for a resolved credential — the single place that\n * decides which auth header goes on the wire.\n *\n * Exported (and pure) so the header shape is test-pinned without a network\n * round-trip: `authToken` → `Authorization: Bearer …`, `apiKey` → `x-api-key`,\n * and `apiKey: null` alongside `authToken` is what suppresses `x-api-key` so a\n * gateway sees exactly one credential.\n */\nexport function buildAnthropicClientOptions(\n apiKey: string,\n auth: { bearer: boolean, claudeCode: boolean },\n baseURL?: string,\n extraBetas?: readonly string[],\n extraHeaders?: Record<string, string>,\n): Record<string, unknown> {\n const base = baseURL ? { baseURL } : {}\n const betaHeader = resolveAnthropicBetas(auth.claudeCode, extraBetas)\n if (auth.claudeCode) {\n const callerHeaders = filterReservedExtraHeaders(extraHeaders, OAUTH_RESERVED_EXTRA_HEADER_NAMES)\n const defaultHeaders: Record<string, string> = {\n ...callerHeaders,\n 'anthropic-dangerous-direct-browser-access': 'true',\n 'user-agent': 'zidane/2.0.0',\n 'x-app': 'cli',\n }\n if (betaHeader)\n defaultHeaders['anthropic-beta'] = betaHeader\n return {\n apiKey: null,\n authToken: apiKey,\n dangerouslyAllowBrowser: true,\n defaultHeaders,\n ...base,\n }\n }\n // Non-persona paths — only emit the beta header when the caller opted in,\n // and let caller headers through except `anthropic-beta` (reserved so the\n // fast-mode / opt-in beta computation stays authoritative).\n const defaultHeaders = filterReservedExtraHeaders(extraHeaders, API_KEY_RESERVED_EXTRA_HEADER_NAMES)\n if (betaHeader)\n defaultHeaders['anthropic-beta'] = betaHeader\n const headers = Object.keys(defaultHeaders).length > 0 ? { defaultHeaders } : {}\n // Bearer without the persona: a gateway token (`bex_…`, an internal router\n // key) in front of `baseURL`.\n if (auth.bearer)\n return { apiKey: null, authToken: apiKey, ...headers, ...base }\n return { apiKey, ...headers, ...base }\n}\n\nfunction createClient(\n SDK: AnthropicCtor,\n apiKey: string,\n auth: { bearer: boolean, claudeCode: boolean },\n baseURL?: string,\n extraBetas?: readonly string[],\n extraHeaders?: Record<string, string>,\n): AnthropicInstance {\n return new SDK(\n buildAnthropicClientOptions(apiKey, auth, baseURL, extraBetas, extraHeaders) as ConstructorParameters<AnthropicCtor>[0],\n )\n}\n\ntype BudgetedThinkingLevel = Exclude<ThinkingLevel, 'off' | 'adaptive'>\n\n/**\n * Map `ThinkingLevel` budgeted tiers to Anthropic's `output_config.effort`\n * enum. `minimal` collapses to `low` (the closest equivalent — Anthropic does\n * not have a sub-`low` tier).\n */\nconst EFFORT_FOR_LEVEL: Record<BudgetedThinkingLevel, 'low' | 'medium' | 'high' | 'xhigh' | 'max'> = {\n minimal: 'low',\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n max: 'max',\n}\n\n/**\n * Description of the Anthropic-side thinking configuration derived from a\n * ThinkingLevel. `null` means no thinking-related field should be set on the\n * request.\n *\n * Two shapes:\n * - `adaptive` — `thinking.type='adaptive'`. The model self-budgets per turn.\n * When `effort` is set, also emits `output_config.effort` to hint at the\n * level of reasoning the caller wants. This is the post-Claude-4.6 default\n * for level-based control and avoids the `thinking.type='enabled'`\n * deprecation warning. When `maxTokensCap` is set, the request builder\n * reduces `max_tokens` to `min(max_tokens, maxTokensCap)` — adaptive thinks\n * and replies inside one envelope, so capping the envelope soft-bounds the\n * thinking too.\n * - `enabled` — explicit-budget path: `thinking.type='enabled'` with an\n * explicit `budget_tokens`. `maxTokensBump` is added to the request's\n * `max_tokens` so the budget sits on top of the response cap. When the\n * caller's level maps to an effort tier, `effort` is carried too and\n * emitted as `output_config.effort` alongside the explicit budget —\n * both fields are valid together on 4.6+ models. The only way to pin a\n * precise token budget; Anthropic emits a deprecation warning for this\n * shape on opus 4.6+, so we only route here when the caller explicitly\n * sets `behavior.thinkingBudget`.\n *\n * In both shapes Anthropic requires `temperature` to be `1`.\n */\nexport type AnthropicThinkingPlan\n = | { kind: 'enabled', budgetTokens: number, maxTokensBump: number, effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max' }\n | { kind: 'adaptive', effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max', maxTokensCap?: number, taskBudgetTokens?: number }\n\n/**\n * Decide how to translate a `ThinkingLevel` into Anthropic's request shape.\n * Pure / synchronous — exported so tests can assert routing without standing\n * up the SDK.\n *\n * Routing rules:\n * - `'off'` → `null` (no thinking field, no effort hint).\n * - `'adaptive'` → adaptive thinking with no effort hint (model decides\n * everything). When `taskBudget` is set, it is carried as\n * `taskBudgetTokens` and emitted as `output_config.task_budget` — the\n * native whole-task thinking cap. LEGACY: when `customBudget` is set, it\n * is carried as `maxTokensCap` so the request builder caps `max_tokens`\n * accordingly — this clamps the whole response envelope (text and\n * tool-call JSON included), which is the wrong knob for bounding thinking\n * now that `taskBudget` exists; kept only for callers who already depend\n * on it. Prefer `taskBudget`.\n * - Budgeted levels → adaptive thinking with an `effort` hint (plus\n * `taskBudgetTokens` when `taskBudget` is set), unless `customBudget` is\n * provided.\n * - Any level + `customBudget` → explicit-budget `enabled` path, carrying\n * the level's `effort` hint alongside the budget. The caller has opted\n * into precise budget control and accepts the Anthropic deprecation\n * warning that comes with it on opus 4.6+. `'adaptive'` is the sole\n * exception: it never falls back to enabled, since adaptive is the\n * current Anthropic API surface for self-budgeted thinking. `taskBudget`\n * is adaptive-only and ignored on this path.\n * - `effortHint: false` (default `true`) strips the `effort` hint from\n * every shape — budgeted levels plan plain adaptive and the\n * `customBudget` path plans `enabled` with no `output_config.effort` —\n * for Anthropic models that reject the effort field.\n */\nexport function planAnthropicThinking(\n level: ThinkingLevel,\n customBudget?: number,\n taskBudget?: number,\n effortHint = true,\n): AnthropicThinkingPlan | null {\n if (level === 'off')\n return null\n const taskBudgetTokens = typeof taskBudget === 'number' && taskBudget > 0 ? taskBudget : undefined\n if (level === 'adaptive') {\n return {\n kind: 'adaptive',\n ...(typeof customBudget === 'number' && customBudget > 0 ? { maxTokensCap: customBudget } : {}),\n ...(taskBudgetTokens !== undefined ? { taskBudgetTokens } : {}),\n }\n }\n if (customBudget !== undefined) {\n return {\n kind: 'enabled',\n budgetTokens: customBudget,\n maxTokensBump: customBudget,\n ...(effortHint ? { effort: EFFORT_FOR_LEVEL[level] } : {}),\n }\n }\n return {\n kind: 'adaptive',\n ...(effortHint ? { effort: EFFORT_FOR_LEVEL[level] } : {}),\n ...(taskBudgetTokens !== undefined ? { taskBudgetTokens } : {}),\n }\n}\n\n/**\n * Map Anthropic's native `stop_reason` to the zidane `TurnFinishReason` union.\n *\n * `pause_turn` and `model_context_window_exceeded` are 4.6+ stop reasons that\n * pre-Z21 collapsed to `'other'` and silently terminated the run. They now\n * map to `'pause'` and `'length'` respectively, and the surrounding caller\n * adjusts the `done` flag so the loop can recover.\n */\nfunction mapStopReason(stopReason: string | null | undefined): TurnFinishReason | undefined {\n if (!stopReason)\n return undefined\n switch (stopReason) {\n case 'end_turn':\n case 'stop_sequence':\n return 'stop'\n case 'tool_use':\n return 'tool-calls'\n case 'max_tokens':\n case 'model_context_window_exceeded':\n // 4.6+: response bumped against the model's context window mid-stream.\n // Treat as a length-class stop so consumers can prune + retry; the\n // partial assistant response is preserved.\n return 'length'\n case 'refusal':\n return 'content-filter'\n // 4.6+: server-side mid-turn pause for long thinking. The loop\n // continues with a synthetic continue message rather than terminating.\n case 'pause_turn':\n return 'pause'\n default:\n return 'other'\n }\n}\n\n/**\n * Conservative per-family output ceiling, used ONLY as a fallback when pi-ai's\n * model registry doesn't know the model (or pi-ai is externalized) — see\n * {@link resolveAnthropicMaxOutput}, which is the primary path. These are\n * non-beta floors we can always send without a 400; the registry reports the\n * real (often much higher, e.g. Opus 4.x = 128K native) max when available.\n */\nexport function defaultAnthropicMaxTokens(modelId: string): number {\n const id = modelId.toLowerCase()\n if (id.includes('haiku'))\n return 8192\n if (id.includes('sonnet'))\n return 64000\n // opus + anything unrecognized → 32000: a safe floor when the registry is\n // unavailable. The registry lifts this to the model's true max when present.\n return 32000\n}\n\n/**\n * Lazily-loaded pi-ai model-registry getter (cached; keeps the multi-MB\n * registry out of the static bundle, mirroring `cost.ts`). Returns `null` when\n * pi-ai is externalized, in which case callers fall back to the family map.\n */\nconst loadPiModelGetter = lazyAsync(\n async (): Promise<((provider: string, model: string) => { maxTokens?: number } | undefined) | null> => {\n try {\n const mod = await import('@earendil-works/pi-ai/providers/all')\n return mod.getBuiltinModel as unknown as (provider: string, model: string) => { maxTokens?: number } | undefined\n }\n catch {\n return null\n }\n },\n)\n\n/**\n * Resolve a model's max output tokens. Prefers pi-ai's bundled registry — the\n * authoritative source the SDK itself uses to build requests, so e.g. Opus 4.x\n * reports its full native 128K (no extended-output beta required) — and falls\n * back to {@link defaultAnthropicMaxTokens} only when the registry lacks the\n * model. Used as BOTH the default (when the caller leaves `maxTokens` unset)\n * and a hard clamp, since a `max_tokens` above the model's max would 400.\n */\nexport async function resolveAnthropicMaxOutput(modelId: string): Promise<number> {\n const getModel = await loadPiModelGetter()\n const registryMax = getModel?.('anthropic', modelId)?.maxTokens\n return typeof registryMax === 'number' && registryMax > 0\n ? registryMax\n : defaultAnthropicMaxTokens(modelId)\n}\n\nconst EPHEMERAL: Anthropic.CacheControlEphemeral = { type: 'ephemeral' }\n\n/**\n * Mutate an Anthropic request in place to add cache breakpoints on the three\n * stable prefixes:\n * 1. System prompt — `cache_control` on the static prefix only (see below).\n * 2. Tool definitions — last tool.\n * 3. Conversation — last content block of the last message.\n *\n * Each breakpoint tells Anthropic to cache the prefix ending at that block;\n * subsequent turns reuse the cached prefix and pay only for the delta. Safe\n * no-op when any prefix is empty (no tools, empty system, etc.).\n *\n * System-prompt boundary handling (`SYSTEM_PROMPT_BOUNDARY`):\n *\n * - When `originalSystem` is provided AND carries the marker, the system\n * block is rewritten as a 2-block array: static (cached) then dynamic\n * (uncached). The caller is expected to have already stripped the marker\n * from `params.system` via {@link renderSystemForWire} — this helper\n * only re-splits to apply `cache_control` cleanly.\n * - When `originalSystem` is omitted or marker-free, the existing single-\n * block treatment applies: `cache_control` on the whole system string.\n * - Array system prompts: existing semantics — `cache_control` lands on the\n * last block. Callers building their own multi-block layout own the\n * breakpoint placement.\n */\nexport function applyAnthropicCacheBreakpoints(\n params: Anthropic.MessageCreateParamsStreaming,\n originalSystem?: string,\n): void {\n if (typeof params.system === 'string') {\n if (params.system.length > 0) {\n // `originalSystem` is the authoritative source for the boundary split\n // — `params.system` may have been pre-rendered by `renderSystemForWire`.\n // Fall back to `params.system` itself when no original was supplied\n // (or an empty original) so an external caller that embedded a marker\n // directly doesn't leak it into the cached block on the wire.\n const splitSource = originalSystem && originalSystem.length > 0\n ? originalSystem\n : params.system\n const parts = splitSystemPrompt(splitSource)\n if (parts.dynamic.length > 0) {\n const blocks: Anthropic.TextBlockParam[] = []\n if (parts.static.length > 0)\n blocks.push({ type: 'text', text: parts.static, cache_control: EPHEMERAL })\n blocks.push({ type: 'text', text: parts.dynamic })\n params.system = blocks\n }\n else {\n // No marker — wrap the (already marker-free) string in a single\n // cached block. `parts.static` equals `splitSource` in this branch.\n params.system = [{ type: 'text', text: parts.static, cache_control: EPHEMERAL }]\n }\n }\n }\n else if (Array.isArray(params.system) && params.system.length > 0) {\n const lastIdx = params.system.length - 1\n params.system = params.system.map((block, i) =>\n i === lastIdx ? { ...block, cache_control: EPHEMERAL } : block,\n )\n }\n\n if (params.tools && params.tools.length > 0) {\n const lastIdx = params.tools.length - 1\n params.tools = params.tools.map((tool, i) =>\n i === lastIdx ? { ...tool, cache_control: EPHEMERAL } : tool,\n ) as Anthropic.ToolUnion[]\n }\n\n if (params.messages.length === 0)\n return\n const lastMsgIdx = params.messages.length - 1\n const lastMsg = params.messages[lastMsgIdx]\n if (typeof lastMsg.content === 'string') {\n if (lastMsg.content.length === 0)\n return\n params.messages[lastMsgIdx] = {\n ...lastMsg,\n content: [{ type: 'text', text: lastMsg.content, cache_control: EPHEMERAL }],\n }\n return\n }\n if (!Array.isArray(lastMsg.content) || lastMsg.content.length === 0)\n return\n const blocks = lastMsg.content\n // `ThinkingBlockParam` and `RedactedThinkingBlockParam` do not accept `cache_control`\n // in the SDK types — walk backward past any trailing thinking blocks to find the\n // nearest cache-eligible block. Returns -1 when every block is a thinking variant,\n // which skips this breakpoint; system + tools still carry the cache prefix.\n let targetIdx = blocks.length - 1\n while (targetIdx >= 0 && isThinkingBlock(blocks[targetIdx]))\n targetIdx -= 1\n if (targetIdx < 0)\n return\n const nextBlocks = blocks.slice()\n nextBlocks[targetIdx] = { ...nextBlocks[targetIdx], cache_control: EPHEMERAL } as typeof nextBlocks[number]\n params.messages[lastMsgIdx] = { ...lastMsg, content: nextBlocks }\n}\n\nfunction isThinkingBlock(block: { type: string }): boolean {\n return block.type === 'thinking' || block.type === 'redacted_thinking'\n}\n\n/**\n * Duck-type check for an Anthropic SDK `APIError` — avoids a runtime dependency\n * on `@anthropic-ai/sdk` so `classifyError` stays usable even when the optional\n * peer dep isn't loaded (e.g. host code calling it on an unrelated provider's\n * error).\n *\n * Anthropic's APIError shape: `.status: number` + `.error` (parsed body, object\n * or null). Plain `Error`s don't have `.status` + `.error`.\n */\nfunction looksLikeAnthropicApiError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as { status?: unknown, error?: unknown }\n return typeof e.status === 'number' && 'error' in e\n}\n\n/**\n * Anthropic SSE `error` event types that warrant a retry. `overloaded_error`\n * is the headline case (capacity event mid-stream); `api_error` and\n * `timeout_error` are both server-side transient failures. `invalid_request_error`,\n * `authentication_error`, `permission_error`, `not_found_error`, `billing_error`\n * are terminal — retrying them would just hammer a bad request.\n *\n * `rate_limit_error` here would be redundant: the SDK's pre-stream retry\n * handles 429, and a mid-stream `rate_limit_error` is exceedingly rare. We\n * include it anyway for symmetry with the HTTP-level retry policy.\n */\nconst SSE_RETRYABLE_ERROR_TYPES = new Set<string>([\n 'overloaded_error',\n 'api_error',\n 'timeout_error',\n 'rate_limit_error',\n])\n\n/**\n * Try to parse an Anthropic SSE `error` event payload out of a thrown error's\n * message. The SDK's `MessageStream` surfaces mid-stream error events by\n * throwing an `AnthropicError` whose `.message` contains the raw JSON shape:\n *\n * {\"type\":\"error\",\"error\":{\"type\":\"overloaded_error\",\"message\":\"Overloaded\"},\"request_id\":\"req_...\"}\n *\n * Returns the inner `{ type, message }` or `null` when the message isn't\n * the canonical SSE error shape. Tolerant: silently returns `null` for\n * non-string input, non-JSON content, or any shape mismatch.\n */\nfunction matchSseErrorEvent(message: unknown): { type: string, message: string } | null {\n if (typeof message !== 'string' || message.length === 0)\n return null\n // Cheap pre-check to avoid JSON.parse on every error message.\n if (!message.includes('\"type\":\"error\"') && !message.includes('\"type\": \"error\"'))\n return null\n // The message may be the JSON itself or have it embedded (some SDK paths\n // prefix a human prelude). Scan for the first `{` and try from there.\n const start = message.indexOf('{')\n if (start < 0)\n return null\n try {\n const parsed = JSON.parse(message.slice(start)) as unknown\n if (!parsed || typeof parsed !== 'object')\n return null\n const outer = parsed as { type?: unknown, error?: unknown }\n if (outer.type !== 'error' || !outer.error || typeof outer.error !== 'object')\n return null\n const inner = outer.error as { type?: unknown, message?: unknown }\n if (typeof inner.type !== 'string')\n return null\n return {\n type: inner.type,\n message: typeof inner.message === 'string' ? inner.message : inner.type,\n }\n }\n catch {\n return null\n }\n}\n\n/**\n * Classify an Anthropic SDK / HTTP error for typed-error wrapping.\n *\n * - `prompt is too long` (400 invalid_request_error) → `context_exceeded`.\n * - `'tool_use' ids were found without 'tool_result' blocks` /\n * `'tool_result must be preceded by a tool_call'` (400) →\n * `tool_pairing_corruption`. The local repair pass should have caught\n * this — the typed error tells consumers their pre-send pipeline is\n * shipping orphaned blocks somehow (custom `context:transform` hook,\n * bypassed loop, mid-stream session edit) so they can patch the root\n * cause instead of chasing 400s.\n * - Any other Anthropic `APIError`-shaped value → `provider_error` with the\n * native status/type code.\n * - Unknown errors → `null` (loop wraps in `AgentProviderError` generically).\n *\n * Anthropic's wire error shape is `{ type: 'error', error: { type, message } }` — the\n * SDK stores the parsed body on `err.error`. We walk both levels so callers see the\n * most specific `providerCode` we can find.\n */\nexport function classifyAnthropicError(err: unknown): ClassifiedError | null {\n const prelude = classifyErrorPrelude(err)\n if (prelude === 'not-object')\n return null\n if (prelude === 'aborted')\n return { kind: 'aborted' }\n\n interface InnerError { type?: string, message?: string }\n interface AnthError {\n name?: string\n message?: string\n status?: number\n error?: InnerError & { error?: InnerError }\n }\n const anyErr = err as AnthError\n\n // Mid-stream SSE error event — the SDK's `MessageStream` surfaces these by\n // throwing an `AnthropicError` (plain Error subclass, no `.status`) whose\n // `.message` carries the raw SSE error JSON, e.g.\n // `{\"type\":\"error\",\"error\":{\"type\":\"overloaded_error\",\"message\":\"Overloaded\"},\"request_id\":\"...\"}`.\n // The pre-stream HTTP retry inside the SDK can't help here — the response\n // already opened with a 200, then the server emitted `event: error`\n // mid-stream. Classify these so the loop-level retry kicks in.\n //\n // Guarded on `!looksLikeAnthropicApiError(err)` so we don't shadow the\n // structured `APIError` branch below (which also stringifies its body into\n // `.message` and would otherwise match this same pattern).\n if (!looksLikeAnthropicApiError(err)) {\n const sseError = matchSseErrorEvent(anyErr.message)\n if (sseError) {\n return {\n kind: 'provider_error',\n providerCode: sseError.type,\n message: sseError.message,\n retryable: SSE_RETRYABLE_ERROR_TYPES.has(sseError.type),\n }\n }\n return null\n }\n\n // Prefer the inner error when present (Anthropic's nested shape).\n const innerType = anyErr.error?.error?.type\n const outerType = anyErr.error?.type\n const nativeType = innerType && innerType !== 'error' ? innerType : outerType\n const message = anyErr.error?.error?.message\n ?? anyErr.error?.message\n ?? anyErr.message\n ?? ''\n\n if (matchesContextExceeded(message)) {\n return {\n kind: 'context_exceeded',\n providerCode: nativeType ?? 'invalid_request_error',\n message,\n }\n }\n\n if (matchesToolPairingError(message)) {\n return {\n kind: 'tool_pairing_corruption',\n providerCode: nativeType ?? 'invalid_request_error',\n message,\n }\n }\n\n const status = anyErr.status\n const retryable = typeof status === 'number'\n ? isRetryableHttpStatus(status)\n : undefined\n\n return {\n kind: 'provider_error',\n providerCode: nativeType ?? (status ? String(status) : undefined),\n message,\n ...(retryable !== undefined ? { retryable } : {}),\n }\n}\n\nfunction shouldFallbackFromFastMode(err: unknown): boolean {\n const classified = classifyAnthropicError(err)\n if (classified?.kind === 'provider_error' && classified.retryable === true)\n return true\n\n const message = err instanceof Error ? err.message : String(err)\n return /\\b(?:fast|speed)\\b/i.test(message)\n && /capacity|overload|rate|unavailable|unsupported|not supported|not permitted|extra inputs|invalid/i.test(message)\n}\n\n/**\n * Anthropic signs thinking blocks so they can be echoed through tool-use\n * chains without exposing raw reasoning. A signature can become\n * unverifiable after a model/route/account switch, a legacy session edit,\n * or a provider-side signing-key rotation. We cannot validate the opaque\n * signature locally; Anthropic reports this as a request-validation 400\n * (`invalid_request_error`) with no machine-readable subcode — the message\n * string is the only signal.\n *\n * Detection is layered, most-stable signal first:\n *\n * 1. Structural: the validation 400 pinpoints a wire location\n * (`messages.N.content.M: …`) and OUR OWN request payload has a\n * thinking/redacted_thinking block at that exact position. The locator\n * format is API-stable; the prose around it is not (Anthropic has\n * already reworded it once, from \"Invalid signature in thinking block\"\n * to \"Invalid `signature` in `thinking` block\", which silently\n * disabled a wording-only matcher). This deliberately classifies ANY\n * validation complaint targeting one of our opaque thinking blocks as\n * recoverable — dropping the block is the correct remedy for the whole\n * class (bad signature, corrupt data, ordering), and if the scrubbed\n * retry still 400s the error propagates unchanged.\n * 2. Wording fallback: some error shapes omit the locator; match the\n * known prose variants, tolerating backticks and minor rewording.\n *\n * Match narrowly: retrying any broader 400 could hide a real bad request.\n */\nfunction isRecoverableThinkingBlockError(err: unknown, messages?: Anthropic.MessageParam[]): boolean {\n if (!isRequestValidationError(err))\n return false\n\n if (messages !== undefined) {\n const path = parseFailingBlockPath(err)\n if (path !== undefined) {\n const content = messages[path.message]?.content\n const block = Array.isArray(content) ? content[path.content] : undefined\n if (block !== undefined && (block.type === 'thinking' || block.type === 'redacted_thinking'))\n return true\n }\n }\n\n return /invalid\\s+`?signature`?\\s+in\\s+(?:the\\s+)?`?thinking`?\\s+block/i.test(errorText(err))\n}\n\n/**\n * True when the error looks like an Anthropic request-validation failure:\n * status 400 and, when the nested API body carries a type, that type is\n * `invalid_request_error`. Both fields are optional on SDK-wrapped or\n * gateway shapes, so each is enforced only when present — but a present,\n * different value (e.g. 429, `authentication_error`) disqualifies: the\n * recovery permanently scrubs session history, so it must never fire on a\n * non-validation failure that coincidentally mentions a locator.\n */\nfunction isRequestValidationError(err: unknown): boolean {\n // Field probes only — a non-object error (string throw from a custom\n // stream factory) has neither status nor type and falls through to the\n // caller's prose match, same as the pre-structural matcher.\n const status = typeof err === 'object' && err !== null && 'status' in err\n ? (err as { status?: unknown }).status\n : undefined\n if (status !== undefined && status !== 400)\n return false\n const nestedType = typeof err === 'object' && err !== null ? nestedErrorType(err) : undefined\n return nestedType === undefined || nestedType === 'invalid_request_error'\n}\n\n/** The `error.error.type` of a nested Anthropic API error body, when present. */\nfunction nestedErrorType(err: object): string | undefined {\n const outer = 'error' in err ? (err as { error?: unknown }).error : undefined\n if (typeof outer !== 'object' || outer === null)\n return undefined\n const inner = 'error' in outer ? (outer as { error?: unknown }).error : outer\n if (typeof inner !== 'object' || inner === null)\n return undefined\n const type = 'type' in inner ? (inner as { type?: unknown }).type : undefined\n return typeof type === 'string' ? type : undefined\n}\n\n/** Error message + nested API error body, flattened for pattern matching. */\nfunction errorText(err: unknown): string {\n const nested = typeof err === 'object' && err !== null && 'error' in err\n ? JSON.stringify((err as { error?: unknown }).error)\n : ''\n return `${err instanceof Error ? err.message : String(err)} ${nested}`\n}\n\n/**\n * Full wire locator of the block the 400 pinpointed (`messages.5.content.0:\n * …`), or `undefined` when the shape doesn't carry one. Unlike the prose,\n * this locator prefix is a stable part of Anthropic's validation-error\n * format — it lets {@link isRecoverableThinkingBlockError} verify against\n * the actual request payload instead of trusting wording.\n */\nfunction parseFailingBlockPath(err: unknown): { message: number, content: number } | undefined {\n const match = /\\bmessages\\.(\\d+)\\.content\\.(\\d+)\\b/.exec(errorText(err))\n if (!match)\n return undefined\n const message = Number(match[1])\n const content = Number(match[2])\n return Number.isSafeInteger(message) && Number.isSafeInteger(content)\n ? { message, content }\n : undefined\n}\n\n/**\n * Wire index of the message the 400 pinpointed (`messages.5.content.0:\n * Invalid signature…`), or `undefined` when the shape doesn't carry one.\n * Lets the retry scrub only blocks at/before the failing message — later\n * messages can hold STILL-VALID thinking (e.g. minted this run after a\n * mid-session account switch) that the active tool-use chain requires.\n */\nfunction parseFailingMessageIndex(err: unknown): number | undefined {\n const match = /\\bmessages\\.(\\d+)\\./.exec(errorText(err))\n if (!match)\n return undefined\n const index = Number(match[1])\n return Number.isSafeInteger(index) ? index : undefined\n}\n\n/**\n * Hard cap on signature-400 scrub retries within one `stream()` call. Two\n * passes resolve every real-world shape (bounded pass, then the\n * everything-but-active-chain pass); the third is margin. Beyond that the\n * error is not signature poisoning and must surface.\n */\nconst MAX_THINKING_SCRUB_PASSES = 3\n\n/**\n * Wire index where the ACTIVE chain begins: the last user message that\n * carries real user content (string content, or any non-tool_result\n * block). Everything from there onward is the current prompt plus the\n * in-flight assistant/tool exchange — its thinking blocks were minted by\n * this very call chain and Anthropic requires them to be echoed back, so\n * escalated scrubs must never cross this boundary. `undefined` when no\n * such message exists (degenerate resume shapes) — callers treat that as\n * \"no protected region\".\n */\nfunction activeChainScrubBoundary(messages: Anthropic.MessageParam[]): number | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i]\n if (message?.role !== 'user')\n continue\n if (typeof message.content === 'string')\n return i\n if (message.content.some(block => block.type !== 'tool_result'))\n return i\n }\n return undefined\n}\n\n/**\n * Decide what a scrub pass may touch. The bound NEVER crosses into the\n * active chain wholesale — not even on pass 0 with no parseable locator\n * (an unbounded first pass would strip the in-flight chain's thinking and\n * trade a recoverable 400 for an \"expected thinking block\" one). When the\n * API-reported failing index lies INSIDE the chain, only that single\n * message is scrubbed on top of the pre-chain region: the block the API\n * itself rejected fails whether or not it is echoed, but sibling chain\n * messages below it may hold still-valid thinking the exchange requires.\n *\n * - pass 0 with a pre-chain locator → bounded at the locator (preserves\n * later, still-valid historical thinking).\n * - pass 0 without a locator, and every escalated pass → the full\n * pre-chain history.\n * - no active chain at all (degenerate resume shapes) → locator bound on\n * pass 0, full history afterwards.\n */\nfunction planThinkingScrub(\n scrubPass: number,\n failingIndex: number | undefined,\n messages: Anthropic.MessageParam[],\n): { upToIndex?: number, alsoMessageIndex?: number } {\n const chainStart = activeChainScrubBoundary(messages)\n if (chainStart === undefined) {\n return scrubPass === 0 && failingIndex !== undefined\n ? { upToIndex: failingIndex }\n : {} // no protected region — scrub the full history\n }\n const beforeChain = chainStart - 1\n const inChain = failingIndex !== undefined && failingIndex >= chainStart\n const upToIndex = scrubPass === 0 && failingIndex !== undefined && !inChain\n ? failingIndex\n : beforeChain\n return inChain ? { upToIndex, alsoMessageIndex: failingIndex } : { upToIndex }\n}\n\n/**\n * Remove opaque historical Anthropic reasoning from a retry payload. Text,\n * tool calls/results, images, and every other block are preserved exactly.\n * If a message consisted only of reasoning, leave a neutral text marker so\n * the wire never receives an empty content array and conversation role\n * boundaries stay intact.\n *\n * `upToIndex` bounds the scrub to messages at/before the API-reported\n * failing index (see {@link parseFailingMessageIndex}); `undefined` strips\n * everywhere. `alsoMessageIndex` additionally scrubs that single message\n * beyond the bound (see {@link planThinkingScrub}). A later stale block\n * simply triggers the same recovery again — each pass removes at least\n * one block, so the recursion is bounded.\n */\nfunction withoutHistoricalThinking(\n messages: Anthropic.MessageParam[],\n upToIndex?: number,\n alsoMessageIndex?: number,\n): {\n messages: Anthropic.MessageParam[]\n removed: number\n} {\n let removed = 0\n const scrubbed = messages.map((message, index) => {\n if (upToIndex !== undefined && index > upToIndex && index !== alsoMessageIndex)\n return message\n if (!Array.isArray(message.content))\n return message\n const content = message.content.filter((block) => {\n if (block.type === 'thinking' || block.type === 'redacted_thinking') {\n removed += 1\n return false\n }\n return true\n })\n return {\n ...message,\n content: content.length > 0\n ? content\n : [{ type: 'text' as const, text: '[historical reasoning omitted]' }],\n }\n })\n return { messages: scrubbed, removed }\n}\n\n/**\n * Build a user `SessionMessage` from multimodal prompt parts.\n *\n * - Text parts → text blocks.\n * - Image parts → base64 image blocks.\n * - Document parts → canonical document blocks. `toAnthropic` emits native\n * document blocks only for base64 PDFs; text/non-PDF documents fall back to\n * attachment-tagged text to avoid invalid Anthropic source media types.\n *\n * The format mirrors `defaultPromptMessage`'s output on shape — Anthropic-specific\n * handling differs only in that `promptMessage` being present tells the agent loop\n * to route PromptPart[] through this function rather than throwing on base64 docs.\n */\nfunction anthropicPromptMessage(parts: PromptPart[]): SessionMessage {\n const content: SessionContentBlock[] = []\n\n for (const part of parts) {\n if (part.type === 'text') {\n if (part.text.length > 0)\n content.push({ type: 'text', text: part.text })\n continue\n }\n\n if (part.type === 'image') {\n content.push({ type: 'image', mediaType: part.mediaType, data: part.data })\n continue\n }\n\n if (part.type === 'audio')\n throw unsupportedMediaError('audio', 'anthropic')\n if (part.type === 'video')\n throw unsupportedMediaError('video', 'anthropic')\n\n // document\n content.push({\n type: 'document',\n mediaType: part.mediaType,\n data: part.data,\n encoding: part.encoding,\n ...(part.name ? { name: part.name } : {}),\n })\n }\n\n return { role: 'user', content }\n}\n\nexport function anthropic(\n anthropicParams?: AnthropicParams,\n): Provider {\n const configuredApiKey = getConfiguredApiKey(anthropicParams)\n // Factory-time snapshot — used only for the static `meta.isOAuth` flag.\n // Keys resolve per call (env / file creds can change between construction\n // and a turn), so `countTokens` / `stream` re-derive the auth shape from the\n // freshly resolved key instead of trusting this.\n const isOAuth = resolveAnthropicAuthShape(configuredApiKey, anthropicParams?.authMode).bearer\n const defaultModel = anthropicParams?.defaultModel || 'claude-opus-4-8'\n let runtimeCredentials = extractRuntimeCredentials(anthropicParams)\n\n return {\n name: 'anthropic',\n meta: {\n defaultModel,\n isOAuth,\n // True when server-side context management is configured: the API may\n // clear old tool results mid-conversation without the client knowing.\n // The loop reads this to disable the read_file dedup, whose client-side\n // hash can't tell that a server-cleared body left the wire — otherwise\n // the \"unchanged\" replay stub would strand the model on missing content.\n clearsContextServerSide: Boolean(anthropicParams?.contextManagement),\n capabilities: {\n vision: true,\n imageInToolResult: true,\n documents: true,\n // Anthropic Messages API has no audio/video input content block.\n audio: false,\n video: false,\n // Anthropic Messages API supports `web_search_20250305` as a\n // server-side tool — the model issues searches and consumes\n // results without a client-visible tool_use round-trip. The\n // canonical `web_search` `ToolDef` ships as a cross-provider\n // fallback; when this capability is set, `formatTools` rewrites\n // it to the server-tool wire shape and drops the function-tool\n // version so the model uses the native path.\n nativeWebSearch: { maxUses: 5 },\n // Anthropic adaptive has no native per-turn thinking budget knob, so\n // `thinkingBudget` caps the whole response envelope (`max_tokens`).\n // The loop skips `thinkingDecay` for adaptive here — see the flag docs.\n adaptiveBudgetIsEnvelope: true,\n // Caller overrides last — a gateway in front of `baseURL` may not\n // implement every native affordance (server-side web search is the\n // usual casualty), and advertising one it lacks 400s every turn.\n ...anthropicParams?.capabilities,\n },\n },\n\n formatTools(tools: ToolSpec[]): Anthropic.ToolUnion[] {\n // Sanitize before forwarding — Anthropic 400s on the strict shapes\n // (root `$ref`, root `oneOf|anyOf|allOf`, `type: ['object', 'null']`,\n // missing `properties`, `nullable: true`) that show up in MCP-server\n // schemas. The strict `anthropic` profile coerces those without\n // changing semantics for valid schemas. Warnings are de-duped to\n // `console.warn` so the same bad schema doesn't spam the log every\n // turn.\n //\n // `web_search` is special-cased: when present, it's emitted as the\n // server-tool `web_search_20250305` block and removed from the\n // function-tools list. The model invokes it without a client-side\n // round-trip; Anthropic injects the search results into the\n // assistant message directly. See the `nativeWebSearch` capability\n // declared above.\n const nativeWebSearch = this.meta.capabilities?.nativeWebSearch\n // `hasWebSearch` is the existence check; `fnTools` is the filtered\n // list. Computing existence via `tools.some` (rather than comparing\n // pre/post-filter lengths) makes the rewrite intent obvious at a\n // glance.\n const hasWebSearch = !!nativeWebSearch && tools.some(t => t.name === 'web_search')\n const fnTools = hasWebSearch ? tools.filter(t => t.name !== 'web_search') : tools\n const sanitized = sanitizeToolSpecs(fnTools, { profile: 'anthropic' })\n const out: Anthropic.ToolUnion[] = sanitized.map(t => ({\n name: t.name,\n description: t.description,\n input_schema: t.inputSchema as Anthropic.Tool['input_schema'],\n }))\n if (hasWebSearch && nativeWebSearch) {\n // `max_uses` is optional — omit when the capability doesn't pin a value\n // so Anthropic's server-side default applies.\n const block: Anthropic.WebSearchTool20250305 = {\n type: 'web_search_20250305',\n name: 'web_search',\n }\n if (typeof nativeWebSearch.maxUses === 'number')\n block.max_uses = nativeWebSearch.maxUses\n out.push(block)\n }\n return out\n },\n\n userMessage(content: string): SessionMessage {\n return { role: 'user', content: [{ type: 'text', text: content }] }\n },\n\n assistantMessage(content: string): SessionMessage {\n return { role: 'assistant', content: [{ type: 'text', text: content }] }\n },\n\n toolResultsMessage(results: ToolResult[]): SessionMessage {\n return {\n role: 'user',\n content: results.map(r => ({\n type: 'tool_result' as const,\n callId: r.id,\n output: r.content,\n ...(r.isError ? { isError: true as const } : {}),\n ...(r.metadata ? { metadata: r.metadata } : {}),\n })),\n }\n },\n\n promptMessage: anthropicPromptMessage,\n\n classifyError: classifyAnthropicError,\n\n async countTokens(payload, signal): Promise<number | null> {\n try {\n const SDK = await loadAnthropicSdk()\n const identity = credentialIdentity(anthropicParams)\n const apiKey = await resolveOAuthApiKey(\n {\n provider: identity.providerId,\n providerId: identity.providerId,\n params: runtimeCredentials ? { ...anthropicParams, ...runtimeCredentials } : anthropicParams,\n envKey: identity.envKey,\n missingError: identity.missingError,\n refreshError: identity.refreshError,\n },\n )\n // Derive OAuth-ness from the freshly resolved key — auth mode can\n // change between factory construction and this call (env var set,\n // creds file rotated), and a stale flag would skew the count's\n // system-prompt folding relative to what `stream()` actually sends.\n const callAuth = resolveAnthropicAuthShape(apiKey, anthropicParams?.authMode)\n const callIsOAuth = callAuth.claudeCode\n const client = createClient(\n SDK,\n apiKey,\n callAuth,\n anthropicParams?.baseURL,\n anthropicParams?.extraBetas,\n await resolveExtraHeaders(anthropicParams?.extraHeaders),\n )\n\n // Mirror `stream()`'s wire-payload build so the count matches what is\n // actually sent: OAuth folds the real system prompt into a synthetic\n // user/assistant pair and sends the canonical Claude Code prompt as\n // the system block.\n const wireSystem = renderSystemForWire(payload.system)\n const system = callIsOAuth\n ? `You are Claude Code, Anthropic's official CLI for Claude.`\n : wireSystem\n const messages: SessionMessage[] = callIsOAuth && payload.system\n ? [\n { role: 'user', content: [{ type: 'text' as const, text: wireSystem }] },\n { role: 'assistant', content: [{ type: 'text' as const, text: 'Understood. I will proceed with these instructions above the rest of my system prompt.' }] },\n ...payload.messages,\n ]\n : [...payload.messages]\n\n const res = await client.messages.countTokens(\n {\n model: payload.model as Model,\n system,\n tools: payload.tools as Anthropic.Tool[],\n messages: messages.map(m => toAnthropic(m)) as Anthropic.MessageParam[],\n },\n signal ? { signal } : undefined,\n )\n return res.input_tokens\n }\n catch (err) {\n // Unreachable under the active auth / network — caller falls back to\n // the heuristic. Surface the cause when debugging so a silent auth /\n // scope rejection (e.g. OAuth lacking the count endpoint) is visible.\n if (process.env.ZIDANE_DEBUG_COUNT)\n console.error('[anthropic.countTokens] failed:', err)\n return null\n }\n },\n\n async stream(options, callbacks: StreamCallbacks): Promise<TurnResult> {\n const SDK = await loadAnthropicSdk()\n const identity = credentialIdentity(anthropicParams)\n const apiKey = await resolveOAuthApiKey(\n {\n provider: identity.providerId,\n providerId: identity.providerId,\n params: runtimeCredentials ? { ...anthropicParams, ...runtimeCredentials } : anthropicParams,\n envKey: identity.envKey,\n missingError: identity.missingError,\n refreshError: identity.refreshError,\n },\n {\n ...callbacks,\n async onOAuthRefresh(ctx) {\n if (ctx.source === 'params') {\n runtimeCredentials = {\n access: ctx.credentials.access,\n refresh: ctx.credentials.refresh,\n expires: ctx.credentials.expires,\n }\n }\n await callbacks.onOAuthRefresh?.(ctx)\n },\n },\n )\n // Fast mode (`speed: \"fast\"`) is gated behind the `fast-mode-2026-02-01`\n // beta — without the header Anthropic rejects the body field with\n // `speed: Extra inputs are not permitted`. Inject it here (not in the\n // static default betas) so the header is present exactly when the request\n // will carry `speed: \"fast\"`, keeping non-fast requests byte-stable for\n // the prompt cache.\n const requestBetas = options.modelOptions?.fast\n ? [...(anthropicParams?.extraBetas ?? []), FAST_MODE_BETA]\n : anthropicParams?.extraBetas\n // Per-call OAuth detection — see the matching note in `countTokens`.\n const callAuth = resolveAnthropicAuthShape(apiKey, anthropicParams?.authMode)\n const callIsOAuth = callAuth.claudeCode\n const client = createClient(\n SDK,\n apiKey,\n callAuth,\n anthropicParams?.baseURL,\n requestBetas,\n await resolveExtraHeaders(anthropicParams?.extraHeaders),\n )\n\n // System prompt injection is handled by agent.ts — we just pass it through.\n // For OAuth, the Claude Code API requires the system block to start with\n // the canonical Claude Code prompt — we prepend it to the real system prompt.\n // For OAuth, the CC API requires the system block to start with the canonical\n // Claude Code prompt. The real system prompt is injected as user+assistant messages.\n //\n // The boundary marker (`SYSTEM_PROMPT_BOUNDARY`) is structural metadata\n // for `applyAnthropicCacheBreakpoints`; strip it before the bytes hit\n // the wire so it never reaches the model. The original (un-rendered)\n // string is preserved as `options.system` and forwarded to the cache\n // helper below for an accurate static/dynamic split.\n const wireSystem = renderSystemForWire(options.system)\n const system = callIsOAuth\n ? `You are Claude Code, Anthropic's official CLI for Claude.`\n : wireSystem\n const messages: SessionMessage[] = callIsOAuth && options.system\n ? [\n { role: 'user', content: [{ type: 'text' as const, text: wireSystem }] },\n { role: 'assistant', content: [{ type: 'text' as const, text: 'Understood. I will proceed with these instructions above the rest of my system prompt.' }] },\n ...options.messages,\n ]\n : [...options.messages]\n const thinking = options.thinking ?? 'off'\n\n const modelId = options.model as Model\n // The model's max output, sourced from pi-ai's registry (Opus 4.x = 128K\n // native; conservative family fallback when the registry misses). Used as\n // BOTH the default (when `maxTokens` is unset — the field is required) AND\n // a hard clamp, since a value above the model's max would 400.\n const modelMaxOutput = await resolveAnthropicMaxOutput(modelId)\n\n // `context_management` is an untyped beta in SDK v0.90; widen `params` with\n // an optional field so we don't have to double-cast through `unknown` to\n // attach it below.\n const params: Anthropic.MessageCreateParamsStreaming & {\n context_management?: AnthropicContextManagement\n speed?: 'fast' | 'standard'\n // `task_budget` shipped after the pinned SDK's `OutputConfig` type;\n // widen until the SDK catches up.\n output_config?: { task_budget?: { type: 'tokens', total: number } }\n } = {\n // Forward-compat escape hatch for un-typed beta fields. Spread first so\n // the typed core (model / max_tokens / system / tools / messages /\n // stream) and the explicit `context_management` below override on\n // collision — explicit always wins.\n ...((anthropicParams?.extraBodyParams ?? {}) as Record<string, unknown>),\n model: modelId,\n // Required field — default to the model's full max output when unset.\n // The ceiling (incl. a caller cap above the model max) is enforced once,\n // after the thinking plan, since the `enabled` path adds budget on top.\n max_tokens: options.maxTokens ?? modelMaxOutput,\n system,\n tools: options.tools as Anthropic.ToolUnion[],\n messages: messages.map(m => toAnthropic(m)) as Anthropic.MessageParam[],\n stream: true,\n }\n\n // Server-side context management (beta `context-management-2025-06-27`).\n // Skipped when the caller didn't pass a config — leaving the field off\n // the wire keeps requests valid on accounts that don't have the beta enabled.\n if (anthropicParams?.contextManagement)\n params.context_management = anthropicParams.contextManagement\n\n // Prompt caching — inject `cache_control: { type: 'ephemeral' }` breakpoints\n // on the three largest stable prefixes: system prompt, tool definitions, and\n // the last message's final content block. Three of Anthropic's four allowed\n // breakpoints; leaves headroom for a future thinking-block breakpoint.\n //\n // On the API-key path, `params.system` was derived from\n // `renderSystemForWire(options.system)` so the cache helper can split\n // the original on the `SYSTEM_PROMPT_BOUNDARY` marker and apply\n // `cache_control` to the static prefix only — per-turn churn in the\n // dynamic suffix no longer invalidates the cached doctrine above.\n //\n // On the OAuth path, `params.system` is the canonical Claude Code\n // prompt (`\"You are Claude Code...\"`) — `options.system` lives in the\n // injected user message instead, so we do NOT forward it here. Passing\n // it would replace the CC prompt with the user's doctrine. The user\n // message is still naturally cached via the last-message breakpoint\n // applied below; per-turn churn there bypasses the boundary feature on\n // OAuth, but parity with the pre-feature behavior is preserved.\n //\n // Skipped when `options.cache === false` (opt-out) or on any shape that would\n // produce an invalid request (empty system string, zero tools, empty message list).\n if (options.cache !== false)\n applyAnthropicCacheBreakpoints(params, callIsOAuth ? undefined : options.system)\n\n // Enable thinking / extended thinking when requested. The default path\n // for level-based control is `thinking.type='adaptive'` plus an\n // `output_config.effort` hint — Anthropic deprecated the explicit\n // `thinking.type='enabled' + budget_tokens` shape on opus 4.6+ and\n // recommends adaptive instead. Callers who need a precise token budget\n // can opt into the explicit-budget path by setting\n // `behavior.thinkingBudget`, accepting Anthropic's deprecation\n // warning in exchange. Either shape requires `temperature=1`.\n //\n // `display: 'summarized'` is set explicitly on both shapes — Opus 4.7\n // and Claude Mythos Preview silently flipped the default to\n // `'omitted'` (signature-only, no text), which makes the SDK fire\n // `thinking` deltas with empty `thinking` content and breaks every\n // host that surfaces traces live. Older models still default to\n // `'summarized'` so this is a no-op there.\n const plan = planAnthropicThinking(thinking, options.thinkingBudget, options.taskBudget, options.effortHint !== false)\n if (plan) {\n if (plan.kind === 'enabled') {\n params.thinking = { type: 'enabled', budget_tokens: plan.budgetTokens, display: 'summarized' }\n params.max_tokens = plan.maxTokensBump + params.max_tokens\n // The explicit budget pins tokens; the effort hint still shapes HOW\n // the model spends them. Both fields are valid together on 4.6+.\n // Merge onto any `output_config` the caller passed via\n // `extraBodyParams` (spread first above) rather than clobbering it.\n if (plan.effort)\n params.output_config = { ...params.output_config, effort: plan.effort }\n }\n else {\n params.thinking = { type: 'adaptive', display: 'summarized' }\n const outputConfig: NonNullable<typeof params.output_config> = {}\n if (plan.effort)\n outputConfig.effort = plan.effort\n // Native whole-task thinking cap — adaptive-only. Coexists with the\n // effort hint.\n if (typeof plan.taskBudgetTokens === 'number' && plan.taskBudgetTokens > 0)\n outputConfig.task_budget = { type: 'tokens', total: plan.taskBudgetTokens }\n // Merge onto any caller-supplied `output_config` from\n // `extraBodyParams`; don't drop their fields.\n if (Object.keys(outputConfig).length > 0)\n params.output_config = { ...params.output_config, ...outputConfig }\n // LEGACY (`thinkingBudget` on adaptive): soft-cap the response\n // envelope (`max_tokens`). This clamps text and tool-call JSON too —\n // prefer `taskBudget` above. Reduce only; never raise above the\n // caller's request.\n if (typeof plan.maxTokensCap === 'number' && plan.maxTokensCap > 0)\n params.max_tokens = Math.min(params.max_tokens, plan.maxTokensCap)\n }\n params.temperature = 1\n }\n\n // The `enabled` thinking path adds the budget ON TOP of `max_tokens`,\n // which — now that the base defaults to the model's full max — can push\n // the envelope past the model's hard ceiling → 400. Clamp the final value\n // so the budget shares the envelope rather than overflowing it.\n params.max_tokens = Math.min(params.max_tokens, modelMaxOutput)\n\n // Fast mode (`speed: \"fast\"`) — ~2.5× output throughput at premium\n // pricing. Supported only on the models in `FAST_MODE_OPTIONS`\n // (chat/anthropic-models.ts); the model-options surface only offers it\n // on those, and Anthropic 400s a request with\n // `speed` on an unsupported model. `speed` is an untyped beta field in\n // SDK v0.90 (widened on `params` above).\n if (options.modelOptions?.fast)\n params.speed = 'fast'\n\n // Tool choice forcing\n if (options.toolChoice) {\n if (options.toolChoice.type === 'tool' && options.toolChoice.name)\n params.tool_choice = { type: 'tool', name: options.toolChoice.name }\n else if (options.toolChoice.type === 'required')\n params.tool_choice = { type: 'any' }\n else\n params.tool_choice = { type: 'auto' }\n }\n\n const runStream = async (\n requestParams: typeof params,\n fastRequested: boolean,\n scrubPass = 0,\n ): Promise<TurnResult> => {\n const s = client.messages.stream(requestParams, {\n signal: options.signal,\n })\n\n let text = ''\n let observedOutput = false\n\n s.on('text', (delta) => {\n observedOutput = true\n text += delta\n callbacks.onText(delta)\n })\n\n if (callbacks.onThinking) {\n s.on('thinking', (delta) => {\n observedOutput = true\n callbacks.onThinking!(delta)\n })\n }\n\n // Liveness signal for tool-only turns: `inputJson` fires as a\n // `tool_use` block's arguments stream in. Without it, a turn that\n // emits only tool calls (no text/thinking) never trips the loop's\n // \"first response observed\" gate and the first-response watchdog can\n // abort an actively-progressing stream. (B2)\n if (callbacks.onToolCallDelta) {\n s.on('inputJson', () => {\n observedOutput = true\n callbacks.onToolCallDelta!()\n })\n }\n\n // Server-tool blocks (e.g. `web_search_20250305`) execute on Anthropic's\n // side and arrive in the assistant content stream — not via our tool\n // loop. The SDK fires `contentBlock` once per block as it closes, so\n // listen for the two we care about and dispatch generic callbacks.\n // Other block types (text, thinking, tool_use, …) have their own paths\n // above / in `finalMessage()` — ignored here to avoid double-emission.\n if (callbacks.onServerToolUse || callbacks.onServerToolResult) {\n s.on('contentBlock', (block) => {\n observedOutput = true\n if (block.type === 'server_tool_use') {\n callbacks.onServerToolUse?.({\n id: block.id,\n name: block.name,\n input: (block.input as Record<string, unknown> | undefined) ?? {},\n })\n }\n else if (block.type === 'web_search_tool_result') {\n callbacks.onServerToolResult?.({\n toolUseId: block.tool_use_id,\n toolName: 'web_search',\n content: block.content,\n })\n }\n })\n }\n\n let response: Anthropic.Message\n try {\n response = await s.finalMessage()\n }\n catch (err) {\n if (!observedOutput && scrubPass < MAX_THINKING_SCRUB_PASSES && isRecoverableThinkingBlockError(err, requestParams.messages)) {\n const failingIndex = parseFailingMessageIndex(err)\n // Escalation ladder: the first pass scrubs only at/before the\n // API-reported failing index (preserving still-valid thinking\n // later in the wire). If the retry hits ANOTHER signature 400 —\n // the API reports one bad locator at a time, so scattered stale\n // blocks surface index-by-index — stop chasing locators and\n // scrub the full history EXCEPT the active chain. See\n // {@link planThinkingScrub} for the chain-protection rules\n // (never strip the in-flight exchange's thinking wholesale;\n // Anthropic requires it echoed back).\n const plan = planThinkingScrub(scrubPass, failingIndex, requestParams.messages)\n const scrubbed = withoutHistoricalThinking(requestParams.messages, plan.upToIndex, plan.alsoMessageIndex)\n // Guard against an infinite retry if Anthropic reports the same\n // error despite there being nothing left to strip (or if this is\n // already the scrubbed retry).\n if (scrubbed.removed > 0) {\n // Ask the loop to repair + persist its canonical history before\n // retrying. Direct provider consumers may omit the callback;\n // the scrubbed wire retry still succeeds for this turn.\n try {\n await callbacks.onHistoryRepair?.({\n type: 'invalid-anthropic-thinking-signature',\n ...(failingIndex !== undefined ? { wireMessageIndex: failingIndex } : {}),\n })\n }\n catch (repairErr) {\n // Canonical persistence is best-effort; never let a host\n // callback block the safe wire-level recovery for this turn.\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/anthropic] history repair callback failed: ${errorMessage(repairErr)}\\n`)\n }\n return await runStream(\n { ...requestParams, messages: scrubbed.messages },\n fastRequested,\n scrubPass + 1,\n )\n }\n }\n if (fastRequested && !observedOutput && shouldFallbackFromFastMode(err)) {\n const standardParams = { ...requestParams }\n delete standardParams.speed\n return await runStream(standardParams, false)\n }\n throw err\n }\n\n const toolCalls = response.content\n .filter((b): b is Anthropic.ToolUseBlock => b.type === 'tool_use')\n .map(b => ({ id: b.id, name: b.name, input: b.input as Record<string, unknown> }))\n\n const finishReason = mapStopReason(response.stop_reason)\n\n // `pause_turn` is *not* terminal — the loop has to inject a continue\n // message and run another turn. Without this branch, the previous\n // `toolCalls.length === 0` shortcut would mark the run done at the\n // pause and silently drop the work in flight.\n const isPause = response.stop_reason === 'pause_turn'\n\n const rawUsage: TurnUsage = {\n input: response.usage.input_tokens,\n output: response.usage.output_tokens,\n cacheCreation: response.usage.cache_creation_input_tokens ?? undefined,\n cacheRead: response.usage.cache_read_input_tokens ?? undefined,\n ...(finishReason ? { finishReason } : {}),\n modelId: response.model ?? (options.model as string),\n }\n // OAuth = subscription traffic (Claude Pro/Max via a Claude Code\n // account): usage is included in the flat plan, not billed per\n // token. Estimating API-list prices for it produces large dollar\n // figures the user never pays — so `cost` stays absent and cost\n // surfaces (footer counter, usage-by-model rows) show tokens\n // without a price, matching Claude Code's own /cost behavior.\n // API-key turns estimate as before; a mixed session (OAuth primary\n // with an API-key fallback, or vice versa) prices each turn by how\n // it was actually served.\n const usage = callIsOAuth\n ? rawUsage\n : await fillEstimatedCost(rawUsage, 'anthropic', fastRequested\n ? fastModeCostMultiplier(response.model ?? (options.model as string))\n : undefined)\n return {\n assistantMessage: fromAnthropic({ role: 'assistant', content: response.content }),\n text,\n toolCalls,\n done: !isPause && (response.stop_reason === 'end_turn' || toolCalls.length === 0),\n usage,\n }\n }\n\n return await runStream(params, options.modelOptions?.fast === true)\n },\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache-telemetry-BhTryxFT.js","names":[],"sources":["../src/cache-telemetry.ts"],"sourcesContent":["/**\n * Per-dimension cache-break detection.\n *\n * The agent surfaces `cacheRead` / `cacheCreation` in {@link TurnUsage}, but\n * doesn't answer the load-bearing diagnostic question: *which* dimension\n * busted the cache? When `cacheCreation > 0` on turn N+1 with no obvious\n * reason, you want to know whether the system prompt, the tool list, the\n * thinking config, or the model id changed — without re-reading the loop\n * source.\n *\n * This module hashes each cache-affecting dimension at `turn:before`, then\n * on `turn:after` checks whether the latest turn read its share of the\n * cached prefix. If not, it diffs the previous turn's dimension hashes\n * against the current ones and logs the responsible dimension(s).\n *\n * Wire contract:\n *\n * - Install via {@link installCacheBreakLogger} on the agent's hook bus.\n * Returns an uninstall fn. Idempotent — calling install twice on the\n * same bus leaves two listeners; callers manage lifecycle.\n * - Output sink defaults to `console.error`; pass `log` to redirect into a\n * {@link Logger}, span attribute writer, or test buffer.\n * - Heuristic only: a missing cache read on turn 1 is expected (nothing\n * cached yet); the logger only reports from turn 2 onward.\n * - Pure helper {@link diffCacheDimensions} is exported for tests so the\n * diff logic can be exercised without standing up the loop.\n *\n * The dimensions tracked are the inputs the provider treats as part of the\n * cache key — `system`, `tools`, `model`, thinking config. Tools are hashed\n * after `provider.formatTools`, so MCP catalogues showing up mid-run also\n * register as a break. Messages are NOT tracked: the conversation grows by\n * design, and the last-message breakpoint is meant to advance every turn.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\nimport type { StreamOptions } from './providers'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Snapshot of per-request dimensions that contribute to the provider-side\n * prompt cache key. Hashed at `turn:before` and stashed for diffing on the\n * next request.\n *\n * Hashes are FNV-1a 32-bit (cheap, no allocations on the hot path,\n * collision rate good enough for diff detection — we're not signing\n * anything). For verbose output the helper also captures byte lengths so\n * `system: 1024 → 1031` reads like a real diff line.\n */\nexport interface CacheDimensionSnapshot {\n /** Hash of `options.system` (with boundary marker preserved). */\n systemHash: number\n /** Byte length of `options.system`. */\n systemBytes: number\n /** Hash of the JSON-serialized `options.tools` array. */\n toolsHash: number\n /** Number of tools in the array. */\n toolsCount: number\n /** Model id. */\n model: string\n /** Thinking level — `'off'` when absent. */\n thinking: string\n /** Exact thinking budget — `0` when absent. */\n thinkingBudget: number\n /** Whole-task thinking budget (Anthropic adaptive-only) — `0` when absent. */\n taskBudget: number\n /** Whether the level-derived effort hint is emitted — `true` when absent. */\n effortHint: boolean\n}\n\n/** Result of {@link diffCacheDimensions}. */\nexport interface CacheDimensionDiff {\n /** Dimension names whose hashes differ between the two snapshots. */\n changed: readonly CacheDimensionName[]\n /** Per-dimension details — only filled for changed dimensions. */\n details: Readonly<Partial<Record<CacheDimensionName, string>>>\n}\n\n/** Cache-affecting dimension names. */\nexport type CacheDimensionName\n = | 'system'\n | 'tools'\n | 'model'\n | 'thinking'\n | 'thinkingBudget'\n | 'taskBudget'\n | 'effortHint'\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\nexport interface CacheBreakLoggerOptions {\n /**\n * Sink for log lines. Defaults to `console.error`. Use a {@link Logger} or\n * a test buffer here when you want the output to land somewhere other than\n * stderr.\n */\n log?: (line: string) => void\n /**\n * When `true`, emit a log line on EVERY turn (not just cache breaks) with\n * the per-dimension hashes. Useful for offline correlation; off by default\n * to keep the logger zero-noise on happy paths.\n */\n verbose?: boolean\n /**\n * Threshold for \"this turn missed cache\". The logger reports when\n * `cacheRead < expectedReadAtLeast` AND the dimensions differ from the\n * previous turn. Defaults to `1` — any cache read at all means the\n * provider matched some prefix, so a strict \"did we get any cache?\" check\n * is the right signal. Raise to detect partial-prefix breaks (e.g. the\n * tools cache hit but the system prompt didn't, which lowers `cacheRead`\n * without zeroing it).\n */\n expectedReadAtLeast?: number\n}\n\n// ---------------------------------------------------------------------------\n// Hashing\n// ---------------------------------------------------------------------------\n\nconst FNV_OFFSET = 0x811C9DC5\nconst FNV_PRIME = 0x01000193\n\n/**\n * FNV-1a-style 32-bit hash over UTF-16 code units. Pure, allocation-free,\n * ~10 ns per KB. Cryptographic strength is irrelevant here — we only need\n * change detection, and this has effectively zero collision risk for the\n * small number of dimensions tracked.\n *\n * Feeds the FULL char code into the mix (same as read-state's\n * `hashContent`) — masking to the low byte would collapse code points that\n * share a low byte, so a non-ASCII system-prompt change could hash\n * identically and silently hide a cache-dimension drift.\n *\n * Exported for tests; callers shouldn't typically import directly.\n */\nexport function fnv1a32(s: string): number {\n let h = FNV_OFFSET\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n // Multiply by FNV_PRIME mod 2^32, using `Math.imul` to keep within\n // 32-bit signed range (avoid >>> 0 ping-pong on every iteration).\n h = Math.imul(h, FNV_PRIME)\n }\n // Map signed → unsigned 32-bit so the printed hex is stable.\n return h >>> 0\n}\n\n/**\n * Build a {@link CacheDimensionSnapshot} from a `turn:before` payload.\n *\n * Pure — exported so hosts can compute snapshots outside the logger (e.g.\n * stamping them onto custom telemetry spans). The hash is deterministic on\n * byte-identical inputs.\n */\nexport function snapshotCacheDimensions(options: StreamOptions): CacheDimensionSnapshot {\n const toolsJson = JSON.stringify(options.tools ?? [])\n return {\n systemHash: fnv1a32(options.system),\n systemBytes: options.system.length,\n toolsHash: fnv1a32(toolsJson),\n toolsCount: Array.isArray(options.tools) ? options.tools.length : 0,\n model: options.model,\n thinking: options.thinking ?? 'off',\n thinkingBudget: options.thinkingBudget ?? 0,\n taskBudget: options.taskBudget ?? 0,\n effortHint: options.effortHint !== false,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Diffing\n// ---------------------------------------------------------------------------\n\n/**\n * Compare two snapshots and return the list of dimensions whose bytes\n * differ. The `details` map carries a short human-readable description per\n * changed dimension (e.g. `system: 1024 → 1031 bytes`).\n *\n * Pure / no I/O. Use this in tests to assert the diff logic; the logger\n * itself just formats and emits the result.\n */\nexport function diffCacheDimensions(\n prev: CacheDimensionSnapshot,\n curr: CacheDimensionSnapshot,\n): CacheDimensionDiff {\n const changed: CacheDimensionName[] = []\n const details: Partial<Record<CacheDimensionName, string>> = {}\n\n if (prev.systemHash !== curr.systemHash) {\n changed.push('system')\n details.system = `${prev.systemBytes} → ${curr.systemBytes} bytes (hash ${hex(prev.systemHash)} → ${hex(curr.systemHash)})`\n }\n if (prev.toolsHash !== curr.toolsHash) {\n changed.push('tools')\n details.tools = `${prev.toolsCount} → ${curr.toolsCount} tools (hash ${hex(prev.toolsHash)} → ${hex(curr.toolsHash)})`\n }\n if (prev.model !== curr.model) {\n changed.push('model')\n details.model = `${prev.model} → ${curr.model}`\n }\n if (prev.thinking !== curr.thinking) {\n changed.push('thinking')\n details.thinking = `${prev.thinking} → ${curr.thinking}`\n }\n if (prev.thinkingBudget !== curr.thinkingBudget) {\n changed.push('thinkingBudget')\n details.thinkingBudget = `${prev.thinkingBudget} → ${curr.thinkingBudget}`\n }\n if (prev.taskBudget !== curr.taskBudget) {\n changed.push('taskBudget')\n details.taskBudget = `${prev.taskBudget} → ${curr.taskBudget}`\n }\n if (prev.effortHint !== curr.effortHint) {\n changed.push('effortHint')\n details.effortHint = `${prev.effortHint} → ${curr.effortHint}`\n }\n\n return { changed, details }\n}\n\nfunction hex(n: number): string {\n return `0x${n.toString(16).padStart(8, '0')}`\n}\n\n// ---------------------------------------------------------------------------\n// Hook installer\n// ---------------------------------------------------------------------------\n\n/**\n * Install the cache-break logger on an agent's hook bus.\n *\n * Subscribes to `turn:before` (snapshot) + `turn:after` (compare with prior\n * snapshot, log breaks). Returns an `uninstall` fn that detaches both\n * listeners. Idempotent w.r.t. agent lifecycle — installs/uninstalls cleanly\n * across runs.\n *\n * Recommended setup:\n *\n * ```ts\n * const uninstall = installCacheBreakLogger(agent.hooks, {\n * log: line => logger.debug(line),\n * verbose: !!process.env.ZIDANE_DEBUG?.includes('cache'),\n * })\n * // ...\n * uninstall()\n * ```\n *\n * Performance: hashing runs on each `turn:before` and is bounded by the\n * input size (system + tools serialization). Disabled callers pay nothing\n * (don't install).\n */\nexport function installCacheBreakLogger(\n hooks: Hookable<AgentHooks>,\n options: CacheBreakLoggerOptions = {},\n): () => void {\n const log = options.log ?? ((line: string) => console.error(line))\n const verbose = options.verbose === true\n const expectedReadAtLeast = options.expectedReadAtLeast ?? 1\n\n // Per-bus latest snapshot. Keyed implicitly by the bus instance —\n // installing on a fresh bus starts from empty. Across runs on the same\n // bus, the snapshot persists, which is what we want: the first turn of\n // run N+1 should hit cache if it shares a prefix with the last turn of\n // run N. The detector reports correctly across that boundary.\n let prev: CacheDimensionSnapshot | undefined\n let turnIndex = 0\n let pending: { snapshot: CacheDimensionSnapshot, turnIndex: number, turnId: string } | undefined\n\n function onTurnBefore(ctx: { turn: number, turnId: string, options: StreamOptions }): void {\n const curr = snapshotCacheDimensions(ctx.options)\n turnIndex += 1\n\n if (verbose) {\n log(formatVerboseLine({\n turn: turnIndex,\n turnId: ctx.turnId,\n snapshot: curr,\n }))\n }\n\n // Stash for the matching `turn:after`. We can't compute the diff here —\n // the cache-read result lands on `turn:after.usage`.\n pending = { snapshot: curr, turnIndex, turnId: ctx.turnId }\n }\n\n function onTurnAfter(ctx: { turn: number, turnId: string, usage: { cacheRead?: number, cacheCreation?: number } }): void {\n if (!pending || pending.turnId !== ctx.turnId)\n return\n const { snapshot: curr, turnIndex: tIdx } = pending\n pending = undefined\n\n // Skip break detection on providers that don't report `cacheRead` —\n // treating undefined as 0 would log a false break every turn on Codex,\n // Cerebras, or any host without a cache_read field. We can only diagnose\n // breaks when the provider tells us how much cache was actually used.\n if (prev && tIdx > 1 && typeof ctx.usage.cacheRead === 'number') {\n const cacheRead = ctx.usage.cacheRead\n if (cacheRead < expectedReadAtLeast) {\n const diff = diffCacheDimensions(prev, curr)\n if (diff.changed.length > 0) {\n log(formatBreakLine({\n turn: tIdx,\n turnId: ctx.turnId,\n cacheRead,\n cacheCreation: ctx.usage.cacheCreation ?? 0,\n diff,\n }))\n }\n }\n }\n\n prev = curr\n }\n\n // Hookable's `.hook()` returns its own unregister fn — keep both so\n // `uninstall()` is symmetric with the install path and survives a\n // future hookable rename of `removeHook`.\n const unregisterBefore = hooks.hook('turn:before', onTurnBefore)\n const unregisterAfter = hooks.hook('turn:after', onTurnAfter)\n\n return () => {\n unregisterBefore()\n unregisterAfter()\n }\n}\n\n// ---------------------------------------------------------------------------\n// Formatting\n// ---------------------------------------------------------------------------\n\nfunction formatBreakLine(input: {\n turn: number\n turnId: string\n cacheRead: number\n cacheCreation: number\n diff: CacheDimensionDiff\n}): string {\n const names = input.diff.changed.join(', ')\n const details = input.diff.changed\n .map(d => ` ${d}: ${input.diff.details[d]}`)\n .join('\\n')\n return [\n `[zidane:cache-break] turn=${input.turn} turnId=${input.turnId} cacheRead=${input.cacheRead} cacheCreation=${input.cacheCreation}`,\n ` changed: ${names}`,\n details,\n ].join('\\n')\n}\n\nfunction formatVerboseLine(input: {\n turn: number\n turnId: string\n snapshot: CacheDimensionSnapshot\n}): string {\n const s = input.snapshot\n return `[zidane:cache] turn=${input.turn} turnId=${input.turnId} system=${hex(s.systemHash)}(${s.systemBytes}b) tools=${hex(s.toolsHash)}(${s.toolsCount}) model=${s.model} thinking=${s.thinking}/${s.thinkingBudget}${s.taskBudget ? `/task=${s.taskBudget}` : ''}`\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"contexts-o8y5B3Tm.js","names":[],"sources":["../src/contexts/process.ts","../src/contexts/types.ts"],"sourcesContent":["/**\n * In-process execution context.\n *\n * Runs everything in the current Node/Bun process.\n * No isolation — fastest, used as the default.\n */\n\nimport type { Buffer } from 'node:buffer'\nimport type { ChildProcess } from 'node:child_process'\nimport type { WriteStream } from 'node:fs'\nimport type { CappedSlot } from './output-cap'\nimport type { ContextCapabilities, ExecResult, ExecutionContext, ExecutionHandle, ListFilesEntry, SpawnConfig, TaskEntry, TaskExitInfo, TaskHandle, TaskStallInfo } from './types'\nimport { createWriteStream } from 'node:fs'\nimport { mkdir, readdir, readFile, realpath, stat, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { lazyAsync } from '../lazy'\nimport { globFilesLocal, globLiteralPrefix } from './glob'\nimport { appendCapped, DEFAULT_MAX_OUTPUT_BUFFER, outputExceededMessage } from './output-cap'\n\n/**\n * Lazily load `node:child_process`'s `spawn` (memoized after first use).\n *\n * Deliberately NOT a static top-level import. `createAgent` pulls this module\n * into the graph as the default execution context, so a static\n * `import … from 'node:child_process'` would make merely importing zidane\n * depend on subprocess support. Runtimes that gate that builtin at import time\n * — Supabase Edge Functions, Cloudflare `nodejs_compat`, other shell-less V8\n * isolates — would then fail to load the package at all. Deferring the import\n * to the first `exec` keeps `import 'zidane'` + the provider loop runnable\n * there; only actually invoking a shell command touches subprocess, which is\n * exactly where an unsupported host should fail (the agent loop turns that\n * throw into a clean tool-result error).\n */\nconst loadSpawn = lazyAsync(async () => (await import('node:child_process')).spawn)\n\n/**\n * Whether the host supports POSIX process groups (the `detached: true` +\n * `process.kill(-pid)` combination). Windows doesn't — its job-object\n * model is shaped differently — so on win32 we fall back to killing the\n * shell wrapper alone (matches pre-fix behavior; better than nothing).\n */\nexport const SUPPORTS_PROCESS_GROUPS = process.platform !== 'win32'\n\n/**\n * Default cap on captured stdout / stderr per child. Matches the\n * pre-fix `child_process.exec` setting so existing callers see the\n * same buffer envelope. Output beyond this is truncated and a\n * marker is appended to stderr.\n */\n\n/**\n * How long `destroy()` waits for a SIGTERM'd background task to settle\n * before escalating to SIGKILL and abandoning the wait. Matches the MCP\n * connection's default `closeTimeout` so `agent.destroy()` stays bounded\n * by a single grace period regardless of which leg is slowest.\n */\nconst DESTROY_SIGTERM_GRACE_MS = 5_000\n\n/**\n * How long an `exec` kill (timeout / abort / output-overflow) waits after\n * SIGTERM before escalating the process group to SIGKILL. A child that\n * traps or ignores TERM (docker CLI parked on a container, a shell trap,\n * an uninterruptible wait) otherwise never exits — the exec promise never\n * settles and the caller's transport deadline fires instead of a clean\n * `command timed out` result (prod incident 2026-07-17).\n */\nconst EXEC_KILL_ESCALATION_GRACE_MS = 5_000\n\n/**\n * How long after the child's `exit` event we keep waiting for `close`\n * before settling with the output collected so far.\n *\n * `close` fires only once every stdio pipe has closed — and a\n * daemonized grandchild that inherited stdout/stderr (a dev server\n * started with `&`, `nohup` without redirection, simulator helpers, …)\n * keeps those pipes open indefinitely. Pre-fix, a foreground `exec`\n * whose command finished in milliseconds would hang until its timeout,\n * then `killProcessGroup` took the intentionally-backgrounded daemon\n * down with it. The process itself is done at `exit`; this grace only\n * exists to let normally-closing pipes flush their tail bytes.\n */\nconst EXIT_PIPE_DRAIN_GRACE_MS = 1_500\n\n/**\n * Sanitize a task id before it's joined into a filesystem path.\n *\n * We mint `bash_<n>` ids ourselves (no user input flows into the path\n * for `ProcessContext`), so this is defensive — but third-party contexts\n * MAY accept caller-provided ids, so the helper exists for them too.\n * Anything that doesn't match the expected shape is rejected — never\n * coerced — so the call site sees a clear error rather than a\n * traversal-shaped path.\n */\nconst TASK_ID_RE = /^[a-z][\\w-]*$/i\n\nfunction assertSafeTaskId(taskId: string): void {\n if (!TASK_ID_RE.test(taskId))\n throw new Error(`Invalid task id \"${taskId}\" — must match ${TASK_ID_RE}.`)\n}\n\n/**\n * Format `date` as `YYYYMMDD-HHMMSS-mmm` in UTC.\n *\n * Pinned to UTC so the lexical sort of two timestamps always matches\n * their chronological order (local time + DST does not). Used as the\n * per-context suffix on background-task log filenames; see the field\n * doc on `contextTimestamp` for the why.\n */\nexport function formatContextTimestamp(date: Date): string {\n const pad2 = (n: number): string => n.toString().padStart(2, '0')\n const pad3 = (n: number): string => n.toString().padStart(3, '0')\n const y = date.getUTCFullYear()\n const M = pad2(date.getUTCMonth() + 1)\n const d = pad2(date.getUTCDate())\n const h = pad2(date.getUTCHours())\n const m = pad2(date.getUTCMinutes())\n const s = pad2(date.getUTCSeconds())\n const ms = pad3(date.getUTCMilliseconds())\n return `${y}${M}${d}-${h}${m}${s}-${ms}`\n}\n\n/** Pattern of a background-task log filename. Used by tests + tooling. */\nexport const TASK_LOG_FILENAME_RE = /^(bash_\\d+)\\.(\\d{8}-\\d{6}-\\d{3})\\.log$/\n\n/** Copy only the named keys (when present) from an env object. */\nfunction pickEnv(env: NodeJS.ProcessEnv, keys: string[]): NodeJS.ProcessEnv {\n const out: NodeJS.ProcessEnv = {}\n for (const key of keys) {\n if (env[key] !== undefined)\n out[key] = env[key]\n }\n return out\n}\n\nexport function createProcessContext(config?: SpawnConfig): ExecutionContext {\n let counter = 0\n const handles = new Map<string, ExecutionHandle>()\n const defaultCwd = config?.cwd ?? process.cwd()\n const defaultEnv = config?.env\n const destroyGraceMs = config?.destroyGraceMs ?? DESTROY_SIGTERM_GRACE_MS\n\n // Base environment children inherit. Defaults to the full parent env\n // (back-compat); when `inheritEnv: false`, only a minimal allow-list is\n // forwarded so parent secrets (e.g. API keys Bun loaded from `.env`) aren't\n // exposed to tool commands. Explicit `env` / per-call env always apply on top.\n const baseEnv: NodeJS.ProcessEnv = config?.inheritEnv === false\n ? pickEnv(process.env, ['PATH', 'HOME', 'SHELL', 'LANG', 'LC_ALL', 'USER', 'TERM', 'TMPDIR'])\n : process.env\n\n // Optional workspace containment for the file methods. Unset = no checks.\n const workspaceRoot = config?.workspaceRoot !== undefined\n ? resolve(defaultCwd, config.workspaceRoot)\n : undefined\n\n /** True when `target` is the root itself or lives under it. */\n function isWithinRoot(root: string, target: string): boolean {\n const rel = relative(root, target)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n }\n\n /**\n * Resolve `path` against the handle cwd and, when a `workspaceRoot` is\n * configured, reject anything that escapes it. Uses `realpath` on the\n * nearest existing ancestor so symlink escapes (and `..`) are caught for\n * both existing and not-yet-created targets.\n */\n async function resolveContained(handle: ExecutionHandle, path: string): Promise<string> {\n const full = resolve(handle.cwd, path)\n if (workspaceRoot === undefined)\n return full\n\n // Canonicalize the deepest existing ancestor to defeat symlink escapes;\n // the non-existent tail (for writes) is appended back verbatim.\n let existing = full\n const tail: string[] = []\n while (true) {\n try {\n existing = await realpath(existing)\n break\n }\n catch {\n const parent = dirname(existing)\n if (parent === existing) {\n // Reached the filesystem root without an existing ancestor.\n existing = parent\n break\n }\n // `relative` (not slice) so the segment is correct even on Windows\n // drive roots where `dirname('C:\\\\foo')` is `C:\\\\` (no 1-char sep).\n tail.unshift(relative(parent, existing))\n existing = parent\n }\n }\n const canonical = tail.length ? resolve(existing, ...tail) : existing\n const canonicalRoot = await realpath(workspaceRoot).catch(() => workspaceRoot)\n if (!isWithinRoot(canonicalRoot, canonical)) {\n throw new Error(\n `Path escapes the workspace root: ${path} (resolved outside ${canonicalRoot})`,\n )\n }\n return full\n }\n\n /**\n * Per-context background-task registry. Entries live for the context's\n * lifetime — even after the child exits — so the model can read output\n * of completed tasks until `destroy()` tears everything down. Kept as\n * a plain Map (not a class) per code-quality checklist #7: no premature\n * abstraction. The state and the operations on it live inline.\n */\n const tasks = new Map<string, TaskState>()\n let taskCounter = 0\n\n /**\n * Per-context UTC timestamp segment baked into every background task's\n * log filename. Same `taskCounter` value across two contexts (e.g. a\n * TUI restart) would otherwise re-open the SAME `bash_<n>.log` file —\n * we open with `flags: 'a'` so the new task would APPEND into the old\n * log, producing scrambled output. The timestamp guarantees each\n * context owns a distinct log filename without forcing the\n * model-facing task id (`bash_<n>`) to grow longer.\n *\n * Format: `YYYYMMDD-HHMMSS-mmm` in UTC.\n * - Sortable (lexical sort = chronological sort).\n * - Unambiguous (UTC sidesteps DST / locale shifts).\n * - Filesystem-safe (digits + hyphens only).\n * - Millisecond precision avoids same-second-restart collisions.\n *\n * The timestamp is computed once per context, NOT per task — all of a\n * context's tasks share the same suffix so a directory listing groups\n * cleanly by \"which run produced these\".\n */\n const contextTimestamp = formatContextTimestamp(new Date())\n\n /**\n * Last-resort orphan reaper. With `detached: true`, background tasks\n * are in their OWN process group — when the parent (zidane TUI) dies,\n * the OS does NOT send them SIGHUP and they keep running indefinitely.\n * The user's \"Ctrl+C the TUI\" intent is \"stop everything I started\",\n * not \"leak a `sleep 60` into the background\".\n *\n * `process.on('exit')` fires SYNCHRONOUSLY on `process.exit()` AND on\n * natural shutdown — exactly the seam we need. The handler can only\n * do synchronous work (Node ignores async), but `process.kill` IS\n * synchronous, so a SIGTERM-the-group sweep lands cleanly. The kill\n * is best-effort: already-dead children throw ESRCH (swallowed).\n *\n * Registered lazily on first `execBackground` so contexts that never\n * background a task don't pay the listener cost. Deregistered in\n * `destroy()` so reconstructed contexts don't accumulate listeners\n * (Node warns past 10 — a long-running session that switches sessions\n * frequently would otherwise hit that).\n */\n let exitHandlerRegistered = false\n const exitHandler = (): void => {\n for (const task of tasks.values()) {\n if (task.status !== 'running')\n continue\n const pid = task.child?.pid\n if (pid === undefined)\n continue\n try {\n if (SUPPORTS_PROCESS_GROUPS)\n process.kill(-pid, 'SIGTERM')\n else\n process.kill(pid, 'SIGTERM')\n }\n catch {\n // ESRCH (already dead) / EPERM (lost ownership). Swallow —\n // the process either won't kill cleanly OR is already gone,\n // both acceptable at shutdown.\n }\n }\n }\n\n return {\n type: 'process',\n\n capabilities: {\n shell: true,\n filesystem: true,\n glob: true,\n network: true,\n gpu: false,\n // Background tasks are OS children of this process's machine —\n // they survive `agent.destroy()` reassignment games but NOT a\n // host machine reboot, and the orphan reaper kills them on\n // process exit. That's the 'process-lifetime' tier.\n detachedTasks: 'process-lifetime',\n } satisfies ContextCapabilities,\n\n async spawn(overrides?: SpawnConfig): Promise<ExecutionHandle> {\n const id = `process-${++counter}`\n const cwd = overrides?.cwd ?? defaultCwd\n\n // Only create `cwd` when it is genuinely missing. An unconditional\n // `mkdir(cwd, { recursive: true })` on an already-existing directory is a\n // wasted write that needlessly demands FS-write permission — which breaks\n // read-only / no-write runtimes (Supabase Edge Functions write only to\n // `/tmp`; Deno edge isolates) where the cwd already exists. `stat` is a\n // read (permitted there); the write is attempted only on a confirmed\n // ENOENT. Any other stat failure (e.g. EACCES on a sandbox-restricted\n // path) must NOT escalate to a mkdir we'd also be denied — assume present\n // and let a later file op surface a precise error if it truly isn't.\n let needsCreate = false\n try {\n await stat(cwd)\n }\n catch (err) {\n needsCreate = (err as NodeJS.ErrnoException)?.code === 'ENOENT'\n }\n if (needsCreate)\n await mkdir(cwd, { recursive: true })\n\n const handle: ExecutionHandle = { id, type: 'process', cwd }\n handles.set(id, handle)\n return handle\n },\n\n async exec(\n handle: ExecutionHandle,\n command: string,\n options?: { cwd?: string, env?: Record<string, string>, timeout?: number, signal?: AbortSignal },\n ): Promise<ExecResult> {\n const cwd = options?.cwd ? resolve(handle.cwd, options.cwd) : handle.cwd\n\n // Pre-aborted fast path: skip the spawn entirely and synthesize a\n // killed-by-signal result. Saves a spawn round-trip + dodges Node\n // emitting an immediate `AbortError`.\n if (options?.signal?.aborted) {\n return { stdout: '', stderr: 'aborted by signal before spawn', exitCode: 143 }\n }\n\n const timeoutMs = (options?.timeout ?? config?.limits?.timeout ?? 30) * 1000\n const maxBuffer = DEFAULT_MAX_OUTPUT_BUFFER\n\n // Lazy subprocess load (see `loadSpawn`) — awaited before the Promise\n // executor so the spawn primitive is ready synchronously inside it.\n const spawnChild = await loadSpawn()\n\n return new Promise<ExecResult>((resolveP) => {\n // Spawn as a NEW process group leader so we can kill the whole\n // subtree on abort. Without `detached: true`, sending SIGTERM to\n // the shell's pid only kills the shell wrapper — its child\n // processes (the actual `sleep`, `npm`, `python`, …) get\n // reparented to init and keep running. `process.kill(-pid, …)`\n // with a NEGATIVE pid targets the whole process group; that's\n // the POSIX idiom for \"shut down everything I started\".\n //\n // On Windows there are no process groups in the POSIX sense, so\n // we leave `detached` off and accept the shell-only kill — the\n // platform's job-object machinery is the path forward there if\n // we ever need it, but it's not the bug zidane's users are\n // hitting today.\n const child = spawnChild('/bin/sh', ['-c', command], {\n cwd,\n env: { ...baseEnv, ...defaultEnv, ...options?.env },\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: SUPPORTS_PROCESS_GROUPS,\n })\n\n // Byte-capped accumulation via the shared exec-transport primitive\n // (see contexts/output-cap.ts) — byte accounting + codepoint-safe\n // cuts. Overflow orchestration (kill the process group, matching\n // `execAsync`'s maxBuffer behavior) stays here.\n const stdoutSlot: CappedSlot = { text: '', bytes: 0 }\n const stderrSlot: CappedSlot = { text: '', bytes: 0 }\n let bufferTruncated = false\n let timedOut = false\n let killedByAbort = false\n let settled = false\n\n // Every exec-initiated kill escalates: SIGTERM now, SIGKILL after a\n // short grace unless the child settled in between. Without this, a\n // TERM-ignoring child leaves the promise pending forever.\n const killWithEscalation = (): void => {\n killProcessGroup(child, 'SIGTERM')\n const escalation = setTimeout(() => {\n if (!settled)\n killProcessGroup(child, 'SIGKILL')\n }, EXEC_KILL_ESCALATION_GRACE_MS)\n escalation.unref?.()\n }\n\n const onChunk = (slot: CappedSlot, chunk: Buffer): void => {\n const outcome = appendCapped(slot, chunk, maxBuffer)\n if (outcome !== 'ok' && !bufferTruncated) {\n bufferTruncated = true\n killWithEscalation()\n }\n }\n\n child.stdout?.on('data', chunk => onChunk(stdoutSlot, chunk as Buffer))\n child.stderr?.on('data', chunk => onChunk(stderrSlot, chunk as Buffer))\n\n const timeoutTimer = timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true\n killWithEscalation()\n }, timeoutMs)\n : undefined\n\n const onAbort = (): void => {\n killedByAbort = true\n killWithEscalation()\n }\n const userSignal = options?.signal\n if (userSignal)\n userSignal.addEventListener('abort', onAbort, { once: true })\n\n let exitGraceTimer: NodeJS.Timeout | undefined\n\n const settle = (exitCode: number, extraStderr?: string): void => {\n if (settled)\n return\n settled = true\n if (timeoutTimer)\n clearTimeout(timeoutTimer)\n if (exitGraceTimer)\n clearTimeout(exitGraceTimer)\n if (userSignal)\n userSignal.removeEventListener('abort', onAbort)\n const finalStderr = extraStderr\n ? (stderrSlot.text ? `${stderrSlot.text}\\n${extraStderr}` : extraStderr)\n : stderrSlot.text\n resolveP({ stdout: stdoutSlot.text, stderr: finalStderr, exitCode })\n }\n\n // Shared classification ladder for `close` (fast path: pipes\n // drained normally) and the post-`exit` grace (pipes held open\n // by a daemonized grandchild). Order matters: killed-by-our-\n // abort wins over timeout wins over natural exit, because the\n // abort listener fires first when the user cancels mid-\n // timeout-window.\n const settleFromStatus = (code: number | null, signal: NodeJS.Signals | null): void => {\n if (killedByAbort) {\n settle(143, 'aborted by signal')\n return\n }\n if (timedOut) {\n settle(124, `command timed out after ${timeoutMs}ms`)\n return\n }\n if (bufferTruncated) {\n settle(143, outputExceededMessage(maxBuffer, 'process killed'))\n return\n }\n if (signal) {\n // Killed by some other signal we didn't issue. Treat as\n // signal-killed for consumer compat.\n settle(128 + 15, `terminated by signal ${signal}`)\n return\n }\n settle(typeof code === 'number' ? code : 1)\n }\n\n child.on('error', (err) => {\n // Spawn failure (ENOENT on `/bin/sh`, EACCES, …). Mirror\n // `execAsync`'s \"rejects with an Error\" shape by surfacing\n // the message on stderr and a non-zero exit.\n settle(1, err.message)\n })\n\n child.on('close', (code, signal) => settleFromStatus(code, signal))\n\n child.on('exit', (code, signal) => {\n // The command is DONE here — `close` only adds \"every stdio\n // pipe closed\", which a daemonized grandchild can block\n // forever (see EXIT_PIPE_DRAIN_GRACE_MS). Give the pipes a\n // short drain grace, then settle with what we have and\n // release them. The daemon keeps running — that was the\n // command's intent; killing the group here would be wrong.\n exitGraceTimer = setTimeout(() => {\n if (settled)\n return\n child.stdout?.destroy()\n child.stderr?.destroy()\n settleFromStatus(code, signal)\n }, EXIT_PIPE_DRAIN_GRACE_MS)\n exitGraceTimer.unref?.()\n })\n })\n },\n\n async readFile(handle: ExecutionHandle, path: string): Promise<string> {\n return readFile(await resolveContained(handle, path), 'utf-8')\n },\n\n async fileSize(handle: ExecutionHandle, path: string): Promise<number | null> {\n try {\n // Same containment as every other file method. `stat` (NOT lstat):\n // the caller wants the TARGET's size — an lstat of a symlink\n // reports the link itself and a link to a 10GB file would sail\n // past the whole-read guard this probe exists to feed.\n const info = await stat(await resolveContained(handle, path))\n return info.isFile() ? info.size : null\n }\n catch {\n return null\n }\n },\n\n async readFileBinary(handle: ExecutionHandle, path: string): Promise<Uint8Array> {\n // No encoding → returns a Buffer (which is a Uint8Array). Used by\n // read_file to ferry image / binary content into the multimodal route.\n const buf = await readFile(await resolveContained(handle, path))\n return new Uint8Array(buf)\n },\n\n async writeFile(handle: ExecutionHandle, path: string, content: string): Promise<void> {\n const fullPath = await resolveContained(handle, path)\n await mkdir(dirname(fullPath), { recursive: true })\n await writeFile(fullPath, content, 'utf-8')\n },\n\n async listFiles(handle: ExecutionHandle, path: string, options): Promise<ListFilesEntry[]> {\n if (options?.glob) {\n await resolveContained(handle, globLiteralPrefix(path) || '.')\n const entries = await globFilesLocal(path, handle.cwd, {\n limit: options?.limit ?? 1000,\n metadata: options?.metadata,\n gitignore: options?.gitignore,\n signal: options?.signal,\n })\n if (workspaceRoot === undefined)\n return entries\n const contained: ListFilesEntry[] = []\n for (const entry of entries) {\n try {\n await resolveContained(handle, entry.path)\n contained.push(entry)\n }\n catch {\n // Symlink escapes can be discovered by broad globs like `**/*`.\n // Keep the process context's workspaceRoot invariant by filtering\n // them exactly as read/write paths are filtered.\n }\n }\n return contained\n }\n // Literal listing — suffix directories with `/` so consumers (the\n // `list_files` tool, path suggestions) can tell files from dirs\n // without a second probe. Mirrors `ls -p` in the docker context.\n const dirents = await readdir(await resolveContained(handle, path), { withFileTypes: true })\n return dirents.map(d => (d.isDirectory() ? `${d.name}/` : d.name))\n },\n\n async execBackground(\n handle: ExecutionHandle,\n command: string,\n options: {\n cwd?: string\n env?: Record<string, string>\n outputDir: string\n onExit?: (info: TaskExitInfo) => void\n maxOutputBytes?: number\n stallTimeoutMs?: number\n onStall?: (info: TaskStallInfo) => void\n },\n ): Promise<TaskHandle> {\n const cwd = options.cwd ? resolve(handle.cwd, options.cwd) : handle.cwd\n\n // Resolve the subprocess primitive up front (see `loadSpawn`) so a runtime\n // without `node:child_process` fails BEFORE we allocate the log dir +\n // output stream — no orphaned fd / log file on an unsupported host.\n const spawnChild = await loadSpawn()\n\n await mkdir(options.outputDir, { recursive: true })\n\n // Mint id + path. The id is sequential per context (model-facing,\n // short, ergonomic for `shell_kill`). The log FILENAME embeds the\n // context's start timestamp so two contexts sharing an `outputDir`\n // (TUI restart on the same session, concurrent zidane instances,\n // …) never resolve to the same file — we open with `flags: 'a'`\n // and a name collision would interleave their output. Path\n // validation is defensive — we mint our own ids so it never trips\n // today, but it pins the invariant for forks / third parties.\n const taskId = `bash_${++taskCounter}`\n assertSafeTaskId(taskId)\n const outputPath = resolve(options.outputDir, `${taskId}.${contextTimestamp}.log`)\n\n // Install the orphan reaper on first task. See `exitHandler`'s\n // JSDoc for the kill-on-shutdown rationale.\n if (!exitHandlerRegistered) {\n process.on('exit', exitHandler)\n exitHandlerRegistered = true\n }\n\n // Open the output file. The timestamped path is unique per context\n // so a brand-new file is the expected outcome; `flags: 'a'` is kept\n // as the safe default (preserves bytes if the path collides for any\n // reason — same-millisecond context creation, manual pre-population,\n // etc.) rather than blindly truncating. Streams are opened BEFORE\n // the spawn to avoid a race where the child writes before the stream\n // is ready — `child_process` buffers stdio until the consumer\n // attaches, but the FS handle has to exist either way for our pipe.\n const outputStream: WriteStream = createWriteStream(outputPath, { flags: 'a' })\n // Surface FS errors (ENOSPC, EACCES on a remounted FS, etc.)\n // under ZIDANE_DEBUG instead of crashing the host via an\n // unhandled 'error' event. Without a listener Node escalates\n // any stream-level error to an uncaughtException and the whole\n // process exits — the model and the user would lose every\n // unrelated in-flight piece of work to one bad task's disk\n // hiccup. Swallow + log is the safer default for a fire-and-\n // forget log writer; the task's exit code still reports.\n outputStream.on('error', (err) => {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/contexts] task ${taskId} log stream error: ${err.message}\\n`)\n })\n\n // Spawn as a NEW process group leader so we can kill the whole\n // subtree on demand. Same primitive `exec` uses for foreground\n // shells — see the long comment in that method for the\n // process-group rationale.\n const child = spawnChild('/bin/sh', ['-c', command], {\n cwd,\n env: { ...baseEnv, ...defaultEnv, ...options.env },\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: SUPPORTS_PROCESS_GROUPS,\n })\n\n const state: TaskState = {\n taskId,\n handleId: handle.id,\n pid: child.pid ?? -1,\n command,\n cwd,\n startedAt: Date.now(),\n outputPath,\n outputStream,\n child,\n status: 'running',\n bytesWritten: 0,\n settled: false,\n onExit: options.onExit,\n }\n tasks.set(taskId, state)\n\n // Output cap (optional). Beyond the cap the process keeps\n // running — head-priority retention: bytes already on disk stay,\n // subsequent bytes are counted but dropped, and a structured\n // truncation marker is appended at settle so the model can\n // pattern-match the loss. Killing on overflow (what the\n // foreground `exec` does) would be wrong here: long-running\n // servers legitimately log forever.\n const maxOutputBytes = typeof options.maxOutputBytes === 'number' && options.maxOutputBytes > 0\n ? options.maxOutputBytes\n : undefined\n let droppedBytes = 0\n // Latched on the first overflowing chunk. Without it, a codepoint-\n // boundary backoff (below) leaves a few bytes of \"room\" that later\n // chunks would dribble into — appending disjoint fragments from\n // much-later output right after the cut point.\n let capExhausted = false\n\n // Stall watchdog (optional). One-shot per quiet period: fires\n // `onStall` after `stallTimeoutMs` of no output, then stays quiet\n // until fresh output re-arms it. `unref()` so a pending timer\n // never holds the host process open.\n const stallTimeoutMs = typeof options.stallTimeoutMs === 'number' && options.stallTimeoutMs > 0\n ? options.stallTimeoutMs\n : undefined\n const onStall = options.onStall\n let stallTimer: NodeJS.Timeout | undefined\n let lastOutputAt = Date.now()\n const armStallTimer = (): void => {\n if (!stallTimeoutMs || !onStall)\n return\n if (stallTimer)\n clearTimeout(stallTimer)\n stallTimer = setTimeout(() => {\n stallTimer = undefined\n if (state.settled)\n return\n try {\n onStall({\n taskId,\n command,\n outputPath,\n stalledForMs: Date.now() - lastOutputAt,\n bytesWritten: state.bytesWritten,\n })\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/contexts] task ${taskId} onStall threw: ${err instanceof Error ? err.message : String(err)}\\n`)\n }\n }, stallTimeoutMs)\n stallTimer.unref?.()\n }\n const clearStallTimer = (): void => {\n if (stallTimer) {\n clearTimeout(stallTimer)\n stallTimer = undefined\n }\n }\n armStallTimer()\n\n // Pipe both streams into the same file. Order between stdout and\n // stderr is preserved per-stream; cross-stream ordering depends on\n // Node's event loop — acceptable interleaving for log-shaped\n // output. Tracked-bytes is updated on every chunk for the\n // listBackground UX (counted even past the cap, so the UX shows\n // the task's TRUE output volume).\n const appendChunk = (chunk: Buffer): void => {\n state.bytesWritten += chunk.length\n lastOutputAt = Date.now()\n armStallTimer()\n if (maxOutputBytes !== undefined) {\n const onDisk = state.bytesWritten - droppedBytes - chunk.length\n if (capExhausted || onDisk >= maxOutputBytes) {\n capExhausted = true\n droppedBytes += chunk.length\n return\n }\n const room = maxOutputBytes - onDisk\n if (chunk.length > room) {\n capExhausted = true\n // Cut on a UTF-8 codepoint boundary, same as the foreground\n // exec cap: back off while the byte after the cut is a\n // continuation byte (0b10xxxxxx) so the log never ends in a\n // mangled half-codepoint right before the truncation marker.\n let end = room\n while (end > 0 && (chunk[end] & 0xC0) === 0x80)\n end--\n droppedBytes += chunk.length - end\n if (end > 0)\n outputStream.write(chunk.subarray(0, end))\n return\n }\n }\n outputStream.write(chunk)\n }\n child.stdout?.on('data', chunk => appendChunk(chunk as Buffer))\n child.stderr?.on('data', chunk => appendChunk(chunk as Buffer))\n\n // Settle path — at-most-once via `settled` flag (checklist #14).\n // Three trigger sources: `close` (natural OR signal-killed),\n // `error` (spawn failure), explicit `killBackground` (which\n // routes through `close` itself after the SIGTERM lands).\n const settle = (cause: 'close' | 'error', code: number | null, signal: NodeJS.Signals | null, errMessage?: string): void => {\n if (state.settled)\n return\n state.settled = true\n clearStallTimer()\n state.endedAt = Date.now()\n\n // Determine final status from cause + signal.\n const status: TaskExitInfo['status']\n = signal === 'SIGTERM' || state.killRequested\n ? 'killed'\n : 'exited'\n // Signal-killed children report null `code` from Node; map back\n // to the POSIX `128 + signum` convention so consumers can read\n // an integer either way.\n const exitCode = code !== null\n ? code\n : signal === 'SIGTERM'\n ? 143\n : signal\n ? 128\n : 1\n state.status = status\n state.exitCode = exitCode\n if (signal)\n state.signal = signal\n\n // Flush + close the WriteStream BEFORE firing onExit — model\n // may read the file in the same turn it receives the\n // notification, and a still-open stream can hold tail bytes\n // back from disk. `stream.end(callback)` is the documented\n // \"all queued writes are flushed when this fires\" idiom.\n //\n // ORDER MATTERS: any error preamble we want in the log file\n // (spawn failures with no stdout, buffer overflows) MUST be\n // written BEFORE `end()` — once `end()` is called the stream\n // is closed for writing and subsequent `.write()` calls are\n // dropped. Earlier revisions had this reversed and silently\n // lost ENOENT-on-`/bin/sh` messages.\n if (errMessage) {\n try {\n outputStream.write(`\\n${errMessage}\\n`)\n }\n catch {\n // Stream may have errored before this — best-effort only.\n }\n }\n if (droppedBytes > 0) {\n try {\n // Structured + loud — the model can pattern-match the tag\n // rather than misread a truncated log as the full output.\n outputStream.write(`\\n<output-truncated bytes-dropped=\"${droppedBytes}\"/>\\n`)\n }\n catch {\n // Best-effort only, same as the error preamble above.\n }\n }\n outputStream.end(() => {\n // `stateToTaskExitInfo` reads the same fields we just set\n // on `state`, so the snapshot the consumer gets matches the\n // post-settle state exactly. `onExit` is optional — pull-based\n // consumers reconcile via `listBackground` instead.\n try {\n state.onExit?.(stateToTaskExitInfo(state))\n }\n catch (err) {\n // Defensive — a buggy onExit callback shouldn't crash the\n // host. Surface via stderr under ZIDANE_DEBUG; otherwise\n // swallow. Matches the spawn-tool's bubbleError pattern.\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/contexts] task ${taskId} onExit threw: ${err instanceof Error ? err.message : String(err)}\\n`)\n }\n })\n // Release the process + stream references at settle: entries\n // outlive their processes in the registry (until explicit\n // `destroy`), and a session that backgrounds many tasks would\n // otherwise retain every dead ChildProcess and ended\n // WriteStream — plus the closures they anchor — for its whole\n // lifetime. Everything the post-exit readers (`listBackground` /\n // `waitBackground` / `killBackground`) project stays on `state`.\n state.child = undefined\n state.outputStream = undefined\n }\n\n child.on('close', (code, signal) => settle('close', code, signal))\n child.on('error', err => settle('error', null, null, `[spawn error] ${err.message}`))\n\n child.on('exit', (code, signal) => {\n // Same pipe-hostage hazard as foreground `exec`: a grandchild\n // that inherited the pipes blocks `close` forever, leaving the\n // task `running` (and any waiter parked) long after the\n // command exited. Settle after a short drain grace; `settle`'s\n // latch makes this a no-op when `close` already fired.\n const exitGraceTimer = setTimeout(() => {\n if (state.settled)\n return\n child.stdout?.destroy()\n child.stderr?.destroy()\n settle('close', code, signal)\n }, EXIT_PIPE_DRAIN_GRACE_MS)\n exitGraceTimer.unref?.()\n })\n\n return { taskId, pid: state.pid, outputPath }\n },\n\n async killBackground(handle: ExecutionHandle, taskId: string): Promise<TaskExitInfo | null> {\n const state = tasks.get(taskId)\n // Two miss cases collapse into one `null` return: unknown id, AND\n // known-id-but-not-owned-by-this-handle. The second case is the\n // subagent-can't-kill-parent-tasks defense; surfacing it as a\n // distinct error would leak the existence of the parent's task\n // to the subagent's model, which violates the per-handle\n // isolation contract.\n if (!state || state.handleId !== handle.id)\n return null\n // Already exited — return the cached info. We don't keep a\n // separate cached exit; the state itself carries every field\n // `TaskExitInfo` needs and `stateToTaskExitInfo` projects it.\n if (state.status !== 'running')\n return stateToTaskExitInfo(state)\n\n // Mark the intent BEFORE issuing the kill so the close handler\n // classifies the exit as `'killed'` even on platforms where the\n // SIGTERM-via-group lands faster than the close event drains.\n // (Checklist #14: at-most-once settle, plus correct status\n // classification regardless of event ordering.)\n state.killRequested = true\n\n // Wait for the existing close listener to fire — `settle()` does\n // all the flushing + onExit work. We just sit on a one-shot\n // promise tied to the `child.on('close')` we already registered\n // at spawn time.\n const closed = new Promise<void>((resolveP) => {\n if (state.settled) {\n resolveP()\n return\n }\n const originalOnExit = state.onExit\n state.onExit = (info) => {\n originalOnExit?.(info)\n resolveP()\n }\n })\n\n killProcessGroup(state.child, 'SIGTERM')\n await closed\n return stateToTaskExitInfo(state)\n },\n\n async waitBackground(\n handle: ExecutionHandle,\n taskId: string,\n options?: { timeoutMs?: number, signal?: AbortSignal },\n ): Promise<TaskExitInfo | null> {\n const state = tasks.get(taskId)\n // Same two-misses-collapse-to-null contract as `killBackground`:\n // unknown id AND known-but-other-handle both return `null` so a\n // subagent can't observe the parent's tasks through the wait seam.\n if (!state || state.handleId !== handle.id)\n return null\n if (state.settled)\n return stateToTaskExitInfo(state)\n\n return new Promise<TaskExitInfo | null>((resolveP) => {\n let done = false\n let timer: NodeJS.Timeout | undefined\n const signal = options?.signal\n const finish = (value: TaskExitInfo | null): void => {\n if (done)\n return\n done = true\n if (timer)\n clearTimeout(timer)\n signal?.removeEventListener('abort', onAbort)\n resolveP(value)\n }\n function onAbort(): void {\n finish(null)\n }\n\n // Chain onto the live exit callback rather than replacing it —\n // the original (notification enqueue, kill-waiter, a sibling\n // waiter) must keep firing. The `done` latch makes a late exit\n // after timeout a no-op for THIS waiter.\n const originalOnExit = state.onExit\n state.onExit = (info) => {\n originalOnExit?.(info)\n finish(stateToTaskExitInfo(state))\n }\n\n if (signal) {\n if (signal.aborted) {\n finish(null)\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n const timeoutMs = options?.timeoutMs\n if (typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs > 0) {\n timer = setTimeout(finish, timeoutMs, null)\n timer.unref?.()\n }\n })\n },\n\n async reassignBackgroundTasks(\n fromHandle: ExecutionHandle,\n toHandle: ExecutionHandle,\n newOnExit?: (info: TaskExitInfo) => void,\n ): Promise<readonly TaskEntry[]> {\n // No-op when source = destination — keeps the spawn.ts call site\n // unconditional without forcing it to dedupe.\n if (fromHandle.id === toHandle.id)\n return []\n const promoted: TaskEntry[] = []\n for (const state of tasks.values()) {\n if (state.handleId !== fromHandle.id || state.status !== 'running')\n continue\n state.handleId = toHandle.id\n // Replace the natural-exit callback. The original closed over\n // the spawning agent's hook bus, which is about to be destroyed\n // — without rewiring, the task's eventual `background:exit`\n // fires into a torn-down hookable and the parent never learns.\n if (newOnExit)\n state.onExit = newOnExit\n promoted.push(stateToTaskEntry(state))\n }\n return promoted\n },\n\n async listBackground(handle: ExecutionHandle): Promise<readonly TaskEntry[]> {\n // Snapshot — callers must not assume the returned array stays\n // in sync with the live registry. Sorted by startedAt so the\n // model / UI sees consistent ordering across calls. Scoped to\n // the calling handle so subagents don't see the parent's tasks\n // (and vice versa) in their listing.\n return [...tasks.values()]\n .filter(s => s.handleId === handle.id)\n .sort((a, b) => a.startedAt - b.startedAt)\n .map(stateToTaskEntry)\n },\n\n async destroy(handle: ExecutionHandle): Promise<void> {\n // Kill every still-running background task SPAWNED THROUGH THIS\n // HANDLE before tearing the handle down. SIGTERM the groups,\n // await the close + flush, THEN drop the registry entries.\n // Sequential — destroy is one-shot teardown, the few ms of extra\n // latency aren't worth the synchronization complexity.\n //\n // The handle scope matters when the same `ExecutionContext` is\n // shared across a parent agent and its `spawn`-ed subagents (the\n // default — `spawn.ts` passes `execution: ctx.execution`). Each\n // agent mints its own `ExecutionHandle` and registers its\n // background tasks under that handle's id. Without the filter,\n // a child agent's `destroy()` (fired by `spawn.ts`'s `finally`\n // when the subagent finishes / is cancelled) would walk the\n // shared registry and SIGTERM the parent's tasks too. So\n // cancelling a subagent that has its own background shells now\n // correctly kills JUST those subagent shells, leaving the\n // parent's intact.\n const survivors = [...tasks.values()].filter(s => s.handleId === handle.id && !s.settled)\n await Promise.all(survivors.map(async (state) => {\n state.killRequested = true\n await new Promise<void>((resolveP) => {\n let graceTimer: NodeJS.Timeout | undefined\n const originalOnExit = state.onExit\n state.onExit = (info) => {\n originalOnExit?.(info)\n if (graceTimer)\n clearTimeout(graceTimer)\n resolveP()\n }\n killProcessGroup(state.child, 'SIGTERM')\n // Bounded drain: a task that traps/ignores SIGTERM — or whose\n // `close` event is held back by an escaped grandchild (setsid'd\n // out of the process group) keeping the inherited stdio pipe\n // open — must not wedge `agent.destroy()` forever. After the\n // grace period, SIGKILL the group and stop waiting: `close`\n // may STILL never fire (the pipe holder isn't in the group),\n // so resolving here is the only way destroy() stays bounded.\n graceTimer = setTimeout(() => {\n killProcessGroup(state.child, 'SIGKILL')\n // settle() normally flushes + ends the log stream, but it\n // only runs off `close` — which we just gave up on. Tear the\n // stream down so the fd doesn't leak into a long-lived host.\n state.outputStream?.destroy()\n resolveP()\n }, destroyGraceMs)\n graceTimer.unref?.()\n })\n }))\n // Drop only this handle's tasks from the registry. Other handles\n // (siblings, parent) keep their entries.\n for (const [taskId, state] of tasks) {\n if (state.handleId === handle.id)\n tasks.delete(taskId)\n }\n handles.delete(handle.id)\n // Drop the orphan reaper ONLY when no handles remain — otherwise\n // a child's `destroy()` would strip the parent's safety net. The\n // reaper protects every still-tracked task in the context, so it\n // sticks around until the LAST handle is gone.\n //\n // Without this guard, the spawn-tool sequence\n // parent.spawn(child) → child.run() → child.destroy() (auto)\n // would deregister the handler mid-parent-lifetime. The parent's\n // own subsequent Ctrl+C orphan-kill safety would silently degrade.\n if (exitHandlerRegistered && handles.size === 0) {\n process.off('exit', exitHandler)\n exitHandlerRegistered = false\n }\n },\n }\n}\n\n/**\n * Per-task state. Lives in the context's `tasks` registry. Fields are\n * mutated in place by the spawn / close / kill / destroy code paths —\n * the registry isn't immutable. Treat the type as a record-of-cells,\n * not a value.\n */\ninterface TaskState {\n taskId: string\n /**\n * `ExecutionHandle.id` of the spawning agent. The registry is\n * context-scoped (one Map shared across all handles a context minted),\n * so a per-task owner tag is what scopes `listBackground` /\n * `killBackground` / `destroy` to the calling handle's slice.\n *\n * Without this, a subagent spawned via `spawn` tool — which inherits\n * the parent's `ExecutionContext` but mints its OWN handle — would\n * see (and accidentally kill on `destroy()`) every task the parent\n * had running. Came up the first time the model spawned a subagent\n * that ran a background task: the subagent's run-end `destroy()`\n * SIGTERMed the parent's `npm run dev` mid-flight.\n */\n handleId: string\n pid: number\n command: string\n cwd: string\n startedAt: number\n outputPath: string\n /**\n * Live-only references, released (set `undefined`) at settle so the\n * registry doesn't retain dead `ChildProcess` and ended `WriteStream`\n * objects until `destroy()`. Present iff `settled` is false.\n */\n outputStream?: WriteStream\n child?: ChildProcess\n status: 'running' | 'exited' | 'killed'\n exitCode?: number\n signal?: NodeJS.Signals\n bytesWritten: number\n /**\n * `at-most-once` settle latch. Multiple trigger sources (`close`,\n * `error`, `kill`) can race; the flag dedupes so `onExit` fires\n * exactly once per task (checklist #14).\n */\n settled: boolean\n /**\n * Set by `killBackground` / `destroy` before issuing SIGTERM so the\n * close handler classifies the exit as `'killed'` even when the\n * platform delivers the close event ahead of our intent record.\n */\n killRequested?: boolean\n /**\n * `Date.now()` at settle time. Lets `durationMs` (and pull-based\n * reconcile consumers reading `TaskEntry.endedAt`) report the task's\n * real lifetime instead of a projection-time delta.\n */\n endedAt?: number\n /**\n * Optional push-style exit callback. Undefined for pull-based\n * consumers (remote / durable hosts reconcile via `listBackground`).\n * Mutated in place by the kill / destroy / wait chains.\n */\n onExit?: (info: TaskExitInfo) => void\n}\n\n/**\n * Send `signal` to the child's whole process group. Falls back to a\n * single-process kill on Windows (no POSIX process groups). Shared\n * across the foreground `exec` path, the background spawn / kill\n * paths, and the shutdown-time orphan reaper — keeping one definition\n * so the kill semantics can't drift between them.\n */\nexport function killProcessGroup(child: ChildProcess | undefined, signal: NodeJS.Signals): void {\n const pid = child?.pid\n if (pid === undefined)\n return\n try {\n if (SUPPORTS_PROCESS_GROUPS)\n process.kill(-pid, signal)\n else\n process.kill(pid, signal)\n }\n catch {\n // ESRCH / EPERM — process is already gone (race with natural exit)\n // or we lost the right to kill it. Both are safe to swallow.\n }\n}\n\n/**\n * Project a `TaskState` to the `TaskEntry` shape `listBackground` and\n * `reassignBackgroundTasks` return. Single helper keeps the shape\n * consistent across both call sites (and a future addition of fields\n * to `TaskEntry` only needs to land here).\n */\nfunction stateToTaskEntry(state: TaskState): TaskEntry {\n return {\n taskId: state.taskId,\n pid: state.pid,\n command: state.command,\n cwd: state.cwd,\n startedAt: state.startedAt,\n ...(state.endedAt !== undefined ? { endedAt: state.endedAt } : {}),\n outputPath: state.outputPath,\n status: state.status,\n ...(state.exitCode !== undefined ? { exitCode: state.exitCode } : {}),\n ...(state.signal ? { signal: state.signal } : {}),\n bytesWritten: state.bytesWritten,\n }\n}\n\n/**\n * Project a settled `TaskState` to the `TaskExitInfo` shape `settle()`\n * fires from `onExit` and `killBackground` returns on the\n * cached-exit path. Pre-condition: `state.status !== 'running'`\n * (callers gate on this).\n */\nfunction stateToTaskExitInfo(state: TaskState): TaskExitInfo {\n return {\n taskId: state.taskId,\n status: state.status as Exclude<TaskState['status'], 'running'>,\n exitCode: state.exitCode ?? 0,\n ...(state.signal ? { signal: state.signal } : {}),\n outputPath: state.outputPath,\n // Settle-time delta when available (always set since `endedAt` was\n // introduced); the `Date.now()` fallback covers defensive callers\n // projecting an unsettled state.\n durationMs: (state.endedAt ?? Date.now()) - state.startedAt,\n command: state.command,\n }\n}\n","/**\n * Execution context types.\n *\n * An execution context defines *where* and *how* an agent's tools run.\n * The agent loop and tools interact through this interface without knowing\n * whether they're running in-process, in a Docker container, or in a\n * remote sandbox.\n */\n\n// ---------------------------------------------------------------------------\n// Capabilities\n// ---------------------------------------------------------------------------\n\n/**\n * Lifetime guarantee of background tasks started through\n * {@link ExecutionContext.execBackground}.\n *\n * - `'none'` — the context cannot detach tasks at all.\n * - `'process-lifetime'` — tasks live as long as the HOST process; a\n * crash/restart of the host orphans or kills them (`ProcessContext`).\n * - `'durable'` — tasks live on a remote runner and survive host process\n * death (remote execution contexts driven by durable runtimes).\n *\n * Durable-execution adapters (e.g. `zidane/restate`) consult this to\n * decide whether backgrounding is safe to expose: a `'durable'` context\n * keeps its tasks across worker crashes, so there is no reason to strip\n * the capability from the model.\n */\nexport type DetachedTasksCapability = 'none' | 'process-lifetime' | 'durable'\n\nexport interface ContextCapabilities {\n /** Can execute shell commands */\n shell: boolean\n /** Can read/write files in a workspace */\n filesystem: boolean\n /** Can enumerate glob matches through `ExecutionContext.listFiles(..., { glob: true })`. */\n glob?: boolean\n /** Can make outbound network requests */\n network: boolean\n /** Has GPU access */\n gpu: boolean\n /**\n * Background-task lifetime guarantee. Optional for backward\n * compatibility — when absent, callers infer `'process-lifetime'`\n * if the context implements `execBackground`, `'none'` otherwise\n * (see {@link resolveDetachedTasksCapability}).\n */\n detachedTasks?: DetachedTasksCapability\n}\n\n/**\n * Effective {@link DetachedTasksCapability} of a context, with the\n * backward-compatible inference for contexts that predate the field.\n */\nexport function resolveDetachedTasksCapability(context: ExecutionContext): DetachedTasksCapability {\n return context.capabilities.detachedTasks\n ?? (context.execBackground ? 'process-lifetime' : 'none')\n}\n\n// ---------------------------------------------------------------------------\n// Execution handle\n// ---------------------------------------------------------------------------\n\n/** Opaque handle to a running execution context instance */\nexport interface ExecutionHandle {\n id: string\n type: ContextType\n /** Working directory within the context */\n cwd: string\n}\n\n// ---------------------------------------------------------------------------\n// Exec result\n// ---------------------------------------------------------------------------\n\nexport interface ExecResult {\n stdout: string\n stderr: string\n exitCode: number\n}\n\nexport interface ListFileMetadata {\n path: string\n size?: number\n mtimeMs?: number\n}\n\nexport type ListFilesEntry = string | ListFileMetadata\n\nexport interface ListFilesOptions {\n /** Treat `path` as a glob match pattern instead of a literal directory path. */\n glob?: boolean\n limit?: number\n metadata?: boolean\n /**\n * Skip git-ignored files when globbing. Default `true`. Contexts that can\n * resolve git's ignore rules (e.g. the local process context) honor it; the\n * rest treat it as a no-op. Set `false` to match every file, ignored or not.\n */\n gitignore?: boolean\n /** Abort the listing early — forwarded from the calling tool's `ctx.signal`. */\n signal?: AbortSignal\n}\n\n// ---------------------------------------------------------------------------\n// Spawn config\n// ---------------------------------------------------------------------------\n\nexport interface SpawnConfig {\n /** Working directory (created if it doesn't exist) */\n cwd?: string\n /**\n * Optional workspace-containment root for the file methods (`readFile`,\n * `writeFile`, `listFiles`) of the process context.\n *\n * When set, a resolved path that escapes this root (via absolute path,\n * `..`, or a symlink pointing outside) is rejected before any I/O. Default\n * unset = no containment (back-compat; the agent can read/write anywhere\n * the host process can). Set this for untrusted workloads to confine file\n * tools to a directory. Shell commands are not covered — use a sandbox /\n * docker context for full isolation.\n */\n workspaceRoot?: string\n /** Environment variables */\n env?: Record<string, string>\n /**\n * Whether spawned shells inherit the parent `process.env` (process context).\n *\n * Default `true` (preserves existing behavior — children see the parent\n * environment including anything Bun auto-loaded from `.env`). Set `false`\n * for untrusted workloads so secrets in the parent environment aren't\n * readable by tool commands; only `env` / per-call env plus a minimal base\n * (`PATH`, `HOME`, `SHELL`, `LANG`, `LC_ALL`, `USER`, `TERM`, `TMPDIR`) are\n * passed through.\n */\n inheritEnv?: boolean\n /** Docker image (only for 'docker' context) */\n image?: string\n /** Docker container name prefix (only for 'docker' context) */\n name?: string\n /** Host paths mounted into the context (only for 'docker' context today) */\n mounts?: ContextMount[]\n /** Resource limits */\n limits?: {\n /** Memory limit in MB */\n memory?: number\n /** CPU limit (e.g. '1.0' = 1 core) */\n cpu?: string\n /** Timeout in seconds for the entire context lifetime */\n timeout?: number\n }\n /** Sandbox provider config (only for 'sandbox' context) */\n sandbox?: {\n provider: string\n apiKey?: string\n /**\n * Attach to a pre-existing sandbox by id rather than creating one\n * (provider-specific; E2B maps it to `Sandbox.connect`). The provider\n * leaves a connected sandbox running on teardown.\n */\n sandboxId?: string\n [key: string]: unknown\n }\n\n /**\n * How long `destroy()` waits for a SIGTERM'd background task to settle\n * before escalating to SIGKILL and abandoning the wait (process context\n * only). Bounds `agent.destroy()` against tasks that trap SIGTERM or\n * whose stdio pipes are held open by escaped grandchildren.\n *\n * Default: `5000`.\n */\n destroyGraceMs?: number\n\n /**\n * Publish container ports on the host (docker context only).\n *\n * Each entry maps a container port to either an explicit host port or\n * (when `host` is omitted) a Docker-assigned random port. Retrieve the\n * actual host port at runtime via `getMappedPort(container)` on the\n * docker context.\n */\n ports?: Array<{ container: number, host?: number, proto?: 'tcp' | 'udp' }>\n\n /**\n * UID/GID the container should run as (docker context only).\n *\n * Accepts the same forms Docker's `--user` does: `uid`, `uid:gid`, or a\n * named user that exists in the image. Default is the image's default\n * user (typically root). Setting this to the host user's `uid:gid`\n * avoids the EACCES-on-cleanup problem when sharing a workspace via\n * a `shared` mount.\n */\n user?: string\n\n /**\n * User-defined Docker network to join (docker context only).\n *\n * Defaults to Docker's default bridge. Use a user network when you\n * need multiple sibling containers (e.g. agent + database + dev\n * server) to discover each other by name.\n */\n network?: string\n\n /**\n * Docker labels to attach to the container (docker context only).\n *\n * Useful for ownership tracking — callers can sweep abandoned\n * containers (e.g. from a crashed parent process) by filtering on\n * a label they own: `docker ps -aq --filter label=my-app=true`.\n */\n labels?: Record<string, string>\n\n /**\n * Container hardening options (docker context only).\n *\n * All fields are opt-in and OFF by default to preserve existing behavior\n * (containers run with the image's default user/capabilities). Enable them\n * for untrusted workloads. `dropAllCapabilities` and a non-root `user`\n * (see {@link SpawnConfig.user}) can break images that expect root or\n * specific capabilities, so they are not applied unless requested.\n */\n hardening?: ContextHardening\n}\n\n/**\n * Opt-in container hardening for the docker context. Every field defaults to\n * \"unset\" so an omitted `hardening` (or omitted field) reproduces the prior,\n * unrestricted behavior — this keeps the option purely additive.\n */\nexport interface ContextHardening {\n /**\n * Drop all Linux capabilities (`CapDrop: ['ALL']`). Strong isolation, but\n * breaks images needing capabilities (e.g. binding low ports, `ping`).\n * Default: `false`.\n */\n dropAllCapabilities?: boolean\n /**\n * Set `no-new-privileges` so processes can't gain privileges via setuid\n * binaries. Low blast radius; safe for most workloads. Default: `false`.\n */\n noNewPrivileges?: boolean\n /**\n * Mount the container root filesystem read-only. Pair with writable mounts\n * for scratch space. Default: `false`.\n */\n readonlyRootfs?: boolean\n /**\n * Max number of processes (`PidsLimit`) — caps fork-bombs. Default: unset\n * (no limit). A few hundred is usually plenty for an agent sandbox.\n */\n pidsLimit?: number\n}\n\nexport interface ContextMount {\n /** Absolute host path to mount. */\n source: string\n /** Absolute path inside the execution context. */\n target: string\n /** Mount read-only. Defaults to false for Docker's native bind behavior. */\n readonly?: boolean\n /**\n * Apply the SELinux shared label (`:z`) so the host user and the container\n * user can both read/write the mount (docker context only). No-op on\n * non-SELinux hosts. Combine with `SpawnConfig.user` to avoid root-owned\n * files leaking onto the host. Mutually exclusive with `readonly`.\n */\n shared?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Execution context interface\n// ---------------------------------------------------------------------------\n\nexport type ContextType = 'process' | 'docker' | 'sandbox'\n\nexport interface ExecutionContext {\n /** Context type identifier */\n readonly type: ContextType\n\n /** What this context supports */\n readonly capabilities: ContextCapabilities\n\n /** Spawn a new execution environment */\n spawn: (config?: SpawnConfig) => Promise<ExecutionHandle>\n\n /**\n * Execute a shell command in the context.\n *\n * `signal` propagates abort all the way down to the underlying child\n * process — the implementation is expected to wire it into whatever\n * spawn primitive it uses so the OS receives a SIGTERM (or equivalent)\n * when the caller aborts. Without it, a cancelled tool returns its\n * cancellation marker to the model but the underlying process keeps\n * running in the background, orphaning compute / locks / IO. Pass\n * `ctx.signal` from a tool body to inherit the per-call + run-level\n * abort union; pass a freshly-built one for a host-driven kill.\n *\n * Implementations are free to ignore `signal` (the contract degrades\n * gracefully — the process simply won't be killed), but the in-process\n * default DOES honor it via `child_process.exec`'s native `signal`\n * option.\n */\n /**\n * Run a shell command. `timeout` is in SECONDS (default 30) — every\n * implementation converts internally (process ×1000 to ms, docker wraps\n * with coreutils `timeout`, sandbox SDKs take seconds/ms per their API).\n * Documented here because the unit is invisible at call sites and a\n * caller assuming milliseconds would set an instant-kill timeout.\n */\n exec: (handle: ExecutionHandle, command: string, options?: { cwd?: string, env?: Record<string, string>, timeout?: number, signal?: AbortSignal }) => Promise<ExecResult>\n\n /**\n * Start a process in the background. Settles as soon as `spawn` returns\n * — does NOT wait for the child to exit. Stdout + stderr stream\n * interleaved to the file at the returned `outputPath`. The caller\n * (typically the agent) reads incremental output via the regular\n * {@link ExecutionContext.readFile} seam.\n *\n * Optional — contexts without background support (some remote sandboxes)\n * just don't implement it. The shell tool surfaces a clean\n * \"background mode is not supported in this execution context\" error\n * when this is undefined.\n *\n * `onExit` is called once when the child terminates (natural, killed,\n * or error). The same instance that called `execBackground` is the\n * exclusive owner of the callback — it's not a multi-cast bus. Hosts\n * wire this to the agent's pending-notification queue so the model\n * gets a `<task-notification>` on its next turn.\n *\n * `onExit` is OPTIONAL: remote / durable contexts may have no legal\n * way to push a callback from a timer back into the host (a Restate\n * journal, for instance, forbids out-of-band writes). Such contexts\n * simply record the exit in their registry; the agent loop reconciles\n * by polling {@link ExecutionContext.listBackground} at run\n * boundaries, where a host journal wrapper is legal (the\n * `background:reconcile` hook is the journalable seam).\n *\n * See `docs/RUN_IN_BACKGROUND.md` for the broader design contract\n * (file location, replay semantics, suppression rules).\n */\n execBackground?: (\n handle: ExecutionHandle,\n command: string,\n options: {\n cwd?: string\n env?: Record<string, string>\n /**\n * Absolute directory the context appends `<task-id>.log` to. The\n * agent owns this path because it carries session-shaped knowledge\n * (`<userDir>/<sessionId>/tasks/`) the context can't synthesize.\n * Must already exist OR be creatable by the context — the\n * implementation handles `mkdir -p` defensively.\n */\n outputDir: string\n /** Push-style exit callback. Optional — see the method doc. */\n onExit?: (info: TaskExitInfo) => void\n /**\n * Cap on bytes written to the output file. Beyond the cap the\n * process KEEPS RUNNING; further output is counted but dropped,\n * and a `<output-truncated bytes-dropped=\"N\"/>` marker is\n * appended when the task settles. Unset / non-positive = no cap.\n */\n maxOutputBytes?: number\n /**\n * Stall watchdog: when the task produces no output for this many\n * milliseconds, `onStall` fires ONCE (one-shot — re-arms only\n * after fresh output arrives). The process is NOT killed; the\n * consumer decides (typically by telling the model the task may\n * be stuck at an interactive prompt). Unset = no watchdog.\n */\n stallTimeoutMs?: number\n /** One-shot stall callback — see `stallTimeoutMs`. */\n onStall?: (info: TaskStallInfo) => void\n },\n ) => Promise<TaskHandle>\n\n /**\n * SIGTERM the whole process group of a running background task.\n * Idempotent — second call returns `null` (or the cached exit info).\n * Resolves once the process has exited AND its output stream has\n * been flushed + closed.\n *\n * `null` return on miss (unknown id, already cleaned up) so the\n * shell_kill tool can surface a clean \"no such task\" message\n * without throwing.\n */\n killBackground?: (\n handle: ExecutionHandle,\n taskId: string,\n ) => Promise<TaskExitInfo | null>\n\n /**\n * Snapshot of every task in the context's registry — running AND\n * terminated (entries remain until the next context destroy, so\n * the model can still read output of exited tasks).\n */\n listBackground?: (\n handle: ExecutionHandle,\n ) => Promise<readonly TaskEntry[]>\n\n /**\n * Block until a background task terminates, then resolve with its\n * exit info. Resolves immediately for already-terminated tasks.\n *\n * Returns `null` when:\n * - the task id is unknown (or owned by another handle — same\n * isolation contract as `killBackground`), OR\n * - `timeoutMs` elapsed / `signal` aborted before the task exited.\n *\n * Callers that need to distinguish \"unknown\" from \"still running\"\n * should consult `listBackground` first (the `wait_task` tool does).\n *\n * This is the injectable wait seam for the `wait_task` tool:\n * in-process contexts implement it on the existing `onExit`\n * machinery; durable hosts implement it as an awakeable park\n * (runner fires task-exit over their bridge → resolveAwakeable).\n */\n waitBackground?: (\n handle: ExecutionHandle,\n taskId: string,\n options?: { timeoutMs?: number, signal?: AbortSignal },\n ) => Promise<TaskExitInfo | null>\n\n /**\n * Transfer ownership of every still-running task from `fromHandle`\n * to `toHandle`. Used by the spawn tool to \"promote\" a subagent's\n * background tasks up to the parent's handle so they outlive the\n * subagent's destroy() — matching shell semantics, where a `&`-ed\n * command outlives the parent process.\n *\n * Side effects:\n * - The task's `handleId` is rewritten, so subsequent\n * `listBackground(toHandle)` / `killBackground(toHandle, …)` /\n * `destroy(toHandle)` see it (and `fromHandle`-scoped operations\n * don't).\n * - When `newOnExit` is provided, the original `onExit` is\n * REPLACED with it. Critical: the task's natural exit callback\n * was captured against the spawning agent's hook bus, which is\n * about to be destroyed; without rewiring, the parent never\n * learns when the task terminates. Pass a closure that fires\n * the parent agent's `background:exit` hook.\n *\n * Returns the entries that were actually reassigned (running tasks\n * only; terminated ones stay where they are). Implementations that\n * don't support reassignment can leave this undefined; the caller\n * (spawn.ts) falls back to the current behavior of killing child\n * tasks at subagent shutdown.\n */\n reassignBackgroundTasks?: (\n fromHandle: ExecutionHandle,\n toHandle: ExecutionHandle,\n newOnExit?: (info: TaskExitInfo) => void,\n ) => Promise<readonly TaskEntry[]>\n\n /** Read a file from the context's filesystem */\n readFile: (handle: ExecutionHandle, path: string) => Promise<string>\n\n /**\n * Read a file from the context's filesystem as raw bytes.\n *\n * Used by `read_file` to dispatch image / binary files into the multimodal\n * `ToolResultContent[]` route. Optional — when not implemented, the tool\n * falls back to `base64 < path` via the `exec` seam, which works in any\n * shell-capable context. Implementations that already have a native\n * binary read (in-process `fs.readFile` without encoding, container API,\n * sandbox SDK) should override for the latency win.\n */\n readFileBinary?: (handle: ExecutionHandle, path: string) => Promise<Uint8Array>\n\n /**\n * Cheap size probe for a file in the context's filesystem, in bytes.\n * `null` = unknowable (missing file, not a regular file, probe failed,\n * or containment rejected the path — a probe must not leak even the\n * size of files outside the workspace root).\n *\n * Exists because `readFile` has no partial-read form: tools that must\n * materialize a whole file (read_file's text path, edit/multi_edit,\n * grep's no-rg fallback) consult this first to refuse multi-GB targets\n * instead of OOMing the HOST — the read lands in host memory no matter\n * where the file lives. Per-context posture:\n *\n * - `process` — implements it (local stat, µs).\n * - `docker` — implements it via `wc -c` through its exec; its\n * `readFile` gets transport headroom above the tool ceiling so\n * the probe (tool policy), not the 10MB exec cap, decides read\n * limits — same experience as every other context.\n * - `sandbox` (and the e2b / daytona providers behind it) —\n * implements it via `wc -c` through the provider's exec: one extra\n * round-trip per whole-read tool call, paid so a multi-GB sandbox\n * file is refused BEFORE the SDK transfers it into host memory.\n *\n * KNOWN RESIDUAL (sandbox providers): this guards the `readFile` path\n * only. Their `exec` output is buffered inside the provider SDKs\n * (e2b `commands.run`, daytona `executeCommand`), which we cannot cap\n * from this side — a command that streams GBs to stdout within its\n * timeout window can still balloon host memory. Docker doesn't share\n * the residual (we own its transport; see `output-cap.ts`). A shell\n * wrapper (`| head -c`) was considered and rejected: preserving exit\n * codes, stderr separation, and timeout semantics through a pipe for\n * EVERY command is fragile, and the timeout already time-bounds the\n * exposure.\n */\n fileSize?: (handle: ExecutionHandle, path: string) => Promise<number | null>\n\n /** Write a file to the context's filesystem */\n writeFile: (handle: ExecutionHandle, path: string, content: string) => Promise<void>\n\n /**\n * List files in the context filesystem.\n *\n * Plain paths list immediate directory entries (legacy behavior). Paths that\n * contain glob metacharacters (`*`, `?`, `[]`, `{}`) request matched file\n * enumeration inside the target context. Contexts that cannot support\n * globbing should throw a clear error rather than silently returning no\n * matches. `metadata` is best-effort and only required when the context can\n * provide it without expensive per-file shell calls.\n */\n listFiles: (handle: ExecutionHandle, path: string, options?: ListFilesOptions) => Promise<ListFilesEntry[]>\n\n /**\n * Resolve the host-side port that a container port was published on\n * (docker context only, and only when the matching entry was created\n * with `host` omitted in `SpawnConfig.ports`).\n *\n * Resolves to `null` if the container port isn't published. Other\n * contexts (process, sandbox) don't implement this; they don't have\n * a port-mapping concept.\n */\n getMappedPort?: (handle: ExecutionHandle, containerPort: number) => Promise<number | null>\n\n /** Destroy the execution environment and clean up resources */\n destroy: (handle: ExecutionHandle) => Promise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Background task types\n// ---------------------------------------------------------------------------\n\n/**\n * Lifecycle status of a background task.\n *\n * - `'running'` — process is still live; `exitCode` / `signal` unset.\n * - `'exited'` — process terminated on its own (clean or non-zero exit).\n * - `'killed'` — the host issued `killBackground` (SIGTERM to the group).\n *\n * The status is a coarse-grained signal; the exit code carries the\n * fine-grained detail (e.g. `143 = SIGTERM` for `'killed'`).\n */\nexport type BackgroundTaskStatus = 'running' | 'exited' | 'killed'\n\n/**\n * Returned synchronously by `execBackground` — the handle the model and\n * the framework use to refer to the task until it terminates.\n *\n * `outputPath` is an absolute path to the log file the context is\n * appending stdout + stderr to (interleaved by emit order). The model\n * reads it via the normal `read_file` tool; no special tool is required.\n */\nexport interface TaskHandle {\n /**\n * Stable id minted by the context — typically `bash_<n>` for\n * `ProcessContext`. Sequential within a single context instance,\n * resets when a new context is constructed. Forwarded to `killBackground` /\n * `listBackground` and stamped into every `<task-notification>` block.\n */\n taskId: string\n /** OS pid of the spawned shell wrapper (process-group leader on POSIX). */\n pid: number\n /** Absolute path to the log file the context is streaming output into. */\n outputPath: string\n}\n\n/**\n * Fired exactly once per task when the child process terminates, via\n * `execBackground`'s `onExit` callback. The agent layer translates this\n * into a queued `<task-notification>` for the next turn.\n *\n * `signal` is set when the child was terminated by a signal (e.g.\n * SIGTERM from our own kill-tree, SIGKILL from oom-killer); absent on\n * natural exit. `exitCode` is `128 + signal-number` on signal-killed\n * children, matching POSIX shell conventions — `143` for SIGTERM, etc.\n */\nexport interface TaskExitInfo {\n taskId: string\n status: Exclude<BackgroundTaskStatus, 'running'>\n exitCode: number\n signal?: NodeJS.Signals\n outputPath: string\n /** `Date.now()` delta between spawn and exit. */\n durationMs: number\n /** The original command string the model invoked — useful for telemetry / banner summary. */\n command: string\n}\n\n/**\n * One row in `listBackground`'s snapshot. Living entries (status `'running'`)\n * have `exitCode` / `signal` / `endedAt` unset; terminated entries carry the\n * same data `TaskExitInfo` returned at exit time.\n */\nexport interface TaskEntry {\n taskId: string\n pid: number\n command: string\n cwd: string\n startedAt: number\n /**\n * Set when the task terminated. Lets pull-based consumers (the agent's\n * run-boundary reconcile) derive `durationMs = endedAt - startedAt`\n * without depending on when the snapshot was taken.\n */\n endedAt?: number\n outputPath: string\n status: BackgroundTaskStatus\n exitCode?: number\n signal?: NodeJS.Signals\n /** Total bytes written to the output file so far — useful for \"task X has produced N KB\" UX hints. */\n bytesWritten: number\n}\n\n/**\n * Fired by the optional stall watchdog (see `execBackground`'s\n * `stallTimeoutMs`) when a running task has produced no output for the\n * configured window. The process is still alive — this is a signal, not\n * a state transition.\n */\nexport interface TaskStallInfo {\n taskId: string\n command: string\n outputPath: string\n /** Milliseconds since the last output chunk (>= the configured window). */\n stalledForMs: number\n /** Total bytes the task has written so far. */\n bytesWritten: number\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,YAAY,UAAU,aAAa,MAAM,OAAO,sBAAA,CAAuB,KAAK;;;;;;;AAQlF,MAAa,0BAA0B,QAAQ,aAAa;;;;;;;;;;;;;AAe5D,MAAM,2BAA2B;;;;;;;;;AAUjC,MAAM,gCAAgC;;;;;;;;;;;;;;AAetC,MAAM,2BAA2B;;;;;;;;;;;AAYjC,MAAM,aAAa;AAEnB,SAAS,iBAAiB,QAAsB;CAC9C,IAAI,CAAC,WAAW,KAAK,MAAM,GACzB,MAAM,IAAI,MAAM,oBAAoB,OAAO,iBAAiB,WAAW,EAAE;AAC7E;;;;;;;;;AAUA,SAAgB,uBAAuB,MAAoB;CACzD,MAAM,QAAQ,MAAsB,EAAE,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG;CAChE,MAAM,QAAQ,MAAsB,EAAE,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG;CAQhE,OAAO,GAPG,KAAK,eAOL,IANA,KAAK,KAAK,YAAY,IAAI,CAMtB,IALJ,KAAK,KAAK,WAAW,CAKb,EAAE,GAJV,KAAK,KAAK,YAAY,CAIT,IAHb,KAAK,KAAK,cAAc,CAGP,IAFjB,KAAK,KAAK,cAAc,CAEH,EAAE,GADtB,KAAK,KAAK,mBAAmB,CACH;AACvC;;AAMA,SAAS,QAAQ,KAAwB,MAAmC;CAC1E,MAAM,MAAyB,CAAC;CAChC,KAAK,MAAM,OAAO,MAChB,IAAI,IAAI,SAAS,KAAA,GACf,IAAI,OAAO,IAAI;CAEnB,OAAO;AACT;AAEA,SAAgB,qBAAqB,QAAwC;CAC3E,IAAI,UAAU;CACd,MAAM,0BAAU,IAAI,IAA6B;CACjD,MAAM,aAAa,QAAQ,OAAO,QAAQ,IAAI;CAC9C,MAAM,aAAa,QAAQ;CAC3B,MAAM,iBAAiB,QAAQ,kBAAkB;CAMjD,MAAM,UAA6B,QAAQ,eAAe,QACtD,QAAQ,QAAQ,KAAK;EAAC;EAAQ;EAAQ;EAAS;EAAQ;EAAU;EAAQ;EAAQ;CAAQ,CAAC,IAC1F,QAAQ;CAGZ,MAAM,gBAAgB,QAAQ,kBAAkB,KAAA,IAC5C,QAAQ,YAAY,OAAO,aAAa,IACxC,KAAA;;CAGJ,SAAS,aAAa,MAAc,QAAyB;EAC3D,MAAM,MAAM,SAAS,MAAM,MAAM;EACjC,OAAO,QAAQ,MAAO,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG;CAChE;;;;;;;CAQA,eAAe,iBAAiB,QAAyB,MAA+B;EACtF,MAAM,OAAO,QAAQ,OAAO,KAAK,IAAI;EACrC,IAAI,kBAAkB,KAAA,GACpB,OAAO;EAIT,IAAI,WAAW;EACf,MAAM,OAAiB,CAAC;EACxB,OAAO,MACL,IAAI;GACF,WAAW,MAAM,SAAS,QAAQ;GAClC;EACF,QACM;GACJ,MAAM,SAAS,QAAQ,QAAQ;GAC/B,IAAI,WAAW,UAAU;IAEvB,WAAW;IACX;GACF;GAGA,KAAK,QAAQ,SAAS,QAAQ,QAAQ,CAAC;GACvC,WAAW;EACb;EAEF,MAAM,YAAY,KAAK,SAAS,QAAQ,UAAU,GAAG,IAAI,IAAI;EAC7D,MAAM,gBAAgB,MAAM,SAAS,aAAa,CAAC,CAAC,YAAY,aAAa;EAC7E,IAAI,CAAC,aAAa,eAAe,SAAS,GACxC,MAAM,IAAI,MACR,oCAAoC,KAAK,qBAAqB,cAAc,EAC9E;EAEF,OAAO;CACT;;;;;;;;CASA,MAAM,wBAAQ,IAAI,IAAuB;CACzC,IAAI,cAAc;;;;;;;;;;;;;;;;;;;;CAqBlB,MAAM,mBAAmB,uCAAuB,IAAI,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;CAqB1D,IAAI,wBAAwB;CAC5B,MAAM,oBAA0B;EAC9B,KAAK,MAAM,QAAQ,MAAM,OAAO,GAAG;GACjC,IAAI,KAAK,WAAW,WAClB;GACF,MAAM,MAAM,KAAK,OAAO;GACxB,IAAI,QAAQ,KAAA,GACV;GACF,IAAI;IACF,IAAI,yBACF,QAAQ,KAAK,CAAC,KAAK,SAAS;SAE5B,QAAQ,KAAK,KAAK,SAAS;GAC/B,QACM,CAIN;EACF;CACF;CAEA,OAAO;EACL,MAAM;EAEN,cAAc;GACZ,OAAO;GACP,YAAY;GACZ,MAAM;GACN,SAAS;GACT,KAAK;GAKL,eAAe;EACjB;EAEA,MAAM,MAAM,WAAmD;GAC7D,MAAM,KAAK,WAAW,EAAE;GACxB,MAAM,MAAM,WAAW,OAAO;GAW9B,IAAI,cAAc;GAClB,IAAI;IACF,MAAM,KAAK,GAAG;GAChB,SACO,KAAK;IACV,cAAe,KAA+B,SAAS;GACzD;GACA,IAAI,aACF,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;GAEtC,MAAM,SAA0B;IAAE;IAAI,MAAM;IAAW;GAAI;GAC3D,QAAQ,IAAI,IAAI,MAAM;GACtB,OAAO;EACT;EAEA,MAAM,KACJ,QACA,SACA,SACqB;GACrB,MAAM,MAAM,SAAS,MAAM,QAAQ,OAAO,KAAK,QAAQ,GAAG,IAAI,OAAO;GAKrE,IAAI,SAAS,QAAQ,SACnB,OAAO;IAAE,QAAQ;IAAI,QAAQ;IAAkC,UAAU;GAAI;GAG/E,MAAM,aAAa,SAAS,WAAW,QAAQ,QAAQ,WAAW,MAAM;GACxE,MAAM,YAAY;GAIlB,MAAM,aAAa,MAAM,UAAU;GAEnC,OAAO,IAAI,SAAqB,aAAa;IAc3C,MAAM,QAAQ,WAAW,WAAW,CAAC,MAAM,OAAO,GAAG;KACnD;KACA,KAAK;MAAE,GAAG;MAAS,GAAG;MAAY,GAAG,SAAS;KAAI;KAClD,OAAO;MAAC;MAAU;MAAQ;KAAM;KAChC,UAAU;IACZ,CAAC;IAMD,MAAM,aAAyB;KAAE,MAAM;KAAI,OAAO;IAAE;IACpD,MAAM,aAAyB;KAAE,MAAM;KAAI,OAAO;IAAE;IACpD,IAAI,kBAAkB;IACtB,IAAI,WAAW;IACf,IAAI,gBAAgB;IACpB,IAAI,UAAU;IAKd,MAAM,2BAAiC;KACrC,iBAAiB,OAAO,SAAS;KAKjC,iBAJoC;MAClC,IAAI,CAAC,SACH,iBAAiB,OAAO,SAAS;KACrC,GAAG,6BACM,CAAC,CAAC,QAAQ;IACrB;IAEA,MAAM,WAAW,MAAkB,UAAwB;KAEzD,IADgB,aAAa,MAAM,OAAO,SAChC,MAAM,QAAQ,CAAC,iBAAiB;MACxC,kBAAkB;MAClB,mBAAmB;KACrB;IACF;IAEA,MAAM,QAAQ,GAAG,SAAQ,UAAS,QAAQ,YAAY,KAAe,CAAC;IACtE,MAAM,QAAQ,GAAG,SAAQ,UAAS,QAAQ,YAAY,KAAe,CAAC;IAEtE,MAAM,eAAe,YAAY,IAC7B,iBAAiB;KACf,WAAW;KACX,mBAAmB;IACrB,GAAG,SAAS,IACZ,KAAA;IAEJ,MAAM,gBAAsB;KAC1B,gBAAgB;KAChB,mBAAmB;IACrB;IACA,MAAM,aAAa,SAAS;IAC5B,IAAI,YACF,WAAW,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;IAE9D,IAAI;IAEJ,MAAM,UAAU,UAAkB,gBAA+B;KAC/D,IAAI,SACF;KACF,UAAU;KACV,IAAI,cACF,aAAa,YAAY;KAC3B,IAAI,gBACF,aAAa,cAAc;KAC7B,IAAI,YACF,WAAW,oBAAoB,SAAS,OAAO;KACjD,MAAM,cAAc,cACf,WAAW,OAAO,GAAG,WAAW,KAAK,IAAI,gBAAgB,cAC1D,WAAW;KACf,SAAS;MAAE,QAAQ,WAAW;MAAM,QAAQ;MAAa;KAAS,CAAC;IACrE;IAQA,MAAM,oBAAoB,MAAqB,WAAwC;KACrF,IAAI,eAAe;MACjB,OAAO,KAAK,mBAAmB;MAC/B;KACF;KACA,IAAI,UAAU;MACZ,OAAO,KAAK,2BAA2B,UAAU,GAAG;MACpD;KACF;KACA,IAAI,iBAAiB;MACnB,OAAO,KAAK,sBAAsB,WAAW,gBAAgB,CAAC;MAC9D;KACF;KACA,IAAI,QAAQ;MAGV,OAAO,KAAU,wBAAwB,QAAQ;MACjD;KACF;KACA,OAAO,OAAO,SAAS,WAAW,OAAO,CAAC;IAC5C;IAEA,MAAM,GAAG,UAAU,QAAQ;KAIzB,OAAO,GAAG,IAAI,OAAO;IACvB,CAAC;IAED,MAAM,GAAG,UAAU,MAAM,WAAW,iBAAiB,MAAM,MAAM,CAAC;IAElE,MAAM,GAAG,SAAS,MAAM,WAAW;KAOjC,iBAAiB,iBAAiB;MAChC,IAAI,SACF;MACF,MAAM,QAAQ,QAAQ;MACtB,MAAM,QAAQ,QAAQ;MACtB,iBAAiB,MAAM,MAAM;KAC/B,GAAG,wBAAwB;KAC3B,eAAe,QAAQ;IACzB,CAAC;GACH,CAAC;EACH;EAEA,MAAM,SAAS,QAAyB,MAA+B;GACrE,OAAO,SAAS,MAAM,iBAAiB,QAAQ,IAAI,GAAG,OAAO;EAC/D;EAEA,MAAM,SAAS,QAAyB,MAAsC;GAC5E,IAAI;IAKF,MAAM,OAAO,MAAM,KAAK,MAAM,iBAAiB,QAAQ,IAAI,CAAC;IAC5D,OAAO,KAAK,OAAO,IAAI,KAAK,OAAO;GACrC,QACM;IACJ,OAAO;GACT;EACF;EAEA,MAAM,eAAe,QAAyB,MAAmC;GAG/E,MAAM,MAAM,MAAM,SAAS,MAAM,iBAAiB,QAAQ,IAAI,CAAC;GAC/D,OAAO,IAAI,WAAW,GAAG;EAC3B;EAEA,MAAM,UAAU,QAAyB,MAAc,SAAgC;GACrF,MAAM,WAAW,MAAM,iBAAiB,QAAQ,IAAI;GACpD,MAAM,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;GAClD,MAAM,UAAU,UAAU,SAAS,OAAO;EAC5C;EAEA,MAAM,UAAU,QAAyB,MAAc,SAAoC;GACzF,IAAI,SAAS,MAAM;IACjB,MAAM,iBAAiB,QAAQ,kBAAkB,IAAI,KAAK,GAAG;IAC7D,MAAM,UAAU,MAAM,eAAe,MAAM,OAAO,KAAK;KACrD,OAAO,SAAS,SAAS;KACzB,UAAU,SAAS;KACnB,WAAW,SAAS;KACpB,QAAQ,SAAS;IACnB,CAAC;IACD,IAAI,kBAAkB,KAAA,GACpB,OAAO;IACT,MAAM,YAA8B,CAAC;IACrC,KAAK,MAAM,SAAS,SAClB,IAAI;KACF,MAAM,iBAAiB,QAAQ,MAAM,IAAI;KACzC,UAAU,KAAK,KAAK;IACtB,QACM,CAIN;IAEF,OAAO;GACT;GAKA,QAAO,MADe,QAAQ,MAAM,iBAAiB,QAAQ,IAAI,GAAG,EAAE,eAAe,KAAK,CAAC,EAAA,CAC5E,KAAI,MAAM,EAAE,YAAY,IAAI,GAAG,EAAE,KAAK,KAAK,EAAE,IAAK;EACnE;EAEA,MAAM,eACJ,QACA,SACA,SASqB;GACrB,MAAM,MAAM,QAAQ,MAAM,QAAQ,OAAO,KAAK,QAAQ,GAAG,IAAI,OAAO;GAKpE,MAAM,aAAa,MAAM,UAAU;GAEnC,MAAM,MAAM,QAAQ,WAAW,EAAE,WAAW,KAAK,CAAC;GAUlD,MAAM,SAAS,QAAQ,EAAE;GACzB,iBAAiB,MAAM;GACvB,MAAM,aAAa,QAAQ,QAAQ,WAAW,GAAG,OAAO,GAAG,iBAAiB,KAAK;GAIjF,IAAI,CAAC,uBAAuB;IAC1B,QAAQ,GAAG,QAAQ,WAAW;IAC9B,wBAAwB;GAC1B;GAUA,MAAM,eAA4B,kBAAkB,YAAY,EAAE,OAAO,IAAI,CAAC;GAS9E,aAAa,GAAG,UAAU,QAAQ;IAChC,IAAI,QAAQ,IAAI,cACd,QAAQ,OAAO,MAAM,0BAA0B,OAAO,qBAAqB,IAAI,QAAQ,GAAG;GAC9F,CAAC;GAMD,MAAM,QAAQ,WAAW,WAAW,CAAC,MAAM,OAAO,GAAG;IACnD;IACA,KAAK;KAAE,GAAG;KAAS,GAAG;KAAY,GAAG,QAAQ;IAAI;IACjD,OAAO;KAAC;KAAU;KAAQ;IAAM;IAChC,UAAU;GACZ,CAAC;GAED,MAAM,QAAmB;IACvB;IACA,UAAU,OAAO;IACjB,KAAK,MAAM,OAAO;IAClB;IACA;IACA,WAAW,KAAK,IAAI;IACpB;IACA;IACA;IACA,QAAQ;IACR,cAAc;IACd,SAAS;IACT,QAAQ,QAAQ;GAClB;GACA,MAAM,IAAI,QAAQ,KAAK;GASvB,MAAM,iBAAiB,OAAO,QAAQ,mBAAmB,YAAY,QAAQ,iBAAiB,IAC1F,QAAQ,iBACR,KAAA;GACJ,IAAI,eAAe;GAKnB,IAAI,eAAe;GAMnB,MAAM,iBAAiB,OAAO,QAAQ,mBAAmB,YAAY,QAAQ,iBAAiB,IAC1F,QAAQ,iBACR,KAAA;GACJ,MAAM,UAAU,QAAQ;GACxB,IAAI;GACJ,IAAI,eAAe,KAAK,IAAI;GAC5B,MAAM,sBAA4B;IAChC,IAAI,CAAC,kBAAkB,CAAC,SACtB;IACF,IAAI,YACF,aAAa,UAAU;IACzB,aAAa,iBAAiB;KAC5B,aAAa,KAAA;KACb,IAAI,MAAM,SACR;KACF,IAAI;MACF,QAAQ;OACN;OACA;OACA;OACA,cAAc,KAAK,IAAI,IAAI;OAC3B,cAAc,MAAM;MACtB,CAAC;KACH,SACO,KAAK;MACV,IAAI,QAAQ,IAAI,cACd,QAAQ,OAAO,MAAM,0BAA0B,OAAO,kBAAkB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,GAAG;KAChI;IACF,GAAG,cAAc;IACjB,WAAW,QAAQ;GACrB;GACA,MAAM,wBAA8B;IAClC,IAAI,YAAY;KACd,aAAa,UAAU;KACvB,aAAa,KAAA;IACf;GACF;GACA,cAAc;GAQd,MAAM,eAAe,UAAwB;IAC3C,MAAM,gBAAgB,MAAM;IAC5B,eAAe,KAAK,IAAI;IACxB,cAAc;IACd,IAAI,mBAAmB,KAAA,GAAW;KAChC,MAAM,SAAS,MAAM,eAAe,eAAe,MAAM;KACzD,IAAI,gBAAgB,UAAU,gBAAgB;MAC5C,eAAe;MACf,gBAAgB,MAAM;MACtB;KACF;KACA,MAAM,OAAO,iBAAiB;KAC9B,IAAI,MAAM,SAAS,MAAM;MACvB,eAAe;MAKf,IAAI,MAAM;MACV,OAAO,MAAM,MAAM,MAAM,OAAO,SAAU,KACxC;MACF,gBAAgB,MAAM,SAAS;MAC/B,IAAI,MAAM,GACR,aAAa,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC;MAC3C;KACF;IACF;IACA,aAAa,MAAM,KAAK;GAC1B;GACA,MAAM,QAAQ,GAAG,SAAQ,UAAS,YAAY,KAAe,CAAC;GAC9D,MAAM,QAAQ,GAAG,SAAQ,UAAS,YAAY,KAAe,CAAC;GAM9D,MAAM,UAAU,OAA0B,MAAqB,QAA+B,eAA8B;IAC1H,IAAI,MAAM,SACR;IACF,MAAM,UAAU;IAChB,gBAAgB;IAChB,MAAM,UAAU,KAAK,IAAI;IAGzB,MAAM,SACF,WAAW,aAAa,MAAM,gBAC5B,WACA;IAIN,MAAM,WAAW,SAAS,OACtB,OACA,WAAW,YACT,MACA,SACE,MACA;IACR,MAAM,SAAS;IACf,MAAM,WAAW;IACjB,IAAI,QACF,MAAM,SAAS;IAcjB,IAAI,YACF,IAAI;KACF,aAAa,MAAM,KAAK,WAAW,GAAG;IACxC,QACM,CAEN;IAEF,IAAI,eAAe,GACjB,IAAI;KAGF,aAAa,MAAM,sCAAsC,aAAa,MAAM;IAC9E,QACM,CAEN;IAEF,aAAa,UAAU;KAKrB,IAAI;MACF,MAAM,SAAS,oBAAoB,KAAK,CAAC;KAC3C,SACO,KAAK;MAIV,IAAI,QAAQ,IAAI,cACd,QAAQ,OAAO,MAAM,0BAA0B,OAAO,iBAAiB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,GAAG;KAC/H;IACF,CAAC;IAQD,MAAM,QAAQ,KAAA;IACd,MAAM,eAAe,KAAA;GACvB;GAEA,MAAM,GAAG,UAAU,MAAM,WAAW,OAAO,SAAS,MAAM,MAAM,CAAC;GACjE,MAAM,GAAG,UAAS,QAAO,OAAO,SAAS,MAAM,MAAM,iBAAiB,IAAI,SAAS,CAAC;GAEpF,MAAM,GAAG,SAAS,MAAM,WAAW;IAajC,iBAPwC;KACtC,IAAI,MAAM,SACR;KACF,MAAM,QAAQ,QAAQ;KACtB,MAAM,QAAQ,QAAQ;KACtB,OAAO,SAAS,MAAM,MAAM;IAC9B,GAAG,wBACU,CAAC,CAAC,QAAQ;GACzB,CAAC;GAED,OAAO;IAAE;IAAQ,KAAK,MAAM;IAAK;GAAW;EAC9C;EAEA,MAAM,eAAe,QAAyB,QAA8C;GAC1F,MAAM,QAAQ,MAAM,IAAI,MAAM;GAO9B,IAAI,CAAC,SAAS,MAAM,aAAa,OAAO,IACtC,OAAO;GAIT,IAAI,MAAM,WAAW,WACnB,OAAO,oBAAoB,KAAK;GAOlC,MAAM,gBAAgB;GAMtB,MAAM,SAAS,IAAI,SAAe,aAAa;IAC7C,IAAI,MAAM,SAAS;KACjB,SAAS;KACT;IACF;IACA,MAAM,iBAAiB,MAAM;IAC7B,MAAM,UAAU,SAAS;KACvB,iBAAiB,IAAI;KACrB,SAAS;IACX;GACF,CAAC;GAED,iBAAiB,MAAM,OAAO,SAAS;GACvC,MAAM;GACN,OAAO,oBAAoB,KAAK;EAClC;EAEA,MAAM,eACJ,QACA,QACA,SAC8B;GAC9B,MAAM,QAAQ,MAAM,IAAI,MAAM;GAI9B,IAAI,CAAC,SAAS,MAAM,aAAa,OAAO,IACtC,OAAO;GACT,IAAI,MAAM,SACR,OAAO,oBAAoB,KAAK;GAElC,OAAO,IAAI,SAA8B,aAAa;IACpD,IAAI,OAAO;IACX,IAAI;IACJ,MAAM,SAAS,SAAS;IACxB,MAAM,UAAU,UAAqC;KACnD,IAAI,MACF;KACF,OAAO;KACP,IAAI,OACF,aAAa,KAAK;KACpB,QAAQ,oBAAoB,SAAS,OAAO;KAC5C,SAAS,KAAK;IAChB;IACA,SAAS,UAAgB;KACvB,OAAO,IAAI;IACb;IAMA,MAAM,iBAAiB,MAAM;IAC7B,MAAM,UAAU,SAAS;KACvB,iBAAiB,IAAI;KACrB,OAAO,oBAAoB,KAAK,CAAC;IACnC;IAEA,IAAI,QAAQ;KACV,IAAI,OAAO,SAAS;MAClB,OAAO,IAAI;MACX;KACF;KACA,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;IAC1D;IACA,MAAM,YAAY,SAAS;IAC3B,IAAI,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,KAAK,YAAY,GAAG;KAChF,QAAQ,WAAW,QAAQ,WAAW,IAAI;KAC1C,MAAM,QAAQ;IAChB;GACF,CAAC;EACH;EAEA,MAAM,wBACJ,YACA,UACA,WAC+B;GAG/B,IAAI,WAAW,OAAO,SAAS,IAC7B,OAAO,CAAC;GACV,MAAM,WAAwB,CAAC;GAC/B,KAAK,MAAM,SAAS,MAAM,OAAO,GAAG;IAClC,IAAI,MAAM,aAAa,WAAW,MAAM,MAAM,WAAW,WACvD;IACF,MAAM,WAAW,SAAS;IAK1B,IAAI,WACF,MAAM,SAAS;IACjB,SAAS,KAAK,iBAAiB,KAAK,CAAC;GACvC;GACA,OAAO;EACT;EAEA,MAAM,eAAe,QAAwD;GAM3E,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,CACvB,QAAO,MAAK,EAAE,aAAa,OAAO,EAAE,CAAC,CACrC,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,CACzC,IAAI,gBAAgB;EACzB;EAEA,MAAM,QAAQ,QAAwC;GAkBpD,MAAM,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,QAAO,MAAK,EAAE,aAAa,OAAO,MAAM,CAAC,EAAE,OAAO;GACxF,MAAM,QAAQ,IAAI,UAAU,IAAI,OAAO,UAAU;IAC/C,MAAM,gBAAgB;IACtB,MAAM,IAAI,SAAe,aAAa;KACpC,IAAI;KACJ,MAAM,iBAAiB,MAAM;KAC7B,MAAM,UAAU,SAAS;MACvB,iBAAiB,IAAI;MACrB,IAAI,YACF,aAAa,UAAU;MACzB,SAAS;KACX;KACA,iBAAiB,MAAM,OAAO,SAAS;KAQvC,aAAa,iBAAiB;MAC5B,iBAAiB,MAAM,OAAO,SAAS;MAIvC,MAAM,cAAc,QAAQ;MAC5B,SAAS;KACX,GAAG,cAAc;KACjB,WAAW,QAAQ;IACrB,CAAC;GACH,CAAC,CAAC;GAGF,KAAK,MAAM,CAAC,QAAQ,UAAU,OAC5B,IAAI,MAAM,aAAa,OAAO,IAC5B,MAAM,OAAO,MAAM;GAEvB,QAAQ,OAAO,OAAO,EAAE;GAUxB,IAAI,yBAAyB,QAAQ,SAAS,GAAG;IAC/C,QAAQ,IAAI,QAAQ,WAAW;IAC/B,wBAAwB;GAC1B;EACF;CACF;AACF;;;;;;;;AAyEA,SAAgB,iBAAiB,OAAiC,QAA8B;CAC9F,MAAM,MAAM,OAAO;CACnB,IAAI,QAAQ,KAAA,GACV;CACF,IAAI;EACF,IAAI,yBACF,QAAQ,KAAK,CAAC,KAAK,MAAM;OAEzB,QAAQ,KAAK,KAAK,MAAM;CAC5B,QACM,CAGN;AACF;;;;;;;AAQA,SAAS,iBAAiB,OAA6B;CACrD,OAAO;EACL,QAAQ,MAAM;EACd,KAAK,MAAM;EACX,SAAS,MAAM;EACf,KAAK,MAAM;EACX,WAAW,MAAM;EACjB,GAAI,MAAM,YAAY,KAAA,IAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;EAChE,YAAY,MAAM;EAClB,QAAQ,MAAM;EACd,GAAI,MAAM,aAAa,KAAA,IAAY,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;EACnE,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;EAC/C,cAAc,MAAM;CACtB;AACF;;;;;;;AAQA,SAAS,oBAAoB,OAAgC;CAC3D,OAAO;EACL,QAAQ,MAAM;EACd,QAAQ,MAAM;EACd,UAAU,MAAM,YAAY;EAC5B,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;EAC/C,YAAY,MAAM;EAIlB,aAAa,MAAM,WAAW,KAAK,IAAI,KAAK,MAAM;EAClD,SAAS,MAAM;CACjB;AACF;;;;;;;AC1mCA,SAAgB,+BAA+B,SAAoD;CACjG,OAAO,QAAQ,aAAa,kBACtB,QAAQ,iBAAiB,qBAAqB;AACtD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"exec-file-size-PXkr-MGA.js","names":[],"sources":["../src/contexts/glob-shell.ts","../src/tools/shell-quote.ts","../src/contexts/exec-file-size.ts"],"sourcesContent":["/**\n * Shell-driven file matching — the node-free half of `./glob`.\n *\n * `globFilesViaShell` runs `find` through a caller-supplied `exec` callback, so\n * it has ZERO Node imports (no `node:fs`, no `node:child_process`). It lives in\n * its own module so remote-only contexts (`createSandboxContext`) can import it\n * without dragging the local-filesystem glob (and its `node:fs/promises`\n * imports) into a workerd/edge bundle. `./glob` re-exports it for back-compat.\n */\n\nimport type { ExecResult } from './types'\n\nconst SAFE_GLOB_PATTERN_RE = /^[\\w./*?[\\]{}!,^@+-]+$/\n\n/**\n * Match files under the execution context's cwd by shelling out to `find`.\n * The `exec` callback runs the command in the target context (sandbox, docker,\n * remote); results are returned as cwd-relative paths, sorted, capped at\n * `limit`.\n */\nexport async function globFilesViaShell(\n pattern: string,\n exec: (command: string) => Promise<ExecResult>,\n limit: number,\n): Promise<string[]> {\n if (!SAFE_GLOB_PATTERN_RE.test(pattern))\n throw new Error('Glob pattern contains unsupported characters (shell fallback only allows path/glob metacharacters)')\n\n const useBasename = !pattern.includes('/')\n const finder = useBasename\n ? `find . -type f -name '${pattern}'`\n : `find . -type f -path './${pattern}'`\n const result = await exec(finder)\n if (result.exitCode !== 0 && !result.stdout.trim()) {\n const detail = result.stderr.trim() || `find exited with code ${result.exitCode}`\n throw new Error(`file matching requires \\`find\\` in the execution context (${detail}). Use the \\`shell\\` tool, or \\`list_files\\` for a single directory.`)\n }\n\n return result.stdout\n .split('\\n')\n .map(line => line.replace(/^\\.\\//, ''))\n .filter(line => line.length > 0)\n .sort()\n .slice(0, limit)\n}\n","/**\n * Shared shell-argument quoter for tool implementations.\n *\n * Single source of truth so `grep`, `binary-read`, and `skills-run-script`\n * don't drift on the POSIX `'\\''` escape pattern.\n */\n\nconst SAFE_TOKEN_RE = /^[\\w@%+=:,./-]+$/\nconst SINGLE_QUOTE_RE = /'/g\n\n/**\n * Wrap an argument in single quotes, escaping embedded single quotes via the\n * standard POSIX `'\\''` close-escape-reopen trick. Tokens that are already\n * shell-safe pass through unchanged so command lines stay readable in logs.\n *\n * NOT a sandbox — only safe when the caller controls the surrounding shell\n * context. Use it for arguments only, never for the verb / subcommand.\n */\nexport function shellQuote(arg: string): string {\n if (SAFE_TOKEN_RE.test(arg))\n return arg\n return `'${arg.replace(SINGLE_QUOTE_RE, '\\'\\\\\\'\\'')}'`\n}\n\n/**\n * Variant that always quotes — useful when the caller doesn't want a\n * conditional `unquoted-when-safe` branch (consistent log shape, paranoid\n * inputs that contain whitespace by construction).\n */\nexport function alwaysQuote(arg: string): string {\n return `'${arg.replace(SINGLE_QUOTE_RE, '\\'\\\\\\'\\'')}'`\n}\n","/**\n * Exec-probed `ExecutionContext.fileSize` — the shared implementation\n * behind every context whose only cheap size primitive is a shell\n * (docker, sandbox providers). One definition so the parse rules\n * (strict integer, non-negative) and the quoting posture can't drift\n * between contexts.\n *\n * `wc -c <` is POSIX (busybox/alpine included), follows symlinks like\n * the process context's `stat` probe, and exits non-zero for missing\n * paths and directories — all of which collapse to `null` (\"unknowable\",\n * the whole-read guard proceeds unguarded).\n */\n\nimport type { ExecResult } from './types'\nimport { alwaysQuote } from '../tools/shell-quote'\n\nexport async function fileSizeViaExec(\n exec: (command: string) => Promise<ExecResult>,\n path: string,\n): Promise<number | null> {\n try {\n const result = await exec(`wc -c < ${alwaysQuote(path)}`)\n if (result.exitCode !== 0)\n return null\n const size = Number(result.stdout.trim())\n return Number.isInteger(size) && size >= 0 ? size : null\n }\n catch {\n return null\n }\n}\n"],"mappings":";AAYA,MAAM,uBAAuB;;;;;;;AAQ7B,eAAsB,kBACpB,SACA,MACA,OACmB;CACnB,IAAI,CAAC,qBAAqB,KAAK,OAAO,GACpC,MAAM,IAAI,MAAM,oGAAoG;CAMtH,MAAM,SAAS,MAAM,KAHN,CADM,QAAQ,SAAS,GAAG,IAErC,yBAAyB,QAAQ,KACjC,2BAA2B,QAAQ,EACP;CAChC,IAAI,OAAO,aAAa,KAAK,CAAC,OAAO,OAAO,KAAK,GAAG;EAClD,MAAM,SAAS,OAAO,OAAO,KAAK,KAAK,yBAAyB,OAAO;EACvE,MAAM,IAAI,MAAM,6DAA6D,OAAO,qEAAqE;CAC3J;CAEA,OAAO,OAAO,OACX,MAAM,IAAI,CAAC,CACX,KAAI,SAAQ,KAAK,QAAQ,SAAS,EAAE,CAAC,CAAC,CACtC,QAAO,SAAQ,KAAK,SAAS,CAAC,CAAC,CAC/B,KAAK,CAAC,CACN,MAAM,GAAG,KAAK;AACnB;;;;;;;;;ACrCA,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;;;;;;;;;AAUxB,SAAgB,WAAW,KAAqB;CAC9C,IAAI,cAAc,KAAK,GAAG,GACxB,OAAO;CACT,OAAO,IAAI,IAAI,QAAQ,iBAAiB,OAAU,EAAE;AACtD;;;;;;AAOA,SAAgB,YAAY,KAAqB;CAC/C,OAAO,IAAI,IAAI,QAAQ,iBAAiB,OAAU,EAAE;AACtD;;;ACfA,eAAsB,gBACpB,MACA,MACwB;CACxB,IAAI;EACF,MAAM,SAAS,MAAM,KAAK,WAAW,YAAY,IAAI,GAAG;EACxD,IAAI,OAAO,aAAa,GACtB,OAAO;EACT,MAAM,OAAO,OAAO,OAAO,OAAO,KAAK,CAAC;EACxC,OAAO,OAAO,UAAU,IAAI,KAAK,QAAQ,IAAI,OAAO;CACtD,QACM;EACJ,OAAO;CACT;AACF"}