zidane 5.6.14 → 5.7.4

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 (119) hide show
  1. package/README.md +3 -1
  2. package/dist/{agent-ClkpElCZ.d.ts → agent-BNS2nx_T.d.ts} +535 -15
  3. package/dist/agent-BNS2nx_T.d.ts.map +1 -0
  4. package/dist/chat/pure.d.ts +4 -0
  5. package/dist/chat/pure.js +3 -0
  6. package/dist/chat.d.ts +31 -661
  7. package/dist/chat.d.ts.map +1 -1
  8. package/dist/chat.js +5 -3
  9. package/dist/chat.js.map +1 -1
  10. package/dist/contexts/docker.d.ts +1 -1
  11. package/dist/contexts/docker.d.ts.map +1 -1
  12. package/dist/contexts/docker.js.map +1 -1
  13. package/dist/{contexts-BOtMvzli.js → contexts-BD2U_xpi.js} +2 -2
  14. package/dist/{contexts-BOtMvzli.js.map → contexts-BD2U_xpi.js.map} +1 -1
  15. package/dist/contexts.d.ts +3 -3
  16. package/dist/contexts.js +1 -1
  17. package/dist/edit-utils-DnfNoj16.js +574 -0
  18. package/dist/edit-utils-DnfNoj16.js.map +1 -0
  19. package/dist/{errors-DdZXnyXE.js → errors-CoQnKRf1.js} +32 -2
  20. package/dist/{errors-DdZXnyXE.js.map → errors-CoQnKRf1.js.map} +1 -1
  21. package/dist/fetch-url-CPxfiXDa.js +518 -0
  22. package/dist/fetch-url-CPxfiXDa.js.map +1 -0
  23. package/dist/image-sniff-B7uFSNO1.js +90 -0
  24. package/dist/image-sniff-B7uFSNO1.js.map +1 -0
  25. package/dist/{index-CbS75MD3.d.ts → index-CZOwAJIX.d.ts} +2 -2
  26. package/dist/index-CZOwAJIX.d.ts.map +1 -0
  27. package/dist/{index-CTDMMdIy.d.ts → index-Ck_AWt8P.d.ts} +3 -4
  28. package/dist/index-Ck_AWt8P.d.ts.map +1 -0
  29. package/dist/{index-v3Tzobqr.d.ts → index-KiS7w0dC.d.ts} +3 -3
  30. package/dist/index-KiS7w0dC.d.ts.map +1 -0
  31. package/dist/index.d.ts +6 -6
  32. package/dist/index.js +13 -12
  33. package/dist/index.js.map +1 -1
  34. package/dist/{interpolate-DM1UcKeQ.js → interpolate-TySiqKzc.js} +23 -23
  35. package/dist/{interpolate-DM1UcKeQ.js.map → interpolate-TySiqKzc.js.map} +1 -1
  36. package/dist/{login-7tHcckmX.js → login-BDeqENSe.js} +7 -58
  37. package/dist/login-BDeqENSe.js.map +1 -0
  38. package/dist/{mcp-DGeB7-3D.js → mcp-Kqzz-Rs_.js} +8 -6
  39. package/dist/mcp-Kqzz-Rs_.js.map +1 -0
  40. package/dist/mcp.d.ts +2 -2
  41. package/dist/mcp.js +1 -1
  42. package/dist/{messages-Dym8S_YH.js → messages-CvRQTdbR.js} +118 -39
  43. package/dist/messages-CvRQTdbR.js.map +1 -0
  44. package/dist/{presets-w9Px_aAm.js → presets-JuOnSI-i.js} +2 -2
  45. package/dist/{presets-w9Px_aAm.js.map → presets-JuOnSI-i.js.map} +1 -1
  46. package/dist/presets.d.ts +3 -3
  47. package/dist/presets.js +1 -1
  48. package/dist/{providers-beXyD9W9.js → providers-h4HJPbbv.js} +485 -31
  49. package/dist/providers-h4HJPbbv.js.map +1 -0
  50. package/dist/providers.d.ts +2 -2
  51. package/dist/providers.js +3 -3
  52. package/dist/restate.d.ts +1 -1
  53. package/dist/restate.d.ts.map +1 -1
  54. package/dist/restate.js.map +1 -1
  55. package/dist/session/sqlite.d.ts +1 -1
  56. package/dist/session/sqlite.d.ts.map +1 -1
  57. package/dist/session/sqlite.js +1 -1
  58. package/dist/session/sqlite.js.map +1 -1
  59. package/dist/{session-BRIsmBSY.js → session-BzLou2_-.js} +2 -2
  60. package/dist/{session-BRIsmBSY.js.map → session-BzLou2_-.js.map} +1 -1
  61. package/dist/session.d.ts +2 -2
  62. package/dist/session.js +2 -2
  63. package/dist/skills.d.ts +3 -3
  64. package/dist/skills.js +1 -1
  65. package/dist/skills.js.map +1 -1
  66. package/dist/{stats-Lc3zL3RM.js → stats-DAKBEKjc.js} +12 -2
  67. package/dist/stats-DAKBEKjc.js.map +1 -0
  68. package/dist/{stdio-loader-EVAF5KlU.js → stdio-loader-Ce68wUmM.js} +4 -4
  69. package/dist/stdio-loader-Ce68wUmM.js.map +1 -0
  70. package/dist/tool-formatters-CU-j3a3e.d.ts +1471 -0
  71. package/dist/tool-formatters-CU-j3a3e.d.ts.map +1 -0
  72. package/dist/tools/fetch-url.d.ts +70 -0
  73. package/dist/tools/fetch-url.d.ts.map +1 -0
  74. package/dist/tools/fetch-url.js +2 -0
  75. package/dist/tools/web-search.d.ts +7 -0
  76. package/dist/tools/web-search.d.ts.map +1 -0
  77. package/dist/tools/web-search.js +190 -0
  78. package/dist/tools/web-search.js.map +1 -0
  79. package/dist/{tools-DhrLrOEr.js → tools-BGtJK0vo.js} +1368 -421
  80. package/dist/tools-BGtJK0vo.js.map +1 -0
  81. package/dist/tools.d.ts +3 -3
  82. package/dist/tools.js +1 -1
  83. package/dist/{turn-operations-UAkOjO-u.js → transcript-anchors-BTSZAPVc.js} +147 -2713
  84. package/dist/transcript-anchors-BTSZAPVc.js.map +1 -0
  85. package/dist/{transcript-anchors-D0TR6djV.d.ts → transcript-anchors-DX90kXc4.d.ts} +13 -1299
  86. package/dist/transcript-anchors-DX90kXc4.d.ts.map +1 -0
  87. package/dist/tui.d.ts +58 -28
  88. package/dist/tui.d.ts.map +1 -1
  89. package/dist/tui.js +1349 -422
  90. package/dist/tui.js.map +1 -1
  91. package/dist/turn-operations-CCHfR9eC.js +1938 -0
  92. package/dist/turn-operations-CCHfR9eC.js.map +1 -0
  93. package/dist/turn-operations-DDIl4YVk.d.ts +658 -0
  94. package/dist/turn-operations-DDIl4YVk.d.ts.map +1 -0
  95. package/dist/{types-oKPBdCmL.js → types-BPw_i5vb.js} +1 -1
  96. package/dist/types-BPw_i5vb.js.map +1 -0
  97. package/dist/{types-KukEp-mi.d.ts → types-CEAMIUXw.d.ts} +1 -1
  98. package/dist/types-CEAMIUXw.d.ts.map +1 -0
  99. package/dist/types.d.ts +4 -4
  100. package/dist/types.js +3 -3
  101. package/docs/CHAT.md +53 -6
  102. package/docs/SKILL.md +3 -0
  103. package/docs/TUI.md +7 -0
  104. package/package.json +18 -2
  105. package/dist/agent-ClkpElCZ.d.ts.map +0 -1
  106. package/dist/index-CTDMMdIy.d.ts.map +0 -1
  107. package/dist/index-CbS75MD3.d.ts.map +0 -1
  108. package/dist/index-v3Tzobqr.d.ts.map +0 -1
  109. package/dist/login-7tHcckmX.js.map +0 -1
  110. package/dist/mcp-DGeB7-3D.js.map +0 -1
  111. package/dist/messages-Dym8S_YH.js.map +0 -1
  112. package/dist/providers-beXyD9W9.js.map +0 -1
  113. package/dist/stats-Lc3zL3RM.js.map +0 -1
  114. package/dist/stdio-loader-EVAF5KlU.js.map +0 -1
  115. package/dist/tools-DhrLrOEr.js.map +0 -1
  116. package/dist/transcript-anchors-D0TR6djV.d.ts.map +0 -1
  117. package/dist/turn-operations-UAkOjO-u.js.map +0 -1
  118. package/dist/types-KukEp-mi.d.ts.map +0 -1
  119. package/dist/types-oKPBdCmL.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"messages-Dym8S_YH.js","names":[],"sources":["../src/system-prompt.ts","../src/providers/cost.ts","../src/providers/schema-sanitize.ts","../src/providers/openai-compat.ts","../src/session/messages.ts"],"sourcesContent":["/**\n * System-prompt boundary marker — splits a system prompt into a stable static\n * prefix (cached) and a per-turn dynamic suffix (NOT cached).\n *\n * Why this exists: providers attach `cache_control` markers on the last block\n * of the system prompt, so the cached prefix covers the entire system text.\n * Any byte change anywhere — including a per-turn `<env>` rewrite — busts the\n * cache for the doctrine that sits below. A literal marker in the system\n * string lets providers split it into:\n *\n * ┌──────────────┐ cache_control: ephemeral\n * │ STATIC half │ — doctrine, skills catalog, tool catalog,\n * │ │ user instructions\n * ├──────────────┤ ← SYSTEM_PROMPT_BOUNDARY\n * │ DYNAMIC half │ — env, cwd, mtimes, anything per-turn\n * └──────────────┘ (no cache_control)\n *\n * The static prefix rides the prompt cache across turns/sessions; the dynamic\n * suffix re-bills per turn. Net effect: a cwd change between turns no longer\n * invalidates 4 KB of doctrine.\n *\n * Wire contract:\n *\n * - `splitSystemPrompt(s)` is pure; missing marker ⇒ entire string is static\n * (current behavior — no caller is forced to opt in).\n * - `renderSystemForWire(s)` strips the marker so it never reaches the model;\n * used by every provider before the bytes hit the wire, including the\n * cache-disabled path on providers that DO support `cache_control`.\n * - The marker uses underscores rather than XML/punctuation so it's\n * unambiguous when scanning prompts manually and unlikely to collide with\n * model-written content.\n * - Providers handle the split internally (Anthropic emits a 2-block array;\n * OpenAI-compat splits the leading `system` message into multi-part text).\n * Callers always pass a single `string` — no API break.\n */\n\n/**\n * Literal marker inserted in a system prompt to separate cacheable doctrine\n * from per-turn dynamic content. Providers split on this token.\n *\n * Underscored on both sides for visual distinctiveness — a stray instance in\n * model-written prose is implausible. Don't change the value without shipping\n * a migration; existing sessions carry the old marker in their cached\n * prompts.\n */\nexport const SYSTEM_PROMPT_BOUNDARY = '__ZIDANE_SYSTEM_PROMPT_BOUNDARY__'\n\n/** Result of {@link splitSystemPrompt} — both halves stripped of the marker. */\nexport interface SystemPromptParts {\n /** Bytes BEFORE the marker (or the entire string when no marker present). Cacheable. */\n static: string\n /** Bytes AFTER the marker. Empty when no marker present. NOT cached. */\n dynamic: string\n}\n\n/**\n * Split a system prompt around the first {@link SYSTEM_PROMPT_BOUNDARY}.\n *\n * Splits on the FIRST occurrence — subsequent markers are folded into the\n * dynamic half. This way callers can append additional `<env>` style blocks\n * with extra markers without each one creating a new cache layer (Anthropic\n * caps breakpoints; we use the budget elsewhere). The marker itself is\n * stripped from both sides — providers attach `cache_control` directly to\n * the static half's text content.\n *\n * A single blank line (`\\n\\n`) immediately adjacent to the marker on each\n * side is trimmed — callers conventionally write\n * `<doctrine>\\n\\n<MARKER>\\n\\n<env>` and expect the rendered wire bytes to\n * read as one logical paragraph. Blank lines beyond that immediate pair\n * (e.g. `\\n\\n\\n<env>`) are preserved verbatim so callers composing their\n * own spacing don't lose intentional gaps.\n *\n * Pure / no I/O / no allocation when the marker is absent (returns the input\n * verbatim on the static side and the empty string on the dynamic side).\n */\nexport function splitSystemPrompt(system: string): SystemPromptParts {\n if (system.length === 0)\n return { static: '', dynamic: '' }\n const idx = system.indexOf(SYSTEM_PROMPT_BOUNDARY)\n if (idx < 0)\n return { static: system, dynamic: '' }\n const staticPart = system.slice(0, idx)\n const dynamicPart = system.slice(idx + SYSTEM_PROMPT_BOUNDARY.length)\n // Strip a single immediately-adjacent newline pair on each side. Callers\n // typically write `<doctrine>\\n\\n<MARKER>\\n\\n<env>` and expect the rendered\n // wire bytes to read as one logical paragraph. The trim is bounded so\n // intentional blank lines further out (e.g. `\\n\\n\\n<env>`) are preserved\n // for callers that compose their own spacing.\n return {\n static: trimTrailingBlankLine(staticPart),\n dynamic: trimLeadingBlankLine(dynamicPart),\n }\n}\n\n/**\n * Compose a system prompt from a static prefix and an optional dynamic\n * suffix. Inserts {@link SYSTEM_PROMPT_BOUNDARY} between them only when the\n * dynamic side is non-empty — single-block prompts stay marker-free, so\n * callers that never opt in pay zero overhead and providers fall back to the\n * existing whole-string caching path.\n *\n * Spacing is `\\n\\n` on both sides of the marker so doctrine fragments,\n * which conventionally separate sections with a blank line, read cleanly\n * around the boundary.\n */\nexport function joinSystemPrompt(staticPart: string, dynamicPart: string): string {\n if (dynamicPart.length === 0)\n return staticPart\n if (staticPart.length === 0)\n return `${SYSTEM_PROMPT_BOUNDARY}\\n\\n${dynamicPart}`\n return `${staticPart}\\n\\n${SYSTEM_PROMPT_BOUNDARY}\\n\\n${dynamicPart}`\n}\n\n/**\n * Append `extra` to the STATIC half of a system prompt, preserving any\n * existing dynamic suffix.\n *\n * Used by the agent to fold in run-stable content (skills catalog, lazy tool\n * catalog) without bumping it into the dynamic half — both catalogs are\n * built once per run and remain byte-stable for the duration, so they\n * belong in the cached prefix.\n *\n * Returns a new string; the input is not mutated. When `extra` is empty,\n * returns the input verbatim.\n */\nexport function appendStaticSection(system: string, extra: string): string {\n if (extra.length === 0)\n return system\n const { static: staticPart, dynamic } = splitSystemPrompt(system)\n const nextStatic = staticPart.length === 0 ? extra : `${staticPart}\\n\\n${extra}`\n return joinSystemPrompt(nextStatic, dynamic)\n}\n\n/**\n * Append `extra` to the DYNAMIC half of a system prompt. Inserts the boundary\n * marker if the input didn't already carry one.\n *\n * Used by hosts (typically the TUI) to inject per-turn state — current cwd,\n * IDE selection, project root — without forcing every caller to know the\n * marker format. The host's `system:transform` hook rewrites the dynamic\n * half each turn; the static doctrine above stays byte-stable and rides the\n * cache.\n *\n * Returns a new string; the input is not mutated. When `extra` is empty,\n * returns the input verbatim.\n */\nexport function appendDynamicSection(system: string, extra: string): string {\n if (extra.length === 0)\n return system\n const { static: staticPart, dynamic } = splitSystemPrompt(system)\n const nextDynamic = dynamic.length === 0 ? extra : `${dynamic}\\n\\n${extra}`\n return joinSystemPrompt(staticPart, nextDynamic)\n}\n\n/**\n * Replace the entire dynamic half of a system prompt with `next`. Used by\n * the TUI's `<env>` rewriter where the entire dynamic section is regenerated\n * each turn rather than appended to.\n *\n * When `next` is empty, drops the dynamic half (and the marker) entirely.\n */\nexport function replaceDynamicSection(system: string, next: string): string {\n const { static: staticPart } = splitSystemPrompt(system)\n return joinSystemPrompt(staticPart, next)\n}\n\n/**\n * Strip the boundary marker so it never reaches the wire — collapses\n * `<static>${BOUNDARY}<dynamic>` into `<static>\\n\\n<dynamic>` for providers\n * that can't honor `cache_control` (vanilla OpenAI Chat Completions, Codex,\n * Cerebras, ...) or for the cache-disabled path on any provider.\n *\n * Cache-aware providers re-derive the split from the original (un-rendered)\n * system string via `splitSystemPrompt` — `renderSystemForWire` is the\n * marker-free counterpart used to build the actual wire bytes.\n *\n * No-op when the input has no marker. Pure / no I/O.\n */\nexport function renderSystemForWire(system: string): string {\n if (system.length === 0)\n return system\n const parts = splitSystemPrompt(system)\n if (parts.dynamic.length === 0)\n return parts.static\n if (parts.static.length === 0)\n return parts.dynamic\n return `${parts.static}\\n\\n${parts.dynamic}`\n}\n\n/** True when `system` contains the boundary marker. */\nexport function hasSystemPromptBoundary(system: string): boolean {\n return system.includes(SYSTEM_PROMPT_BOUNDARY)\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\n/** Strip a single trailing `\\n\\n` (and any leftover trailing whitespace within that pair). */\nfunction trimTrailingBlankLine(s: string): string {\n // Walk back across at most two `\\n` separated only by spaces/tabs.\n let end = s.length\n let newlines = 0\n while (end > 0 && newlines < 2) {\n const ch = s.charCodeAt(end - 1)\n if (ch === 0x0A) {\n newlines += 1\n end -= 1\n continue\n }\n if (ch === 0x20 || ch === 0x09) {\n end -= 1\n continue\n }\n break\n }\n return newlines > 0 ? s.slice(0, end) : s\n}\n\n/** Strip a single leading `\\n\\n` (and any leftover leading whitespace within that pair). */\nfunction trimLeadingBlankLine(s: string): string {\n let start = 0\n let newlines = 0\n while (start < s.length && newlines < 2) {\n const ch = s.charCodeAt(start)\n if (ch === 0x0A) {\n newlines += 1\n start += 1\n continue\n }\n if (ch === 0x20 || ch === 0x09) {\n start += 1\n continue\n }\n break\n }\n return newlines > 0 ? s.slice(start) : s\n}\n","import type { TurnUsage } from '../types'\nimport { getModel } from '@earendil-works/pi-ai'\n\ninterface ModelCostRates {\n input?: number\n output?: number\n cacheRead?: number\n cacheWrite?: number\n}\n\n/**\n * Fill in `usage.cost` from pi-ai's bundled price registry when the\n * provider didn't report one. Provider-reported cost (e.g. OpenRouter's\n * `total_cost`, OpenAI's pi-ai-computed total) always wins — we only\n * estimate when `usage.cost` is undefined.\n *\n * Lookup is `(provider, usage.modelId)`. On a miss (unknown / unbundled\n * model), `cost` stays undefined and the footer indicator hides — better\n * than fabricating a $0.\n *\n * The number is an estimate: token counts come from the API, rates from\n * the locally-bundled registry that refreshes with `@earendil-works/pi-ai`\n * version bumps. If a provider changes prices between bumps the figure\n * will drift until the dep updates.\n */\nexport function fillEstimatedCost(usage: TurnUsage, provider: string): TurnUsage {\n if (usage.cost !== undefined)\n return usage\n if (!usage.modelId)\n return usage\n\n // pi-ai's `getModel` is typed for `KnownProvider` × `keyof models[P]`; we\n // accept arbitrary strings here (the openai-compat adapter routes both known\n // and user-defined endpoint names through this helper) and let the function's\n // runtime throw on a miss.\n const lookup = getModel as (p: string, m: string) => { cost?: ModelCostRates } | undefined\n let model: { cost?: ModelCostRates } | undefined\n try {\n model = lookup(provider, usage.modelId)\n }\n catch {\n return usage\n }\n\n const rates = model?.cost\n if (!rates)\n return usage\n\n const total\n = ((rates.input ?? 0) * (usage.input ?? 0)\n + (rates.output ?? 0) * (usage.output ?? 0)\n + (rates.cacheRead ?? 0) * (usage.cacheRead ?? 0)\n + (rates.cacheWrite ?? 0) * (usage.cacheCreation ?? 0)) / 1_000_000\n\n if (total <= 0)\n return usage\n\n return { ...usage, cost: total }\n}\n","/**\n * JSON Schema sanitizer for tool `inputSchema` forwarded to LLM providers.\n *\n * Why this exists: MCP servers ship arbitrary JSON Schema in `tools/list`.\n * Anthropic and (to a lesser extent) OpenAI reject specific keyword\n * combinations with a 400 on `messages.create`, and the error message\n * lands deep inside an SDK exception that consumers blame on their own\n * code. The most common failure modes in the wild:\n *\n * - Root `$ref` (server-published schema lives under `#/$defs/X`).\n * - Root `oneOf` / `anyOf` / `allOf` (Anthropic allows neither at the\n * top level; expects `type: 'object'`).\n * - `type: ['object', 'null']` at the root (nullable variants of an\n * otherwise valid object schema).\n * - Missing `properties` on an object root.\n * - `nullable: true` (Swagger / OpenAPI 3.0 idiom — not standard JSON\n * Schema, ignored by every modern provider but Anthropic warns).\n * - Nested `$ref` pointing at `#/definitions/X` without the schema\n * actually inlining the referenced definition.\n *\n * Pure rewrite. Returns a new schema (or the same reference when nothing\n * needed to change — the hot path on stable tool registries) and a list\n * of `warnings` describing each rewrite. Providers call\n * `sanitizeToolSpecs` from `formatTools` so every tool — eager MCP, lazy\n * MCP, native, skill — gets the same treatment, and warnings surface\n * through `console.warn` once per batch (the seen-set in\n * `sanitizeToolSpecs` is local to one call, so a stable bad schema logs\n * once per request rather than every turn).\n *\n * Scope:\n * - Pure function. No I/O, no globals.\n * - Bounded recursion (`MAX_DEPTH = 32`) so a malicious server can't\n * wedge the agent with a deeply self-referential schema.\n * - Bounded `$ref` resolution (`MAX_REF_HOPS = 16`) for the same\n * reason. Cycles fall back to `{}` (permissive) rather than\n * throwing — the request still succeeds, the schema is just looser.\n *\n * Non-goals:\n * - Validating that the schema is internally consistent.\n * - Cleaning up tool *descriptions* (handled elsewhere).\n * - Provider-side feature detection (handled by the provider's own\n * `formatTools`; this module is a syntactic guard, not a semantic one).\n */\n\n/** Strictness profile — picks which transformations to apply. */\nexport type SchemaSanitizeProfile = 'anthropic' | 'openai' | 'permissive'\n\nexport interface SchemaSanitizeOptions {\n /** Strictness profile. Defaults to `'permissive'`. */\n profile?: SchemaSanitizeProfile\n /**\n * Tool name for context in warnings. Optional — when set, every warning\n * is prefixed with `[tool:<name>] ` so log lines correlate to the tool.\n */\n toolName?: string\n}\n\nexport interface SchemaSanitizeResult {\n /** Sanitized schema, safe to forward to the provider. */\n schema: Record<string, unknown>\n /**\n * Human-readable strings describing each rewrite the sanitizer performed.\n * Empty on a clean schema. Consumers may log these (recommended) or\n * thread them through a hook for observability.\n */\n warnings: string[]\n}\n\n/** Max nesting depth before sub-schemas are replaced with `{}`. */\nconst MAX_DEPTH = 32\n\n/** Max `$ref` hop count before resolution gives up. */\nconst MAX_REF_HOPS = 16\n\n/** Keys that hold a single sub-schema. */\nconst SUBSCHEMA_KEYS = ['items', 'additionalProperties', 'contains', 'not', 'if', 'then', 'else', 'propertyNames'] as const\n\n/**\n * Keys that hold a record of sub-schemas.\n *\n * `$defs` / `definitions` are intentionally NOT included — they're\n * metaschema storage, only reached via `$ref` resolution (which has its\n * own walker in `resolveRef`), and `enforceRoot` strips them at the\n * wire layer once inlining is done. Recursing into them would cost\n * cycles and produce warnings about sub-trees the provider never sees.\n */\nconst SUBSCHEMA_RECORD_KEYS = ['properties', 'patternProperties', 'dependentSchemas'] as const\n\n/** Keys that hold an array of sub-schemas. */\nconst SUBSCHEMA_ARRAY_KEYS = ['oneOf', 'anyOf', 'allOf', 'prefixItems'] as const\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Resolve a JSON pointer like `#/$defs/Foo` against `root`. Returns\n * `undefined` when the pointer doesn't resolve or hops past `MAX_REF_HOPS`.\n * The caller treats `undefined` as \"give up, leave as a permissive schema\".\n */\nfunction resolveRef(root: Record<string, unknown>, ref: string, hops = 0): Record<string, unknown> | undefined {\n if (hops > MAX_REF_HOPS)\n return undefined\n if (!ref.startsWith('#/'))\n return undefined\n\n const parts = ref.slice(2).split('/').map(decodeRefSegment)\n let cursor: unknown = root\n for (const part of parts) {\n if (!isPlainObject(cursor))\n return undefined\n cursor = cursor[part]\n }\n if (!isPlainObject(cursor))\n return undefined\n\n if (typeof cursor.$ref === 'string')\n return resolveRef(root, cursor.$ref, hops + 1)\n return cursor\n}\n\nfunction decodeRefSegment(seg: string): string {\n return seg.replace(/~1/g, '/').replace(/~0/g, '~')\n}\n\ninterface SanitizeCtx {\n root: Record<string, unknown>\n warnings: string[]\n profile: SchemaSanitizeProfile\n prefix: string\n}\n\n/**\n * Recursively sanitize a sub-schema. Returns the **original** reference\n * when nothing needed to change — every turn's `formatTools` re-runs the\n * sanitizer, and the no-alloc fast path keeps the hot loop cheap when\n * the registered tool set is already clean. Returns a fresh object when\n * any rewrite happened; never mutates the input.\n *\n * The dirty-tracking is local to each frame so a clean sub-tree under a\n * dirty parent doesn't get cloned uselessly.\n */\nfunction sanitizeNode(node: unknown, ctx: SanitizeCtx, depth: number, path: string): Record<string, unknown> {\n if (depth > MAX_DEPTH) {\n ctx.warnings.push(`${ctx.prefix}schema nested deeper than ${MAX_DEPTH} levels at ${path || '$'} — replaced with permissive {}`)\n return {}\n }\n if (!isPlainObject(node))\n return {}\n\n let dirty = false\n let working: Record<string, unknown> = node\n\n // Inline `$ref` first so subsequent passes see the resolved shape.\n // Sibling keys (per JSON Schema 2019-09+ semantics) win over the\n // referenced definition. Unresolvable refs collapse to whatever\n // siblings remained so the surrounding schema still validates.\n if (typeof working.$ref === 'string') {\n const ref = working.$ref\n const resolved = resolveRef(ctx.root, ref)\n const { $ref: _drop, ...rest } = working\n if (resolved) {\n ctx.warnings.push(`${ctx.prefix}inlined $ref \"${ref}\" at ${path || '$'}`)\n working = { ...resolved, ...rest }\n }\n else {\n ctx.warnings.push(`${ctx.prefix}dropped unresolvable $ref \"${ref}\" at ${path || '$'}`)\n working = rest\n }\n dirty = true\n }\n\n // OpenAPI 3.0 `nullable` → JSON-Schema `type: [..., 'null']`. Modern\n // providers don't recognise `nullable`; Anthropic in particular ignores\n // it silently, which lets the model emit `null` and then the validator\n // complains the value isn't the declared type. Translation keeps the\n // intent explicit on the wire.\n if (working.nullable === true) {\n if (!dirty) {\n working = { ...working }\n dirty = true\n }\n const t = working.type\n if (typeof t === 'string') {\n working.type = [t, 'null']\n ctx.warnings.push(`${ctx.prefix}converted nullable:true → type:[${t},null] at ${path || '$'}`)\n }\n else if (Array.isArray(t) && !t.includes('null')) {\n working.type = [...t, 'null']\n ctx.warnings.push(`${ctx.prefix}converted nullable:true → type:[…,null] at ${path || '$'}`)\n }\n else if (Array.isArray(t)) {\n // Redundant — type already includes 'null'. Silent strip keeps the\n // wire shape clean without surfacing a warning the caller can't act on.\n }\n else {\n ctx.warnings.push(`${ctx.prefix}stripped nullable:true at ${path || '$'} (no base type)`)\n }\n delete working.nullable\n }\n\n // Sub-schema recursion. Sweep every key that JSON Schema 2020-12\n // reserves for nested schemas; leave annotation keywords (`title`,\n // `description`, `examples`, `default`, ...) and validation keywords\n // (`minimum`, `pattern`, ...) alone.\n for (const key of SUBSCHEMA_KEYS) {\n const child = working[key]\n if (isPlainObject(child)) {\n const sanitized = sanitizeNode(child, ctx, depth + 1, `${path}/${key}`)\n if (sanitized !== child) {\n if (!dirty) {\n working = { ...working }\n dirty = true\n }\n working[key] = sanitized\n }\n }\n }\n\n for (const key of SUBSCHEMA_RECORD_KEYS) {\n const rec = working[key]\n if (isPlainObject(rec)) {\n let recDirty = false\n let out: Record<string, unknown> = rec\n for (const [k, v] of Object.entries(rec)) {\n const sanitized = sanitizeNode(v, ctx, depth + 1, `${path}/${key}/${k}`)\n if (sanitized !== v) {\n if (!recDirty) {\n out = { ...rec }\n recDirty = true\n }\n out[k] = sanitized\n }\n }\n if (recDirty) {\n if (!dirty) {\n working = { ...working }\n dirty = true\n }\n working[key] = out\n }\n }\n }\n\n for (const key of SUBSCHEMA_ARRAY_KEYS) {\n const arr = working[key]\n if (Array.isArray(arr)) {\n let arrDirty = false\n let out: unknown[] = arr\n for (let i = 0; i < arr.length; i++) {\n const sanitized = sanitizeNode(arr[i], ctx, depth + 1, `${path}/${key}/${i}`)\n if (sanitized !== arr[i]) {\n if (!arrDirty) {\n out = [...arr]\n arrDirty = true\n }\n out[i] = sanitized\n }\n }\n if (arrDirty) {\n if (!dirty) {\n working = { ...working }\n dirty = true\n }\n working[key] = out\n }\n }\n }\n\n return working\n}\n\n/**\n * Apply root-level coercions every supported provider expects:\n *\n * - `type === 'object'` (Anthropic requires it; OpenAI tolerates more\n * but rejects unions at root in strict mode).\n * - `properties` is a plain object (Anthropic 400s when missing).\n * - No root-level `$ref` / `oneOf` / `anyOf` / `allOf` (Anthropic rejects;\n * OpenAI behaviour varies by endpoint).\n * - `$defs` / `definitions` stripped (recursive pass already inlined\n * refs; the metaschema storage is dead weight at the wire layer).\n *\n * Applied for every profile because portability matters more than the\n * marginal permissiveness of any one host. Profile-specific extras\n * (currently `$schema` stripping on `anthropic`) are gated below.\n */\nfunction enforceRoot(schema: Record<string, unknown>, ctx: SanitizeCtx): Record<string, unknown> {\n // Decide what the final shape needs first, then materialise a clone\n // only when at least one change is required. The recursive pass calls\n // this last, so a fully-clean schema reaches the wire as the exact\n // reference the consumer passed in.\n let out: Record<string, unknown> = schema\n let dirty = false\n const dirtyOnce = (): void => {\n if (!dirty) {\n out = { ...schema }\n dirty = true\n }\n }\n\n const t = out.type\n if (Array.isArray(t)) {\n if (t.includes('object')) {\n dirtyOnce()\n out.type = 'object'\n if (t.length > 1)\n ctx.warnings.push(`${ctx.prefix}collapsed root type:[${t.join(',')}] to 'object'`)\n }\n else {\n dirtyOnce()\n ctx.warnings.push(`${ctx.prefix}root type:[${t.join(',')}] does not include 'object' — coerced to 'object'`)\n out.type = 'object'\n }\n }\n else if (typeof t === 'string' && t !== 'object') {\n dirtyOnce()\n ctx.warnings.push(`${ctx.prefix}coerced root type:'${t}' → 'object'`)\n out.type = 'object'\n }\n else if (t === undefined) {\n dirtyOnce()\n out.type = 'object'\n }\n\n // Anthropic + OpenAI both require `properties` on object schemas — even\n // when empty. Synthesise the object so the wire shape is well-formed.\n if (!isPlainObject(out.properties)) {\n if ('properties' in out)\n ctx.warnings.push(`${ctx.prefix}replaced non-object 'properties' at root with {}`)\n dirtyOnce()\n out.properties = {}\n }\n\n for (const key of ['oneOf', 'anyOf', 'allOf'] as const) {\n if (key in out) {\n dirtyOnce()\n ctx.warnings.push(`${ctx.prefix}stripped root '${key}' (providers require a single object schema)`)\n delete out[key]\n }\n }\n\n if ('$ref' in out) {\n dirtyOnce()\n ctx.warnings.push(`${ctx.prefix}stripped root $ref`)\n delete out.$ref\n }\n\n if ('$defs' in out) {\n dirtyOnce()\n delete out.$defs\n }\n if ('definitions' in out) {\n dirtyOnce()\n delete out.definitions\n }\n\n if (ctx.profile === 'anthropic' && '$schema' in out) {\n dirtyOnce()\n delete out.$schema\n }\n\n return out\n}\n\n/**\n * Sanitize a single tool's `inputSchema` for safe forwarding to the\n * provider. Returns the rewritten schema + a list of warnings describing\n * everything that changed.\n *\n * Never mutates the input. Returns the **same reference** when no rewrite\n * was needed (clean-schema fast path) — `sanitizeToolSpecs` relies on\n * this to keep the formatTools hot loop allocation-free across turns\n * when the registered tool set is already wire-valid.\n */\nexport function sanitizeToolSchema(\n input: unknown,\n options: SchemaSanitizeOptions = {},\n): SchemaSanitizeResult {\n const profile: SchemaSanitizeProfile = options.profile ?? 'permissive'\n const prefix = options.toolName ? `[tool:${options.toolName}] ` : ''\n\n // Non-object inputs always allocate a fresh `{}` root — there's no\n // possible identity to preserve, and the wire still needs a valid object\n // schema either way. `sanitizeToolSpecs` won't hit its no-alloc path for\n // these, which is fine; the model has nothing useful to call on a\n // malformed inputSchema anyway.\n if (!isPlainObject(input)) {\n return {\n schema: { type: 'object', properties: {} },\n warnings: [],\n }\n }\n\n const ctx: SanitizeCtx = {\n root: input,\n warnings: [],\n profile,\n prefix,\n }\n\n const recursed = sanitizeNode(input, ctx, 0, '')\n const enforced = enforceRoot(recursed, ctx)\n\n return { schema: enforced, warnings: ctx.warnings }\n}\n\n/**\n * Convenience: sanitize a batch of tools and emit a single de-duped\n * `console.warn` per unique warning line. Returns the rewritten tools\n * preserving original ordering and reference identity for clean schemas\n * (no reallocation when nothing needed to change).\n *\n * The sanitiser runs every request, so log noise from a stable bad\n * schema would multiply across turns; the de-dupe keeps the signal\n * useful in production logs without dropping the first occurrence.\n */\nexport function sanitizeToolSpecs<T extends { name: string, inputSchema?: unknown }>(\n tools: readonly T[],\n options: { profile?: SchemaSanitizeProfile, onWarning?: (line: string) => void } = {},\n): T[] {\n const seen = new Set<string>()\n const out: T[] = []\n for (const tool of tools) {\n const result = sanitizeToolSchema(tool.inputSchema, {\n profile: options.profile,\n toolName: tool.name,\n })\n if (result.warnings.length === 0 && result.schema === tool.inputSchema) {\n out.push(tool)\n continue\n }\n for (const line of result.warnings) {\n if (seen.has(line))\n continue\n seen.add(line);\n (options.onWarning ?? defaultWarn)(line)\n }\n out.push({ ...tool, inputSchema: result.schema })\n }\n return out\n}\n\nfunction defaultWarn(line: string): void {\n console.warn(`[zidane:schema] ${line}`)\n}\n","/**\n * OpenAI-compatible provider factory + shared utilities.\n *\n * `openaiCompat(params)` returns a `Provider` that talks to any OpenAI-compatible\n * HTTP endpoint (OpenRouter, Cerebras, Baseten, Fireworks, Groq, local LM servers, ...).\n * Helpers (`consumeSSE`, `toOAIMessages`, ...) are shared by the bespoke `openrouter`\n * and `cerebras` wrappers, which pin defaults on top of this factory.\n */\n\nimport type { Provider, ProviderCapabilities, StreamCallbacks, StreamOptions, ToolResult, ToolSpec, TurnResult } from '.'\nimport type { ClassifiedError } from '../errors'\nimport type { SessionContentBlock, SessionMessage, ThinkingLevel, ToolResultContent, TurnFinishReason } from '../types'\nimport { errorMessage, matchesContextExceeded } from '../errors'\nimport { renderSystemForWire, splitSystemPrompt } from '../system-prompt'\nimport { fillEstimatedCost } from './cost'\nimport { sanitizeToolSpecs } from './schema-sanitize'\n\n// ---------------------------------------------------------------------------\n// OpenAI-compatible types\n// ---------------------------------------------------------------------------\n\nexport interface OAIMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content?: unknown\n tool_calls?: { id: string, type: 'function', function: { name: string, arguments: string } }[]\n tool_call_id?: string\n /**\n * OpenRouter normalized reasoning envelope. Echoed back unmodified on assistant\n * messages so the gateway can resume an extended-reasoning chain on the\n * upstream route. Ignored by hosts that don't speak the envelope.\n */\n reasoning_details?: unknown[]\n}\n\nexport interface OAITool {\n type: 'function'\n function: { name: string, description: string, parameters: Record<string, unknown> }\n}\n\n// Sentinel tags zidane uses to round-trip tool_calls / tool_results through\n// the OpenAI-compat session shape. `toOpenAI` writes them; `fromOpenAI` reads\n// them back. The `__zidane_` prefix avoids collisions with any literal `_tag`\n// value a host might emit on real wire messages.\nexport const TOOL_RESULTS_TAG = '__zidane_tool_results__'\nexport const ASSISTANT_TOOL_CALLS_TAG = '__zidane_assistant_tc__'\n\n// ---------------------------------------------------------------------------\n// SSE stream parser\n// ---------------------------------------------------------------------------\n\n/**\n * Ceiling on how many bytes may accumulate between two newline boundaries when\n * parsing an SSE stream. A broken or malicious server that emits an unbounded\n * stream of non-newline bytes would otherwise grow `buffer` without end and\n * eventually OOM the host. 8 MB is safely above the largest tool-call arg\n * JSON we reasonably expect and still catches a pathological stream in ~1 s on\n * a 10 MB/s connection.\n */\nconst SSE_MAX_BUFFER_BYTES = 8 * 1024 * 1024\n\nexport class OpenAICompatStreamError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'OpenAICompatStreamError'\n }\n}\n\nexport async function consumeSSE(\n response: Response,\n callbacks: StreamCallbacks,\n signal?: AbortSignal,\n) {\n const reader = response.body!.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let text = ''\n let thinking = ''\n let finishReason = 'stop'\n let usage: { input: number, output: number, cost?: number, cacheRead?: number, cacheCreation?: number } = { input: 0, output: 0 }\n const tcMap = new Map<number, { id: string, name: string, args: string }>()\n // OpenRouter `reasoning_details` deltas, keyed by item index. Items are merged\n // field-by-field: text/summary concatenate, signature/data/format/id/type are\n // last-write-wins (they only appear once per item in practice).\n const reasoningMap = new Map<number, Record<string, unknown>>()\n let sawReasoningDetails = false\n\n try {\n while (true) {\n if (signal?.aborted)\n break\n const { done, value } = await reader.read()\n if (done)\n break\n\n buffer += decoder.decode(value, { stream: true })\n // Cap the line-less buffer. Any single event must fit under the ceiling.\n if (buffer.length > SSE_MAX_BUFFER_BYTES) {\n throw new OpenAICompatStreamError(\n `SSE buffer exceeded ${SSE_MAX_BUFFER_BYTES} bytes without a line boundary — upstream may be streaming non-SSE data.`,\n )\n }\n const lines = buffer.split('\\n')\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n if (!line.startsWith('data: '))\n continue\n const data = line.slice(6).trim()\n if (data === '[DONE]')\n continue\n\n let chunk: Record<string, unknown>\n try {\n chunk = JSON.parse(data) as Record<string, unknown>\n }\n catch {\n // Malformed JSON frame — skip (SSE keepalives, injected comments, etc.)\n continue\n }\n\n const choices = chunk.choices as Array<Record<string, unknown>> | undefined\n const choice = choices?.[0]\n if (!choice)\n continue\n const fr = choice.finish_reason as string | undefined\n if (fr)\n finishReason = fr\n\n const delta = choice.delta as Record<string, unknown> | undefined\n\n // OpenRouter normalized reasoning envelope. Items arrive as deltas keyed\n // by `index`; merge into a stable list and stream the visible text portion\n // through `onThinking` so the UI sees reasoning live.\n const reasoningDeltaArr = delta?.reasoning_details as Array<Record<string, unknown>> | undefined\n if (reasoningDeltaArr && reasoningDeltaArr.length > 0) {\n sawReasoningDetails = true\n for (const item of reasoningDeltaArr) {\n const idx = (typeof item.index === 'number' ? item.index : 0)\n const existing = reasoningMap.get(idx) ?? {}\n // Concatenate text/summary across deltas.\n if (typeof item.text === 'string') {\n existing.text = ((existing.text as string | undefined) ?? '') + item.text\n thinking += item.text\n callbacks.onThinking?.(item.text)\n }\n if (typeof item.summary === 'string') {\n existing.summary = ((existing.summary as string | undefined) ?? '') + item.summary\n thinking += item.summary\n callbacks.onThinking?.(item.summary)\n }\n // Last-write-wins for opaque fields.\n for (const key of ['type', 'signature', 'data', 'format', 'id'] as const) {\n const v = item[key]\n if (typeof v === 'string')\n existing[key] = v\n }\n reasoningMap.set(idx, existing)\n }\n }\n\n // Legacy text-only reasoning fields (DeepSeek `reasoning_content`,\n // pre-`reasoning_details` OpenRouter, Qwen-thinking, …). Suppressed\n // when the structured envelope is in flight to avoid double-counting\n // the same reasoning text into the `thinking` buffer.\n if (!sawReasoningDetails) {\n const thinkingDelta = (delta?.reasoning_content ?? delta?.reasoning) as string | undefined\n if (thinkingDelta) {\n thinking += thinkingDelta\n callbacks.onThinking?.(thinkingDelta)\n }\n }\n\n const contentDelta = delta?.content as string | undefined\n if (contentDelta) {\n text += contentDelta\n callbacks.onText(contentDelta)\n }\n\n const toolCallsDelta = delta?.tool_calls as Array<{\n index: number\n id?: string\n function?: { name?: string, arguments?: string }\n }> | undefined\n if (toolCallsDelta) {\n for (const tc of toolCallsDelta) {\n const existing = tcMap.get(tc.index)\n if (existing) {\n if (tc.function?.arguments)\n existing.args += tc.function.arguments\n }\n else {\n tcMap.set(tc.index, {\n id: tc.id || `call_${tc.index}`,\n name: tc.function?.name || '',\n args: tc.function?.arguments || '',\n })\n }\n }\n }\n\n const chunkUsage = chunk.usage as\n | {\n prompt_tokens?: number\n completion_tokens?: number\n total_cost?: number\n cache_discount?: number\n prompt_tokens_details?: {\n cached_tokens?: number\n cache_creation_input_tokens?: number\n cache_write_tokens?: number\n }\n cache_creation_input_tokens?: number\n }\n | undefined\n if (chunkUsage) {\n const cachedRead = chunkUsage.prompt_tokens_details?.cached_tokens\n // OpenRouter surfaces cache writes via `prompt_tokens_details.cache_creation_input_tokens`\n // (Anthropic passthrough) or `cache_write_tokens` (normalized form). Either is cumulative\n // for the request, so pick whichever the route populated.\n const cachedWrite = chunkUsage.prompt_tokens_details?.cache_creation_input_tokens\n ?? chunkUsage.prompt_tokens_details?.cache_write_tokens\n ?? chunkUsage.cache_creation_input_tokens\n usage = {\n input: chunkUsage.prompt_tokens ?? 0,\n output: chunkUsage.completion_tokens ?? 0,\n cost: chunkUsage.total_cost ?? undefined,\n ...(typeof cachedRead === 'number' && cachedRead > 0 ? { cacheRead: cachedRead } : {}),\n ...(typeof cachedWrite === 'number' && cachedWrite > 0 ? { cacheCreation: cachedWrite } : {}),\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock()\n }\n\n // Tool call JSON is accumulated across many deltas. If the stream truncates\n // mid-object (network hiccup, server crash) `tc.args` won't parse — fall\n // back to an empty input and surface a stream error rather than letting the\n // whole turn reject with `SyntaxError: Unexpected end of JSON input`, which\n // gives callers no actionable signal.\n const toolCalls: Array<{ id: string, name: string, input: Record<string, unknown> }> = []\n for (const tc of tcMap.values()) {\n if (!tc.args) {\n toolCalls.push({ id: tc.id, name: tc.name, input: {} })\n continue\n }\n try {\n toolCalls.push({ id: tc.id, name: tc.name, input: JSON.parse(tc.args) as Record<string, unknown> })\n }\n catch (err) {\n throw new OpenAICompatStreamError(\n `Tool call \"${tc.name}\" (${tc.id}) arguments were truncated or malformed: ${errorMessage(err)}`,\n )\n }\n }\n\n // Sort by index so `reasoning_details` round-trip preserves item order.\n const reasoningDetails = Array.from(reasoningMap.entries())\n .sort(([a], [b]) => a - b)\n .map(([, item]) => item)\n\n return { text, thinking, toolCalls, finishReason, usage, reasoningDetails }\n}\n\n// ---------------------------------------------------------------------------\n// Message conversion: SessionMessage[] → OAIMessage[]\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a single image block as an OpenAI `image_url` multi-part entry.\n */\nfunction toImageUrlPart(img: { mediaType: string, data: string }) {\n return {\n type: 'image_url' as const,\n image_url: { url: `data:${img.mediaType};base64,${img.data}` },\n }\n}\n\n/**\n * Summarize a `tool_result` output for the companion-user-message path — text blocks\n * are joined (separated by `\\n`) so the tool message carries all textual context; image\n * blocks are collected in a flat list for the companion user message.\n *\n * Used only on the fallback path; the native path walks `output` in-order to preserve\n * text↔image interleaving.\n */\nfunction summarizeToolResultOutput(output: string | ToolResultContent[]): {\n text: string\n images: Array<{ mediaType: string, data: string }>\n} {\n if (typeof output === 'string')\n return { text: output, images: [] }\n\n const texts: string[] = []\n const images: Array<{ mediaType: string, data: string }> = []\n for (const block of output) {\n if (block.type === 'text')\n texts.push(block.text)\n else if (block.type === 'image')\n images.push({ mediaType: block.mediaType, data: block.data })\n }\n return { text: texts.join('\\n'), images }\n}\n\n/**\n * Options that influence OpenAI-compat message shaping.\n *\n * `imageInToolResult` defaults to `false`: the loop routes images via a companion\n * user message emitted immediately after the `tool` message. Providers with\n * genuine multi-part tool support can set it to `true` to emit a single\n * multi-part `tool` message instead.\n */\ninterface ToOAIOptions {\n imageInToolResult?: boolean\n /**\n * Host accepts OpenRouter-style `reasoning_details` echoed back on assistant\n * messages. When `true`, `provider_reasoning` blocks whose `producer`/`model`\n * match the active route are forwarded; when `false` (default), they are\n * silently dropped to avoid 400s on hosts that strict-validate the schema.\n */\n supportsReasoning?: boolean\n /**\n * Active model id. Used to gate `provider_reasoning` blocks — reasoning state\n * is bound to the route that produced it, so a model switch invalidates the\n * embedded signatures.\n */\n model?: string\n}\n\nexport function toOAIMessages(system: string, messages: SessionMessage[], options: ToOAIOptions = {}): OAIMessage[] {\n // Collapse the system-prompt boundary marker before it leaves the harness —\n // it's purely structural metadata for `applyOAICacheBreakpoints`, not\n // content the model should see. Cache-aware routes call the breakpoint\n // helper with the ORIGINAL `system` so the split is preserved on the wire;\n // cache-disabled routes get a clean, marker-free system message here.\n const renderedSystem = renderSystemForWire(system)\n const out: OAIMessage[] = [{ role: 'system', content: renderedSystem }]\n const nativeImageInTool = options.imageInToolResult === true\n const reasoningEnabled = options.supportsReasoning === true\n const activeModel = options.model\n\n for (const msg of messages) {\n const toolResults = msg.content.filter(b => b.type === 'tool_result')\n const toolCalls = msg.content.filter(b => b.type === 'tool_call')\n const textBlocks = msg.content.filter(b => b.type === 'text')\n const imageBlocks = msg.content.filter(b => b.type === 'image')\n // Provider-bound reasoning state — attached to the assistant message below\n // when the host supports it AND the active route matches the producer.\n const reasoningBlocks = reasoningEnabled\n ? msg.content\n .filter(b => b.type === 'provider_reasoning')\n .filter((b) => {\n if (b.producer !== 'openrouter')\n return false\n if (b.model && activeModel && b.model !== activeModel)\n return false\n return true\n })\n : []\n const reasoningDetails = reasoningBlocks.flatMap(b => b.details)\n\n // Tool results → individual tool messages (plus optional companion user message for images)\n if (toolResults.length > 0) {\n for (const tr of toolResults) {\n // Pure-string output → text-only fast path, wire-identical to pre-multimodal behavior.\n if (typeof tr.output === 'string') {\n out.push({ role: 'tool', tool_call_id: tr.callId, content: tr.output })\n continue\n }\n\n // Native multi-part path — walk blocks in order so text↔image interleaving is preserved.\n if (nativeImageInTool) {\n const parts = tr.output.map(block => block.type === 'image'\n ? toImageUrlPart({ mediaType: block.mediaType, data: block.data })\n : { type: 'text' as const, text: block.text })\n out.push({ role: 'tool', tool_call_id: tr.callId, content: parts })\n continue\n }\n\n // Companion-user-message fallback (Claude Desktop / Cline pattern).\n // Works on any Chat Completions endpoint that accepts images in user messages.\n const { text, images } = summarizeToolResultOutput(tr.output)\n if (images.length === 0) {\n // Structured array happened to be text-only — collapse to pure-text path.\n out.push({ role: 'tool', tool_call_id: tr.callId, content: text })\n continue\n }\n\n const noun = images.length === 1 ? 'image' : 'images'\n const attachedMarker = `[${images.length} ${noun} attached — see next user message]`\n const toolMarker = text.length > 0 ? `${text}\\n\\n${attachedMarker}` : attachedMarker\n out.push({ role: 'tool', tool_call_id: tr.callId, content: toolMarker })\n\n out.push({\n role: 'user',\n content: [\n ...images.map(toImageUrlPart),\n { type: 'text' as const, text: `(${noun} returned by tool call ${tr.callId})` },\n ],\n })\n }\n continue\n }\n\n // Tool calls → assistant message with tool_calls array\n if (toolCalls.length > 0) {\n const textContent = textBlocks.length > 0 ? textBlocks[0].text : null\n const m: OAIMessage = {\n role: 'assistant',\n content: textContent,\n tool_calls: toolCalls.map(tc => ({\n id: tc.id,\n type: 'function' as const,\n function: { name: tc.name, arguments: JSON.stringify(tc.input) },\n })),\n }\n if (reasoningDetails.length > 0)\n m.reasoning_details = reasoningDetails\n out.push(m)\n continue\n }\n\n // Images → multimodal array\n if (imageBlocks.length > 0) {\n const parts: unknown[] = imageBlocks.map(img => ({\n type: 'image_url',\n image_url: { url: `data:${img.mediaType};base64,${img.data}` },\n }))\n for (const b of textBlocks) {\n parts.push({ type: 'text', text: b.text })\n }\n const m: OAIMessage = { role: msg.role, content: parts }\n if (msg.role === 'assistant' && reasoningDetails.length > 0)\n m.reasoning_details = reasoningDetails\n out.push(m)\n continue\n }\n\n // Pure text\n let pushed: OAIMessage\n if (textBlocks.length === 1) {\n pushed = { role: msg.role, content: textBlocks[0].text }\n }\n else if (textBlocks.length > 1) {\n pushed = { role: msg.role, content: textBlocks.map(b => ({ type: 'text', text: b.text })) }\n }\n else {\n pushed = { role: msg.role, content: null }\n }\n if (msg.role === 'assistant' && reasoningDetails.length > 0)\n pushed.reasoning_details = reasoningDetails\n out.push(pushed)\n }\n\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Prompt caching\n// ---------------------------------------------------------------------------\n\nconst EPHEMERAL = { type: 'ephemeral' as const }\n\n/**\n * Add `cache_control: { type: 'ephemeral' }` breakpoints to the system message's\n * last text part and the last message's final content part.\n *\n * Mutates `messages` in place. Converts plain-string content into a single-element\n * content array so the cache marker can attach — this shape is accepted verbatim by\n * OpenRouter's Anthropic and Gemini routes and ignored by routes with automatic\n * caching (OpenAI, DeepSeek, Grok, Groq, Moonshot).\n *\n * Skip conditions (safe no-ops):\n * - Empty messages array.\n * - Assistant messages with no text (tool-call-only) — attaching a cache marker to a\n * `tool_calls` block has no defined semantics, so we skip and let the prior\n * system/tools breakpoints carry caching.\n *\n * System-prompt boundary handling: when `originalSystem` (the un-rendered\n * system text passed to {@link toOAIMessages}) contains\n * {@link SYSTEM_PROMPT_BOUNDARY}, the leading system message is rewritten as\n * a two-part array — `cache_control` lands on the static prefix only, so\n * per-turn churn in the dynamic suffix doesn't invalidate the cached\n * doctrine above. `originalSystem` defaults to `undefined` for back-compat;\n * omit it (or pass a marker-free string) for the single-block historic\n * behavior.\n */\nexport function applyOAICacheBreakpoints(messages: OAIMessage[], originalSystem?: string): void {\n if (messages.length === 0)\n return\n\n const first = messages[0]\n if (first.role === 'system')\n markSystemMessage(first, originalSystem)\n\n const lastIdx = messages.length - 1\n if (lastIdx > 0)\n markLastContentPart(messages[lastIdx])\n}\n\n/**\n * Place the cache marker on the static prefix of the system message. When\n * `originalSystem` carried {@link SYSTEM_PROMPT_BOUNDARY}, the message becomes\n * a two-part array — static (cached) then dynamic (uncached). Otherwise falls\n * through to {@link markLastContentPart} for the historic single-block\n * treatment.\n *\n * String-only branch — system messages produced by {@link toOAIMessages} are\n * always plain strings (`{ role: 'system', content: <rendered> }`). Hosts\n * that supply pre-built multi-part system messages bypass this branch and\n * get the generic last-block treatment.\n */\nfunction markSystemMessage(msg: OAIMessage, originalSystem?: string): void {\n if (typeof msg.content !== 'string' || msg.content.length === 0) {\n markLastContentPart(msg)\n return\n }\n // `originalSystem` is the authoritative source for the boundary split —\n // `msg.content` has typically been pre-rendered by `toOAIMessages` and no\n // longer carries the marker. Fall back to `msg.content` itself when no\n // original was supplied (or an empty original) so an external caller that\n // embedded a marker directly doesn't leak it into the cached block.\n const splitSource = originalSystem && originalSystem.length > 0\n ? originalSystem\n : msg.content\n const parts = splitSystemPrompt(splitSource)\n if (parts.dynamic.length === 0) {\n // No marker — wrap the (already marker-free) string in a single cached\n // block. `parts.static` equals `splitSource` in this branch.\n msg.content = [{ type: 'text', text: parts.static, cache_control: EPHEMERAL }]\n return\n }\n const next: Array<Record<string, unknown>> = []\n if (parts.static.length > 0)\n next.push({ type: 'text', text: parts.static, cache_control: EPHEMERAL })\n next.push({ type: 'text', text: parts.dynamic })\n msg.content = next\n}\n\n/**\n * Mark the last content part of an OAI message with `cache_control`. Normalizes\n * string content into a `[{ type: 'text', text, cache_control }]` array so the\n * marker has a block to attach to.\n *\n * No-op for messages without string or array content (tool-call-only assistant\n * messages fall through; the system/tools breakpoints carry the cache prefix).\n */\nfunction markLastContentPart(msg: OAIMessage): void {\n if (typeof msg.content === 'string') {\n if (msg.content.length === 0)\n return\n msg.content = [{ type: 'text', text: msg.content, cache_control: EPHEMERAL }]\n return\n }\n if (!Array.isArray(msg.content) || msg.content.length === 0)\n return\n\n const parts = msg.content as Array<Record<string, unknown>>\n const lastBlockIdx = parts.length - 1\n parts[lastBlockIdx] = { ...parts[lastBlockIdx], cache_control: EPHEMERAL }\n}\n\n/**\n * Return a copy of `tools` with `cache_control` on the last entry.\n *\n * OpenRouter accepts the marker alongside the standard `type` + `function` fields\n * and forwards it when routing to Anthropic/Gemini. Leaves the non-cached tools\n * unchanged so the caller's reference is not mutated.\n */\nexport function applyOAIToolCacheBreakpoint(tools: OAITool[]): OAITool[] {\n if (tools.length === 0)\n return tools\n const lastIdx = tools.length - 1\n return tools.map((tool, i) =>\n i === lastIdx ? ({ ...tool, cache_control: EPHEMERAL } as OAITool & { cache_control: typeof EPHEMERAL }) : tool,\n )\n}\n\n// ---------------------------------------------------------------------------\n// Shared message builders\n// ---------------------------------------------------------------------------\n\nexport function formatTools(tools: ToolSpec[]): OAITool[] {\n // Same sanitization Anthropic gets — OpenAI-compat hosts (OpenRouter,\n // Cerebras, Groq, ...) reject the same root-level shapes (missing\n // `properties` on object schemas, root `$ref` / `oneOf` / `anyOf`). The\n // `openai` profile is a touch more permissive about annotation\n // keywords, but the core rewrites are identical.\n const sanitized = sanitizeToolSpecs(tools, { profile: 'openai' })\n return sanitized.map(t => ({\n type: 'function' as const,\n function: { name: t.name, description: t.description, parameters: t.inputSchema as Record<string, unknown> },\n }))\n}\n\nexport function userMessage(content: string): SessionMessage {\n return { role: 'user', content: [{ type: 'text', text: content }] }\n}\n\nexport function assistantMessage(content: string): SessionMessage {\n return { role: 'assistant', content: [{ type: 'text', text: content }] }\n}\n\nexport function 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 })),\n }\n}\n\nexport function buildAssistantContent(\n text: string,\n toolCalls: { id: string, name: string, input: Record<string, unknown> }[],\n thinking?: string,\n reasoning?: { details: unknown[], producer: 'openrouter', model?: string },\n): SessionMessage {\n const content: SessionContentBlock[] = []\n // `provider_reasoning` precedes `thinking` so re-attached envelopes arrive\n // before the legacy text mirror in the assistant content list.\n if (reasoning && reasoning.details.length > 0) {\n const block: Extract<SessionContentBlock, { type: 'provider_reasoning' }> = {\n type: 'provider_reasoning',\n producer: reasoning.producer,\n details: reasoning.details,\n }\n if (reasoning.model)\n block.model = reasoning.model\n content.push(block)\n }\n if (thinking)\n content.push({ type: 'thinking', text: thinking })\n if (text)\n content.push({ type: 'text', text })\n for (const tc of toolCalls) {\n content.push({ type: 'tool_call', id: tc.id, name: tc.name, input: tc.input })\n }\n return { role: 'assistant', content }\n}\n\n// ---------------------------------------------------------------------------\n// Error classification\n// ---------------------------------------------------------------------------\n\n/**\n * HTTP error thrown when an OpenAI-compatible endpoint returns a non-OK response.\n *\n * The body is best-effort JSON-parsed; `error.message` / `error.code` / `error.type`\n * are extracted for clean downstream classification.\n */\nexport class OpenAICompatHttpError extends Error {\n readonly status: number\n readonly providerCode?: string\n readonly bodyText: string\n\n constructor(status: number, bodyText: string) {\n let message = bodyText\n let code: string | undefined\n try {\n const parsed = JSON.parse(bodyText)\n message = parsed?.error?.message ?? bodyText\n code = parsed?.error?.code ?? parsed?.error?.type\n }\n catch {\n // Leave message as the raw body.\n }\n super(`HTTP ${status}: ${message}`)\n this.name = 'OpenAICompatHttpError'\n this.status = status\n this.providerCode = code\n this.bodyText = bodyText\n }\n}\n\nconst TRAILING_SLASH_RE = /\\/$/\n\n/**\n * Classify an OpenAI-compatible error into `ClassifiedError`.\n *\n * Recognizes:\n * - `AbortError` (from fetch) → `aborted`.\n * - `OpenAICompatHttpError` with a context-exceeded code or message → `context_exceeded`.\n * - Any other `OpenAICompatHttpError` → `provider_error`.\n *\n * Returns `null` for unrecognized error shapes (the loop falls back to `AgentProviderError`).\n */\nexport function classifyOpenAICompatError(err: unknown): ClassifiedError | null {\n if (!err || typeof err !== 'object')\n return null\n\n if ((err as { name?: unknown }).name === 'AbortError')\n return { kind: 'aborted' }\n\n // A truncated / malformed SSE stream is a transient network-level failure —\n // typically worth a retry from the caller's perspective.\n if (err instanceof OpenAICompatStreamError) {\n return {\n kind: 'provider_error',\n providerCode: 'stream_error',\n message: err.message,\n retryable: true,\n }\n }\n\n if (!(err instanceof OpenAICompatHttpError))\n return null\n\n const code = err.providerCode\n const msg = err.message\n\n if (code === 'context_length_exceeded' || matchesContextExceeded(msg)) {\n return {\n kind: 'context_exceeded',\n providerCode: code ?? 'context_length_exceeded',\n message: msg,\n }\n }\n\n return {\n kind: 'provider_error',\n providerCode: code ?? String(err.status),\n message: msg,\n retryable: isRetryableHttpStatus(err.status),\n }\n}\n\n/**\n * 429 + 5xx (except 501 Not Implemented) are safe to retry with backoff;\n * 4xx other than 429 are terminal (bad request, auth, not found, etc.).\n */\nfunction isRetryableHttpStatus(status: number): boolean {\n if (status === 429)\n return true\n if (status >= 500 && status !== 501)\n return true\n return false\n}\n\n// ---------------------------------------------------------------------------\n// Finish-reason mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Map an OpenAI-compatible `finish_reason` string to the zidane `TurnFinishReason` union.\n */\nexport function mapOAIFinishReason(reason: string | null | undefined): TurnFinishReason | undefined {\n if (!reason)\n return undefined\n switch (reason) {\n case 'stop':\n return 'stop'\n case 'tool_calls':\n case 'function_call':\n return 'tool-calls'\n case 'length':\n return 'length'\n case 'content_filter':\n return 'content-filter'\n default:\n return 'other'\n }\n}\n\n// ---------------------------------------------------------------------------\n// openaiCompat factory\n// ---------------------------------------------------------------------------\n\n/**\n * Auth header config. `scheme` is prepended to the api key with a space, e.g.\n * `{ name: 'Authorization', scheme: 'Bearer' }` → `Authorization: Bearer <key>`.\n * Omit `scheme` for raw header values (e.g. `{ name: 'X-Api-Key' }` → `X-Api-Key: <key>`).\n *\n * Real-world examples:\n * - Default OpenAI / OpenRouter / Cerebras: `{ name: 'Authorization', scheme: 'Bearer' }`.\n * - Baseten: `{ name: 'Authorization', scheme: 'Api-Key' }`.\n * - Some gateways: `{ name: 'X-Api-Key' }`.\n */\nexport interface OpenAICompatAuthHeader {\n name: string\n scheme?: string\n}\n\nexport interface OpenAICompatParams {\n /** Bearer-style API key. */\n apiKey: string\n /** Base URL — `/chat/completions` is appended. */\n baseURL: string\n /** Default model id when `run({ model })` is omitted. */\n defaultModel?: string\n /** Provider name exposed as `Provider.name`. Defaults to `'openai-compat'`. */\n name?: string\n /** Auth header shape. Defaults to `{ name: 'Authorization', scheme: 'Bearer' }`. */\n authHeader?: OpenAICompatAuthHeader\n /** Extra headers sent with every request (e.g. referer, user-agent). */\n extraHeaders?: Record<string, string>\n /**\n * Provider-level capability flags. Routed into the message shaper and the\n * agent loop so images in tool results + user messages are handled correctly\n * for the underlying model.\n *\n * Defaults when omitted: `vision: false`, `imageInToolResult: false` — a\n * conservative assumption matching most OSS text-only OpenAI-compat\n * endpoints. Override when routing to a known vision-capable endpoint\n * (e.g. OpenRouter vision models, Baseten image-capable deployments).\n */\n capabilities?: ProviderCapabilities\n /**\n * Whether this endpoint honors `cache_control: { type: 'ephemeral' }` markers\n * on message content parts and tool definitions.\n *\n * - `true` — inject markers when the caller asks for caching. OpenRouter routes\n * to Anthropic/Gemini forward the markers; routes to OpenAI/DeepSeek/\n * Grok/Groq/Moonshot ignore them (those backends cache automatically).\n * - `false` — never inject markers. Safe default for endpoints that strictly\n * validate the request schema (OpenAI direct, most OSS inference\n * servers) and would reject unknown fields.\n *\n * Default: `false`. The `openrouter` wrapper sets this to `true`.\n */\n cacheBreakpoints?: boolean\n /**\n * Whether this endpoint speaks OpenRouter's normalized reasoning envelope —\n * `reasoning: { effort | max_tokens | exclude }` on requests and structured\n * `reasoning_details[]` on assistant messages, round-tripped to preserve\n * extended-reasoning state across turns.\n *\n * - `true` — map zidane's `behavior.thinking` / `behavior.thinkingBudget` to\n * the request's `reasoning` field, capture `reasoning_details`\n * from streaming responses into `provider_reasoning` blocks, and\n * echo them back on subsequent assistant messages.\n * - `false` — never set the field; drop any stored `provider_reasoning`\n * blocks before sending. Safe default for hosts that strict-\n * validate the request schema.\n *\n * Default: `false`. The `openrouter` wrapper sets this to `true`.\n */\n supportsReasoning?: boolean\n /**\n * Generic pass-through for fields on the Chat Completions request body that\n * zidane does not yet type. Spread into the request before the typed core\n * (model / messages / tools / max_tokens / stream / tool_choice), so\n * explicit options always win on collision.\n *\n * Forward-compat escape hatch for endpoints that ship one-off fields ahead\n * of zidane (e.g. OpenAI `reasoning_effort`, OpenRouter `provider` routing,\n * vendor-specific `safety_level` knobs).\n */\n extraBodyParams?: Record<string, unknown>\n}\n\n/**\n * Map zidane's `ThinkingLevel` + optional explicit budget to the OpenRouter\n * `reasoning` request field. Returns `undefined` when reasoning should not be\n * sent (off, or no level + no budget).\n *\n * - `'low' | 'medium' | 'high'` → `{ effort }`.\n * - `'minimal'` → `{ effort: 'low' }` (closest match in OpenRouter's vocabulary).\n * - `'adaptive'` → `{}` (let upstream decide; OpenRouter passes through).\n * - explicit `thinkingBudget` → `{ max_tokens }` overlaid on the level.\n */\nfunction planOpenRouterReasoning(\n thinking: ThinkingLevel | undefined,\n thinkingBudget: number | undefined,\n): Record<string, unknown> | undefined {\n if ((!thinking || thinking === 'off') && typeof thinkingBudget !== 'number')\n return undefined\n const out: Record<string, unknown> = {}\n if (thinking && thinking !== 'off' && thinking !== 'adaptive') {\n out.effort = thinking === 'minimal' ? 'low' : thinking\n }\n if (typeof thinkingBudget === 'number' && thinkingBudget > 0) {\n out.max_tokens = thinkingBudget\n }\n return out\n}\n\n/**\n * Factory for any OpenAI-compatible HTTP endpoint.\n *\n * Speaks the standard `POST /chat/completions` + `stream: true` + SSE dialect.\n * Thin wrappers (`openrouter`, `cerebras`) call this with pinned defaults.\n *\n * @example Baseten (non-standard auth scheme)\n * ```ts\n * openaiCompat({\n * name: 'baseten',\n * apiKey: process.env.BASETEN_API_KEY!,\n * baseURL: process.env.BASETEN_PROXY_URL!,\n * authHeader: { name: 'Authorization', scheme: 'Api-Key' },\n * })\n * ```\n */\nexport function openaiCompat(params: OpenAICompatParams): Provider {\n const name = params.name ?? 'openai-compat'\n const defaultModel = params.defaultModel ?? 'gpt-4o-mini'\n const authHeaderName = params.authHeader?.name ?? 'Authorization'\n const authHeaderValue = params.authHeader?.scheme\n ? `${params.authHeader.scheme} ${params.apiKey}`\n : (params.authHeader ? params.apiKey : `Bearer ${params.apiKey}`)\n const extraHeaders = params.extraHeaders ?? {}\n const endpoint = `${params.baseURL.replace(TRAILING_SLASH_RE, '')}/chat/completions`\n const capabilities: ProviderCapabilities = {\n vision: params.capabilities?.vision ?? false,\n imageInToolResult: params.capabilities?.imageInToolResult ?? false,\n }\n\n const cacheBreakpointsEnabled = params.cacheBreakpoints === true\n const reasoningEnabled = params.supportsReasoning === true\n\n return {\n name,\n meta: { defaultModel, capabilities },\n formatTools,\n userMessage,\n assistantMessage,\n toolResultsMessage,\n classifyError: classifyOpenAICompatError,\n\n async stream(options: StreamOptions, callbacks: StreamCallbacks): Promise<TurnResult> {\n const modelId = options.model || defaultModel\n const messages = toOAIMessages(options.system, options.messages, {\n imageInToolResult: capabilities.imageInToolResult === true,\n supportsReasoning: reasoningEnabled,\n model: modelId,\n })\n // Prompt caching — insert `cache_control: { type: 'ephemeral' }` breakpoints on\n // the system message's last text part, the last tool definition, and the last\n // message's final content part. Only emitted when the factory was constructed\n // with `cacheBreakpoints: true` (set by the `openrouter` wrapper); endpoints\n // that strict-validate the request schema otherwise reject unknown fields.\n const shouldCache = cacheBreakpointsEnabled && options.cache !== false\n if (shouldCache) {\n // Forward the un-rendered `options.system` so the boundary split\n // lands on the wire — `toOAIMessages` already collapsed the marker\n // into the rendered system content, but the cache helper needs the\n // original byte stream to split correctly.\n applyOAICacheBreakpoints(messages, options.system)\n }\n\n // max_tokens must cover both thinking budget and response tokens\n const maxTokens = options.thinkingBudget\n ? options.thinkingBudget + options.maxTokens\n : options.maxTokens\n\n const body: Record<string, unknown> = {\n // Spread first so the typed core below wins on collision — explicit\n // always overrides generic.\n ...(params.extraBodyParams ?? {}),\n model: modelId,\n messages,\n max_tokens: maxTokens,\n stream: true,\n }\n\n // OpenRouter normalized reasoning request field. Only set when the host\n // advertises support — Chat Completions hosts that don't speak it would\n // 400 on the unknown field.\n if (reasoningEnabled) {\n const reasoning = planOpenRouterReasoning(options.thinking, options.thinkingBudget)\n if (reasoning)\n body.reasoning = reasoning\n }\n\n if (options.tools && (options.tools as unknown[]).length > 0) {\n body.tools = shouldCache\n ? applyOAIToolCacheBreakpoint(options.tools as OAITool[])\n : options.tools\n }\n\n if (options.toolChoice) {\n if (options.toolChoice.type === 'tool' && options.toolChoice.name)\n body.tool_choice = { type: 'function', function: { name: options.toolChoice.name } }\n else if (options.toolChoice.type === 'required')\n body.tool_choice = 'required'\n else\n body.tool_choice = 'auto'\n }\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n [authHeaderName]: authHeaderValue,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n },\n body: JSON.stringify(body),\n signal: options.signal,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new OpenAICompatHttpError(response.status, errorText)\n }\n\n const result = await consumeSSE(response, callbacks, options.signal)\n const finishReason = mapOAIFinishReason(result.finishReason)\n\n return {\n assistantMessage: buildAssistantContent(\n result.text,\n result.toolCalls,\n result.thinking,\n reasoningEnabled && result.reasoningDetails.length > 0\n ? { details: result.reasoningDetails, producer: 'openrouter', model: modelId }\n : undefined,\n ),\n text: result.text,\n toolCalls: result.toolCalls,\n done: result.finishReason === 'stop' || result.toolCalls.length === 0,\n usage: fillEstimatedCost({\n input: result.usage.input,\n output: result.usage.output,\n ...(result.usage.cacheRead !== undefined ? { cacheRead: result.usage.cacheRead } : {}),\n ...(result.usage.cacheCreation !== undefined ? { cacheCreation: result.usage.cacheCreation } : {}),\n ...(result.usage.cost !== undefined ? { cost: result.usage.cost } : {}),\n ...(finishReason ? { finishReason } : {}),\n modelId,\n }, name),\n }\n },\n }\n}\n","/**\n * Canonical SessionMessage format with converters from/to Anthropic and OpenAI-compat formats.\n */\n\nimport type { Provider } from '../providers'\nimport type { SessionContentBlock, SessionMessage, ToolResultContent } from '../types'\nimport { ASSISTANT_TOOL_CALLS_TAG, TOOL_RESULTS_TAG } from '../providers/openai-compat'\n\ntype ToolCallBlock = Extract<SessionContentBlock, { type: 'tool_call' }>\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Decode an Anthropic `tool_result.content` wire value back to zidane's canonical\n * `string | ToolResultContent[]` shape.\n *\n * Anthropic accepts three shapes in the wire: `string`, `Array<{type:'text',text}>`,\n * or `Array<{type:'text'|'image', ...}>`. We collapse to `string` only when every\n * block is text to keep simple cases readable.\n */\nfunction decodeAnthropicToolResultContent(content: unknown): string | ToolResultContent[] {\n if (typeof content === 'string')\n return content\n\n if (!Array.isArray(content))\n return JSON.stringify(content)\n\n const blocks: ToolResultContent[] = []\n for (const raw of content) {\n if (!raw || typeof raw !== 'object')\n continue\n const b = raw as Record<string, unknown>\n if (b.type === 'text' && typeof b.text === 'string') {\n blocks.push({ type: 'text', text: b.text })\n continue\n }\n if (b.type === 'image' && b.source && typeof b.source === 'object') {\n const src = b.source as Record<string, unknown>\n if (src.type === 'base64' && typeof src.data === 'string' && typeof src.media_type === 'string') {\n blocks.push({ type: 'image', mediaType: src.media_type, data: src.data })\n continue\n }\n }\n // Unknown block → stringify into text for visibility.\n blocks.push({ type: 'text', text: JSON.stringify(raw) })\n }\n\n if (blocks.length === 0)\n return ''\n\n const hasNonText = blocks.some(b => b.type !== 'text')\n if (!hasNonText)\n return blocks.map(b => (b as { type: 'text', text: string }).text).join('\\n')\n\n return blocks\n}\n\n/**\n * Encode zidane's canonical `string | ToolResultContent[]` shape to Anthropic's\n * wire format for `tool_result.content`.\n */\nfunction encodeAnthropicToolResultContent(\n output: string | ToolResultContent[],\n): string | Array<{ type: 'text', text: string } | { type: 'image', source: { type: 'base64', media_type: string, data: string } }> {\n if (typeof output === 'string')\n return output\n return output.map((b) => {\n if (b.type === 'text')\n return { type: 'text' as const, text: b.text }\n return {\n type: 'image' as const,\n source: { type: 'base64' as const, media_type: b.mediaType, data: b.data },\n }\n })\n}\n\n// ---------------------------------------------------------------------------\n// fromAnthropic\n// ---------------------------------------------------------------------------\n\nexport function fromAnthropic(msg: { role: string, content: unknown }): SessionMessage {\n const role = msg.role as 'user' | 'assistant'\n const content: SessionContentBlock[] = []\n\n if (typeof msg.content === 'string') {\n content.push({ type: 'text', text: msg.content })\n return { role, content }\n }\n\n if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (!block || typeof block !== 'object')\n continue\n const b = block as Record<string, unknown>\n\n if (b.type === 'text') {\n content.push({ type: 'text', text: b.text as string })\n }\n else if (b.type === 'image') {\n const source = b.source as Record<string, unknown>\n if (source?.type === 'base64') {\n content.push({ type: 'image', mediaType: source.media_type as string, data: source.data as string })\n }\n }\n else if (b.type === 'tool_use') {\n content.push({ type: 'tool_call', id: b.id as string, name: b.name as string, input: b.input as Record<string, unknown> })\n }\n else if (b.type === 'tool_result') {\n const output = decodeAnthropicToolResultContent(b.content)\n const block: Extract<SessionContentBlock, { type: 'tool_result' }> = {\n type: 'tool_result',\n callId: b.tool_use_id as string,\n output,\n }\n if (b.is_error === true)\n block.isError = true\n content.push(block)\n }\n else if (b.type === 'thinking') {\n const block: Extract<SessionContentBlock, { type: 'thinking' }> = {\n type: 'thinking',\n text: (b.thinking as string | undefined) ?? '',\n }\n if (typeof b.signature === 'string') {\n block.signature = b.signature\n // Tag the producer so cross-provider senders can drop foreign signatures.\n block.signatureProducer = 'anthropic'\n }\n content.push(block)\n }\n else if (b.type === 'redacted_thinking') {\n // Encrypted reasoning that must be echoed back unmodified to keep\n // interleaved-thinking + tool-use chains working on Sonnet 3.7+/Opus 4.x.\n content.push({ type: 'redacted_thinking', data: (b.data as string | undefined) ?? '' })\n }\n // Unknown block types are silently skipped\n }\n }\n\n return { role, content }\n}\n\n// ---------------------------------------------------------------------------\n// fromOpenAI\n// ---------------------------------------------------------------------------\n\nexport function fromOpenAI(msg: { role: string, content: unknown }): SessionMessage {\n const role = msg.role as 'user' | 'assistant'\n const content: SessionContentBlock[] = []\n const c: unknown = msg.content\n\n if (c == null) {\n return { role, content }\n }\n\n if (typeof c === 'string') {\n content.push({ type: 'text', text: c })\n return { role, content }\n }\n\n if (typeof c === 'object' && !Array.isArray(c) && (c as { _tag?: unknown })._tag === ASSISTANT_TOOL_CALLS_TAG) {\n const tagged = c as { text?: unknown, tool_calls?: unknown }\n if (typeof tagged.text === 'string' && tagged.text) {\n content.push({ type: 'text', text: tagged.text })\n }\n if (Array.isArray(tagged.tool_calls)) {\n for (const raw of tagged.tool_calls) {\n if (!raw || typeof raw !== 'object')\n continue\n const tc = raw as { id?: string, function?: { name?: string, arguments?: unknown } }\n const rawArgs = tc.function?.arguments\n const input: Record<string, unknown> = rawArgs\n ? (typeof rawArgs === 'string' ? JSON.parse(rawArgs) : rawArgs as Record<string, unknown>)\n : {}\n content.push({ type: 'tool_call', id: tc.id ?? '', name: tc.function?.name ?? '', input })\n }\n }\n return { role, content }\n }\n\n if (typeof c === 'object' && !Array.isArray(c) && (c as { _tag?: unknown })._tag === TOOL_RESULTS_TAG) {\n const tagged = c as { results?: unknown }\n if (Array.isArray(tagged.results)) {\n for (const raw of tagged.results) {\n if (!raw || typeof raw !== 'object')\n continue\n const r = raw as { tool_call_id?: string, content?: string | ToolResultContent[] }\n content.push({ type: 'tool_result', callId: r.tool_call_id ?? '', output: r.content ?? '' })\n }\n }\n return { role, content }\n }\n\n if (Array.isArray(c)) {\n for (const block of c) {\n if (!block || typeof block !== 'object')\n continue\n const b = block as Record<string, unknown>\n\n if (b.type === 'text') {\n content.push({ type: 'text', text: b.text as string })\n }\n else if (b.type === 'image_url') {\n const imageUrl = (b.image_url as Record<string, unknown>)?.url as string\n if (imageUrl?.startsWith('data:')) {\n const [meta, data] = imageUrl.slice(5).split(',', 2)\n const mediaType = meta.replace(';base64', '')\n content.push({ type: 'image', mediaType, data })\n }\n }\n }\n return { role, content }\n }\n\n return { role, content }\n}\n\n// ---------------------------------------------------------------------------\n// toAnthropic\n// ---------------------------------------------------------------------------\n\ntype AnthropicWireBlock\n = | { type: 'text', text: string }\n | { type: 'image', source: { type: 'base64', media_type: string, data: string } }\n | { type: 'tool_use', id: string, name: string, input: Record<string, unknown> }\n | { type: 'tool_result', tool_use_id: string, content: ReturnType<typeof encodeAnthropicToolResultContent>, is_error?: true }\n | { type: 'thinking', thinking: string, signature: string }\n | { type: 'redacted_thinking', data: string }\n\nexport function toAnthropic(msg: SessionMessage): { role: string, content: unknown } {\n const blocks: AnthropicWireBlock[] = msg.content\n // Drop foreign reasoning state. Three independent rejection paths,\n // each driven by an Anthropic API constraint we've hit in production:\n //\n // 1. `signatureProducer === 'openai'` — OpenAI's `encrypted_content`\n // isn't a valid Anthropic HMAC; sending it 400s with an unhelpful\n // \"invalid signature\" error.\n // 2. `type === 'thinking' && !signature` — Anthropic's\n // `ThinkingBlockParam` requires a signature field. A non-Anthropic\n // producer that doesn't sign thinking (Cerebras, Gemini, OSS via\n // OpenAI-compat, etc.) leaves a block in the session that 400s\n // here with `messages.N.content.M.thinking.signature: Field\n // required` the next time the user picks an Anthropic model. Drop\n // them — the model can re-think on its own turn, no info loss\n // beyond the prior provider's chain-of-thought, which Anthropic\n // can't consume anyway.\n // 3. `type === 'provider_reasoning'` — opaque OpenRouter envelope;\n // not an Anthropic block type at all.\n .filter((b) => {\n if (b.type === 'provider_reasoning')\n return false\n if (b.type !== 'thinking')\n return true\n if (b.signatureProducer === 'openai')\n return false\n if (!b.signature)\n return false\n return true\n })\n .map((block): AnthropicWireBlock => {\n switch (block.type) {\n case 'text':\n return { type: 'text', text: block.text }\n case 'image':\n return { type: 'image', source: { type: 'base64', media_type: block.mediaType, data: block.data } }\n case 'tool_call':\n return { type: 'tool_use', id: block.id, name: block.name, input: block.input }\n case 'tool_result': {\n const out: AnthropicWireBlock = {\n type: 'tool_result',\n tool_use_id: block.callId,\n content: encodeAnthropicToolResultContent(block.output),\n }\n if (block.isError)\n out.is_error = true\n return out\n }\n case 'thinking':\n // Filter above guarantees `signature` is present and either\n // anthropic-produced or legacy (back-compat for pre-tagging\n // Anthropic sessions where producer was never recorded).\n return { type: 'thinking', thinking: block.text, signature: block.signature ?? '' }\n case 'redacted_thinking':\n return { type: 'redacted_thinking', data: block.data }\n default:\n return { type: 'text', text: '' }\n }\n })\n\n // Simplify to plain string if only one text block\n const first = blocks[0]\n if (blocks.length === 1 && first.type === 'text')\n return { role: msg.role, content: first.text }\n\n return { role: msg.role, content: blocks }\n}\n\n// ---------------------------------------------------------------------------\n// toOpenAI\n// ---------------------------------------------------------------------------\n\nexport function toOpenAI(msg: SessionMessage): { role: string, content: unknown } {\n const toolCalls = msg.content.filter(b => b.type === 'tool_call')\n const toolResults = msg.content.filter(b => b.type === 'tool_result')\n const textBlocks = msg.content.filter(b => b.type === 'text')\n const imageBlocks = msg.content.filter(b => b.type === 'image')\n\n // Tool results → tagged format\n if (toolResults.length > 0) {\n return {\n role: msg.role,\n content: {\n _tag: TOOL_RESULTS_TAG,\n results: toolResults.map(tr => ({ tool_call_id: tr.callId, content: tr.output })),\n },\n }\n }\n\n // Tool calls → tagged format\n if (toolCalls.length > 0) {\n const textContent = textBlocks.length > 0 ? textBlocks[0].text : null\n return {\n role: msg.role,\n content: {\n _tag: ASSISTANT_TOOL_CALLS_TAG,\n text: textContent,\n tool_calls: toolCalls.map(tc => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.name, arguments: JSON.stringify(tc.input) },\n })),\n },\n }\n }\n\n // Images → multimodal array\n if (imageBlocks.length > 0) {\n const parts: unknown[] = imageBlocks.map(img => ({\n type: 'image_url',\n image_url: { url: `data:${img.mediaType};base64,${img.data}` },\n }))\n for (const b of textBlocks)\n parts.push({ type: 'text', text: b.text })\n return { role: msg.role, content: parts }\n }\n\n // Pure text → string\n if (textBlocks.length === 1)\n return { role: msg.role, content: textBlocks[0].text }\n\n // Multiple text blocks or empty\n if (textBlocks.length > 1)\n return { role: msg.role, content: textBlocks.map(b => ({ type: 'text', text: b.text })) }\n\n return { role: msg.role, content: null }\n}\n\n// ---------------------------------------------------------------------------\n// autoDetectAndConvert\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// ensureToolResultPairing\n// ---------------------------------------------------------------------------\n\n/**\n * Placeholder content inserted into a synthetic `tool_result` when the harness\n * has to repair an orphan `tool_use`. Exported so downstream consumers\n * (training-data collectors, HFI submission) can reject any payload containing\n * it — the marker satisfies the wire-level pairing contract structurally but\n * the content itself is fake and would poison fine-tuning data.\n */\nexport const SYNTHETIC_TOOL_RESULT_PLACEHOLDER = '[Tool result missing due to internal error]'\n\n/**\n * Replacement text for an assistant message whose every content block was\n * stripped during pairing repair (e.g. the only blocks were orphan\n * `tool_call`s). Providers reject empty `content` arrays — the marker keeps\n * the turn shape valid while signaling \"this assistant turn lost its\n * outputs\" to the model so it can recover.\n */\nexport const TOOL_USE_INTERRUPTED_MARKER = '[Tool use interrupted]'\n\n/**\n * Replacement text for a user message whose every content block was a\n * `tool_result` with no matching upstream `tool_call` (e.g. the assistant\n * pair was stripped by an earlier compaction). Same role as\n * {@link TOOL_USE_INTERRUPTED_MARKER} on the user side.\n */\nexport const ORPHANED_TOOL_RESULT_MARKER = '[Orphaned tool result removed due to conversation resume]'\n\n/**\n * Classification of a single repair the pairing pass performed. Surfaced via\n * {@link EnsureToolResultPairingOptions.onRepair} so consumers can wire\n * telemetry, debug logs, or strict-mode rejection without re-implementing the\n * walk.\n *\n * Each entry corresponds to one of the corruption modes documented on\n * {@link ensureToolResultPairing}.\n */\nexport type PairingRepairMode\n = | 'orphan-tool-use-prepend' // mode 1: tool_use with no matching tool_result; next msg is user → prepend synthetic\n | 'orphan-tool-use-append' // mode 2: tool_use is followed by nothing (or a non-user msg) → append synthetic user turn\n | 'orphan-tool-result-strip' // mode 3: tool_result with no preceding tool_call → strip block\n | 'duplicate-tool-use-strip' // mode 4: duplicate tool_use id across assistant msgs → strip later instance\n | 'duplicate-tool-result-strip' // mode 5: duplicate tool_result callId in one user msg → strip later instance\n | 'empty-assistant-marker' // mode 6: assistant msg emptied by mode-4 stripping → replace with marker text\n\nexport interface PairingRepair {\n mode: PairingRepairMode\n /** Tool-use / tool-result id this repair concerns. Absent for empty-marker repairs. */\n callId?: string\n /** Zero-based index into the INPUT `messages` array where the corruption was found. */\n messageIndex: number\n}\n\nexport interface EnsureToolResultPairingOptions {\n /**\n * Fired once per repair the pass performed. Synchronous so the loop can\n * stay on its hot path. Throwing from the callback aborts the pass —\n * consumers wanting fail-fast (strict mode) can re-throw with their own\n * typed error.\n */\n onRepair?: (repair: PairingRepair) => void\n}\n\n/**\n * Defensive repair pass that rewrites a message list so it satisfies the\n * wire-level `tool_use` ↔ `tool_result` adjacency contract every modern\n * provider enforces.\n *\n * Anthropic 400s on orphans with `'tool_use' ids were found without\n * 'tool_result' blocks immediately after` (and its inverse, `tool_result\n * must be preceded by a tool_call with the same toolCallId`). OpenAI's\n * Chat Completions API rejects most mismatches with a 400 too.\n *\n * Six repair modes — modeled after Anthropic's Claude Code defenses:\n *\n * | # | Corruption | Repair |\n * |---|------------|--------|\n * | 1 | assistant `tool_use` whose next user msg lacks a matching `tool_result` | **Prepend** a synthetic `tool_result` block carrying {@link SYNTHETIC_TOOL_RESULT_PLACEHOLDER} with `isError: true` |\n * | 2 | assistant `tool_use` followed by nothing (or by a non-user msg) | **Insert** a new synthetic user message with the same placeholder |\n * | 3 | user `tool_result` with no preceding assistant `tool_call` | **Strip** the orphan block; if it empties the msg, replace with {@link ORPHANED_TOOL_RESULT_MARKER} text block |\n * | 4 | duplicate `tool_call.id` across assistant messages (CC-1212) | **Strip** later instances + their matching tool_results |\n * | 5 | duplicate `tool_result.callId` within a single user message | **Dedupe** by `callId`, keep first |\n * | 6 | assistant message emptied by mode-4 stripping | **Replace** content with {@link TOOL_USE_INTERRUPTED_MARKER} text block |\n *\n * **Repair, not drop.** Earlier versions of this pass simply dropped orphan\n * blocks, which (a) silently rewrote history the model had reasoned over and\n * (b) cascaded — dropping an assistant tool_call would orphan its\n * tool_result, which would then get dropped too, removing any trace of\n * \"what the model tried to do\" from the transcript. The repair-based pass\n * preserves the model's tool_use shape and patches the dangling result with\n * an `is_error` placeholder so the model sees \"I tried X, the result was\n * lost\" and can retry intelligently.\n *\n * Adjacency contract: `tool_result` blocks must live in the user message\n * IMMEDIATELY following the assistant message that emitted the matching\n * `tool_use`. The pass enforces strict adjacency — a tool_result two\n * messages downstream of its tool_call is still an orphan.\n *\n * Idempotent: returns the input reference unchanged when no repairs were\n * necessary. Re-running on already-repaired output is a no-op.\n *\n * Pure: does not mutate input messages or their content arrays — every\n * repair allocates a fresh array / object.\n */\nexport function ensureToolResultPairing(\n messages: SessionMessage[],\n options: EnsureToolResultPairingOptions = {},\n): SessionMessage[] {\n if (messages.length === 0)\n return messages\n\n const fireRepair = options.onRepair ?? (() => {})\n\n // Pre-pass: first-occurrence index of every assistant `tool_call.id`.\n // Mode 4 strips any block whose first occurrence wasn't in the current\n // message — the first instance wins so the original semantics stand.\n const firstSeenAt = new Map<string, number>()\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i]\n if (msg.role !== 'assistant')\n continue\n for (const block of msg.content) {\n if (block.type === 'tool_call' && !firstSeenAt.has(block.id))\n firstSeenAt.set(block.id, i)\n }\n }\n\n // Scoped to this call. Keyed by INPUT index of the user message that\n // should receive prepended synthetic results from the prior assistant\n // turn's mode-1 repair. Drained as the outer loop reaches each user msg.\n const pendingOrphansByUserIndex = new Map<number, string[]>()\n\n let changed = false\n const markChanged = () => { changed = true }\n const out: SessionMessage[] = []\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i]\n\n if (msg.role === 'assistant') {\n processAssistantMessage({\n index: i,\n msg,\n messages,\n firstSeenAt,\n pendingOrphansByUserIndex,\n out,\n fireRepair,\n markChanged,\n })\n }\n else {\n processUserMessage({\n index: i,\n msg,\n pendingOrphansByUserIndex,\n out,\n fireRepair,\n markChanged,\n })\n }\n }\n\n return changed ? out : messages\n}\n\ninterface ProcessAssistantArgs {\n index: number\n msg: SessionMessage\n messages: SessionMessage[]\n firstSeenAt: Map<string, number>\n pendingOrphansByUserIndex: Map<number, string[]>\n out: SessionMessage[]\n fireRepair: (repair: PairingRepair) => void\n markChanged: () => void\n}\n\n/**\n * Process one assistant message: strip mode-4 duplicates, queue mode-1 / 2\n * orphan-tool-use repairs for the next user message (or insert a synthetic\n * one), and emit a mode-6 marker when dedup empties the message.\n */\nfunction processAssistantMessage(args: ProcessAssistantArgs): void {\n const { index, msg, messages, firstSeenAt, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args\n\n // Mode 4: drop tool_call blocks that are duplicates either:\n // - across messages (first occurrence was in an earlier assistant msg), or\n // - within this same message (same id appearing twice in one turn —\n // pathological but observed in the wild with retry-replay providers).\n //\n // The per-message `seenHere` set guards against the intra-message case:\n // `firstSeenAt` records only the first OCCURRENCE per id, so two blocks\n // sharing an id within the same message both pass the cross-message\n // check (`firstSeenAt.get(id) === index`). The local set tracks which\n // ids we've already kept this iteration and strips later repeats.\n const dedupedContent: SessionContentBlock[] = []\n const seenHere = new Set<string>()\n let dedupedChanged = false\n for (const block of msg.content) {\n if (block.type === 'tool_call') {\n const isCrossMsgDup = firstSeenAt.get(block.id) !== index\n const isIntraMsgDup = seenHere.has(block.id)\n if (isCrossMsgDup || isIntraMsgDup) {\n dedupedChanged = true\n fireRepair({ mode: 'duplicate-tool-use-strip', callId: block.id, messageIndex: index })\n continue\n }\n seenHere.add(block.id)\n }\n dedupedContent.push(block)\n }\n\n const surviving = collectIds<ToolCallBlock>(dedupedContent, 'tool_call', b => b.id)\n const next = messages[index + 1]\n const nextIsUser = next && next.role === 'user'\n const nextResultIds = nextIsUser ? collectResultIds(next.content) : new Set<string>()\n const orphans: string[] = []\n for (const id of surviving) {\n if (!nextResultIds.has(id))\n orphans.push(id)\n }\n\n // Emit the (possibly deduped) assistant message — modes 1 / 2 patch the\n // matching user side rather than touching tool_use blocks.\n emitAssistantAfterDedup({ index, msg, dedupedContent, dedupedChanged, out, fireRepair, markChanged })\n\n if (orphans.length === 0)\n return\n\n // Mode 1: stash for the next user msg processor to prepend.\n if (nextIsUser) {\n markChanged()\n pendingOrphansByUserIndex.set(index + 1, orphans)\n for (const callId of orphans)\n fireRepair({ mode: 'orphan-tool-use-prepend', callId, messageIndex: index })\n return\n }\n\n // Mode 2: synthesize a user message with placeholders for every orphan.\n markChanged()\n out.push({\n role: 'user',\n content: orphans.map(callId => syntheticResultBlock(callId)),\n })\n for (const callId of orphans)\n fireRepair({ mode: 'orphan-tool-use-append', callId, messageIndex: index })\n}\n\ninterface EmitAssistantArgs {\n index: number\n msg: SessionMessage\n dedupedContent: SessionContentBlock[]\n dedupedChanged: boolean\n out: SessionMessage[]\n fireRepair: (repair: PairingRepair) => void\n markChanged: () => void\n}\n\nfunction emitAssistantAfterDedup(args: EmitAssistantArgs): void {\n const { index, msg, dedupedContent, dedupedChanged, out, fireRepair, markChanged } = args\n if (!dedupedChanged) {\n out.push(msg)\n return\n }\n markChanged()\n if (dedupedContent.length === 0) {\n out.push({ ...msg, content: [{ type: 'text', text: TOOL_USE_INTERRUPTED_MARKER }] })\n fireRepair({ mode: 'empty-assistant-marker', messageIndex: index })\n return\n }\n out.push({ ...msg, content: dedupedContent })\n}\n\ninterface ProcessUserArgs {\n index: number\n msg: SessionMessage\n pendingOrphansByUserIndex: Map<number, string[]>\n out: SessionMessage[]\n fireRepair: (repair: PairingRepair) => void\n markChanged: () => void\n}\n\n/**\n * Process one user message: dedup mode-5 duplicate tool_results, strip\n * mode-3 orphans whose tool_call is missing from the previously-emitted\n * assistant message, and prepend any synthetic results queued by the prior\n * assistant's mode-1 repair.\n */\nfunction processUserMessage(args: ProcessUserArgs): void {\n const { index, msg, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args\n const queuedOrphans = pendingOrphansByUserIndex.get(index)\n pendingOrphansByUserIndex.delete(index)\n\n const prev = out.length > 0 ? out[out.length - 1] : null\n const validIds = prev && prev.role === 'assistant'\n ? collectIds<ToolCallBlock>(prev.content, 'tool_call', b => b.id)\n : new Set<string>()\n\n // Pass 1 — mode 5: dedupe by callId within this msg.\n const seenCallIds = new Set<string>()\n let modifiedHere = false\n const afterDedup: SessionContentBlock[] = []\n for (const block of msg.content) {\n if (block.type !== 'tool_result') {\n afterDedup.push(block)\n continue\n }\n if (seenCallIds.has(block.callId)) {\n modifiedHere = true\n fireRepair({ mode: 'duplicate-tool-result-strip', callId: block.callId, messageIndex: index })\n continue\n }\n seenCallIds.add(block.callId)\n afterDedup.push(block)\n }\n\n // Pass 2 — mode 3: strip orphan tool_results.\n const afterStrip: SessionContentBlock[] = []\n for (const block of afterDedup) {\n if (block.type === 'tool_result' && !validIds.has(block.callId)) {\n modifiedHere = true\n fireRepair({ mode: 'orphan-tool-result-strip', callId: block.callId, messageIndex: index })\n continue\n }\n afterStrip.push(block)\n }\n\n // Pass 3: prepend queued mode-1 synthetic results.\n const finalContent: SessionContentBlock[] = queuedOrphans\n ? [...queuedOrphans.map(syntheticResultBlock), ...afterStrip]\n : afterStrip\n\n if (!modifiedHere && queuedOrphans === undefined) {\n out.push(msg)\n return\n }\n\n markChanged()\n if (finalContent.length === 0) {\n out.push({ ...msg, content: [{ type: 'text', text: ORPHANED_TOOL_RESULT_MARKER }] })\n return\n }\n out.push({ ...msg, content: finalContent })\n}\n\nfunction syntheticResultBlock(callId: string): SessionContentBlock {\n return {\n type: 'tool_result',\n callId,\n output: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,\n isError: true,\n }\n}\n\nfunction collectResultIds(content: SessionContentBlock[]): Set<string> {\n const ids = new Set<string>()\n for (const block of content) {\n if (block.type === 'tool_result')\n ids.add(block.callId)\n }\n return ids\n}\n\nfunction collectIds<B extends SessionContentBlock>(\n content: SessionContentBlock[],\n type: B['type'],\n getId: (block: B) => string,\n): Set<string> {\n const ids = new Set<string>()\n for (const block of content) {\n if (block.type === type)\n ids.add(getId(block as B))\n }\n return ids\n}\n\n// ---------------------------------------------------------------------------\n// Resume-time filters (L1)\n// ---------------------------------------------------------------------------\n\n/**\n * Drop ASSISTANT turns whose every `tool_call` block is unresolved\n * (no matching `tool_result` block anywhere later in the transcript).\n * Tool_call blocks with at least one matching tool_result are kept — modes\n * 1/3 of {@link ensureToolResultPairing} handle the partial-pair case at\n * wire-send time.\n *\n * Use case: session resume. A turn that emitted three `tool_use` blocks but\n * never persisted the matching `tool_result` turn (process death, crash,\n * `kill -9`) leaves the transcript with an orphan that Anthropic rejects on\n * the FIRST API call after reload. Dropping the whole assistant turn — not\n * just the orphan blocks — preserves text-only turns that legitimately\n * carry reasoning the model wants to see again.\n *\n * Does NOT mint fresh ids: re-id'ing on every resume would cause\n * exponential transcript growth across repeated resumes of an interrupted\n * session.\n *\n * Pure: returns the input reference unchanged when no turn was dropped.\n */\nexport function filterUnresolvedToolUses<T extends { role: string, content: SessionContentBlock[] }>(\n turns: T[],\n): T[] {\n if (turns.length === 0)\n return turns\n\n // Gather every resolved tool_result id (visible anywhere later in the\n // transcript). A tool_use is \"resolved\" iff its id appears in this set.\n const resolvedIds = new Set<string>()\n for (const turn of turns) {\n for (const block of turn.content) {\n if (block.type === 'tool_result')\n resolvedIds.add(block.callId)\n }\n }\n\n let changed = false\n const out: T[] = []\n for (const turn of turns) {\n if (turn.role !== 'assistant') {\n out.push(turn)\n continue\n }\n const toolCalls = turn.content.filter(b => b.type === 'tool_call')\n if (toolCalls.length === 0) {\n out.push(turn)\n continue\n }\n const allUnresolved = toolCalls.every(b => b.type === 'tool_call' && !resolvedIds.has(b.id))\n if (allUnresolved) {\n changed = true\n continue\n }\n out.push(turn)\n }\n return changed ? out : turns\n}\n\n/**\n * Classification of a session's tail state for resume purposes.\n *\n * - `'clean'` — the trailing turn is a user message (the model would respond\n * next), or the transcript is empty. Safe to resume by appending a fresh\n * user prompt.\n * - `'interrupted'` — the trailing turn is an assistant message that has at\n * least one `tool_call` block with no matching `tool_result` anywhere\n * later. The run was almost certainly killed between persisting the\n * assistant turn and persisting its tool-results turn. Hosts that want\n * Claude-Code-style auto-resume can detect this and inject a\n * `\"Continue from where you left off.\"` user message before the next run.\n * - `'completed'` — the trailing turn is an assistant message with no\n * orphan tool_calls. The model considers itself done. Hosts that want to\n * resume must supply a new prompt; auto-continuation here would be a\n * non-sequitur.\n */\nexport type TurnInterruptionState = 'clean' | 'interrupted' | 'completed'\n\n/**\n * Inspect a session's trailing turn to classify whether the run was\n * interrupted mid-tool-call. Pure / synchronous — does not modify the input.\n *\n * Pair with {@link filterUnresolvedToolUses} on the resume path: filter\n * first so the result reflects the post-cleanup state (a turn whose orphans\n * were stripped reads as 'completed', not 'interrupted').\n */\nexport function detectTurnInterruption<T extends { role: string, content: SessionContentBlock[] }>(\n turns: T[],\n): TurnInterruptionState {\n if (turns.length === 0)\n return 'clean'\n const last = turns[turns.length - 1]\n if (last.role === 'user')\n return 'clean'\n if (last.role !== 'assistant')\n return 'clean'\n // Walk all turns to find resolved tool_result ids — a tool_use anywhere\n // earlier in the run may still be answered by a later tool_result.\n const resolvedIds = new Set<string>()\n for (const turn of turns) {\n for (const block of turn.content) {\n if (block.type === 'tool_result')\n resolvedIds.add(block.callId)\n }\n }\n for (const block of last.content) {\n if (block.type === 'tool_call' && !resolvedIds.has(block.id))\n return 'interrupted'\n }\n return 'completed'\n}\n\n/**\n * Default directive injected by {@link ensureEndsWithUserMessage} when the\n * caller doesn't pass one. Neutral enough to read sensibly in any context —\n * compaction passes its own directive, the agent loop falls back to this.\n *\n * The trailing newline is omitted on purpose; provider `userMessage()`\n * helpers wrap the content in the right block shape and any trailing\n * whitespace would land in the wire payload as-is.\n */\nexport const DEFAULT_USER_TAIL_DIRECTIVE = 'Continue.'\n\n/**\n * Guarantee a message list ends with a `role: 'user'` message, appending a\n * synthetic directive when it doesn't.\n *\n * Why: Anthropic's API (and several OpenAI-compat reasoning models — opus\n * 4.7, the o-series translation layers on Bedrock/Vertex) rejects requests\n * whose final message is `role: 'assistant'` with\n * \"This model does not support assistant message prefill. The conversation\n * must end with a user message.\"\n *\n * Triggers in zidane:\n * - Compaction over a session whose last turn is the agent's response\n * (the common case — autocompact fires right after `agent.run()`).\n * - Schema enforcement after an assistant text turn (no `tool_use`, so\n * `ensureToolResultPairing` doesn't synthesize a user tail).\n * - `context:transform` hooks that strip the trailing user turn.\n *\n * The pair-repair pass {@link ensureToolResultPairing} already covers the\n * orphan-`tool_use` case via mode 2; this helper closes the plain-text\n * assistant-tail case. They compose: run pair-repair first, then this\n * pass, so an assistant turn with orphan tool_uses gets its synthetic\n * `tool_result` user turn AND this pass is a no-op (the synthetic turn\n * is already user-role).\n *\n * Empty input passes through unchanged — the caller's \"nothing to send\"\n * guard is responsible for that case; we don't materialize a conversation\n * out of an empty list.\n *\n * Idempotent: returns the input reference unchanged when the list is\n * already user-terminated (or empty).\n *\n * @param messages Wire-ready message list.\n * @param provider Provider whose `userMessage()` shape we use so the\n * synthetic turn matches the wire format the rest of\n * the list already follows.\n * @param directive Text to put in the synthetic user message. Defaults\n * to {@link DEFAULT_USER_TAIL_DIRECTIVE} (\"Continue.\").\n * Compaction overrides this with a summary-specific cue.\n */\nexport function ensureEndsWithUserMessage(\n messages: SessionMessage[],\n provider: Provider,\n directive: string = DEFAULT_USER_TAIL_DIRECTIVE,\n): SessionMessage[] {\n if (messages.length === 0)\n return messages\n if (messages[messages.length - 1].role === 'user')\n return messages\n return [...messages, provider.userMessage(directive)]\n}\n\n// ---------------------------------------------------------------------------\n// toWireMessages — canonical \"give me provider-ready messages\" projection\n// ---------------------------------------------------------------------------\n\nexport interface ToWireMessagesOptions {\n /**\n * Pairing-repair telemetry. Fires once per repair the underlying\n * {@link ensureToolResultPairing} pass performs. Mirrors the\n * `pairing:repair` hook surface so consumers can wire structured logging\n * without an agent instance.\n */\n onRepair?: (repair: PairingRepair) => void\n /**\n * When set, also runs {@link ensureEndsWithUserMessage} so the result is\n * safe for assistant-prefill-rejecting models (opus 4.7, o-series\n * translation layers). Omit to skip — pure pairing repair only.\n */\n provider?: Provider\n /**\n * Override for the synthetic user-tail directive. Only consulted when\n * `provider` is set. Defaults to {@link DEFAULT_USER_TAIL_DIRECTIVE}.\n */\n userTailDirective?: string\n}\n\n/**\n * Build a wire-ready `SessionMessage[]` from raw persisted `SessionTurn[]`.\n *\n * **The canonical \"I want to send these to a provider\" projection.** Use\n * this — not raw `session.turns` / `store.getTurns()` — when constructing a\n * provider request outside `agent.run()`. The agent's own wire pipeline\n * (`executeTurn` in `src/loop.ts`) applies the same repair just-in-time;\n * downstream consumers that bypass the loop hit `tool_result must be\n * preceded by a tool_call` 400s precisely because they shipped raw turns.\n *\n * Pipeline:\n * 1. Filter `system`-role turns (the wire only carries user/assistant).\n * 2. {@link ensureToolResultPairing} — repairs all six orphan / dup\n * corruption modes by inserting synthetic placeholders rather than\n * dropping (preserves the model's tool_use shape).\n * 3. {@link ensureEndsWithUserMessage} when `options.provider` is set —\n * guards against assistant-tail prefill rejection (opus 4.7, o-series).\n *\n * Pure + idempotent. Does not mutate the input turns. Re-running on the\n * output is a no-op.\n *\n * **Do not feed the result back into `session.setTurns` / `appendTurns`.**\n * The repair can insert `SYNTHETIC_TOOL_RESULT_PLACEHOLDER` / `[Orphaned\n * tool result removed …]` markers; round-tripping those into persisted\n * state contaminates the session's reasoning history and defeats the\n * \"don't rewrite the past\" invariant the wire-only repair was designed\n * around.\n */\nexport function toWireMessages(\n turns: readonly { role: 'user' | 'assistant' | 'system', content: SessionContentBlock[] }[],\n options: ToWireMessagesOptions = {},\n): SessionMessage[] {\n const messages: SessionMessage[] = []\n for (const t of turns) {\n if (t.role === 'system')\n continue\n messages.push({ role: t.role, content: t.content })\n }\n const paired = ensureToolResultPairing(messages, options.onRepair ? { onRepair: options.onRepair } : {})\n if (!options.provider)\n return paired\n return ensureEndsWithUserMessage(paired, options.provider, options.userTailDirective)\n}\n\nexport function autoDetectAndConvert(msg: { role: string, content: unknown }): SessionMessage {\n const c: unknown = msg.content\n\n // OpenAI-compat tagged messages\n if (c && typeof c === 'object' && !Array.isArray(c)) {\n const tag = (c as { _tag?: unknown })._tag\n if (typeof tag === 'string' && tag.startsWith('__zidane_'))\n return fromOpenAI(msg)\n }\n\n // Array: inspect block types\n if (Array.isArray(c)) {\n for (const block of c) {\n if (!block || typeof block !== 'object')\n continue\n const b = block as Record<string, unknown>\n if (b.type === 'tool_use' || (b.type === 'tool_result' && 'tool_use_id' in b)) {\n return fromAnthropic(msg)\n }\n if (b.type === 'image_url') {\n return fromOpenAI(msg)\n }\n }\n // Default array → Anthropic\n return fromAnthropic(msg)\n }\n\n // String → either format is the same\n if (typeof c === 'string') {\n return fromAnthropic(msg)\n }\n\n // Default\n return fromAnthropic(msg)\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"providers-beXyD9W9.js","names":["BASE_URL","getApiKey"],"sources":["../src/atomic-write.ts","../src/providers/oauth.ts","../src/providers/anthropic.ts","../src/providers/cerebras.ts","../src/providers/openai.ts","../src/providers/openrouter.ts"],"sourcesContent":["/**\n * Synchronous atomic file write helper.\n *\n * Used by the chat-layer state / safelist / credentials / keybindings writers\n * and the OAuth credentials store. The pattern is always the same: write to a\n * sibling temp file, then `renameSync` over the target. `rename(2)` is atomic\n * on the same filesystem, so concurrent readers either see the previous file\n * or the new one — never a half-written intermediate. The temp suffix folds\n * `process.pid` + `Date.now()` so two writers in the same process can't pick\n * the same tmp path.\n *\n * `ensureDir` runs `mkdirSync(dirname(path), { recursive: true })` before\n * the write so callers don't have to repeat the boilerplate. `mode` is\n * forwarded to `writeFileSync` (e.g. `0o600` for credentials files).\n */\n\nimport { mkdirSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\n\nexport interface AtomicWriteOptions {\n /** When set, create the parent directory recursively before writing. */\n ensureDir?: boolean\n /** Forwarded to `writeFileSync` — typically `0o600` for credentials files. */\n mode?: number\n}\n\nexport function writeFileAtomic(\n path: string,\n contents: string,\n options: AtomicWriteOptions = {},\n): void {\n if (options.ensureDir)\n mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.${process.pid}.${Date.now()}.tmp`\n writeFileSync(tmp, contents, options.mode !== undefined ? { mode: options.mode } : undefined)\n renameSync(tmp, path)\n}\n","import type { OAuthCredentials } from '@earendil-works/pi-ai/oauth'\nimport type { StreamCallbacks } from '.'\nimport type { OAuthRefreshHookContext } from '../types'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { getOAuthApiKey } from '@earendil-works/pi-ai/oauth'\nimport { writeFileAtomic } from '../atomic-write'\nimport { errorMessage } from '../errors'\n\n/**\n * Resolve the creds-file path at call time rather than module-load time.\n * Hosts that `chdir` between import and first OAuth use (test suites,\n * multi-project CLIs) previously got a stale path captured at import.\n *\n * Order:\n * 1. `ZIDANE_CREDENTIALS_PATH` — explicit override. The TUI sets this at\n * launch to point at `<storageDir>/credentials.json` so the harness\n * providers find the same file the TUI manages.\n * 2. `cwd/.credentials.json` — legacy default for `bun run auth` users\n * and library consumers who relied on the original location.\n */\nfunction credentialsFilePath(): string {\n return process.env.ZIDANE_CREDENTIALS_PATH ?? resolve(process.cwd(), '.credentials.json')\n}\n\n/**\n * POSIX mode for credentials on disk. Read+write for the owner only — refresh\n * tokens and API keys leak if the file is world-readable on a shared box.\n * Ignored on Windows.\n */\nconst CREDENTIALS_FILE_MODE = 0o600\n\n/**\n * In-process mutex table keyed by provider id. Concurrent `resolveOAuthApiKey`\n * calls for the same provider (typical when a host fans out multiple streams\n * at once) coalesce onto a single refresh. Without this, N concurrent calls\n * each hit `getOAuthApiKey`, producing N writes to `.credentials.json` with\n * last-write-wins semantics — and, in worst-case interleaving, a corrupted\n * file.\n */\nconst refreshLocks = new Map<string, Promise<string>>()\n\nexport interface OAuthParams {\n apiKey?: string\n access?: string\n refresh?: string\n expires?: number\n}\n\nexport interface ResolveOAuthApiKeyOptions<TParams extends OAuthParams = OAuthParams> {\n provider: string\n providerId: string\n params?: TParams\n envKey?: string\n extraCredentialKeys?: (keyof TParams & string)[]\n missingError: string\n refreshError: (reason: string) => string\n readCredentials?: () => Record<string, OAuthCredentials | undefined>\n writeCredentials?: (credentials: Record<string, OAuthCredentials | undefined>) => void\n getOAuthApiKey?: typeof getOAuthApiKey\n}\n\n/**\n * Read the shared credentials file.\n *\n * Returns `{}` when the file is missing OR when the file exists but is corrupted\n * / not valid JSON. Corrupt-file tolerance is important: the refresh-then-persist\n * path below writes atomically, but older builds wrote in place and could have\n * truncated the file on a crash. Treating corruption as \"no stored creds\" lets\n * users re-auth without manually deleting the file.\n *\n * Supports two on-disk shapes:\n * 1. **Legacy** (untagged): `{ \"anthropic\": { access, refresh, expires, ... } }`\n * — written by older `bun run auth`.\n * 2. **New** (kind-tagged): `{ \"anthropic\": { kind: 'oauth', access, refresh, expires, ... } }`\n * — written by the TUI. Entries with `kind: 'apikey'` are skipped — those\n * reach providers via env vars set at TUI launch (see `applyApiKeyEnv`).\n */\nexport function readOAuthCredentials(): Record<string, OAuthCredentials | undefined> {\n const path = credentialsFilePath()\n if (!existsSync(path))\n return {}\n\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n const result: Record<string, OAuthCredentials | undefined> = {}\n for (const [key, value] of Object.entries(parsed)) {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n continue\n const v = value as Record<string, unknown>\n // New schema: explicit `kind` tag.\n if (v.kind === 'apikey')\n continue\n // Both new (`kind: 'oauth'`) and legacy (untagged) entries have `access`;\n // the harmless `kind` field is allowed by `OAuthCredentials`'s\n // `[key: string]: unknown` index signature. Validate the required\n // fields explicitly so the assignment doesn't need a double-cast.\n if (\n typeof v.access === 'string'\n && typeof v.refresh === 'string'\n && typeof v.expires === 'number'\n ) {\n result[key] = { ...v, access: v.access, refresh: v.refresh, expires: v.expires }\n }\n }\n return result\n }\n catch {\n return {}\n }\n}\n\n/**\n * Atomically write `.credentials.json` with mode 0o600.\n *\n * Writes to a sibling temp file first, then renames. `rename(2)` is atomic on\n * the same filesystem, so readers either see the old file or the new one —\n * never a half-written one. This matters when a host has multiple processes\n * pointed at the same creds file.\n *\n * Merges the incoming OAuth entries on top of the raw file contents so that\n * entries this layer doesn't manage (e.g. `kind: 'apikey'` from the TUI\n * wizard) survive an OAuth refresh cycle. Without this merge,\n * `readOAuthCredentials` filters those entries out, and a naive overwrite\n * would delete them.\n */\nexport function writeOAuthCredentials(credentials: Record<string, OAuthCredentials | undefined>) {\n const path = credentialsFilePath()\n\n let existing: Record<string, unknown> = {}\n try {\n if (existsSync(path)) {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw)\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n existing = parsed as Record<string, unknown>\n }\n }\n catch { /* corrupt file — start fresh */ }\n\n const merged = { ...existing, ...credentials }\n // Remove keys explicitly set to undefined (provider was deleted).\n for (const [key, value] of Object.entries(merged)) {\n if (value === undefined)\n delete merged[key]\n }\n\n writeFileAtomic(path, JSON.stringify(merged, null, 2), { mode: CREDENTIALS_FILE_MODE })\n}\n\nexport function credentialsFromParams<TParams extends OAuthParams>(params?: TParams, extraKeys: (keyof TParams & string)[] = []): OAuthCredentials | undefined {\n if (typeof params?.access !== 'string' || typeof params.refresh !== 'string' || typeof params.expires !== 'number')\n return undefined\n\n const extras = Object.fromEntries(\n extraKeys\n .map(key => [key, params[key]])\n .filter(([, value]) => value !== undefined),\n )\n\n return {\n access: params.access,\n refresh: params.refresh,\n expires: params.expires,\n ...extras,\n }\n}\n\nexport async function resolveOAuthApiKey<TParams extends OAuthParams>(\n options: ResolveOAuthApiKeyOptions<TParams>,\n callbacks?: Pick<StreamCallbacks, 'onOAuthRefresh'>,\n): Promise<string> {\n if (typeof options.params?.apiKey === 'string')\n return options.params.apiKey\n\n const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys)\n if (paramsCredentials) {\n return await withRefreshLock(\n `params:${options.providerId}`,\n () => resolveCredentialSource('params', paramsCredentials),\n )\n }\n\n if (typeof options.params?.access === 'string')\n return options.params.access\n\n if (options.envKey && process.env[options.envKey])\n return process.env[options.envKey]!\n\n const readCredentials = options.readCredentials ?? readOAuthCredentials\n const writeCredentials = options.writeCredentials ?? writeOAuthCredentials\n\n return await withRefreshLock(`file:${options.providerId}`, async () => {\n // Read under lock so we're always operating on the latest file state and\n // never interleave read/write against another concurrent resolve.\n const allCredentials = readCredentials()\n const storedCredentials = allCredentials[options.providerId]\n if (!storedCredentials)\n throw new Error(options.missingError)\n\n return await resolveCredentialSource('file', storedCredentials, allCredentials, writeCredentials)\n })\n\n async function resolveCredentialSource(\n source: OAuthRefreshHookContext['source'],\n current: OAuthCredentials,\n allCredentials?: Record<string, OAuthCredentials | undefined>,\n persistCredentials?: (credentials: Record<string, OAuthCredentials | undefined>) => void,\n ): Promise<string> {\n try {\n const refreshOAuthApiKey = options.getOAuthApiKey ?? getOAuthApiKey\n const result = await refreshOAuthApiKey(options.providerId, { [options.providerId]: current } as Record<string, OAuthCredentials>)\n if (!result)\n throw new Error(options.missingError)\n\n if (result.newCredentials !== current) {\n if (source === 'file' && allCredentials && persistCredentials) {\n allCredentials[options.providerId] = result.newCredentials\n persistCredentials(allCredentials)\n }\n\n await callbacks?.onOAuthRefresh?.({\n provider: options.provider,\n providerId: options.providerId,\n source,\n previousCredentials: { ...current },\n credentials: { ...result.newCredentials },\n })\n }\n\n return result.apiKey\n }\n catch (err) {\n throw new Error(options.refreshError(errorMessage(err)))\n }\n }\n}\n\n/**\n * Coalesce concurrent refresh calls onto a single in-flight promise per key.\n * The key combines the source (`params` vs `file`) and provider id, so a\n * per-agent param-only refresh does not starve a separate file-backed one.\n */\nasync function withRefreshLock(key: string, fn: () => Promise<string>): Promise<string> {\n const existing = refreshLocks.get(key)\n if (existing)\n return existing\n\n const task = (async () => {\n try {\n return await fn()\n }\n finally {\n refreshLocks.delete(key)\n }\n })()\n refreshLocks.set(key, task)\n return task\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, StreamCallbacks, ToolResult, ToolSpec, TurnResult } from '.'\nimport type { ClassifiedError } from '../errors'\nimport type { PromptPart, SessionContentBlock, SessionMessage, ThinkingLevel, TurnFinishReason } from '../types'\nimport { matchesContextExceeded } from '../errors'\nimport { fromAnthropic, toAnthropic } from '../session/messages'\nimport { renderSystemForWire, splitSystemPrompt } from '../system-prompt'\nimport { fillEstimatedCost } from './cost'\nimport { 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 only imported when the anthropic provider actually needs to open a\n// connection, so installing-without-using-it remains free for consumers who route\n// through other providers (openrouter, cerebras, openaiCompat, openai).\nlet _sdkCtor: AnthropicCtor | null = null\n\nasync function loadAnthropicSdk(): Promise<AnthropicCtor> {\n if (_sdkCtor)\n return _sdkCtor\n try {\n const mod = await import('@anthropic-ai/sdk')\n _sdkCtor = mod.default as unknown as AnthropicCtor\n return _sdkCtor\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 {\n apiKey?: string\n access?: string\n refresh?: string\n expires?: number\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\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 * 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\nfunction getConfiguredApiKey(anthropicParams?: AnthropicParams): string {\n if (anthropicParams?.apiKey)\n return anthropicParams.apiKey\n\n if (anthropicParams?.access)\n return anthropicParams.access\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\nfunction createClient(\n SDK: AnthropicCtor,\n apiKey: string,\n isOAuth: boolean,\n baseURL?: string,\n extraBetas?: readonly string[],\n): AnthropicInstance {\n const base = baseURL ? { baseURL } : {}\n const betaHeader = resolveAnthropicBetas(isOAuth, extraBetas)\n if (isOAuth) {\n const defaultHeaders: Record<string, string> = {\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 new SDK({\n apiKey: null,\n authToken: apiKey,\n dangerouslyAllowBrowser: true,\n defaultHeaders,\n ...base,\n })\n }\n // API-key path — only emit the beta header when the caller actually opted in.\n const defaultHeaders: Record<string, string> | undefined = betaHeader\n ? { 'anthropic-beta': betaHeader }\n : undefined\n return new SDK({\n apiKey,\n ...(defaultHeaders ? { defaultHeaders } : {}),\n ...base,\n })\n}\n\ntype BudgetedThinkingLevel = Exclude<ThinkingLevel, 'off' | 'adaptive'>\n\n/**\n * Map `ThinkingLevel` budgeted tiers to Anthropic's `output_config.effort`\n * enum. Anthropic exposes `low | medium | high | xhigh | max`; we surface the\n * three middle levels plus a `minimal` alias that collapses to `low` (the\n * closest equivalent — Anthropic does not have a sub-`low` tier).\n */\nconst EFFORT_FOR_LEVEL: Record<BudgetedThinkingLevel, 'low' | 'medium' | 'high'> = {\n minimal: 'low',\n low: 'low',\n medium: 'medium',\n high: 'high',\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. The only\n * way to pin a precise token budget; Anthropic emits a deprecation\n * warning for this shape on opus 4.6+, so we only route here when the\n * caller explicitly 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 }\n | { kind: 'adaptive', effort?: 'low' | 'medium' | 'high', maxTokensCap?: 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 `customBudget` is set, it is carried as `maxTokensCap`\n * so the request builder caps `max_tokens` accordingly — adaptive has no\n * native budget knob, but capping the response envelope soft-bounds the\n * thinking that lives inside it.\n * - `'minimal' | 'low' | 'medium' | 'high'` → adaptive thinking with an\n * `effort` hint, unless `customBudget` is provided.\n * - Any level + `customBudget` → explicit-budget `enabled` path. The caller\n * has opted into precise budget control and accepts the Anthropic\n * deprecation warning that comes with it on opus 4.6+. `'adaptive'` is the\n * sole exception: it never falls back to enabled, since adaptive is the\n * current Anthropic API surface for self-budgeted thinking.\n */\nexport function planAnthropicThinking(\n level: ThinkingLevel,\n customBudget?: number,\n): AnthropicThinkingPlan | null {\n if (level === 'off')\n return null\n if (level === 'adaptive') {\n if (typeof customBudget === 'number' && customBudget > 0)\n return { kind: 'adaptive', maxTokensCap: customBudget }\n return { kind: 'adaptive' }\n }\n if (customBudget !== undefined) {\n return { kind: 'enabled', budgetTokens: customBudget, maxTokensBump: customBudget }\n }\n return { kind: 'adaptive', effort: EFFORT_FOR_LEVEL[level] }\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\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.Tool[]\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 * Phrasings Anthropic uses for the wire-level `tool_use` / `tool_result`\n * adjacency rejection. The local pre-send pairing repair\n * ({@link ensureToolResultPairing}) should catch every corruption mode\n * before the request leaves the harness — but a 400 still slipping\n * through is the canonical \"the harness's defenses failed\" signal, so\n * surface it as a typed `tool_pairing_corruption` error rather than a\n * generic provider_error.\n */\n// Phrasings tolerated:\n// - `'tool_use' ids were found without 'tool_result' blocks immediately after` (Anthropic Python SDK)\n// - `tool_use ids were found without tool_result blocks immediately after` (canonical)\n// - `tool_result must be preceded by a tool_call with the same toolCallId` (downstream consumer error, the screenshot)\n// - `unexpected `tool_result` block` (truncated 400)\n//\n// Backticks / single-quote wrapping by various SDKs are tolerated by the\n// `[^a-z0-9]*` glue between keywords rather than expecting verbatim\n// whitespace. Anchored on the keyword pair so we don't false-positive on\n// unrelated 400s mentioning either word alone.\nconst TOOL_PAIRING_ERROR_PATTERNS: readonly RegExp[] = [\n /tool_use[^a-z0-9]+ids?[^a-z0-9]+were[^a-z0-9]+found[^a-z0-9]+without[^a-z0-9]+tool_result/i,\n /tool_result[^a-z0-9]+must[^a-z0-9]+be[^a-z0-9]+preceded[^a-z0-9]+by[^a-z0-9]+a[^a-z0-9]+tool_call/i,\n /unexpected[^a-z0-9]+tool_result[^a-z0-9]+block/i,\n /tool_use[^a-z0-9]+block.*requires.*tool_result/i,\n]\n\nfunction matchesToolPairingError(message: unknown): boolean {\n if (typeof message !== 'string' || message.length === 0)\n return false\n return TOOL_PAIRING_ERROR_PATTERNS.some(re => re.test(message))\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 if (!err || typeof err !== 'object')\n return null\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 if (anyErr.name === 'AbortError')\n return { kind: 'aborted' }\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 ? status === 429 || (status >= 500 && status !== 501)\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\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 with `encoding: 'text'` → inlined as a text block with an\n * `<attachment>` header so the model sees a clearly-delimited attachment.\n * - Document parts with `encoding: 'base64'` → serialized as an `<attachment\n * encoding=\"base64\">`-tagged text block. Native Anthropic document/PDF blocks\n * are not yet wired through the SessionContentBlock union, so consumers\n * needing true PDF ingestion should preprocess to text first.\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 // document\n if (part.encoding === 'text') {\n const header = part.name\n ? `<attachment name=\"${part.name}\" media_type=\"${part.mediaType}\">`\n : `<attachment media_type=\"${part.mediaType}\">`\n content.push({ type: 'text', text: `${header}\\n${part.data}\\n</attachment>` })\n continue\n }\n\n // Base64 documents (e.g. PDF) — serialized as an attachment-tagged marker. The\n // Anthropic message converter (toAnthropic) does not yet map this to the native\n // document block; consumers needing PDF ingestion should preprocess to text for now.\n const header = part.name\n ? `<attachment name=\"${part.name}\" media_type=\"${part.mediaType}\" encoding=\"base64\">`\n : `<attachment media_type=\"${part.mediaType}\" encoding=\"base64\">`\n content.push({ type: 'text', text: `${header}\\n${part.data}\\n</attachment>` })\n }\n\n return { role: 'user', content }\n}\n\nexport function anthropic(\n anthropicParams?: AnthropicParams,\n): Provider {\n const configuredApiKey = getConfiguredApiKey(anthropicParams)\n const isOAuth = configuredApiKey.includes('sk-ant-oat')\n const defaultModel = anthropicParams?.defaultModel || 'claude-opus-4-7'\n let runtimeCredentials = (\n typeof anthropicParams?.access === 'string'\n && typeof anthropicParams.refresh === 'string'\n && typeof anthropicParams.expires === 'number'\n )\n ? {\n access: anthropicParams.access,\n refresh: anthropicParams.refresh,\n expires: anthropicParams.expires,\n }\n : undefined\n\n return {\n name: 'anthropic',\n meta: {\n defaultModel,\n isOAuth,\n capabilities: {\n vision: true,\n imageInToolResult: true,\n },\n },\n\n formatTools(tools: ToolSpec[]): Anthropic.Tool[] {\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 const sanitized = sanitizeToolSpecs(tools, { profile: 'anthropic' })\n return sanitized.map(t => ({\n name: t.name,\n description: t.description,\n input_schema: t.inputSchema as Anthropic.Tool['input_schema'],\n }))\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 })),\n }\n },\n\n promptMessage: anthropicPromptMessage,\n\n classifyError: classifyAnthropicError,\n\n async stream(options, callbacks: StreamCallbacks): Promise<TurnResult> {\n const SDK = await loadAnthropicSdk()\n const apiKey = await resolveOAuthApiKey(\n {\n provider: 'anthropic',\n providerId: 'anthropic',\n params: runtimeCredentials ? { ...anthropicParams, ...runtimeCredentials } : anthropicParams,\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 ...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 const client = createClient(\n SDK,\n apiKey,\n apiKey.includes('sk-ant-oat'),\n anthropicParams?.baseURL,\n anthropicParams?.extraBetas,\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 = isOAuth\n ? `You are Claude Code, Anthropic's official CLI for Claude.`\n : wireSystem\n const messages: SessionMessage[] = isOAuth && 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\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 } = {\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 max_tokens: options.maxTokens,\n system,\n tools: options.tools as Anthropic.Tool[],\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, isOAuth ? 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)\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 }\n else {\n params.thinking = { type: 'adaptive', display: 'summarized' }\n if (plan.effort)\n params.output_config = { effort: plan.effort }\n // Adaptive has no native budget knob — soft-cap the response envelope\n // (`max_tokens`) so unbounded thinking can't run away. Reduce only;\n // never raise above the 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 // 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 s = client.messages.stream(params, {\n signal: options.signal,\n })\n\n let text = ''\n\n s.on('text', (delta) => {\n text += delta\n callbacks.onText(delta)\n })\n\n if (callbacks.onThinking) {\n s.on('thinking', (delta) => {\n callbacks.onThinking!(delta)\n })\n }\n\n const response = await s.finalMessage()\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 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: fillEstimatedCost({\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 }, 'anthropic'),\n }\n },\n }\n}\n","import type { Provider, ProviderCapabilities } from '.'\nimport { openaiCompat } from './openai-compat'\n\nconst BASE_URL = 'https://api.cerebras.ai/v1'\n\nexport interface CerebrasParams {\n apiKey?: string\n defaultModel?: string\n /**\n * Provider capability flags. Cerebras currently serves text-only OSS models\n * (GLM, Llama-family, Qwen-family) — default: `{ vision: false, imageInToolResult: false }`.\n * Override when routing to a vision-capable deployment.\n */\n capabilities?: ProviderCapabilities\n}\n\nfunction getApiKey(params?: CerebrasParams): string {\n // Resolution order is intentionally aligned with `anthropic` + `openai` —\n // explicit params > env var > fail. Host code that composes providers from\n // config (e.g. a YAML-driven CLI) relies on `params.apiKey` winning in all\n // providers, not just some.\n if (typeof params?.apiKey === 'string' && params.apiKey.length > 0)\n return params.apiKey\n\n if (process.env.CEREBRAS_API_KEY)\n return process.env.CEREBRAS_API_KEY\n\n throw new Error('No Cerebras API key found. Pass `apiKey` or set CEREBRAS_API_KEY in your environment.')\n}\n\n/**\n * Cerebras provider.\n *\n * Thin wrapper around {@link openaiCompat} with Cerebras-specific defaults\n * (base URL, default model).\n */\nexport function cerebras(params?: CerebrasParams): Provider {\n const apiKey = getApiKey(params)\n return openaiCompat({\n name: 'cerebras',\n apiKey,\n baseURL: BASE_URL,\n defaultModel: params?.defaultModel || 'zai-glm-4.7',\n capabilities: params?.capabilities ?? { vision: false, imageInToolResult: false },\n })\n}\n","import type {\n AssistantMessage as PiAssistantMessage,\n Context as PiContext,\n Message as PiMessage,\n Model as PiModel,\n Tool as PiTool,\n Usage as PiUsage,\n} from '@earendil-works/pi-ai'\nimport type { Provider, StreamCallbacks, StreamOptions, ToolSpec, TurnResult } from '.'\nimport type { ClassifiedError } from '../errors'\nimport type { SessionContentBlock, SessionMessage, TurnFinishReason } from '../types'\nimport { getModel } from '@earendil-works/pi-ai'\nimport { streamOpenAICodexResponses } from '@earendil-works/pi-ai/openai-codex-responses'\nimport { matchesContextExceeded } from '../errors'\nimport { renderSystemForWire } from '../system-prompt'\nimport { fillEstimatedCost } from './cost'\nimport { resolveOAuthApiKey } from './oauth'\nimport {\n assistantMessage,\n toolResultsMessage,\n userMessage,\n} from './openai-compat'\nimport { sanitizeToolSpecs } from './schema-sanitize'\n\nconst PROVIDER_ID = 'openai-codex'\nconst DEFAULT_MODEL = 'gpt-5.4'\n\ntype CodexModel = PiModel<'openai-codex-responses'>\n\nexport interface OpenAIParams {\n /** OpenAI Codex OAuth access token. Falls back to OPENAI_CODEX_API_KEY and .credentials.json. */\n apiKey?: string\n /** Alias for apiKey, matching the OAuth credential field. */\n access?: string\n refresh?: string\n expires?: number\n accountId?: string\n defaultModel?: string\n transport?: 'sse' | 'websocket' | 'auto'\n}\n\n// pi-ai's `getModel` is typed for `KnownProvider` × `keyof models[P]`;\n// we look up by free-form `modelId` (the registry holds string ids at\n// runtime) and let pi-ai's miss-throw fall through via the wider lookup.\nconst lookupModel = getModel as (provider: string, modelId: string) => CodexModel | undefined\n\nfunction resolveModel(modelId: string): CodexModel {\n const model = lookupModel(PROVIDER_ID, modelId)\n if (model)\n return model\n\n const fallback = lookupModel(PROVIDER_ID, DEFAULT_MODEL)\n if (!fallback)\n throw new Error(`OpenAI Codex model registry is missing the default model: ${DEFAULT_MODEL}`)\n\n return { ...fallback, id: modelId, name: modelId }\n}\n\nfunction emptyUsage(): PiUsage {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n }\n}\n\nfunction formatTools(tools: ToolSpec[]): PiTool[] {\n // Codex's Responses API runs the same schema validator as the OpenAI\n // Chat Completions endpoint — apply the same sanitisation pass so MCP\n // tools with non-standard root shapes don't 400 here either.\n const sanitized = sanitizeToolSpecs(tools, { profile: 'openai' })\n return sanitized.map(t => ({\n name: t.name,\n description: t.description,\n parameters: t.inputSchema as PiTool['parameters'],\n }))\n}\n\nfunction toPiMessages(messages: SessionMessage[], modelId: string): PiMessage[] {\n const out: PiMessage[] = []\n\n for (const msg of messages) {\n const toolResults = msg.content.filter(b => b.type === 'tool_result')\n if (toolResults.length > 0) {\n for (const result of toolResults) {\n // pi-ai's ToolResultMessage natively accepts (TextContent | ImageContent)[].\n // Map zidane's canonical union directly — no flattening needed.\n const content = typeof result.output === 'string'\n ? [{ type: 'text' as const, text: result.output }]\n : result.output.map(block => block.type === 'image'\n ? { type: 'image' as const, data: block.data, mimeType: block.mediaType }\n : { type: 'text' as const, text: block.text })\n\n out.push({\n role: 'toolResult',\n toolCallId: result.callId,\n toolName: '',\n content,\n isError: result.isError ?? false,\n timestamp: Date.now(),\n })\n }\n continue\n }\n\n const textBlocks = msg.content.filter(b => b.type === 'text')\n const imageBlocks = msg.content.filter(b => b.type === 'image')\n\n if (msg.role === 'user') {\n if (imageBlocks.length === 0 && textBlocks.length === 1) {\n out.push({ role: 'user', content: textBlocks[0].text, timestamp: Date.now() })\n continue\n }\n\n out.push({\n role: 'user',\n content: [\n ...imageBlocks.map(img => ({ type: 'image' as const, data: img.data, mimeType: img.mediaType })),\n ...textBlocks.map(block => ({ type: 'text' as const, text: block.text })),\n ],\n timestamp: Date.now(),\n })\n continue\n }\n\n const content: PiAssistantMessage['content'] = []\n for (const block of msg.content) {\n if (block.type === 'text') {\n content.push({ type: 'text', text: block.text })\n }\n else if (block.type === 'thinking') {\n // Drop thinking blocks minted by another provider — Anthropic signatures\n // are not valid OpenAI `encrypted_content` and Responses API rejects them.\n if (block.signatureProducer === 'anthropic')\n continue\n content.push({ type: 'thinking', thinking: block.text, thinkingSignature: block.signature })\n }\n else if (block.type === 'tool_call') {\n content.push({ type: 'toolCall', id: block.id, name: block.name, arguments: block.input })\n }\n // `redacted_thinking` (Anthropic-only) and `provider_reasoning` (OpenRouter\n // envelope) are intentionally dropped — Codex Responses doesn't accept them.\n }\n\n out.push({\n role: 'assistant',\n content,\n api: 'openai-codex-responses',\n provider: PROVIDER_ID,\n model: modelId,\n usage: emptyUsage(),\n stopReason: 'stop',\n timestamp: Date.now(),\n })\n }\n\n return out\n}\n\nfunction fromPiAssistantMessage(message: PiAssistantMessage): SessionMessage {\n const content: SessionContentBlock[] = []\n\n for (const block of message.content) {\n if (block.type === 'text') {\n content.push({ type: 'text', text: block.text })\n }\n else if (block.type === 'thinking') {\n const out: Extract<SessionContentBlock, { type: 'thinking' }> = {\n type: 'thinking',\n text: block.thinking,\n }\n if (typeof block.thinkingSignature === 'string') {\n out.signature = block.thinkingSignature\n out.signatureProducer = 'openai'\n }\n content.push(out)\n }\n else if (block.type === 'toolCall') {\n content.push({ type: 'tool_call', id: block.id, name: block.name, input: block.arguments })\n }\n }\n\n return { role: 'assistant', content }\n}\n\nfunction extractToolCalls(message: PiAssistantMessage) {\n return message.content\n .filter(block => block.type === 'toolCall')\n .map(block => ({\n id: block.id,\n name: block.name,\n input: block.arguments as Record<string, unknown>,\n }))\n}\n\nfunction extractText(message: PiAssistantMessage): string {\n return message.content\n .filter((block): block is Extract<PiAssistantMessage['content'][number], { type: 'text' }> => block.type === 'text')\n .map(block => block.text)\n .join('')\n}\n\nfunction toTurnUsage(usage: PiUsage, finishReason: TurnFinishReason | undefined, modelId: string) {\n return fillEstimatedCost({\n input: usage.input,\n output: usage.output,\n cacheRead: usage.cacheRead || undefined,\n cacheCreation: usage.cacheWrite || undefined,\n cost: usage.cost.total || undefined,\n ...(finishReason ? { finishReason } : {}),\n modelId,\n }, 'openai')\n}\n\n/**\n * Transient pi-ai error patterns worth a loop-level retry. pi-ai already retries\n * 429 / 5xx pre-stream 3× internally (see `openai-codex-responses.js`); by the\n * time the error reaches us, that budget is exhausted. The loop-level retry\n * with its longer backoff is a second line of defense for capacity events\n * that persist past pi-ai's short retry window, plus mid-stream emissions\n * (pi-ai surfaces those as plain `Error`s with no status).\n *\n * Mirrors pi-ai's own `isRetryableError` regex so the two retry layers\n * recognize the same failure modes.\n */\nconst TRANSIENT_OPENAI_MESSAGE_RE = /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused|gateway.?time.?out|temporarily.?unavailable/i\n\n/** Numeric HTTP status codes pi-ai sometimes attaches when bubbling structured errors. */\nfunction isRetryableStatusCode(err: object): boolean {\n const status = (err as { status?: unknown }).status\n if (typeof status !== 'number')\n return false\n if (status === 429)\n return true\n if (status >= 500 && status !== 501)\n return true\n return false\n}\n\n/**\n * Classify an OpenAI Codex error. pi-ai surfaces errors either as thrown `Error`s\n * (wrapping `event.error.errorMessage`) or via stream event types.\n *\n * Retryable hint is set when pi-ai's own retry budget is exhausted on a\n * transient failure — pattern-matched against `message` since pi-ai strips\n * structured fields on plain-Error throws. Set `behavior.retry.maxAttempts: 1`\n * to disable the loop-level retry.\n */\nexport function classifyOpenAIError(err: unknown): ClassifiedError | null {\n if (!err || typeof err !== 'object')\n return null\n\n const anyErr = err as { name?: string, message?: string, code?: string, type?: string }\n\n if (anyErr.name === 'AbortError')\n return { kind: 'aborted' }\n\n const message = anyErr.message ?? ''\n const code = anyErr.code ?? anyErr.type\n\n if (code === 'context_length_exceeded' || matchesContextExceeded(message)) {\n return {\n kind: 'context_exceeded',\n providerCode: code ?? 'context_length_exceeded',\n message,\n }\n }\n\n // pi-ai wraps API errors in generic `Error` — treat as provider_error when we have a message.\n if (message.length > 0) {\n const retryable = isRetryableStatusCode(err) || TRANSIENT_OPENAI_MESSAGE_RE.test(message)\n return {\n kind: 'provider_error',\n providerCode: code,\n message,\n ...(retryable ? { retryable: true } : {}),\n }\n }\n\n return null\n}\n\nfunction applyPayloadOverrides(payload: unknown, options: StreamOptions): unknown {\n const body = payload as Record<string, unknown>\n\n if (options.toolChoice) {\n if (options.toolChoice.type === 'tool' && options.toolChoice.name)\n body.tool_choice = { type: 'function', name: options.toolChoice.name }\n else if (options.toolChoice.type === 'required')\n body.tool_choice = 'required'\n else\n body.tool_choice = 'auto'\n }\n\n return body\n}\n\nexport function openai(params?: OpenAIParams): Provider {\n const defaultModel = params?.defaultModel || DEFAULT_MODEL\n let runtimeCredentials = (\n typeof params?.access === 'string'\n && typeof params.refresh === 'string'\n && typeof params.expires === 'number'\n )\n ? {\n access: params.access,\n refresh: params.refresh,\n expires: params.expires,\n ...(params.accountId ? { accountId: params.accountId } : {}),\n }\n : undefined\n\n return {\n name: 'openai',\n meta: {\n defaultModel,\n isOAuth: true,\n capabilities: {\n vision: true,\n imageInToolResult: true,\n },\n },\n formatTools,\n userMessage,\n assistantMessage,\n toolResultsMessage,\n classifyError: classifyOpenAIError,\n\n async stream(options: StreamOptions, callbacks: StreamCallbacks): Promise<TurnResult> {\n const modelId = options.model || defaultModel\n const model = resolveModel(modelId)\n const apiKey = await resolveOAuthApiKey(\n {\n provider: 'openai',\n providerId: PROVIDER_ID,\n params: runtimeCredentials ? { ...params, ...runtimeCredentials } : params,\n envKey: 'OPENAI_CODEX_API_KEY',\n extraCredentialKeys: ['accountId'],\n missingError: 'No OpenAI Codex OAuth token found. Run `bun run auth --openai` first.',\n refreshError: reason => `OpenAI Codex OAuth token refresh failed. Run \\`bun run auth --openai\\` again. ${reason}`,\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 ...(typeof ctx.credentials.accountId === 'string' ? { accountId: ctx.credentials.accountId } : {}),\n }\n }\n await callbacks.onOAuthRefresh?.(ctx)\n },\n },\n )\n // Codex's Responses API has no `cache_control` analogue, but the\n // system-prompt boundary marker is structural metadata that should\n // never reach the model. Strip it via `renderSystemForWire`; the\n // doctrine + env are collapsed into a single byte stream the model\n // sees as one logical prompt.\n const context: PiContext = {\n systemPrompt: renderSystemForWire(options.system),\n messages: toPiMessages(options.messages, modelId),\n tools: options.tools as PiTool[],\n }\n // OpenAI's `reasoning_effort` accepts the budgeted levels only. `'adaptive'`\n // is Anthropic-specific (model self-budgets); when supplied here we degrade\n // to no reasoning rather than forwarding an unknown value the API would reject.\n const reasoningLevel\n = options.thinking && options.thinking !== 'off' && options.thinking !== 'adaptive'\n ? options.thinking\n : undefined\n const stream = streamOpenAICodexResponses(model, context, {\n apiKey,\n maxTokens: options.maxTokens,\n signal: options.signal,\n transport: params?.transport,\n reasoningEffort: reasoningLevel,\n reasoningSummary: reasoningLevel ? 'auto' : undefined,\n onPayload: payload => applyPayloadOverrides(payload, options),\n })\n\n let finalMessage: PiAssistantMessage | undefined\n let text = ''\n let thinking = ''\n\n for await (const event of stream) {\n if (event.type === 'text_delta') {\n text += event.delta\n callbacks.onText(event.delta)\n }\n else if (event.type === 'thinking_delta') {\n thinking += event.delta\n callbacks.onThinking?.(event.delta)\n }\n else if (event.type === 'thinking_end') {\n const delta = event.content.startsWith(thinking)\n ? event.content.slice(thinking.length)\n : (thinking ? '' : event.content)\n if (delta) {\n thinking += delta\n callbacks.onThinking?.(delta)\n }\n }\n else if (event.type === 'done') {\n finalMessage = event.message\n }\n else if (event.type === 'error') {\n throw new Error(event.error.errorMessage || 'OpenAI Codex API error')\n }\n }\n\n finalMessage ??= await stream.result()\n text ||= extractText(finalMessage)\n\n const toolCalls = extractToolCalls(finalMessage)\n const assistantTurn = fromPiAssistantMessage(finalMessage)\n const finishReason: TurnFinishReason = toolCalls.length > 0 ? 'tool-calls' : 'stop'\n\n return {\n assistantMessage: assistantTurn,\n text,\n toolCalls,\n done: toolCalls.length === 0,\n usage: toTurnUsage(finalMessage.usage, finishReason, modelId),\n }\n },\n }\n}\n","import type { Provider, ProviderCapabilities } from '.'\nimport { openaiCompat } from './openai-compat'\n\nconst BASE_URL = 'https://openrouter.ai/api/v1'\n\nexport interface OpenRouterParams {\n apiKey?: string\n defaultModel?: string\n /**\n * Provider capability flags. OpenRouter itself is a router — whether vision or\n * native image-in-tool-result are supported depends on the downstream model.\n * Default: `{ vision: true, imageInToolResult: false }` — matches the default\n * `anthropic/claude-sonnet-4-6` model (vision-capable via companion user-message\n * fallback since OpenRouter exposes Claude over the Chat Completions dialect).\n *\n * Override when routing to a known-text-only model (e.g. `meta-llama/llama-3-8b-instruct`).\n */\n capabilities?: ProviderCapabilities\n}\n\nfunction getApiKey(params?: OpenRouterParams): string {\n // Resolution order aligned with other providers — explicit params win, env\n // is the fallback. See cerebras.ts for the rationale.\n if (typeof params?.apiKey === 'string' && params.apiKey.length > 0)\n return params.apiKey\n\n if (process.env.OPENROUTER_API_KEY)\n return process.env.OPENROUTER_API_KEY\n\n throw new Error('No OpenRouter API key found. Pass `apiKey` or set OPENROUTER_API_KEY in your environment.')\n}\n\n/**\n * OpenRouter provider.\n *\n * Thin wrapper around {@link openaiCompat} with OpenRouter-specific defaults\n * (base URL, default model) and required attribution headers.\n */\nexport function openrouter(params?: OpenRouterParams): Provider {\n const apiKey = getApiKey(params)\n return openaiCompat({\n name: 'openrouter',\n apiKey,\n baseURL: BASE_URL,\n defaultModel: params?.defaultModel || 'anthropic/claude-sonnet-4-6',\n extraHeaders: {\n 'HTTP-Referer': 'https://github.com/Tahul/zidane',\n 'X-Title': 'zidane',\n },\n capabilities: params?.capabilities ?? { vision: true, imageInToolResult: false },\n // OpenRouter honors `cache_control` markers for Anthropic + Gemini routes and\n // silently ignores them for routes that cache automatically. Safe to turn on\n // by default — the caller can still flip `behavior.cache = false` to opt out\n // without needing to re-instantiate the provider.\n cacheBreakpoints: true,\n // OpenRouter speaks the normalized `reasoning` request field and round-trips\n // structured `reasoning_details` on assistant messages. Captured into\n // `provider_reasoning` blocks and echoed back to preserve extended-reasoning\n // state across turns on the same upstream route.\n supportsReasoning: true,\n })\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"stats-Lc3zL3RM.js","names":[],"sources":["../src/stats.ts"],"sourcesContent":["/**\n * Pure derivations over `AgentStats`.\n *\n * Both helpers are tree-shakeable — import only what you need. They never\n * touch agent state, never do I/O, and always operate on the recursive\n * `AgentStats` tree returned by `agent.run()`.\n */\n\nimport type { AgentStats, TurnUsage } from './types'\n\n/**\n * Per-model usage rollup produced by {@link statsByModel}.\n *\n * `turns` counts the number of `TurnUsage` entries attributed to the model\n * across the whole tree (parent loop + every recursively-spawned child).\n * Cache and cost numbers are summed from the same set of turns.\n */\nexport interface ModelUsage {\n input: number\n output: number\n cost: number\n cacheRead: number\n cacheCreation: number\n turns: number\n}\n\n/**\n * Common shape for token-usage helpers. Mirrors `AgentStats`'s cumulative\n * fields. Callers working with `TurnUsage` / `SessionRun.totalUsage`\n * (`input` / `output` / `cacheRead` / `cacheCreation`) map their fields in\n * at the call site — we don't accept both shapes here because\n * `AgentStats.output` is a structured-output payload (`Record<string,\n * unknown>`), not a token count, so the field name `output` is ambiguous.\n */\nexport interface TokenUsageLike {\n totalIn?: number\n totalOut?: number\n totalCacheRead?: number\n totalCacheCreation?: number\n}\n\n/**\n * Clamp to a non-negative finite integer. Used to defend display logic\n * against providers that report `NaN`, `Infinity`, or negatives in their\n * usage payloads — the rendered string stays readable instead of leaking\n * `NaN`s into the footer / spawn-end markers.\n */\nfunction safeNumber(n: number | undefined): number {\n if (typeof n !== 'number' || !Number.isFinite(n) || n < 0)\n return 0\n return n\n}\n\nfunction normalize(stats: TokenUsageLike): {\n input: number\n output: number\n cacheRead: number\n cacheCreation: number\n} {\n return {\n input: safeNumber(stats.totalIn),\n output: safeNumber(stats.totalOut),\n cacheRead: safeNumber(stats.totalCacheRead),\n cacheCreation: safeNumber(stats.totalCacheCreation),\n }\n}\n\n/**\n * Sum of `input + cacheRead + cacheCreation` — i.e. the number of tokens the\n * model actually saw, regardless of how they were billed.\n *\n * With provider prompt caching (Anthropic, OpenRouter→Anthropic, Gemini),\n * `AgentStats.totalIn` only counts **new uncached** tokens per turn; the\n * bulk of an N-turn run's context lives in `totalCacheRead`, plus a smaller\n * `totalCacheCreation` chunk for whichever turns added new breakpoints.\n * Showing only `totalIn` in a per-run summary makes long subagent runs look\n * suspiciously cheap (e.g. \"13 in\" for 8 turns over a large repo).\n *\n * `TurnUsage` / `SessionRun.totalUsage` callers map their fields to the\n * `total*` names at the call site (see {@link TokenUsageLike}).\n */\nexport function effectiveInputTokens(stats: TokenUsageLike): number {\n const { input, cacheRead, cacheCreation } = normalize(stats)\n return input + cacheRead + cacheCreation\n}\n\n/**\n * Render a one-line token summary suitable for spawn-end markers, child\n * tool-result text, and any other per-run \"what did this cost?\" surface.\n *\n * `in 52413 (cache 50000) / 4075 out`\n *\n * The `in` total includes cached reads and cache-creation tokens (see\n * {@link effectiveInputTokens}); the `(cache N)` parenthetical breaks out\n * the cached portion so the user can tell at a glance how much was reused\n * vs new. The parenthetical is omitted when its value is zero, so\n * non-caching providers and brand-new runs read simply as `in N / M out`.\n *\n * Output-side caching is not a concept any current provider exposes —\n * Anthropic's `cacheRead` / `cacheCreation` are both input-billed. If a\n * future provider ships an output cache, extend `TokenUsageLike` with\n * output-cache fields and add a parallel `(cache N)` slot on the `out`\n * side here; today the right side is always plain `${N} out`.\n */\nexport function formatTokenUsage(stats: TokenUsageLike): string {\n const { input, output, cacheRead, cacheCreation } = normalize(stats)\n const inputTotal = input + cacheRead + cacheCreation\n const inputCache = cacheRead + cacheCreation\n const inPart = inputCache > 0\n ? `in ${inputTotal} (cache ${inputCache})`\n : `in ${inputTotal}`\n return `${inPart} / ${output} out`\n}\n\n/**\n * Depth-first walk over the stats tree, returning every `TurnUsage` entry\n * — parent loop first, then each child subtree in completion order.\n *\n * Closes the cache-token aggregation gap: `TurnUsage.cacheRead` /\n * `cacheCreation` live only on per-turn entries, and the top-level\n * `AgentStats` deliberately doesn't carry cumulative forms (one source of\n * truth, no risk of drift). Anything that needs a tree-wide sum walks\n * through this.\n */\nexport function flattenTurns(stats: AgentStats): TurnUsage[] {\n const out: TurnUsage[] = []\n collectTurns(stats, out)\n return out\n}\n\nfunction collectTurns(stats: AgentStats, out: TurnUsage[]): void {\n if (stats.turnUsage)\n out.push(...stats.turnUsage)\n if (stats.children) {\n for (const child of stats.children)\n collectTurns(child.stats, out)\n }\n}\n\n/**\n * Group cumulative usage by `TurnUsage.modelId`. Each entry sums the input,\n * output, cache, cost, and turn-count across every turn the tree attributed\n * to that model — naturally handling cross-model runs (vision-fallback,\n * model-shifted subagents, mixed-provider workflows).\n *\n * Turns missing `modelId` (mock providers, providers that don't echo a model\n * id) are bucketed under the literal string `'(unknown)'`.\n */\nexport function statsByModel(stats: AgentStats): Map<string, ModelUsage> {\n const out = new Map<string, ModelUsage>()\n for (const turn of flattenTurns(stats)) {\n const key = turn.modelId ?? '(unknown)'\n let entry = out.get(key)\n if (!entry) {\n entry = { input: 0, output: 0, cost: 0, cacheRead: 0, cacheCreation: 0, turns: 0 }\n out.set(key, entry)\n }\n entry.input += turn.input\n entry.output += turn.output\n entry.cost += turn.cost ?? 0\n entry.cacheRead += turn.cacheRead ?? 0\n entry.cacheCreation += turn.cacheCreation ?? 0\n entry.turns += 1\n }\n return out\n}\n"],"mappings":";;;;;;;AA+CA,SAAS,WAAW,GAA+B;CACjD,IAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,EAAE,IAAI,IAAI,GACtD,OAAO;CACT,OAAO;;AAGT,SAAS,UAAU,OAKjB;CACA,OAAO;EACL,OAAO,WAAW,MAAM,QAAQ;EAChC,QAAQ,WAAW,MAAM,SAAS;EAClC,WAAW,WAAW,MAAM,eAAe;EAC3C,eAAe,WAAW,MAAM,mBAAmB;EACpD;;;;;;;;;;;;;;;;;;;;AAwCH,SAAgB,iBAAiB,OAA+B;CAC9D,MAAM,EAAE,OAAO,QAAQ,WAAW,kBAAkB,UAAU,MAAM;CACpE,MAAM,aAAa,QAAQ,YAAY;CACvC,MAAM,aAAa,YAAY;CAI/B,OAAO,GAHQ,aAAa,IACxB,MAAM,WAAW,UAAU,WAAW,KACtC,MAAM,aACO,KAAK,OAAO;;;;;;;;;;;;AAa/B,SAAgB,aAAa,OAAgC;CAC3D,MAAM,MAAmB,EAAE;CAC3B,aAAa,OAAO,IAAI;CACxB,OAAO;;AAGT,SAAS,aAAa,OAAmB,KAAwB;CAC/D,IAAI,MAAM,WACR,IAAI,KAAK,GAAG,MAAM,UAAU;CAC9B,IAAI,MAAM,UACR,KAAK,MAAM,SAAS,MAAM,UACxB,aAAa,MAAM,OAAO,IAAI;;;;;;;;;;;AAapC,SAAgB,aAAa,OAA4C;CACvE,MAAM,sBAAM,IAAI,KAAyB;CACzC,KAAK,MAAM,QAAQ,aAAa,MAAM,EAAE;EACtC,MAAM,MAAM,KAAK,WAAW;EAC5B,IAAI,QAAQ,IAAI,IAAI,IAAI;EACxB,IAAI,CAAC,OAAO;GACV,QAAQ;IAAE,OAAO;IAAG,QAAQ;IAAG,MAAM;IAAG,WAAW;IAAG,eAAe;IAAG,OAAO;IAAG;GAClF,IAAI,IAAI,KAAK,MAAM;;EAErB,MAAM,SAAS,KAAK;EACpB,MAAM,UAAU,KAAK;EACrB,MAAM,QAAQ,KAAK,QAAQ;EAC3B,MAAM,aAAa,KAAK,aAAa;EACrC,MAAM,iBAAiB,KAAK,iBAAiB;EAC7C,MAAM,SAAS;;CAEjB,OAAO"}