zidane 5.13.26 → 5.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-CjAoILwj.js → acp-DgKEJH3P.js} +7 -7
- package/dist/{acp-CjAoILwj.js.map → acp-DgKEJH3P.js.map} +1 -1
- package/dist/acp-cli.js +11 -6
- package/dist/acp-cli.js.map +1 -1
- package/dist/acp.d.ts +2 -2
- package/dist/acp.js +1 -1
- package/dist/{tools-B32lZXLE.js → agent-B-cKanSC.js} +8035 -9459
- package/dist/agent-B-cKanSC.js.map +1 -0
- package/dist/{agent-DQIL5BFk.d.ts → agent-BAgOgS37.d.ts} +367 -208
- package/dist/agent-BAgOgS37.d.ts.map +1 -0
- package/dist/agent.d.ts +3 -0
- package/dist/agent.js +5 -0
- package/dist/anthropic-RGqsJlC8.js +807 -0
- package/dist/anthropic-RGqsJlC8.js.map +1 -0
- package/dist/{atomic-write-Bgtr5JPu.js → atomic-write-B7zr1ddF.js} +2 -2
- package/dist/{atomic-write-Bgtr5JPu.js.map → atomic-write-B7zr1ddF.js.map} +1 -1
- package/dist/{auth-DPrSP1YV.js → auth-C-ms5N2x.js} +33 -5
- package/dist/{auth-DPrSP1YV.js.map → auth-C-ms5N2x.js.map} +1 -1
- package/dist/cache-telemetry-BKAwsNxm.js +169 -0
- package/dist/cache-telemetry-BKAwsNxm.js.map +1 -0
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +1 -1
- package/dist/chat.d.ts +21 -7
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +30 -5
- package/dist/chat.js.map +1 -1
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/docker.js +0 -1
- package/dist/contexts/docker.js.map +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/errors-CkTvg97v.d.ts +203 -0
- package/dist/errors-CkTvg97v.d.ts.map +1 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +3 -0
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +2 -2
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +406 -1
- package/dist/headless.js.map +1 -0
- package/dist/{index-YY3vp2F2.d.ts → index-CLuLFFLa.d.ts} +88 -418
- package/dist/index-CLuLFFLa.d.ts.map +1 -0
- package/dist/{index-C5qw3pj7.d.ts → index-CgBIl2mX.d.ts} +2 -2
- package/dist/{index-C5qw3pj7.d.ts.map → index-CgBIl2mX.d.ts.map} +1 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +113 -167
- package/dist/index.js.map +1 -1
- package/dist/{logger-C2ngE8CY.d.ts → logger-CJ-lpKPy.d.ts} +2 -2
- package/dist/{logger-C2ngE8CY.d.ts.map → logger-CJ-lpKPy.d.ts.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/messages-YZQLKaz2.js +1157 -0
- package/dist/messages-YZQLKaz2.js.map +1 -0
- package/dist/oauth-8LqbeI_Q.js +292 -0
- package/dist/oauth-8LqbeI_Q.js.map +1 -0
- package/dist/{messages-Iy_vEycX.js → openai-compat-0Y7jcncn.js} +27 -1379
- package/dist/openai-compat-0Y7jcncn.js.map +1 -0
- package/dist/output/stream-json.d.ts +2 -2
- package/dist/output/stream-json.js +1 -1
- package/dist/output/terminal.d.ts +2 -2
- package/dist/{presets-B9nDwmaz.js → presets-Pr02ZNcy.js} +3 -2
- package/dist/{presets-B9nDwmaz.js.map → presets-Pr02ZNcy.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/prompt.d.ts +2 -0
- package/dist/prompt.js +210 -0
- package/dist/prompt.js.map +1 -0
- package/dist/providers/anthropic.d.ts +2 -0
- package/dist/providers/anthropic.js +2 -0
- package/dist/providers/arcee.d.ts +2 -0
- package/dist/providers/arcee.js +42 -0
- package/dist/providers/arcee.js.map +1 -0
- package/dist/providers/baseten.d.ts +2 -0
- package/dist/providers/baseten.js +66 -0
- package/dist/providers/baseten.js.map +1 -0
- package/dist/providers/cerebras.d.ts +2 -0
- package/dist/providers/cerebras.js +31 -0
- package/dist/providers/cerebras.js.map +1 -0
- package/dist/providers/local.d.ts +2 -0
- package/dist/providers/local.js +44 -0
- package/dist/providers/local.js.map +1 -0
- package/dist/providers/openai-compat.d.ts +2 -0
- package/dist/providers/openai-compat.js +2 -0
- package/dist/providers/openai.d.ts +2 -0
- package/dist/providers/openai.js +504 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +2 -0
- package/dist/providers/openrouter.js +52 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/xai.d.ts +2 -0
- package/dist/providers/xai.js +2 -0
- package/dist/providers-DgmdYGKm.js +168 -0
- package/dist/providers-DgmdYGKm.js.map +1 -0
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +10 -2
- package/dist/restate.d.ts +1 -1
- package/dist/run-summary-DAnBZnrt.d.ts +249 -0
- package/dist/run-summary-DAnBZnrt.d.ts.map +1 -0
- package/dist/run-summary-nD3IQ4ZQ.js +253 -0
- package/dist/run-summary-nD3IQ4ZQ.js.map +1 -0
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-CiA1_8SB.js → session-CqrAFAKJ.js} +2 -2
- package/dist/{session-CiA1_8SB.js.map → session-CqrAFAKJ.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/system-prompt-BlsdjcUi.d.ts +139 -0
- package/dist/system-prompt-BlsdjcUi.d.ts.map +1 -0
- package/dist/timeout-CpFm0jJ6.d.ts +10 -0
- package/dist/timeout-CpFm0jJ6.d.ts.map +1 -0
- package/dist/timeout-kGGSXagK.js +84 -0
- package/dist/timeout-kGGSXagK.js.map +1 -0
- package/dist/{tool-formatters-w5oaoCS4.d.ts → tool-formatters-Ch8oRpYR.d.ts} +2 -2
- package/dist/{tool-formatters-w5oaoCS4.d.ts.map → tool-formatters-Ch8oRpYR.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools-00_Nqazv.js +1689 -0
- package/dist/tools-00_Nqazv.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +2 -1
- package/dist/{transcript-anchors-BUtOd-qa.js → transcript-anchors-BUmvPnis.js} +10 -9
- package/dist/{transcript-anchors-BUtOd-qa.js.map → transcript-anchors-BUmvPnis.js.map} +1 -1
- package/dist/{transcript-anchors-BJ-ESWnm.d.ts → transcript-anchors-CL9UM_M4.d.ts} +4 -4
- package/dist/{transcript-anchors-BJ-ESWnm.d.ts.map → transcript-anchors-CL9UM_M4.d.ts.map} +1 -1
- package/dist/tui.d.ts +3 -3
- package/dist/tui.js +38 -13
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-BSSQHNq1.d.ts → turn-operations-Cj6v_ypW.d.ts} +3 -3
- package/dist/{turn-operations-BSSQHNq1.d.ts.map → turn-operations-Cj6v_ypW.d.ts.map} +1 -1
- package/dist/{turn-operations-B_XeuNp-.js → turn-operations-tG0vuBNc.js} +2 -2
- package/dist/{turn-operations-B_XeuNp-.js.map → turn-operations-tG0vuBNc.js.map} +1 -1
- package/dist/types-CyVGdbia.js.map +1 -1
- package/dist/types.d.ts +4 -3
- package/dist/xai-tPdbAGkq.js +379 -0
- package/dist/xai-tPdbAGkq.js.map +1 -0
- package/docs/ARCHITECTURE.md +4 -2
- package/docs/CHAT.md +4 -0
- package/docs/SKILL.md +31 -5
- package/package.json +63 -1
- package/dist/agent-DQIL5BFk.d.ts.map +0 -1
- package/dist/headless-2SH7dvoW.js +0 -656
- package/dist/headless-2SH7dvoW.js.map +0 -1
- package/dist/index-YY3vp2F2.d.ts.map +0 -1
- package/dist/messages-Iy_vEycX.js.map +0 -1
- package/dist/providers-BMw1D_lj.js +0 -2332
- package/dist/providers-BMw1D_lj.js.map +0 -1
- package/dist/tools-B32lZXLE.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages-YZQLKaz2.js","names":[],"sources":["../src/session/messages.ts"],"sourcesContent":["/**\n * Canonical SessionMessage format with converters from/to Anthropic and OpenAI-compat formats.\n */\n\nimport type { Provider } from '../providers'\nimport type { ResolveContentRef, SessionContentBlock, SessionMessage, ToolResultContent, ToolResultDocumentContent } from '../types'\nimport { fnv1aBase36 } from '../hash'\nimport { ASSISTANT_TOOL_CALLS_TAG, TOOL_RESULTS_TAG } from '../providers/openai-compat'\nimport { audioFormatFromMediaType, reconcileImageMediaType } from '../tools/media-sniff'\nimport { assertResolvedMediaBlock, documentBlockMarker } from '../types'\n\ntype ToolCallBlock = Extract<SessionContentBlock, { type: 'tool_call' }>\ninterface ContentRefBlock {\n type: 'image' | 'document'\n ref: string\n mediaType: string\n encoding?: 'base64' | 'text'\n name?: string\n}\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 * Build an Anthropic `image.source` block, reconciling the declared media\n * type against the actual magic bytes.\n *\n * Anthropic's API rejects the entire request when `media_type` disagrees\n * with the payload (`messages.N.content.K.image.source.base64: The image\n * was specified using the X media type, but the image appears to be a\n * Y image`). The upstream sources of `{type:'image'}` blocks each sniff\n * before producing the block (read_file / MCP / TUI paste), but this\n * wire-boundary reconciliation is a final safety net for any future code\n * path or host-supplied prompt part that slips through without sniffing.\n *\n * The reconciler is conservative: it overrides only when the magic bytes\n * positively identify a *different* known format. Unknown / too-short\n * payloads pass `mediaType` through unchanged.\n */\nfunction anthropicImageSource(mediaType: string, data: string): { type: 'base64', media_type: string, data: string } {\n return { type: 'base64' as const, media_type: reconcileImageMediaType(mediaType, data), data }\n}\n\nfunction anthropicDocumentBlocks(doc: Pick<ToolResultDocumentContent, 'mediaType' | 'data' | 'encoding' | 'name'>): AnthropicWireBlock[] {\n if (doc.encoding === 'base64' && doc.mediaType === 'application/pdf') {\n return [{\n type: 'document',\n source: { type: 'base64', media_type: 'application/pdf', data: doc.data },\n ...(doc.name ? { title: doc.name } : {}),\n }]\n }\n\n const header = doc.name\n ? `<attachment name=\"${doc.name}\" media_type=\"${doc.mediaType}\" encoding=\"${doc.encoding}\">`\n : `<attachment media_type=\"${doc.mediaType}\" encoding=\"${doc.encoding}\">`\n return [{ type: 'text', text: `${header}\\n${doc.data}\\n</attachment>` }]\n}\n\nfunction documentMarker(doc: ToolResultDocumentContent): string {\n return documentBlockMarker(doc, 'document attached')\n}\n\nfunction isContentRefBlock(block: SessionContentBlock | ToolResultContent): block is (SessionContentBlock | ToolResultContent) & ContentRefBlock {\n return (block.type === 'image' || block.type === 'document')\n && typeof (block as { ref?: unknown }).ref === 'string'\n}\n\nfunction unresolvedContentRefError(block: ContentRefBlock): Error {\n return new Error(`Cannot project content ref \"${block.ref}\" to provider wire messages without resolveContentRef.`)\n}\n\n/**\n * Coerce a tool-call id to Anthropic's wire constraint `^[a-zA-Z0-9_-]+$`.\n *\n * Sessions started under another provider (OpenAI-compat / OpenRouter /\n * Cerebras) or carrying MCP-namespaced calls can hold ids with `.`, `:`,\n * `/` or other characters that 400 the request the moment the user switches\n * to an Anthropic model (`messages.N.content.M.tool_use.id: String should\n * match pattern '^[a-zA-Z0-9_-]+$'`).\n *\n * The mapping is deterministic — every disallowed char becomes `_` — so a\n * `tool_use` and its paired `tool_result` (sanitized identically below) keep\n * matching ids and the pairing survives. When sanitization actually changed\n * the id, a short hash of the ORIGINAL id is appended so distinct inputs\n * that would collapse to the same replacement (`call.1` vs `call_1` vs\n * `call:1`) stay distinct on the wire. Empty ids get a stable placeholder\n * so the block stays well-formed.\n */\nconst ANTHROPIC_TOOL_ID_INVALID = /[^\\w-]/g\nfunction sanitizeAnthropicToolId(id: string): string {\n if (!id)\n return 'tool_0'\n // `replace` with a global regex is idempotent for already-valid ids and\n // avoids the stateful `lastIndex` pitfall of reusing a `/g` regex `.test()`.\n const sanitized = id.replace(ANTHROPIC_TOOL_ID_INVALID, '_')\n if (sanitized === id)\n return id\n // Append a short hash of the ORIGINAL id so distinct inputs that collapse to\n // the same replacement stay distinct, deterministically, so a `tool_use` and\n // its paired `tool_result` keep matching ids.\n return `${sanitized}_${fnv1aBase36(id)}`\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 if (b.type === 'document') {\n assertResolvedMediaBlock(b, 'Anthropic wire messages')\n return { type: 'text' as const, text: documentMarker(b) }\n }\n if (b.type === 'audio')\n return { type: 'text' as const, text: `[audio omitted: ${b.mediaType}]` }\n if (b.type === 'video')\n return { type: 'text' as const, text: `[video omitted: ${b.mediaType}]` }\n assertResolvedMediaBlock(b, 'Anthropic wire messages')\n return {\n type: 'image' as const,\n source: anthropicImageSource(b.mediaType, 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 === 'document') {\n const source = b.source as Record<string, unknown>\n if (source?.type === 'base64' && typeof source.media_type === 'string' && typeof source.data === 'string') {\n content.push({\n type: 'document',\n mediaType: source.media_type,\n data: source.data,\n encoding: 'base64',\n ...(typeof b.title === 'string' ? { name: b.title } : {}),\n })\n }\n else if (source?.type === 'text' && typeof source.media_type === 'string' && typeof source.data === 'string') {\n content.push({\n type: 'document',\n mediaType: source.media_type,\n data: source.data,\n encoding: 'text',\n ...(typeof b.title === 'string' ? { name: b.title } : {}),\n })\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 else if (b.type === 'server_tool_use') {\n // Anthropic server-tool invocation (e.g. `web_search_20250305`). Captured\n // so multi-turn citation context survives — Anthropic's docs require\n // these blocks to be echoed back on follow-up turns.\n content.push({\n type: 'server_tool_use',\n producer: 'anthropic',\n id: (b.id as string | undefined) ?? '',\n name: (b.name as string | undefined) ?? '',\n input: (b.input as Record<string, unknown> | undefined) ?? {},\n })\n }\n else if (b.type === 'web_search_tool_result') {\n // Pairs with `server_tool_use`. `content` is opaque (search hit array\n // with `encrypted_content` / `encrypted_index`) — store unchanged.\n content.push({\n type: 'server_tool_result',\n producer: 'anthropic',\n toolUseId: (b.tool_use_id as string | undefined) ?? '',\n toolName: 'web_search',\n content: b.content,\n })\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 // Malformed argument JSON (truncated stream, hand-edited history)\n // must not kill the whole conversion — fall back to `{}`, same\n // degradation `consumeSSE` applies to truncated tool-call args.\n let input: Record<string, unknown> = {}\n if (typeof rawArgs === 'string') {\n try {\n input = JSON.parse(rawArgs) as Record<string, unknown>\n }\n catch {\n input = {}\n }\n }\n else if (rawArgs && typeof rawArgs === 'object') {\n input = 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, text?: 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 // Sibling text preserved by `toOpenAI` alongside the results.\n if (typeof tagged.text === 'string' && tagged.text)\n content.push({ type: 'text', text: tagged.text })\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: 'document', source: { type: 'base64' | 'text', media_type: string, data: string }, title?: 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 | { type: 'server_tool_use', id: string, name: string, input: Record<string, unknown> }\n | { type: 'web_search_tool_result', tool_use_id: string, content: unknown }\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 provider-bound reasoning\n // state; 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 .flatMap((block): AnthropicWireBlock[] => {\n switch (block.type) {\n case 'text':\n return [{ type: 'text', text: block.text }]\n case 'image':\n assertResolvedMediaBlock(block, 'Anthropic wire messages')\n return [{ type: 'image', source: anthropicImageSource(block.mediaType, block.data) }]\n case 'audio':\n // Anthropic has no audio input block. The prompt path throws before a\n // top-level audio block can reach here; this is the defensive marker\n // for any other code path (e.g. a cross-provider session replay).\n return [{ type: 'text', text: `[audio omitted: ${block.mediaType}]` }]\n case 'video':\n return [{ type: 'text', text: `[video omitted: ${block.mediaType}]` }]\n case 'document':\n assertResolvedMediaBlock(block, 'Anthropic wire messages')\n return anthropicDocumentBlocks(block)\n case 'tool_call':\n return [{ type: 'tool_use', id: sanitizeAnthropicToolId(block.id), name: block.name, input: block.input }]\n case 'tool_result': {\n const out: AnthropicWireBlock = {\n type: 'tool_result',\n tool_use_id: sanitizeAnthropicToolId(block.callId),\n content: encodeAnthropicToolResultContent(block.output),\n }\n if (block.isError)\n out.is_error = true\n const docs = typeof block.output === 'string'\n ? []\n : block.output.filter((part): part is ToolResultDocumentContent => part.type === 'document' && typeof part.data === 'string')\n return [out, ...docs.flatMap(anthropicDocumentBlocks)]\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 case 'server_tool_use':\n // Anthropic-produced server-tool invocation echoed back so the\n // model can resume the citation chain. Producer is always\n // `anthropic` (other providers don't emit this variant).\n return [{ type: 'server_tool_use', id: sanitizeAnthropicToolId(block.id), name: block.name, input: block.input }]\n case 'server_tool_result':\n return [{ type: 'web_search_tool_result', tool_use_id: sanitizeAnthropicToolId(block.toolUseId), content: block.content }]\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 const audioBlocks = msg.content.filter(b => b.type === 'audio')\n const videoBlocks = msg.content.filter(b => b.type === 'video')\n const documentBlocks = msg.content.filter(b => b.type === 'document')\n\n // Tool results → tagged format. Sibling text blocks (a user follow-up\n // typed while tools ran) ride along on `text` — the early return here\n // used to silently drop them, breaking round-trip fidelity vs. the wire\n // shaper (`toOAIMessages`), which preserves them.\n if (toolResults.length > 0) {\n const siblingText = textBlocks.map(b => b.text).join('\\n')\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 ...(siblingText ? { text: siblingText } : {}),\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/audio/video/documents → multimodal array. Documents have no portable\n // OpenAI Chat Completions block here, so preserve them as markers.\n if (imageBlocks.length > 0 || audioBlocks.length > 0 || videoBlocks.length > 0 || documentBlocks.length > 0) {\n const parts: unknown[] = []\n for (const img of imageBlocks) {\n assertResolvedMediaBlock(img, 'OpenAI-compatible wire messages')\n parts.push({\n type: 'image_url',\n image_url: { url: `data:${img.mediaType};base64,${img.data}` },\n })\n }\n for (const b of audioBlocks)\n parts.push({ type: 'input_audio', input_audio: { data: b.data, format: audioFormatFromMediaType(b.mediaType) } })\n for (const b of videoBlocks)\n parts.push({ type: 'video_url', video_url: { url: `data:${b.mediaType};base64,${b.data}` } })\n for (const b of documentBlocks) {\n assertResolvedMediaBlock(b, 'OpenAI-compatible wire messages')\n parts.push({ type: 'text', text: documentMarker(b) })\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 | 'duplicate-tool-use-remint' // persist-time: fresh turn reused an existing tool_call id → re-minted before persist (loop-side, not part of ensureToolResultPairing)\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\n// ---------------------------------------------------------------------------\n// remintDuplicateToolCallIds — persist-time duplicate-id healing\n// ---------------------------------------------------------------------------\n\nexport interface RemintResult {\n /** Tool calls with duplicate ids replaced. Same reference when unchanged. */\n toolCalls: Array<{ id: string, name: string, input: Record<string, unknown> }>\n /** Assistant content with matching `tool_call` block ids rewritten. Same reference when unchanged. */\n content: SessionContentBlock[]\n /** One entry per re-minted occurrence, in emission order. */\n reminted: Array<{ oldId: string, newId: string }>\n}\n\n/**\n * Re-mint tool-call ids that collide with ids already present in the\n * transcript (or with an earlier sibling in the same turn), BEFORE the turn\n * is persisted and its tools dispatched.\n *\n * Why this exists: some providers/serving stacks emit non-globally-unique\n * tool_use ids (per-turn counters like `call_0`, retry-replay duplicates,\n * Kimi-K2.6 on Fireworks). The wire-time pairing pass\n * ({@link ensureToolResultPairing}) can only STRIP such duplicates — which\n * erases the model's tool calls and results from the wire view, stalls\n * progress, and re-accumulates repairs quadratically because the persisted\n * session keeps the corrupted ids. Re-minting at persist time keeps the\n * pairing intact instead: the call, its execution, and its tool_result all\n * carry the fresh id, so the pairing pass has nothing to strip on any later\n * turn.\n *\n * Fresh ids are `${oldId}_r{n}` with `n` bumped until unique — deterministic\n * shape, Anthropic-wire-safe charset (given a safe input id), and visibly\n * traceable back to the original.\n *\n * Alignment contract: `toolCalls` and the `tool_call` blocks inside\n * `content` are two views of the same emission and MUST be in the same\n * order (true for every provider — both are built from one source array).\n * Occurrences are matched positionally per old id so intra-turn duplicates\n * map to distinct fresh ids on both views.\n *\n * Pure: returns the input references unchanged when no id collides.\n */\nexport function remintDuplicateToolCallIds(\n toolCalls: ReadonlyArray<{ id: string, name: string, input: Record<string, unknown> }>,\n content: readonly SessionContentBlock[],\n priorIds: ReadonlySet<string>,\n): RemintResult {\n const seen = new Set(priorIds)\n const reminted: Array<{ oldId: string, newId: string }> = []\n // Final id per occurrence, queued per old id so the content walk below\n // can re-associate positionally (handles intra-turn duplicates).\n const finalIdQueues = new Map<string, string[]>()\n\n let changed = false\n const outCalls = toolCalls.map((tc) => {\n let finalId = tc.id\n if (seen.has(tc.id)) {\n let n = 1\n while (seen.has(`${tc.id}_r${n}`))\n n++\n finalId = `${tc.id}_r${n}`\n reminted.push({ oldId: tc.id, newId: finalId })\n changed = true\n }\n seen.add(finalId)\n const queue = finalIdQueues.get(tc.id)\n if (queue)\n queue.push(finalId)\n else\n finalIdQueues.set(tc.id, [finalId])\n return finalId === tc.id ? tc : { ...tc, id: finalId }\n })\n\n if (!changed) {\n return {\n toolCalls: toolCalls as RemintResult['toolCalls'],\n content: content as SessionContentBlock[],\n reminted,\n }\n }\n\n const outContent = content.map((block) => {\n if (block.type !== 'tool_call')\n return block\n const queue = finalIdQueues.get(block.id)\n const finalId = queue?.shift()\n if (finalId === undefined || finalId === block.id)\n return block\n return { ...block, id: finalId }\n })\n\n return { toolCalls: outCalls as RemintResult['toolCalls'], content: outContent, reminted }\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 * Resolve persisted content references into provider-native payload bytes.\n * The returned data is wire-only and must not be written back to turns.\n */\n resolveContentRef?: ResolveContentRef\n}\n\nasync function resolveRefContentBlock(\n block: SessionContentBlock,\n resolveContentRef: ResolveContentRef | undefined,\n): Promise<{ block: SessionContentBlock, changed: boolean }> {\n if (isContentRefBlock(block)) {\n if (!resolveContentRef)\n throw unresolvedContentRefError(block)\n const { data } = await resolveContentRef({\n type: block.type,\n ref: block.ref,\n mediaType: block.mediaType,\n ...(block.type === 'document' ? { encoding: block.encoding } : {}),\n ...(block.name ? { name: block.name } : {}),\n })\n if (block.type === 'image') {\n return {\n block: { type: 'image', mediaType: block.mediaType, data, ...(block.name ? { name: block.name } : {}) },\n changed: true,\n }\n }\n return {\n block: { type: 'document', mediaType: block.mediaType, encoding: block.encoding, data, ...(block.name ? { name: block.name } : {}) },\n changed: true,\n }\n }\n\n if (block.type !== 'tool_result' || typeof block.output === 'string')\n return { block, changed: false }\n\n const resolved = await resolveToolResultContent(block.output, resolveContentRef)\n if (!resolved.changed)\n return { block, changed: false }\n return { block: { ...block, output: resolved.output }, changed: true }\n}\n\nasync function resolveToolResultContent(\n output: ToolResultContent[],\n resolveContentRef: ResolveContentRef | undefined,\n): Promise<{ output: ToolResultContent[], changed: boolean }> {\n let changed = false\n const next: ToolResultContent[] = []\n for (const part of output) {\n if (!isContentRefBlock(part)) {\n next.push(part)\n continue\n }\n if (!resolveContentRef)\n throw unresolvedContentRefError(part)\n const { data } = await resolveContentRef({\n type: part.type,\n ref: part.ref,\n mediaType: part.mediaType,\n ...(part.type === 'document' ? { encoding: part.encoding } : {}),\n ...(part.name ? { name: part.name } : {}),\n })\n changed = true\n if (part.type === 'image') {\n next.push({ type: 'image', mediaType: part.mediaType, data, ...(part.name ? { name: part.name } : {}) })\n }\n else {\n next.push({ type: 'document', mediaType: part.mediaType, encoding: part.encoding, data, ...(part.name ? { name: part.name } : {}) })\n }\n }\n return { output: changed ? next : output, changed }\n}\n\n/**\n * Resolve image/document ref blocks into data blocks for provider wire use.\n * This is intentionally wire-only: callers must never persist the returned\n * messages back into `session.turns`.\n *\n * Returns the input array reference unchanged when no block carried a ref —\n * the common case (most turns hold no refs, and re-running on already-resolved\n * output is a no-op), so the multi-pass wire pipeline doesn't pay per-message\n * copies for nothing.\n */\nexport async function resolveContentRefsInMessages(\n messages: readonly SessionMessage[],\n options: { resolveContentRef?: ResolveContentRef } = {},\n): Promise<SessionMessage[]> {\n let anyChanged = false\n const out: SessionMessage[] = []\n for (const msg of messages) {\n let changed = false\n const content: SessionContentBlock[] = []\n for (const block of msg.content) {\n const resolved = await resolveRefContentBlock(block, options.resolveContentRef)\n if (resolved.changed)\n changed = true\n content.push(resolved.block)\n }\n if (changed)\n anyChanged = true\n out.push(changed ? { ...msg, content } : msg)\n }\n return anyChanged ? out : (messages as SessionMessage[])\n}\n\nfunction finalizeWireMessages(messages: SessionMessage[], options: ToWireMessagesOptions): SessionMessage[] {\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\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 async function toWireMessages(\n turns: readonly { role: 'user' | 'assistant' | 'system', content: SessionContentBlock[] }[],\n options: ToWireMessagesOptions = {},\n): Promise<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 resolved = await resolveContentRefsInMessages(messages, { resolveContentRef: options.resolveContentRef })\n return finalizeWireMessages(resolved, options)\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":";;;;;;;;;;;;;AAgCA,SAAS,iCAAiC,SAAgD;CACxF,IAAI,OAAO,YAAY,UACrB,OAAO;CAET,IAAI,CAAC,MAAM,QAAQ,OAAO,GACxB,OAAO,KAAK,UAAU,OAAO;CAE/B,MAAM,SAA8B,CAAC;CACrC,KAAK,MAAM,OAAO,SAAS;EACzB,IAAI,CAAC,OAAO,OAAO,QAAQ,UACzB;EACF,MAAM,IAAI;EACV,IAAI,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,UAAU;GACnD,OAAO,KAAK;IAAE,MAAM;IAAQ,MAAM,EAAE;GAAK,CAAC;GAC1C;EACF;EACA,IAAI,EAAE,SAAS,WAAW,EAAE,UAAU,OAAO,EAAE,WAAW,UAAU;GAClE,MAAM,MAAM,EAAE;GACd,IAAI,IAAI,SAAS,YAAY,OAAO,IAAI,SAAS,YAAY,OAAO,IAAI,eAAe,UAAU;IAC/F,OAAO,KAAK;KAAE,MAAM;KAAS,WAAW,IAAI;KAAY,MAAM,IAAI;IAAK,CAAC;IACxE;GACF;EACF;EAEA,OAAO,KAAK;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,GAAG;EAAE,CAAC;CACzD;CAEA,IAAI,OAAO,WAAW,GACpB,OAAO;CAGT,IAAI,CADe,OAAO,MAAK,MAAK,EAAE,SAAS,MACjC,GACZ,OAAO,OAAO,KAAI,MAAM,EAAqC,IAAI,EAAE,KAAK,IAAI;CAE9E,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAS,qBAAqB,WAAmB,MAAoE;CACnH,OAAO;EAAE,MAAM;EAAmB,YAAY,wBAAwB,WAAW,IAAI;EAAG;CAAK;AAC/F;AAEA,SAAS,wBAAwB,KAAwG;CACvI,IAAI,IAAI,aAAa,YAAY,IAAI,cAAc,mBACjD,OAAO,CAAC;EACN,MAAM;EACN,QAAQ;GAAE,MAAM;GAAU,YAAY;GAAmB,MAAM,IAAI;EAAK;EACxE,GAAI,IAAI,OAAO,EAAE,OAAO,IAAI,KAAK,IAAI,CAAC;CACxC,CAAC;CAMH,OAAO,CAAC;EAAE,MAAM;EAAQ,MAAM,GAHf,IAAI,OACf,qBAAqB,IAAI,KAAK,gBAAgB,IAAI,UAAU,cAAc,IAAI,SAAS,MACvF,2BAA2B,IAAI,UAAU,cAAc,IAAI,SAAS,IAChC,IAAI,IAAI,KAAK;CAAiB,CAAC;AACzE;AAEA,SAAS,eAAe,KAAwC;CAC9D,OAAO,oBAAoB,KAAK,mBAAmB;AACrD;AAEA,SAAS,kBAAkB,OAAsH;CAC/I,QAAQ,MAAM,SAAS,WAAW,MAAM,SAAS,eAC5C,OAAQ,MAA4B,QAAQ;AACnD;AAEA,SAAS,0BAA0B,OAA+B;CAChE,uBAAO,IAAI,MAAM,+BAA+B,MAAM,IAAI,uDAAuD;AACnH;;;;;;;;;;;;;;;;;;AAmBA,MAAM,4BAA4B;AAClC,SAAS,wBAAwB,IAAoB;CACnD,IAAI,CAAC,IACH,OAAO;CAGT,MAAM,YAAY,GAAG,QAAQ,2BAA2B,GAAG;CAC3D,IAAI,cAAc,IAChB,OAAO;CAIT,OAAO,GAAG,UAAU,GAAG,YAAY,EAAE;AACvC;;;;;AAMA,SAAS,iCACP,QACkI;CAClI,IAAI,OAAO,WAAW,UACpB,OAAO;CACT,OAAO,OAAO,KAAK,MAAM;EACvB,IAAI,EAAE,SAAS,QACb,OAAO;GAAE,MAAM;GAAiB,MAAM,EAAE;EAAK;EAC/C,IAAI,EAAE,SAAS,YAAY;GACzB,yBAAyB,GAAG,yBAAyB;GACrD,OAAO;IAAE,MAAM;IAAiB,MAAM,eAAe,CAAC;GAAE;EAC1D;EACA,IAAI,EAAE,SAAS,SACb,OAAO;GAAE,MAAM;GAAiB,MAAM,mBAAmB,EAAE,UAAU;EAAG;EAC1E,IAAI,EAAE,SAAS,SACb,OAAO;GAAE,MAAM;GAAiB,MAAM,mBAAmB,EAAE,UAAU;EAAG;EAC1E,yBAAyB,GAAG,yBAAyB;EACrD,OAAO;GACL,MAAM;GACN,QAAQ,qBAAqB,EAAE,WAAW,EAAE,IAAI;EAClD;CACF,CAAC;AACH;AAMA,SAAgB,cAAc,KAAyD;CACrF,MAAM,OAAO,IAAI;CACjB,MAAM,UAAiC,CAAC;CAExC,IAAI,OAAO,IAAI,YAAY,UAAU;EACnC,QAAQ,KAAK;GAAE,MAAM;GAAQ,MAAM,IAAI;EAAQ,CAAC;EAChD,OAAO;GAAE;GAAM;EAAQ;CACzB;CAEA,IAAI,MAAM,QAAQ,IAAI,OAAO,GAC3B,KAAK,MAAM,SAAS,IAAI,SAAS;EAC/B,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B;EACF,MAAM,IAAI;EAEV,IAAI,EAAE,SAAS,QACb,QAAQ,KAAK;GAAE,MAAM;GAAQ,MAAM,EAAE;EAAe,CAAC;OAElD,IAAI,EAAE,SAAS,SAAS;GAC3B,MAAM,SAAS,EAAE;GACjB,IAAI,QAAQ,SAAS,UACnB,QAAQ,KAAK;IAAE,MAAM;IAAS,WAAW,OAAO;IAAsB,MAAM,OAAO;GAAe,CAAC;EAEvG,OACK,IAAI,EAAE,SAAS,YAAY;GAC9B,MAAM,SAAS,EAAE;GACjB,IAAI,QAAQ,SAAS,YAAY,OAAO,OAAO,eAAe,YAAY,OAAO,OAAO,SAAS,UAC/F,QAAQ,KAAK;IACX,MAAM;IACN,WAAW,OAAO;IAClB,MAAM,OAAO;IACb,UAAU;IACV,GAAI,OAAO,EAAE,UAAU,WAAW,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;GACzD,CAAC;QAEE,IAAI,QAAQ,SAAS,UAAU,OAAO,OAAO,eAAe,YAAY,OAAO,OAAO,SAAS,UAClG,QAAQ,KAAK;IACX,MAAM;IACN,WAAW,OAAO;IAClB,MAAM,OAAO;IACb,UAAU;IACV,GAAI,OAAO,EAAE,UAAU,WAAW,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;GACzD,CAAC;EAEL,OACK,IAAI,EAAE,SAAS,YAClB,QAAQ,KAAK;GAAE,MAAM;GAAa,IAAI,EAAE;GAAc,MAAM,EAAE;GAAgB,OAAO,EAAE;EAAiC,CAAC;OAEtH,IAAI,EAAE,SAAS,eAAe;GACjC,MAAM,SAAS,iCAAiC,EAAE,OAAO;GACzD,MAAM,QAA+D;IACnE,MAAM;IACN,QAAQ,EAAE;IACV;GACF;GACA,IAAI,EAAE,aAAa,MACjB,MAAM,UAAU;GAClB,QAAQ,KAAK,KAAK;EACpB,OACK,IAAI,EAAE,SAAS,YAAY;GAC9B,MAAM,QAA4D;IAChE,MAAM;IACN,MAAO,EAAE,YAAmC;GAC9C;GACA,IAAI,OAAO,EAAE,cAAc,UAAU;IACnC,MAAM,YAAY,EAAE;IAEpB,MAAM,oBAAoB;GAC5B;GACA,QAAQ,KAAK,KAAK;EACpB,OACK,IAAI,EAAE,SAAS,qBAGlB,QAAQ,KAAK;GAAE,MAAM;GAAqB,MAAO,EAAE,QAA+B;EAAG,CAAC;OAEnF,IAAI,EAAE,SAAS,mBAIlB,QAAQ,KAAK;GACX,MAAM;GACN,UAAU;GACV,IAAK,EAAE,MAA6B;GACpC,MAAO,EAAE,QAA+B;GACxC,OAAQ,EAAE,SAAiD,CAAC;EAC9D,CAAC;OAEE,IAAI,EAAE,SAAS,0BAGlB,QAAQ,KAAK;GACX,MAAM;GACN,UAAU;GACV,WAAY,EAAE,eAAsC;GACpD,UAAU;GACV,SAAS,EAAE;EACb,CAAC;CAGL;CAGF,OAAO;EAAE;EAAM;CAAQ;AACzB;AAMA,SAAgB,WAAW,KAAyD;CAClF,MAAM,OAAO,IAAI;CACjB,MAAM,UAAiC,CAAC;CACxC,MAAM,IAAa,IAAI;CAEvB,IAAI,KAAK,MACP,OAAO;EAAE;EAAM;CAAQ;CAGzB,IAAI,OAAO,MAAM,UAAU;EACzB,QAAQ,KAAK;GAAE,MAAM;GAAQ,MAAM;EAAE,CAAC;EACtC,OAAO;GAAE;GAAM;EAAQ;CACzB;CAEA,IAAI,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,KAAM,EAAyB,SAAA,2BAAmC;EAC7G,MAAM,SAAS;EACf,IAAI,OAAO,OAAO,SAAS,YAAY,OAAO,MAC5C,QAAQ,KAAK;GAAE,MAAM;GAAQ,MAAM,OAAO;EAAK,CAAC;EAElD,IAAI,MAAM,QAAQ,OAAO,UAAU,GACjC,KAAK,MAAM,OAAO,OAAO,YAAY;GACnC,IAAI,CAAC,OAAO,OAAO,QAAQ,UACzB;GACF,MAAM,KAAK;GACX,MAAM,UAAU,GAAG,UAAU;GAI7B,IAAI,QAAiC,CAAC;GACtC,IAAI,OAAO,YAAY,UACrB,IAAI;IACF,QAAQ,KAAK,MAAM,OAAO;GAC5B,QACM;IACJ,QAAQ,CAAC;GACX;QAEG,IAAI,WAAW,OAAO,YAAY,UACrC,QAAQ;GAEV,QAAQ,KAAK;IAAE,MAAM;IAAa,IAAI,GAAG,MAAM;IAAI,MAAM,GAAG,UAAU,QAAQ;IAAI;GAAM,CAAC;EAC3F;EAEF,OAAO;GAAE;GAAM;EAAQ;CACzB;CAEA,IAAI,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,KAAM,EAAyB,SAAA,2BAA2B;EACrG,MAAM,SAAS;EACf,IAAI,MAAM,QAAQ,OAAO,OAAO,GAC9B,KAAK,MAAM,OAAO,OAAO,SAAS;GAChC,IAAI,CAAC,OAAO,OAAO,QAAQ,UACzB;GACF,MAAM,IAAI;GACV,QAAQ,KAAK;IAAE,MAAM;IAAe,QAAQ,EAAE,gBAAgB;IAAI,QAAQ,EAAE,WAAW;GAAG,CAAC;EAC7F;EAGF,IAAI,OAAO,OAAO,SAAS,YAAY,OAAO,MAC5C,QAAQ,KAAK;GAAE,MAAM;GAAQ,MAAM,OAAO;EAAK,CAAC;EAClD,OAAO;GAAE;GAAM;EAAQ;CACzB;CAEA,IAAI,MAAM,QAAQ,CAAC,GAAG;EACpB,KAAK,MAAM,SAAS,GAAG;GACrB,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B;GACF,MAAM,IAAI;GAEV,IAAI,EAAE,SAAS,QACb,QAAQ,KAAK;IAAE,MAAM;IAAQ,MAAM,EAAE;GAAe,CAAC;QAElD,IAAI,EAAE,SAAS,aAAa;IAC/B,MAAM,WAAY,EAAE,WAAuC;IAC3D,IAAI,UAAU,WAAW,OAAO,GAAG;KACjC,MAAM,CAAC,MAAM,QAAQ,SAAS,MAAM,CAAC,EAAE,MAAM,KAAK,CAAC;KACnD,MAAM,YAAY,KAAK,QAAQ,WAAW,EAAE;KAC5C,QAAQ,KAAK;MAAE,MAAM;MAAS;MAAW;KAAK,CAAC;IACjD;GACF;EACF;EACA,OAAO;GAAE;GAAM;EAAQ;CACzB;CAEA,OAAO;EAAE;EAAM;CAAQ;AACzB;AAiBA,SAAgB,YAAY,KAAyD;CACnF,MAAM,SAA+B,IAAI,QAkBtC,QAAQ,MAAM;EACb,IAAI,EAAE,SAAS,sBACb,OAAO;EACT,IAAI,EAAE,SAAS,YACb,OAAO;EACT,IAAI,EAAE,sBAAsB,UAC1B,OAAO;EACT,IAAI,CAAC,EAAE,WACL,OAAO;EACT,OAAO;CACT,CAAC,EACA,SAAS,UAAgC;EACxC,QAAQ,MAAM,MAAd;GACE,KAAK,QACH,OAAO,CAAC;IAAE,MAAM;IAAQ,MAAM,MAAM;GAAK,CAAC;GAC5C,KAAK;IACH,yBAAyB,OAAO,yBAAyB;IACzD,OAAO,CAAC;KAAE,MAAM;KAAS,QAAQ,qBAAqB,MAAM,WAAW,MAAM,IAAI;IAAE,CAAC;GACtF,KAAK,SAIH,OAAO,CAAC;IAAE,MAAM;IAAQ,MAAM,mBAAmB,MAAM,UAAU;GAAG,CAAC;GACvE,KAAK,SACH,OAAO,CAAC;IAAE,MAAM;IAAQ,MAAM,mBAAmB,MAAM,UAAU;GAAG,CAAC;GACvE,KAAK;IACH,yBAAyB,OAAO,yBAAyB;IACzD,OAAO,wBAAwB,KAAK;GACtC,KAAK,aACH,OAAO,CAAC;IAAE,MAAM;IAAY,IAAI,wBAAwB,MAAM,EAAE;IAAG,MAAM,MAAM;IAAM,OAAO,MAAM;GAAM,CAAC;GAC3G,KAAK,eAAe;IAClB,MAAM,MAA0B;KAC9B,MAAM;KACN,aAAa,wBAAwB,MAAM,MAAM;KACjD,SAAS,iCAAiC,MAAM,MAAM;IACxD;IACA,IAAI,MAAM,SACR,IAAI,WAAW;IAIjB,OAAO,CAAC,KAAK,IAHA,OAAO,MAAM,WAAW,WACjC,CAAC,IACD,MAAM,OAAO,QAAQ,SAA4C,KAAK,SAAS,cAAc,OAAO,KAAK,SAAS,QAAQ,GACzG,QAAQ,uBAAuB,CAAC;GACvD;GACA,KAAK,YAIH,OAAO,CAAC;IAAE,MAAM;IAAY,UAAU,MAAM;IAAM,WAAW,MAAM,aAAa;GAAG,CAAC;GACtF,KAAK,qBACH,OAAO,CAAC;IAAE,MAAM;IAAqB,MAAM,MAAM;GAAK,CAAC;GACzD,KAAK,mBAIH,OAAO,CAAC;IAAE,MAAM;IAAmB,IAAI,wBAAwB,MAAM,EAAE;IAAG,MAAM,MAAM;IAAM,OAAO,MAAM;GAAM,CAAC;GAClH,KAAK,sBACH,OAAO,CAAC;IAAE,MAAM;IAA0B,aAAa,wBAAwB,MAAM,SAAS;IAAG,SAAS,MAAM;GAAQ,CAAC;GAC3H,SACE,OAAO,CAAC;IAAE,MAAM;IAAQ,MAAM;GAAG,CAAC;EACtC;CACF,CAAC;CAGH,MAAM,QAAQ,OAAO;CACrB,IAAI,OAAO,WAAW,KAAK,MAAM,SAAS,QACxC,OAAO;EAAE,MAAM,IAAI;EAAM,SAAS,MAAM;CAAK;CAE/C,OAAO;EAAE,MAAM,IAAI;EAAM,SAAS;CAAO;AAC3C;AAMA,SAAgB,SAAS,KAAyD;CAChF,MAAM,YAAY,IAAI,QAAQ,QAAO,MAAK,EAAE,SAAS,WAAW;CAChE,MAAM,cAAc,IAAI,QAAQ,QAAO,MAAK,EAAE,SAAS,aAAa;CACpE,MAAM,aAAa,IAAI,QAAQ,QAAO,MAAK,EAAE,SAAS,MAAM;CAC5D,MAAM,cAAc,IAAI,QAAQ,QAAO,MAAK,EAAE,SAAS,OAAO;CAC9D,MAAM,cAAc,IAAI,QAAQ,QAAO,MAAK,EAAE,SAAS,OAAO;CAC9D,MAAM,cAAc,IAAI,QAAQ,QAAO,MAAK,EAAE,SAAS,OAAO;CAC9D,MAAM,iBAAiB,IAAI,QAAQ,QAAO,MAAK,EAAE,SAAS,UAAU;CAMpE,IAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,cAAc,WAAW,KAAI,MAAK,EAAE,IAAI,EAAE,KAAK,IAAI;EACzD,OAAO;GACL,MAAM,IAAI;GACV,SAAS;IACP,MAAM;IACN,SAAS,YAAY,KAAI,QAAO;KAAE,cAAc,GAAG;KAAQ,SAAS,GAAG;IAAO,EAAE;IAChF,GAAI,cAAc,EAAE,MAAM,YAAY,IAAI,CAAC;GAC7C;EACF;CACF;CAGA,IAAI,UAAU,SAAS,GAAG;EACxB,MAAM,cAAc,WAAW,SAAS,IAAI,WAAW,GAAG,OAAO;EACjE,OAAO;GACL,MAAM,IAAI;GACV,SAAS;IACP,MAAM;IACN,MAAM;IACN,YAAY,UAAU,KAAI,QAAO;KAC/B,IAAI,GAAG;KACP,MAAM;KACN,UAAU;MAAE,MAAM,GAAG;MAAM,WAAW,KAAK,UAAU,GAAG,KAAK;KAAE;IACjE,EAAE;GACJ;EACF;CACF;CAIA,IAAI,YAAY,SAAS,KAAK,YAAY,SAAS,KAAK,YAAY,SAAS,KAAK,eAAe,SAAS,GAAG;EAC3G,MAAM,QAAmB,CAAC;EAC1B,KAAK,MAAM,OAAO,aAAa;GAC7B,yBAAyB,KAAK,iCAAiC;GAC/D,MAAM,KAAK;IACT,MAAM;IACN,WAAW,EAAE,KAAK,QAAQ,IAAI,UAAU,UAAU,IAAI,OAAO;GAC/D,CAAC;EACH;EACA,KAAK,MAAM,KAAK,aACd,MAAM,KAAK;GAAE,MAAM;GAAe,aAAa;IAAE,MAAM,EAAE;IAAM,QAAQ,yBAAyB,EAAE,SAAS;GAAE;EAAE,CAAC;EAClH,KAAK,MAAM,KAAK,aACd,MAAM,KAAK;GAAE,MAAM;GAAa,WAAW,EAAE,KAAK,QAAQ,EAAE,UAAU,UAAU,EAAE,OAAO;EAAE,CAAC;EAC9F,KAAK,MAAM,KAAK,gBAAgB;GAC9B,yBAAyB,GAAG,iCAAiC;GAC7D,MAAM,KAAK;IAAE,MAAM;IAAQ,MAAM,eAAe,CAAC;GAAE,CAAC;EACtD;EACA,KAAK,MAAM,KAAK,YACd,MAAM,KAAK;GAAE,MAAM;GAAQ,MAAM,EAAE;EAAK,CAAC;EAC3C,OAAO;GAAE,MAAM,IAAI;GAAM,SAAS;EAAM;CAC1C;CAGA,IAAI,WAAW,WAAW,GACxB,OAAO;EAAE,MAAM,IAAI;EAAM,SAAS,WAAW,GAAG;CAAK;CAGvD,IAAI,WAAW,SAAS,GACtB,OAAO;EAAE,MAAM,IAAI;EAAM,SAAS,WAAW,KAAI,OAAM;GAAE,MAAM;GAAQ,MAAM,EAAE;EAAK,EAAE;CAAE;CAE1F,OAAO;EAAE,MAAM,IAAI;EAAM,SAAS;CAAK;AACzC;;;;;;;;AAiBA,MAAa,oCAAoC;;;;;;;;AASjD,MAAa,8BAA8B;;;;;;;AAQ3C,MAAa,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+E3C,SAAgB,wBACd,UACA,UAA0C,CAAC,GACzB;CAClB,IAAI,SAAS,WAAW,GACtB,OAAO;CAET,MAAM,aAAa,QAAQ,mBAAmB,CAAC;CAK/C,MAAM,8BAAc,IAAI,IAAoB;CAC5C,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EACrB,IAAI,IAAI,SAAS,aACf;EACF,KAAK,MAAM,SAAS,IAAI,SACtB,IAAI,MAAM,SAAS,eAAe,CAAC,YAAY,IAAI,MAAM,EAAE,GACzD,YAAY,IAAI,MAAM,IAAI,CAAC;CAEjC;CAKA,MAAM,4CAA4B,IAAI,IAAsB;CAE5D,IAAI,UAAU;CACd,MAAM,oBAAoB;EAAE,UAAU;CAAK;CAC3C,MAAM,MAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EAErB,IAAI,IAAI,SAAS,aACf,wBAAwB;GACtB,OAAO;GACP;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;OAGD,mBAAmB;GACjB,OAAO;GACP;GACA;GACA;GACA;GACA;EACF,CAAC;CAEL;CAEA,OAAO,UAAU,MAAM;AACzB;;;;;;AAkBA,SAAS,wBAAwB,MAAkC;CACjE,MAAM,EAAE,OAAO,KAAK,UAAU,aAAa,2BAA2B,KAAK,YAAY,gBAAgB;CAYvG,MAAM,iBAAwC,CAAC;CAC/C,MAAM,2BAAW,IAAI,IAAY;CACjC,IAAI,iBAAiB;CACrB,KAAK,MAAM,SAAS,IAAI,SAAS;EAC/B,IAAI,MAAM,SAAS,aAAa;GAC9B,MAAM,gBAAgB,YAAY,IAAI,MAAM,EAAE,MAAM;GACpD,MAAM,gBAAgB,SAAS,IAAI,MAAM,EAAE;GAC3C,IAAI,iBAAiB,eAAe;IAClC,iBAAiB;IACjB,WAAW;KAAE,MAAM;KAA4B,QAAQ,MAAM;KAAI,cAAc;IAAM,CAAC;IACtF;GACF;GACA,SAAS,IAAI,MAAM,EAAE;EACvB;EACA,eAAe,KAAK,KAAK;CAC3B;CAEA,MAAM,YAAY,WAA0B,gBAAgB,cAAa,MAAK,EAAE,EAAE;CAClF,MAAM,OAAO,SAAS,QAAQ;CAC9B,MAAM,aAAa,QAAQ,KAAK,SAAS;CACzC,MAAM,gBAAgB,aAAa,iBAAiB,KAAK,OAAO,oBAAI,IAAI,IAAY;CACpF,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,MAAM,WACf,IAAI,CAAC,cAAc,IAAI,EAAE,GACvB,QAAQ,KAAK,EAAE;CAKnB,wBAAwB;EAAE;EAAO;EAAK;EAAgB;EAAgB;EAAK;EAAY;CAAY,CAAC;CAEpG,IAAI,QAAQ,WAAW,GACrB;CAGF,IAAI,YAAY;EACd,YAAY;EACZ,0BAA0B,IAAI,QAAQ,GAAG,OAAO;EAChD,KAAK,MAAM,UAAU,SACnB,WAAW;GAAE,MAAM;GAA2B;GAAQ,cAAc;EAAM,CAAC;EAC7E;CACF;CAGA,YAAY;CACZ,IAAI,KAAK;EACP,MAAM;EACN,SAAS,QAAQ,KAAI,WAAU,qBAAqB,MAAM,CAAC;CAC7D,CAAC;CACD,KAAK,MAAM,UAAU,SACnB,WAAW;EAAE,MAAM;EAA0B;EAAQ,cAAc;CAAM,CAAC;AAC9E;AAYA,SAAS,wBAAwB,MAA+B;CAC9D,MAAM,EAAE,OAAO,KAAK,gBAAgB,gBAAgB,KAAK,YAAY,gBAAgB;CACrF,IAAI,CAAC,gBAAgB;EACnB,IAAI,KAAK,GAAG;EACZ;CACF;CACA,YAAY;CACZ,IAAI,eAAe,WAAW,GAAG;EAC/B,IAAI,KAAK;GAAE,GAAG;GAAK,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;GAA4B,CAAC;EAAE,CAAC;EACnF,WAAW;GAAE,MAAM;GAA0B,cAAc;EAAM,CAAC;EAClE;CACF;CACA,IAAI,KAAK;EAAE,GAAG;EAAK,SAAS;CAAe,CAAC;AAC9C;;;;;;;AAiBA,SAAS,mBAAmB,MAA6B;CACvD,MAAM,EAAE,OAAO,KAAK,2BAA2B,KAAK,YAAY,gBAAgB;CAChF,MAAM,gBAAgB,0BAA0B,IAAI,KAAK;CACzD,0BAA0B,OAAO,KAAK;CAEtC,MAAM,OAAO,IAAI,SAAS,IAAI,IAAI,IAAI,SAAS,KAAK;CACpD,MAAM,WAAW,QAAQ,KAAK,SAAS,cACnC,WAA0B,KAAK,SAAS,cAAa,MAAK,EAAE,EAAE,oBAC9D,IAAI,IAAY;CAGpB,MAAM,8BAAc,IAAI,IAAY;CACpC,IAAI,eAAe;CACnB,MAAM,aAAoC,CAAC;CAC3C,KAAK,MAAM,SAAS,IAAI,SAAS;EAC/B,IAAI,MAAM,SAAS,eAAe;GAChC,WAAW,KAAK,KAAK;GACrB;EACF;EACA,IAAI,YAAY,IAAI,MAAM,MAAM,GAAG;GACjC,eAAe;GACf,WAAW;IAAE,MAAM;IAA+B,QAAQ,MAAM;IAAQ,cAAc;GAAM,CAAC;GAC7F;EACF;EACA,YAAY,IAAI,MAAM,MAAM;EAC5B,WAAW,KAAK,KAAK;CACvB;CAGA,MAAM,aAAoC,CAAC;CAC3C,KAAK,MAAM,SAAS,YAAY;EAC9B,IAAI,MAAM,SAAS,iBAAiB,CAAC,SAAS,IAAI,MAAM,MAAM,GAAG;GAC/D,eAAe;GACf,WAAW;IAAE,MAAM;IAA4B,QAAQ,MAAM;IAAQ,cAAc;GAAM,CAAC;GAC1F;EACF;EACA,WAAW,KAAK,KAAK;CACvB;CAGA,MAAM,eAAsC,gBACxC,CAAC,GAAG,cAAc,IAAI,oBAAoB,GAAG,GAAG,UAAU,IAC1D;CAEJ,IAAI,CAAC,gBAAgB,kBAAkB,KAAA,GAAW;EAChD,IAAI,KAAK,GAAG;EACZ;CACF;CAEA,YAAY;CACZ,IAAI,aAAa,WAAW,GAAG;EAC7B,IAAI,KAAK;GAAE,GAAG;GAAK,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;GAA4B,CAAC;EAAE,CAAC;EACnF;CACF;CACA,IAAI,KAAK;EAAE,GAAG;EAAK,SAAS;CAAa,CAAC;AAC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,2BACd,WACA,SACA,UACc;CACd,MAAM,OAAO,IAAI,IAAI,QAAQ;CAC7B,MAAM,WAAoD,CAAC;CAG3D,MAAM,gCAAgB,IAAI,IAAsB;CAEhD,IAAI,UAAU;CACd,MAAM,WAAW,UAAU,KAAK,OAAO;EACrC,IAAI,UAAU,GAAG;EACjB,IAAI,KAAK,IAAI,GAAG,EAAE,GAAG;GACnB,IAAI,IAAI;GACR,OAAO,KAAK,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAC9B;GACF,UAAU,GAAG,GAAG,GAAG,IAAI;GACvB,SAAS,KAAK;IAAE,OAAO,GAAG;IAAI,OAAO;GAAQ,CAAC;GAC9C,UAAU;EACZ;EACA,KAAK,IAAI,OAAO;EAChB,MAAM,QAAQ,cAAc,IAAI,GAAG,EAAE;EACrC,IAAI,OACF,MAAM,KAAK,OAAO;OAElB,cAAc,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC;EACpC,OAAO,YAAY,GAAG,KAAK,KAAK;GAAE,GAAG;GAAI,IAAI;EAAQ;CACvD,CAAC;CAED,IAAI,CAAC,SACH,OAAO;EACM;EACF;EACT;CACF;CAaF,OAAO;EAAE,WAAW;EAAuC,SAVxC,QAAQ,KAAK,UAAU;GACxC,IAAI,MAAM,SAAS,aACjB,OAAO;GAET,MAAM,UADQ,cAAc,IAAI,MAAM,EAClB,GAAG,MAAM;GAC7B,IAAI,YAAY,KAAA,KAAa,YAAY,MAAM,IAC7C,OAAO;GACT,OAAO;IAAE,GAAG;IAAO,IAAI;GAAQ;EACjC,CAE6E;EAAG;CAAS;AAC3F;AAEA,SAAS,qBAAqB,QAAqC;CACjE,OAAO;EACL,MAAM;EACN;EACA,QAAQ;EACR,SAAS;CACX;AACF;AAEA,SAAS,iBAAiB,SAA6C;CACrE,MAAM,sBAAM,IAAI,IAAY;CAC5B,KAAK,MAAM,SAAS,SAClB,IAAI,MAAM,SAAS,eACjB,IAAI,IAAI,MAAM,MAAM;CAExB,OAAO;AACT;AAEA,SAAS,WACP,SACA,MACA,OACa;CACb,MAAM,sBAAM,IAAI,IAAY;CAC5B,KAAK,MAAM,SAAS,SAClB,IAAI,MAAM,SAAS,MACjB,IAAI,IAAI,MAAM,KAAU,CAAC;CAE7B,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,yBACd,OACK;CACL,IAAI,MAAM,WAAW,GACnB,OAAO;CAIT,MAAM,8BAAc,IAAI,IAAY;CACpC,KAAK,MAAM,QAAQ,OACjB,KAAK,MAAM,SAAS,KAAK,SACvB,IAAI,MAAM,SAAS,eACjB,YAAY,IAAI,MAAM,MAAM;CAIlC,IAAI,UAAU;CACd,MAAM,MAAW,CAAC;CAClB,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,SAAS,aAAa;GAC7B,IAAI,KAAK,IAAI;GACb;EACF;EACA,MAAM,YAAY,KAAK,QAAQ,QAAO,MAAK,EAAE,SAAS,WAAW;EACjE,IAAI,UAAU,WAAW,GAAG;GAC1B,IAAI,KAAK,IAAI;GACb;EACF;EAEA,IADsB,UAAU,OAAM,MAAK,EAAE,SAAS,eAAe,CAAC,YAAY,IAAI,EAAE,EAAE,CAC1E,GAAG;GACjB,UAAU;GACV;EACF;EACA,IAAI,KAAK,IAAI;CACf;CACA,OAAO,UAAU,MAAM;AACzB;;;;;;;;;AA6BA,SAAgB,uBACd,OACuB;CACvB,IAAI,MAAM,WAAW,GACnB,OAAO;CACT,MAAM,OAAO,MAAM,MAAM,SAAS;CAClC,IAAI,KAAK,SAAS,QAChB,OAAO;CACT,IAAI,KAAK,SAAS,aAChB,OAAO;CAGT,MAAM,8BAAc,IAAI,IAAY;CACpC,KAAK,MAAM,QAAQ,OACjB,KAAK,MAAM,SAAS,KAAK,SACvB,IAAI,MAAM,SAAS,eACjB,YAAY,IAAI,MAAM,MAAM;CAGlC,KAAK,MAAM,SAAS,KAAK,SACvB,IAAI,MAAM,SAAS,eAAe,CAAC,YAAY,IAAI,MAAM,EAAE,GACzD,OAAO;CAEX,OAAO;AACT;;;;;;;;;;AAWA,MAAa,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyC3C,SAAgB,0BACd,UACA,UACA,YAAoB,6BACF;CAClB,IAAI,SAAS,WAAW,GACtB,OAAO;CACT,IAAI,SAAS,SAAS,SAAS,GAAG,SAAS,QACzC,OAAO;CACT,OAAO,CAAC,GAAG,UAAU,SAAS,YAAY,SAAS,CAAC;AACtD;AAgCA,eAAe,uBACb,OACA,mBAC2D;CAC3D,IAAI,kBAAkB,KAAK,GAAG;EAC5B,IAAI,CAAC,mBACH,MAAM,0BAA0B,KAAK;EACvC,MAAM,EAAE,SAAS,MAAM,kBAAkB;GACvC,MAAM,MAAM;GACZ,KAAK,MAAM;GACX,WAAW,MAAM;GACjB,GAAI,MAAM,SAAS,aAAa,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;GAChE,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;EAC3C,CAAC;EACD,IAAI,MAAM,SAAS,SACjB,OAAO;GACL,OAAO;IAAE,MAAM;IAAS,WAAW,MAAM;IAAW;IAAM,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;GAAG;GACtG,SAAS;EACX;EAEF,OAAO;GACL,OAAO;IAAE,MAAM;IAAY,WAAW,MAAM;IAAW,UAAU,MAAM;IAAU;IAAM,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;GAAG;GACnI,SAAS;EACX;CACF;CAEA,IAAI,MAAM,SAAS,iBAAiB,OAAO,MAAM,WAAW,UAC1D,OAAO;EAAE;EAAO,SAAS;CAAM;CAEjC,MAAM,WAAW,MAAM,yBAAyB,MAAM,QAAQ,iBAAiB;CAC/E,IAAI,CAAC,SAAS,SACZ,OAAO;EAAE;EAAO,SAAS;CAAM;CACjC,OAAO;EAAE,OAAO;GAAE,GAAG;GAAO,QAAQ,SAAS;EAAO;EAAG,SAAS;CAAK;AACvE;AAEA,eAAe,yBACb,QACA,mBAC4D;CAC5D,IAAI,UAAU;CACd,MAAM,OAA4B,CAAC;CACnC,KAAK,MAAM,QAAQ,QAAQ;EACzB,IAAI,CAAC,kBAAkB,IAAI,GAAG;GAC5B,KAAK,KAAK,IAAI;GACd;EACF;EACA,IAAI,CAAC,mBACH,MAAM,0BAA0B,IAAI;EACtC,MAAM,EAAE,SAAS,MAAM,kBAAkB;GACvC,MAAM,KAAK;GACX,KAAK,KAAK;GACV,WAAW,KAAK;GAChB,GAAI,KAAK,SAAS,aAAa,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;GAC9D,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;EACzC,CAAC;EACD,UAAU;EACV,IAAI,KAAK,SAAS,SAChB,KAAK,KAAK;GAAE,MAAM;GAAS,WAAW,KAAK;GAAW;GAAM,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;EAAG,CAAC;OAGvG,KAAK,KAAK;GAAE,MAAM;GAAY,WAAW,KAAK;GAAW,UAAU,KAAK;GAAU;GAAM,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;EAAG,CAAC;CAEvI;CACA,OAAO;EAAE,QAAQ,UAAU,OAAO;EAAQ;CAAQ;AACpD;;;;;;;;;;;AAYA,eAAsB,6BACpB,UACA,UAAqD,CAAC,GAC3B;CAC3B,IAAI,aAAa;CACjB,MAAM,MAAwB,CAAC;CAC/B,KAAK,MAAM,OAAO,UAAU;EAC1B,IAAI,UAAU;EACd,MAAM,UAAiC,CAAC;EACxC,KAAK,MAAM,SAAS,IAAI,SAAS;GAC/B,MAAM,WAAW,MAAM,uBAAuB,OAAO,QAAQ,iBAAiB;GAC9E,IAAI,SAAS,SACX,UAAU;GACZ,QAAQ,KAAK,SAAS,KAAK;EAC7B;EACA,IAAI,SACF,aAAa;EACf,IAAI,KAAK,UAAU;GAAE,GAAG;GAAK;EAAQ,IAAI,GAAG;CAC9C;CACA,OAAO,aAAa,MAAO;AAC7B;AAEA,SAAS,qBAAqB,UAA4B,SAAkD;CAC1G,MAAM,SAAS,wBAAwB,UAAU,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC,CAAC;CACvG,IAAI,CAAC,QAAQ,UACX,OAAO;CACT,OAAO,0BAA0B,QAAQ,QAAQ,UAAU,QAAQ,iBAAiB;AACtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,eAAsB,eACpB,OACA,UAAiC,CAAC,GACP;CAC3B,MAAM,WAA6B,CAAC;CACpC,KAAK,MAAM,KAAK,OAAO;EACrB,IAAI,EAAE,SAAS,UACb;EACF,SAAS,KAAK;GAAE,MAAM,EAAE;GAAM,SAAS,EAAE;EAAQ,CAAC;CACpD;CAEA,OAAO,qBAAqB,MADL,6BAA6B,UAAU,EAAE,mBAAmB,QAAQ,kBAAkB,CAAC,GACxE,OAAO;AAC/C;AAEA,SAAgB,qBAAqB,KAAyD;CAC5F,MAAM,IAAa,IAAI;CAGvB,IAAI,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,GAAG;EACnD,MAAM,MAAO,EAAyB;EACtC,IAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,WAAW,GACvD,OAAO,WAAW,GAAG;CACzB;CAGA,IAAI,MAAM,QAAQ,CAAC,GAAG;EACpB,KAAK,MAAM,SAAS,GAAG;GACrB,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B;GACF,MAAM,IAAI;GACV,IAAI,EAAE,SAAS,cAAe,EAAE,SAAS,iBAAiB,iBAAiB,GACzE,OAAO,cAAc,GAAG;GAE1B,IAAI,EAAE,SAAS,aACb,OAAO,WAAW,GAAG;EAEzB;EAEA,OAAO,cAAc,GAAG;CAC1B;CAGA,IAAI,OAAO,MAAM,UACf,OAAO,cAAc,GAAG;CAI1B,OAAO,cAAc,GAAG;AAC1B"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { t as lazyAsync } from "./lazy-DLOurOC_.js";
|
|
2
|
+
import { l as errorMessage } from "./errors-DJUxZg9b.js";
|
|
3
|
+
import { t as writeFileAtomic } from "./atomic-write-B7zr1ddF.js";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
6
|
+
import { existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
//#region src/providers/oauth.ts
|
|
9
|
+
const loadGetOAuthApiKey = lazyAsync(async () => (await import("@earendil-works/pi-ai/oauth")).getOAuthApiKey);
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the creds-file path at call time rather than module-load time, so
|
|
12
|
+
* env changes between import and first OAuth use (test suites, multi-project
|
|
13
|
+
* CLIs) are honored.
|
|
14
|
+
*
|
|
15
|
+
* Order:
|
|
16
|
+
* 1. `ZIDANE_CREDENTIALS_PATH` — explicit override. The TUI sets this at
|
|
17
|
+
* launch to point at `<storageDir>/credentials.json` so the harness
|
|
18
|
+
* providers find the same file the TUI manages.
|
|
19
|
+
* 2. `~/.credentials.json` — home-anchored default.
|
|
20
|
+
*
|
|
21
|
+
* The historic `cwd/.credentials.json` fallback was deliberately dropped:
|
|
22
|
+
* launching zidane inside an untrusted checkout would let that repo plant a
|
|
23
|
+
* `.credentials.json` and become the credential source (and receive rotated
|
|
24
|
+
* refresh tokens on the next refresh persist). Same trust model as the TUI's
|
|
25
|
+
* legacy-file migration in `chat/credentials.ts`, which only honors the
|
|
26
|
+
* home-anchored location. The only "trusted" cwd under that model is `$HOME`
|
|
27
|
+
* itself — where the cwd-relative path resolves to `~/.credentials.json`
|
|
28
|
+
* anyway, so anchoring at home preserves the legitimate legacy case.
|
|
29
|
+
*/
|
|
30
|
+
function credentialsFilePath() {
|
|
31
|
+
return process.env.ZIDANE_CREDENTIALS_PATH ?? resolve(homedir(), ".credentials.json");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* POSIX mode for credentials on disk. Read+write for the owner only — refresh
|
|
35
|
+
* tokens and API keys leak if the file is world-readable on a shared box.
|
|
36
|
+
* Ignored on Windows.
|
|
37
|
+
*/
|
|
38
|
+
const CREDENTIALS_FILE_MODE = 384;
|
|
39
|
+
/**
|
|
40
|
+
* In-process mutex table keyed by provider id. Concurrent `resolveOAuthApiKey`
|
|
41
|
+
* calls for the same provider (typical when a host fans out multiple streams
|
|
42
|
+
* at once) coalesce onto a single refresh. Without this, N concurrent calls
|
|
43
|
+
* each hit `getOAuthApiKey`, producing N writes to `.credentials.json` with
|
|
44
|
+
* last-write-wins semantics — and, in worst-case interleaving, a corrupted
|
|
45
|
+
* file.
|
|
46
|
+
*/
|
|
47
|
+
const refreshLocks = /* @__PURE__ */ new Map();
|
|
48
|
+
/**
|
|
49
|
+
* Pull a complete `{ access, refresh, expires }` triple out of provider params
|
|
50
|
+
* when (and only when) all three OAuth fields are present and well-typed.
|
|
51
|
+
* Returns `undefined` for the static-api-key path. Shared by the OAuth-capable
|
|
52
|
+
* providers so the runtime-credential guard can't drift between them.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Whether a credential string is an OAuth *access token* rather than a real,
|
|
56
|
+
* static API key. Anthropic OAuth tokens are prefixed `sk-ant-oat`; OpenAI
|
|
57
|
+
* Codex OAuth access tokens are JWTs (`eyJ...`). Used to decide whether an
|
|
58
|
+
* env-provided value can be refreshed (OAuth) or must be sent verbatim (API key).
|
|
59
|
+
*/
|
|
60
|
+
function isOAuthAccessToken(value) {
|
|
61
|
+
return value.startsWith("sk-ant-oat") || isJwtLikeAccessToken(value);
|
|
62
|
+
}
|
|
63
|
+
function isJwtLikeAccessToken(value) {
|
|
64
|
+
const parts = value.split(".");
|
|
65
|
+
if (parts.length !== 3 || !parts[0]?.startsWith("eyJ")) return false;
|
|
66
|
+
try {
|
|
67
|
+
const header = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
|
|
68
|
+
return Boolean(header && typeof header === "object" && "alg" in header);
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function extractRuntimeCredentials(params) {
|
|
74
|
+
if (typeof params?.access === "string" && typeof params.refresh === "string" && typeof params.expires === "number") return {
|
|
75
|
+
access: params.access,
|
|
76
|
+
refresh: params.refresh,
|
|
77
|
+
expires: params.expires
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Read the shared credentials file.
|
|
82
|
+
*
|
|
83
|
+
* Returns `{}` when the file is missing OR when the file exists but is corrupted
|
|
84
|
+
* / not valid JSON. Corrupt-file tolerance is important: the refresh-then-persist
|
|
85
|
+
* path below writes atomically, but older builds wrote in place and could have
|
|
86
|
+
* truncated the file on a crash. Treating corruption as "no stored creds" lets
|
|
87
|
+
* users re-auth without manually deleting the file.
|
|
88
|
+
*
|
|
89
|
+
* Supports two on-disk shapes:
|
|
90
|
+
* 1. **Legacy** (untagged): `{ "anthropic": { access, refresh, expires, ... } }`
|
|
91
|
+
* — written by older `bun run auth`.
|
|
92
|
+
* 2. **New** (kind-tagged): `{ "anthropic": { kind: 'oauth', access, refresh, expires, ... } }`
|
|
93
|
+
* — written by the TUI. Entries with `kind: 'apikey'` are skipped — those
|
|
94
|
+
* reach providers via env vars set at TUI launch (see `applyApiKeyEnv`).
|
|
95
|
+
*/
|
|
96
|
+
function readOAuthCredentials() {
|
|
97
|
+
const path = credentialsFilePath();
|
|
98
|
+
if (!existsSync(path)) return {};
|
|
99
|
+
try {
|
|
100
|
+
const raw = readFileSync(path, "utf-8");
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
103
|
+
const result = {};
|
|
104
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
105
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
106
|
+
const v = value;
|
|
107
|
+
if (v.kind === "apikey") continue;
|
|
108
|
+
if (typeof v.access === "string" && typeof v.refresh === "string" && typeof v.expires === "number") result[key] = {
|
|
109
|
+
...v,
|
|
110
|
+
access: v.access,
|
|
111
|
+
refresh: v.refresh,
|
|
112
|
+
expires: v.expires
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
} catch {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Cross-process lockfile parameters for the credentials read-merge-write.
|
|
122
|
+
* A holder normally completes in well under a millisecond (read + JSON merge
|
|
123
|
+
* + atomic write of a tiny file), so the stale threshold is generous and the
|
|
124
|
+
* total wait short — on timeout we proceed without the lock rather than fail
|
|
125
|
+
* the refresh (degrades to the historic last-write-wins behavior).
|
|
126
|
+
*/
|
|
127
|
+
const CREDENTIALS_LOCK_STALE_MS = 5e3;
|
|
128
|
+
const CREDENTIALS_LOCK_RETRY_MS = 20;
|
|
129
|
+
const CREDENTIALS_LOCK_MAX_WAIT_MS = 2e3;
|
|
130
|
+
/** Synchronous sleep without spinning — works on Bun and Node main threads. */
|
|
131
|
+
function sleepSync(ms) {
|
|
132
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Best-effort exclusive lock via `O_EXCL` create of `<file>.lock`. Returns
|
|
136
|
+
* `true` when the lock was acquired (caller must release), `false` on
|
|
137
|
+
* timeout (caller proceeds unlocked — see threshold rationale above). Locks
|
|
138
|
+
* older than {@link CREDENTIALS_LOCK_STALE_MS} are treated as leftovers from
|
|
139
|
+
* a crashed process and broken.
|
|
140
|
+
*/
|
|
141
|
+
function acquireCredentialsLock(lockPath) {
|
|
142
|
+
const deadline = Date.now() + CREDENTIALS_LOCK_MAX_WAIT_MS;
|
|
143
|
+
while (true) try {
|
|
144
|
+
writeFileSync(lockPath, String(process.pid), {
|
|
145
|
+
flag: "wx",
|
|
146
|
+
mode: CREDENTIALS_FILE_MODE
|
|
147
|
+
});
|
|
148
|
+
return true;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
if (err.code !== "EEXIST") return false;
|
|
151
|
+
try {
|
|
152
|
+
if (Date.now() - statSync(lockPath).mtimeMs > CREDENTIALS_LOCK_STALE_MS) {
|
|
153
|
+
unlinkSync(lockPath);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
} catch {}
|
|
157
|
+
if (Date.now() >= deadline) return false;
|
|
158
|
+
sleepSync(CREDENTIALS_LOCK_RETRY_MS);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function releaseCredentialsLock(lockPath) {
|
|
162
|
+
try {
|
|
163
|
+
unlinkSync(lockPath);
|
|
164
|
+
} catch {}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Atomically write `.credentials.json` with mode 0o600.
|
|
168
|
+
*
|
|
169
|
+
* Writes to a sibling temp file first, then renames. `rename(2)` is atomic on
|
|
170
|
+
* the same filesystem, so readers either see the old file or the new one —
|
|
171
|
+
* never a half-written one. This matters when a host has multiple processes
|
|
172
|
+
* pointed at the same creds file.
|
|
173
|
+
*
|
|
174
|
+
* Merges the incoming OAuth entries on top of the raw file contents so that
|
|
175
|
+
* entries this layer doesn't manage (e.g. `kind: 'apikey'` from the TUI
|
|
176
|
+
* wizard) survive an OAuth refresh cycle. Without this merge,
|
|
177
|
+
* `readOAuthCredentials` filters those entries out, and a naive overwrite
|
|
178
|
+
* would delete them.
|
|
179
|
+
*
|
|
180
|
+
* The read-merge-write runs under a best-effort cross-process lockfile
|
|
181
|
+
* (`<file>.lock`) so two processes refreshing different providers at the
|
|
182
|
+
* same moment can't drop each other's rotated refresh tokens. On lock
|
|
183
|
+
* timeout the write proceeds unlocked (last-write-wins, the pre-lock
|
|
184
|
+
* behavior) rather than failing the refresh.
|
|
185
|
+
*/
|
|
186
|
+
function writeOAuthCredentials(credentials) {
|
|
187
|
+
const path = credentialsFilePath();
|
|
188
|
+
const lockPath = `${path}.lock`;
|
|
189
|
+
const locked = acquireCredentialsLock(lockPath);
|
|
190
|
+
try {
|
|
191
|
+
let existing = {};
|
|
192
|
+
try {
|
|
193
|
+
if (existsSync(path)) {
|
|
194
|
+
const raw = readFileSync(path, "utf-8");
|
|
195
|
+
const parsed = JSON.parse(raw);
|
|
196
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) existing = parsed;
|
|
197
|
+
}
|
|
198
|
+
} catch {}
|
|
199
|
+
const merged = {
|
|
200
|
+
...existing,
|
|
201
|
+
...credentials
|
|
202
|
+
};
|
|
203
|
+
for (const [key, value] of Object.entries(merged)) if (value === void 0) delete merged[key];
|
|
204
|
+
writeFileAtomic(path, JSON.stringify(merged, null, 2), { mode: CREDENTIALS_FILE_MODE });
|
|
205
|
+
} finally {
|
|
206
|
+
if (locked) releaseCredentialsLock(lockPath);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function credentialsFromParams(params, extraKeys = []) {
|
|
210
|
+
if (typeof params?.access !== "string" || typeof params.refresh !== "string" || typeof params.expires !== "number") return void 0;
|
|
211
|
+
const extras = Object.fromEntries(extraKeys.map((key) => [key, params[key]]).filter(([, value]) => value !== void 0));
|
|
212
|
+
return {
|
|
213
|
+
access: params.access,
|
|
214
|
+
refresh: params.refresh,
|
|
215
|
+
expires: params.expires,
|
|
216
|
+
...extras
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
async function resolveOAuthApiKey(options, callbacks) {
|
|
220
|
+
if (typeof options.params?.apiKey === "string") return options.params.apiKey;
|
|
221
|
+
const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys);
|
|
222
|
+
if (paramsCredentials) return await withRefreshLock(`params:${options.providerId}`, () => resolveCredentialSource("params", paramsCredentials));
|
|
223
|
+
if (typeof options.params?.access === "string") return options.params.access;
|
|
224
|
+
const readCredentials = options.readCredentials ?? readOAuthCredentials;
|
|
225
|
+
const writeCredentials = options.writeCredentials ?? writeOAuthCredentials;
|
|
226
|
+
const envValue = options.envKey ? process.env[options.envKey] : void 0;
|
|
227
|
+
if (envValue) {
|
|
228
|
+
if (!isOAuthAccessToken(envValue)) return envValue;
|
|
229
|
+
if (!readCredentials()[options.providerId]) return envValue;
|
|
230
|
+
return await withRefreshLock(`file:${options.providerId}`, async () => {
|
|
231
|
+
const allCredentials = readCredentials();
|
|
232
|
+
const stored = allCredentials[options.providerId];
|
|
233
|
+
if (!stored) return envValue;
|
|
234
|
+
return await resolveCredentialSource("file", stored, allCredentials, writeCredentials);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return await withRefreshLock(`file:${options.providerId}`, async () => {
|
|
238
|
+
const allCredentials = readCredentials();
|
|
239
|
+
const storedCredentials = allCredentials[options.providerId];
|
|
240
|
+
if (!storedCredentials) throw new Error(options.missingError);
|
|
241
|
+
return await resolveCredentialSource("file", storedCredentials, allCredentials, writeCredentials);
|
|
242
|
+
});
|
|
243
|
+
async function resolveCredentialSource(source, current, allCredentials, persistCredentials) {
|
|
244
|
+
let result;
|
|
245
|
+
try {
|
|
246
|
+
result = await (options.getOAuthApiKey ?? await loadGetOAuthApiKey())(options.providerId, { [options.providerId]: current });
|
|
247
|
+
} catch (err) {
|
|
248
|
+
throw new Error(options.refreshError(errorMessage(err)));
|
|
249
|
+
}
|
|
250
|
+
if (!result) throw new Error(options.refreshError(options.missingError));
|
|
251
|
+
if (result.newCredentials !== current) {
|
|
252
|
+
const refreshed = {
|
|
253
|
+
...current,
|
|
254
|
+
...result.newCredentials
|
|
255
|
+
};
|
|
256
|
+
if (source === "file" && allCredentials && persistCredentials) {
|
|
257
|
+
allCredentials[options.providerId] = refreshed;
|
|
258
|
+
persistCredentials(allCredentials);
|
|
259
|
+
}
|
|
260
|
+
await callbacks?.onOAuthRefresh?.({
|
|
261
|
+
provider: options.provider,
|
|
262
|
+
providerId: options.providerId,
|
|
263
|
+
source,
|
|
264
|
+
previousCredentials: { ...current },
|
|
265
|
+
credentials: { ...refreshed }
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return result.apiKey;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Coalesce concurrent refresh calls onto a single in-flight promise per key.
|
|
273
|
+
* The key combines the source (`params` vs `file`) and provider id, so a
|
|
274
|
+
* per-agent param-only refresh does not starve a separate file-backed one.
|
|
275
|
+
*/
|
|
276
|
+
async function withRefreshLock(key, fn) {
|
|
277
|
+
const existing = refreshLocks.get(key);
|
|
278
|
+
if (existing) return existing;
|
|
279
|
+
const task = (async () => {
|
|
280
|
+
try {
|
|
281
|
+
return await fn();
|
|
282
|
+
} finally {
|
|
283
|
+
refreshLocks.delete(key);
|
|
284
|
+
}
|
|
285
|
+
})();
|
|
286
|
+
refreshLocks.set(key, task);
|
|
287
|
+
return task;
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
export { resolveOAuthApiKey as i, isOAuthAccessToken as n, readOAuthCredentials as r, extractRuntimeCredentials as t };
|
|
291
|
+
|
|
292
|
+
//# sourceMappingURL=oauth-8LqbeI_Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-8LqbeI_Q.js","names":[],"sources":["../src/providers/oauth.ts"],"sourcesContent":["import type { OAuthCredentials } from '@earendil-works/pi-ai/oauth'\nimport type { StreamCallbacks } from '.'\nimport type { OAuthRefreshHookContext } from '../types'\nimport { Buffer } from 'node:buffer'\nimport { existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { resolve } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\nimport { errorMessage } from '../errors'\nimport { lazyAsync } from '../lazy'\n\ntype GetOAuthApiKey = typeof import('@earendil-works/pi-ai/oauth').getOAuthApiKey\n\nconst loadGetOAuthApiKey = lazyAsync(async () => (await import('@earendil-works/pi-ai/oauth')).getOAuthApiKey)\n\n/**\n * Resolve the creds-file path at call time rather than module-load time, so\n * env changes between import and first OAuth use (test suites, multi-project\n * CLIs) are honored.\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. `~/.credentials.json` — home-anchored default.\n *\n * The historic `cwd/.credentials.json` fallback was deliberately dropped:\n * launching zidane inside an untrusted checkout would let that repo plant a\n * `.credentials.json` and become the credential source (and receive rotated\n * refresh tokens on the next refresh persist). Same trust model as the TUI's\n * legacy-file migration in `chat/credentials.ts`, which only honors the\n * home-anchored location. The only \"trusted\" cwd under that model is `$HOME`\n * itself — where the cwd-relative path resolves to `~/.credentials.json`\n * anyway, so anchoring at home preserves the legitimate legacy case.\n */\nexport function credentialsFilePath(): string {\n return process.env.ZIDANE_CREDENTIALS_PATH ?? resolve(homedir(), '.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\n/**\n * Pull a complete `{ access, refresh, expires }` triple out of provider params\n * when (and only when) all three OAuth fields are present and well-typed.\n * Returns `undefined` for the static-api-key path. Shared by the OAuth-capable\n * providers so the runtime-credential guard can't drift between them.\n */\n/**\n * Whether a credential string is an OAuth *access token* rather than a real,\n * static API key. Anthropic OAuth tokens are prefixed `sk-ant-oat`; OpenAI\n * Codex OAuth access tokens are JWTs (`eyJ...`). Used to decide whether an\n * env-provided value can be refreshed (OAuth) or must be sent verbatim (API key).\n */\nexport function isOAuthAccessToken(value: string): boolean {\n return value.startsWith('sk-ant-oat') || isJwtLikeAccessToken(value)\n}\n\nfunction isJwtLikeAccessToken(value: string): boolean {\n const parts = value.split('.')\n if (parts.length !== 3 || !parts[0]?.startsWith('eyJ'))\n return false\n try {\n const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString('utf8')) as unknown\n return Boolean(header && typeof header === 'object' && 'alg' in header)\n }\n catch {\n return false\n }\n}\n\nexport function extractRuntimeCredentials(\n params?: OAuthParams,\n): { access: string, refresh: string, expires: number } | undefined {\n if (\n typeof params?.access === 'string'\n && typeof params.refresh === 'string'\n && typeof params.expires === 'number'\n ) {\n return { access: params.access, refresh: params.refresh, expires: params.expires }\n }\n return undefined\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?: 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 * Cross-process lockfile parameters for the credentials read-merge-write.\n * A holder normally completes in well under a millisecond (read + JSON merge\n * + atomic write of a tiny file), so the stale threshold is generous and the\n * total wait short — on timeout we proceed without the lock rather than fail\n * the refresh (degrades to the historic last-write-wins behavior).\n */\nconst CREDENTIALS_LOCK_STALE_MS = 5000\nconst CREDENTIALS_LOCK_RETRY_MS = 20\nconst CREDENTIALS_LOCK_MAX_WAIT_MS = 2000\n\n/** Synchronous sleep without spinning — works on Bun and Node main threads. */\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)\n}\n\n/**\n * Best-effort exclusive lock via `O_EXCL` create of `<file>.lock`. Returns\n * `true` when the lock was acquired (caller must release), `false` on\n * timeout (caller proceeds unlocked — see threshold rationale above). Locks\n * older than {@link CREDENTIALS_LOCK_STALE_MS} are treated as leftovers from\n * a crashed process and broken.\n */\nfunction acquireCredentialsLock(lockPath: string): boolean {\n const deadline = Date.now() + CREDENTIALS_LOCK_MAX_WAIT_MS\n while (true) {\n try {\n writeFileSync(lockPath, String(process.pid), { flag: 'wx', mode: CREDENTIALS_FILE_MODE })\n return true\n }\n catch (err) {\n // Only EEXIST means \"lock held\" — anything else (EPERM, ENOENT for a\n // missing parent dir, read-only fs) won't resolve by waiting, so bail\n // immediately and let the subsequent write surface the real error.\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST')\n return false\n try {\n const age = Date.now() - statSync(lockPath).mtimeMs\n if (age > CREDENTIALS_LOCK_STALE_MS) {\n unlinkSync(lockPath)\n continue\n }\n }\n catch { /* lock vanished between create-fail and stat — retry */ }\n if (Date.now() >= deadline)\n return false\n sleepSync(CREDENTIALS_LOCK_RETRY_MS)\n }\n }\n}\n\nfunction releaseCredentialsLock(lockPath: string): void {\n try {\n unlinkSync(lockPath)\n }\n catch { /* already gone */ }\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 *\n * The read-merge-write runs under a best-effort cross-process lockfile\n * (`<file>.lock`) so two processes refreshing different providers at the\n * same moment can't drop each other's rotated refresh tokens. On lock\n * timeout the write proceeds unlocked (last-write-wins, the pre-lock\n * behavior) rather than failing the refresh.\n */\nexport function writeOAuthCredentials(credentials: Record<string, OAuthCredentials | undefined>) {\n const path = credentialsFilePath()\n const lockPath = `${path}.lock`\n const locked = acquireCredentialsLock(lockPath)\n\n try {\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 finally {\n if (locked)\n releaseCredentialsLock(lockPath)\n }\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 const readCredentials = options.readCredentials ?? readOAuthCredentials\n const writeCredentials = options.writeCredentials ?? writeOAuthCredentials\n\n // Env key handling. A real API key (e.g. `sk-ant-api…`) is used as-is. But an\n // OAuth *access token* (`sk-ant-oat…`) pasted into the API-key env var can't\n // be refreshed and will 401 once expired — so if refreshable OAuth creds for\n // this provider exist on disk, prefer those (they self-heal via refresh).\n const envValue = options.envKey ? process.env[options.envKey] : undefined\n if (envValue) {\n if (!isOAuthAccessToken(envValue))\n return envValue\n if (!readCredentials()[options.providerId])\n return envValue // no refreshable creds — fall back to the raw token\n return await withRefreshLock(`file:${options.providerId}`, async () => {\n // Re-read under the lock — a concurrent resolve may have rotated the\n // refresh token between the pre-check above and lock acquisition, and\n // refreshing with the stale token would invalidate the new one.\n const allCredentials = readCredentials()\n const stored = allCredentials[options.providerId]\n if (!stored)\n return envValue\n return await resolveCredentialSource('file', stored, allCredentials, writeCredentials)\n })\n }\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 // Only the refresh call itself maps to `refreshError` — a throwing\n // persist or onOAuthRefresh hook after a SUCCESSFUL refresh must not be\n // misreported as a refresh failure, so those run outside the try.\n let result: Awaited<ReturnType<GetOAuthApiKey>>\n try {\n const refreshOAuthApiKey = options.getOAuthApiKey ?? await loadGetOAuthApiKey()\n result = await refreshOAuthApiKey(options.providerId, { [options.providerId]: current } as Record<string, OAuthCredentials>)\n }\n catch (err) {\n throw new Error(options.refreshError(errorMessage(err)))\n }\n if (!result)\n throw new Error(options.refreshError(options.missingError))\n\n if (result.newCredentials !== current) {\n // pi-ai's refresh returns only `{ access, refresh, expires }`. Merge it\n // onto the current credential so the persisted entry keeps its on-disk\n // `kind: 'oauth'` tag and any provider extras (e.g. openai-codex\n // `accountId`). A bare overwrite strips `kind`, and the TUI's auth-state\n // detection keys on `kind === 'oauth'` — so without this the provider\n // reads as signed out after every token refresh, forcing a daily re-login.\n const refreshed = { ...current, ...result.newCredentials }\n if (source === 'file' && allCredentials && persistCredentials) {\n allCredentials[options.providerId] = refreshed\n persistCredentials(allCredentials)\n }\n\n await callbacks?.onOAuthRefresh?.({\n provider: options.provider,\n providerId: options.providerId,\n source,\n previousCredentials: { ...current },\n credentials: { ...refreshed },\n })\n }\n\n return result.apiKey\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"],"mappings":"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"}
|