zidane 7.2.5 → 8.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +34 -6
- package/dist/{acp-D-tVyUjw.js → acp-DQDhi2dj.js} +230 -77
- package/dist/acp-DQDhi2dj.js.map +1 -0
- package/dist/acp-cli.d.ts +1 -1
- package/dist/acp-cli.js +9 -66
- package/dist/acp-cli.js.map +1 -1
- package/dist/acp.d.ts +10 -590
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +3 -3
- package/dist/{agent-D0zZnGbt.js → agent-DSLNmBp0.js} +14401 -13214
- package/dist/agent-DSLNmBp0.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -2
- package/dist/aliasing-lVFbm81n.js +217 -0
- package/dist/aliasing-lVFbm81n.js.map +1 -0
- package/dist/{anthropic-C2aole0G.js → anthropic-CD00LhUP.js} +1442 -1264
- package/dist/anthropic-CD00LhUP.js.map +1 -0
- package/dist/artifacts-CV2hUKnL.js.map +1 -1
- package/dist/{atomic-write-B7zr1ddF.js → atomic-write-Ijgf2Mhc.js} +13 -3
- package/dist/atomic-write-Ijgf2Mhc.js.map +1 -0
- package/dist/call-tool-dHlbGOIY.js +70 -0
- package/dist/call-tool-dHlbGOIY.js.map +1 -0
- package/dist/chat/pure.d.ts +372 -6
- package/dist/chat/pure.d.ts.map +1 -1
- package/dist/chat/pure.js +35 -244
- package/dist/chat/pure.js.map +1 -1
- package/dist/{approvals-DKaJZYlm.js → chat-JPJJ8uu5.js} +15879 -14631
- package/dist/chat-JPJJ8uu5.js.map +1 -0
- package/dist/chat.d.ts +4 -6
- package/dist/chat.js +23 -12
- package/dist/cli-shared-C-YCaglL.js +159 -0
- package/dist/cli-shared-C-YCaglL.js.map +1 -0
- package/dist/client-core-BQSyDALT.d.ts +2115 -0
- package/dist/client-core-BQSyDALT.d.ts.map +1 -0
- package/dist/codemode-worker.d.ts +1 -0
- package/dist/codemode-worker.js +155 -0
- package/dist/codemode-worker.js.map +1 -0
- package/dist/codemode.d.ts +352 -0
- package/dist/codemode.d.ts.map +1 -0
- package/dist/codemode.js +5 -0
- package/dist/connection-Dr-Js4yZ.d.ts +28 -0
- package/dist/connection-Dr-Js4yZ.d.ts.map +1 -0
- package/dist/connection-loop-gdh22LTI.js +112 -0
- package/dist/connection-loop-gdh22LTI.js.map +1 -0
- package/dist/context-segments-BHKlagL6.js +1016 -0
- package/dist/context-segments-BHKlagL6.js.map +1 -0
- package/dist/{context-transport-T_6AYIvy.js → context-transport-DSMTUsK1.js} +3 -3
- package/dist/{context-transport-T_6AYIvy.js.map → context-transport-DSMTUsK1.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +19 -8
- package/dist/contexts/daytona.d.ts.map +1 -1
- package/dist/contexts/daytona.js +1 -440
- package/dist/contexts/docker.d.ts +36 -7
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +1 -456
- package/dist/contexts/e2b.d.ts +68 -13
- package/dist/contexts/e2b.d.ts.map +1 -1
- package/dist/contexts/e2b.js +1 -382
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/contexts/sandbox.js +5 -0
- package/dist/contexts/sandbox.js.map +1 -1
- package/dist/{contexts-CRE9pw07.js → contexts-d7UolYot.js} +1059 -524
- package/dist/contexts-d7UolYot.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/cred-keys-GpBg71cP.js +105 -0
- package/dist/cred-keys-GpBg71cP.js.map +1 -0
- package/dist/{auth-BG2rVKSt.js → credentials-JttEuRON.js} +55 -206
- package/dist/credentials-JttEuRON.js.map +1 -0
- package/dist/{cursor-iojRXHF9.js → cursor-CdjaQTBW.js} +3 -3
- package/dist/{cursor-iojRXHF9.js.map → cursor-CdjaQTBW.js.map} +1 -1
- package/dist/daemon-cli.d.ts +1 -1
- package/dist/daemon-cli.js +3 -2
- package/dist/daemon-cli.js.map +1 -1
- package/dist/daytona-DG3omipZ.js +513 -0
- package/dist/daytona-DG3omipZ.js.map +1 -0
- package/dist/defaults-Cx3zvDOV.js +23 -0
- package/dist/defaults-Cx3zvDOV.js.map +1 -0
- package/dist/dispatcher-BMlu2BmZ.js +4941 -0
- package/dist/dispatcher-BMlu2BmZ.js.map +1 -0
- package/dist/docker-BZhMMJUp.js +727 -0
- package/dist/docker-BZhMMJUp.js.map +1 -0
- package/dist/e2b-CEVOdRib.js +480 -0
- package/dist/e2b-CEVOdRib.js.map +1 -0
- package/dist/edit-utils-EGosADZq.js +234 -0
- package/dist/edit-utils-EGosADZq.js.map +1 -0
- package/dist/environment-8wsKE2Ln.js +214 -0
- package/dist/environment-8wsKE2Ln.js.map +1 -0
- package/dist/eval-BNcvle_J.js +1595 -0
- package/dist/eval-BNcvle_J.js.map +1 -0
- package/dist/eval.d.ts +2 -2
- package/dist/eval.js +1 -1489
- package/dist/executor-Cx5-8yqi.js +621 -0
- package/dist/executor-Cx5-8yqi.js.map +1 -0
- package/dist/extension-authoring-DyGGmq9V.js +385 -0
- package/dist/extension-authoring-DyGGmq9V.js.map +1 -0
- package/dist/extensions.d.ts +2 -2
- package/dist/extensions.js +3 -3
- package/dist/{fetch-url-DX75gpL0.js → fetch-url-Ct2TjDWt.js} +4 -4
- package/dist/{fetch-url-DX75gpL0.js.map → fetch-url-Ct2TjDWt.js.map} +1 -1
- package/dist/{format-BF745pLL.js → format-C2NQEVUH.js} +39 -2
- package/dist/format-C2NQEVUH.js.map +1 -0
- package/dist/format-bytes-C7tfc3Zm.js +55 -0
- package/dist/format-bytes-C7tfc3Zm.js.map +1 -0
- package/dist/headless-C_rNUuYC.js +960 -0
- package/dist/headless-C_rNUuYC.js.map +1 -0
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -413
- package/dist/{history-search-C4bk1Wz_.js → history-search-CcYzF8-0.js} +7 -3
- package/dist/history-search-CcYzF8-0.js.map +1 -0
- package/dist/history-search-worker.d.ts +1 -1
- package/dist/history-search-worker.js +1 -1
- package/dist/host-D7NrI03V.js +378 -0
- package/dist/host-D7NrI03V.js.map +1 -0
- package/dist/host-signals-BcP5QKUD.js +118 -0
- package/dist/host-signals-BcP5QKUD.js.map +1 -0
- package/dist/identity-CwfO8K-t.js +26 -0
- package/dist/identity-CwfO8K-t.js.map +1 -0
- package/dist/{index-BgN1T3lk.d.ts → index-BGe1fQT7.d.ts} +10987 -9320
- package/dist/index-BGe1fQT7.d.ts.map +1 -0
- package/dist/index-BVQsIKGn.d.ts +448 -0
- package/dist/index-BVQsIKGn.d.ts.map +1 -0
- package/dist/{index-C6NEd5y_.d.ts → index-CHABJd-L.d.ts} +3386 -502
- package/dist/index-CHABJd-L.d.ts.map +1 -0
- package/dist/{index-C37_ppWv.d.ts → index-D6a7qXpg.d.ts} +2 -3
- package/dist/index-D6a7qXpg.d.ts.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +27 -23
- package/dist/{completion-core-BRo86R_L.js → inline-ghost-BVRQeGoL.js} +50 -2
- package/dist/inline-ghost-BVRQeGoL.js.map +1 -0
- package/dist/ipc/remote.d.ts +313 -0
- package/dist/ipc/remote.d.ts.map +1 -0
- package/dist/ipc/remote.js +4 -0
- package/dist/ipc.d.ts +6 -964
- package/dist/ipc.js +403 -4
- package/dist/ipc.js.map +1 -0
- package/dist/{isolation-Kfoof0MM.d.ts → isolation-D9L650W5.d.ts} +100 -15
- package/dist/isolation-D9L650W5.d.ts.map +1 -0
- package/dist/json-rpc--HXQti1E.js +350 -0
- package/dist/json-rpc--HXQti1E.js.map +1 -0
- package/dist/links-j8rED1sR.js +603 -0
- package/dist/links-j8rED1sR.js.map +1 -0
- package/dist/local-terminal-D8QThwo_.js +252 -0
- package/dist/local-terminal-D8QThwo_.js.map +1 -0
- package/dist/{logger--C1VQjwJ.js → logger-Gtqlayx8.js} +199 -173
- package/dist/logger-Gtqlayx8.js.map +1 -0
- package/dist/logs-view-CWG8aUAG.js.map +1 -1
- package/dist/{mcp-CgNd-f3F.js → mcp-_wecH9Qe.js} +608 -562
- package/dist/mcp-_wecH9Qe.js.map +1 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/media-sniff-UKijEaiQ.js.map +1 -1
- package/dist/{messages-2jcOA_5R.js → messages-BqiwcD2Q.js} +1017 -1014
- package/dist/messages-BqiwcD2Q.js.map +1 -0
- package/dist/{oauth-DxHO17JE.js → oauth-Px_pBB9W.js} +5 -5
- package/dist/{oauth-DxHO17JE.js.map → oauth-Px_pBB9W.js.map} +1 -1
- package/dist/{openai-compat-Dy56weK8.js → openai-compat-CFrpH7uq.js} +432 -589
- package/dist/openai-compat-CFrpH7uq.js.map +1 -0
- package/dist/orchestration-BCGmB8Ps.js +511 -0
- package/dist/orchestration-BCGmB8Ps.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -2
- package/dist/output/stream-json.d.ts.map +1 -1
- package/dist/output/stream-json.js +1 -1
- package/dist/output/terminal.d.ts +1 -2
- package/dist/output/terminal.d.ts.map +1 -1
- package/dist/output/terminal.js +13 -7
- package/dist/output/terminal.js.map +1 -1
- package/dist/{plugins-gbI7SBA6.js → plugins-DS7mahzZ.js} +20 -101
- package/dist/plugins-DS7mahzZ.js.map +1 -0
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +1 -2
- package/dist/prelude-Cxo4t74B.js +517 -0
- package/dist/prelude-Cxo4t74B.js.map +1 -0
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -2
- package/dist/project-root-B8zADr4o.js +167 -0
- package/dist/project-root-B8zADr4o.js.map +1 -0
- package/dist/project-user-paths-BSgtrRxl.js +58 -0
- package/dist/project-user-paths-BSgtrRxl.js.map +1 -0
- package/dist/{prompts-Pqc3JdW_.d.ts → prompts-DYcvqShf.d.ts} +14 -11
- package/dist/prompts-DYcvqShf.d.ts.map +1 -0
- package/dist/protocol-DnhqqSmE.js +13 -0
- package/dist/protocol-DnhqqSmE.js.map +1 -0
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +1 -2
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +3 -2
- package/dist/providers/cerebras.js.map +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -2
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +15 -12
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +3 -2
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +3 -3
- package/dist/{providers-BVhnFyiY.js → providers-XUR7F4Hi.js} +4 -4
- package/dist/{providers-BVhnFyiY.js.map → providers-XUR7F4Hi.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/registry-D6ShBWJU.js +5234 -0
- package/dist/registry-D6ShBWJU.js.map +1 -0
- package/dist/remote-hosts-BtA_9ZGp.js +97 -0
- package/dist/remote-hosts-BtA_9ZGp.js.map +1 -0
- package/dist/restate.d.ts +2 -3
- package/dist/restate.d.ts.map +1 -1
- package/dist/restate.js +631 -572
- package/dist/restate.js.map +1 -1
- package/dist/run-summary-CklLiZ4y.js +288 -0
- package/dist/run-summary-CklLiZ4y.js.map +1 -0
- package/dist/{sandbox-BH0WQkXn.d.ts → sandbox-C5dR5DwQ.d.ts} +6 -3
- package/dist/sandbox-C5dR5DwQ.d.ts.map +1 -0
- package/dist/session/sqlite.d.ts +1 -2
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +1 -616
- package/dist/session-factory-BUGY5P40.js +1576 -0
- package/dist/session-factory-BUGY5P40.js.map +1 -0
- package/dist/{session-BqRP1hS7.js → session-xh9JXu6i.js} +94 -52
- package/dist/session-xh9JXu6i.js.map +1 -0
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/settings-keys-Ckf29NeM.js +677 -0
- package/dist/settings-keys-Ckf29NeM.js.map +1 -0
- package/dist/shell-path-Dqaoow2J.js +412 -0
- package/dist/shell-path-Dqaoow2J.js.map +1 -0
- package/dist/{skills-BC3dodWp.js → skills-DKqZJm4X.js} +430 -470
- package/dist/skills-DKqZJm4X.js.map +1 -0
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -2
- package/dist/source-mPExaUm8.js +381 -0
- package/dist/source-mPExaUm8.js.map +1 -0
- package/dist/sqlite-DV1DQzN2.js +698 -0
- package/dist/sqlite-DV1DQzN2.js.map +1 -0
- package/dist/{src-CkNQKFKr.js → src-BRMYRJib.js} +903 -1121
- package/dist/src-BRMYRJib.js.map +1 -0
- package/dist/{stdio-DPpBDPx5.js → stdio-C6YyoW3I.js} +4 -4
- package/dist/{stdio-DPpBDPx5.js.map → stdio-C6YyoW3I.js.map} +1 -1
- package/dist/{stdio-loader-Bbx19dMZ.js → stdio-loader-C830Vg-Z.js} +4 -4
- package/dist/{stdio-loader-Bbx19dMZ.js.map → stdio-loader-C830Vg-Z.js.map} +1 -1
- package/dist/system-prompt-MNVHatSD.d.ts.map +1 -1
- package/dist/timeout-DEaqslLO.d.ts.map +1 -1
- package/dist/todo-edits-Dn251YUP.js +1790 -0
- package/dist/todo-edits-Dn251YUP.js.map +1 -0
- package/dist/{turn-operations-DrbJlpq9.js → tool-formatters-IQFDrdrs.js} +1456 -2120
- package/dist/tool-formatters-IQFDrdrs.js.map +1 -0
- package/dist/tool-state-Dku6YqAB.js +989 -0
- package/dist/tool-state-Dku6YqAB.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -2
- package/dist/tools/fetch-url.d.ts.map +1 -1
- package/dist/tools/fetch-url.js +1 -2
- package/dist/tools/prompts.d.ts +1 -1
- package/dist/tools/prompts.js +7 -7
- package/dist/tools/prompts.js.map +1 -1
- package/dist/tools/web-search.d.ts +1 -2
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +2 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/tools-_DnT3AGM.js +578 -0
- package/dist/tools-_DnT3AGM.js.map +1 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +4 -2
- package/dist/tui.d.ts +534 -517
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +34933 -27243
- package/dist/tui.js.map +1 -1
- package/dist/turn-operations-B8sokwi5.js +333 -0
- package/dist/turn-operations-B8sokwi5.js.map +1 -0
- package/dist/turn-operations-DQX5ZZni.d.ts +3089 -0
- package/dist/turn-operations-DQX5ZZni.d.ts.map +1 -0
- package/dist/types-C7ZFOztE.d.ts +132 -0
- package/dist/types-C7ZFOztE.d.ts.map +1 -0
- package/dist/types-CH5XpIHw.js +114 -0
- package/dist/types-CH5XpIHw.js.map +1 -0
- package/dist/{types-CWgENVLY.js → types-Cu0ZGDu9.js} +10 -6
- package/dist/types-Cu0ZGDu9.js.map +1 -0
- package/dist/types-IqegbJEE.d.ts +624 -0
- package/dist/types-IqegbJEE.d.ts.map +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/types.js +1 -1
- package/dist/url-D8HCl65b.js +1259 -0
- package/dist/url-D8HCl65b.js.map +1 -0
- package/dist/wasm-BVT-j5cl.js +33 -0
- package/dist/wasm-BVT-j5cl.js.map +1 -0
- package/dist/workspace-exec-stream-SV2R9d5S.js +355 -0
- package/dist/workspace-exec-stream-SV2R9d5S.js.map +1 -0
- package/dist/ws-CIHMvlSh.js +952 -0
- package/dist/ws-CIHMvlSh.js.map +1 -0
- package/dist/ws-client-N7NxzvQp.js +230 -0
- package/dist/ws-client-N7NxzvQp.js.map +1 -0
- package/dist/{xai-CazjStOK.js → xai-CvJTeLrI.js} +165 -5
- package/dist/xai-CvJTeLrI.js.map +1 -0
- package/dist/xdg-zweed0-t.js +117 -0
- package/dist/xdg-zweed0-t.js.map +1 -0
- package/docs/ACP.md +52 -0
- package/docs/ARCHITECTURE.md +103 -6
- package/docs/ARTIFACTS.md +8 -4
- package/docs/CHAT.md +165 -14
- package/docs/EXECUTION_CONTEXT.md +8 -0
- package/docs/EXTENSIONS.md +123 -2
- package/docs/HEADLESS.md +2 -0
- package/docs/IPC.md +775 -64
- package/docs/MACHINES.md +456 -0
- package/docs/PANELS.md +3 -0
- package/docs/README.md +4 -0
- package/docs/RESTATE.md +3 -2
- package/docs/RUN_IN_BACKGROUND.md +10 -2
- package/docs/SERVER.md +1085 -0
- package/docs/SKILL.md +6 -1
- package/docs/TERMINAL.md +75 -0
- package/docs/TUI.md +65 -14
- package/package.json +58 -26
- package/scripts/eval.ts +4 -0
- package/dist/acp-D-tVyUjw.js.map +0 -1
- package/dist/agent-D0zZnGbt.js.map +0 -1
- package/dist/anthropic-C2aole0G.js.map +0 -1
- package/dist/approvals-DKaJZYlm.js.map +0 -1
- package/dist/atomic-write-B7zr1ddF.js.map +0 -1
- package/dist/auth-BG2rVKSt.js.map +0 -1
- package/dist/completion-core-BRo86R_L.js.map +0 -1
- package/dist/contexts/daytona.js.map +0 -1
- package/dist/contexts/docker.js.map +0 -1
- package/dist/contexts/e2b.js.map +0 -1
- package/dist/contexts-CRE9pw07.js.map +0 -1
- package/dist/eval.js.map +0 -1
- package/dist/format-BF745pLL.js.map +0 -1
- package/dist/glob-DluQFSw1.js +0 -270
- package/dist/glob-DluQFSw1.js.map +0 -1
- package/dist/headless.js.map +0 -1
- package/dist/history-search-C4bk1Wz_.js.map +0 -1
- package/dist/index-BgN1T3lk.d.ts.map +0 -1
- package/dist/index-C37_ppWv.d.ts.map +0 -1
- package/dist/index-C6NEd5y_.d.ts.map +0 -1
- package/dist/ipc-BKVIIOqH.js +0 -331
- package/dist/ipc-BKVIIOqH.js.map +0 -1
- package/dist/ipc.d.ts.map +0 -1
- package/dist/isolation-Kfoof0MM.d.ts.map +0 -1
- package/dist/json-rpc-rSWMUh9b.js +0 -192
- package/dist/json-rpc-rSWMUh9b.js.map +0 -1
- package/dist/logger--C1VQjwJ.js.map +0 -1
- package/dist/mcp-CgNd-f3F.js.map +0 -1
- package/dist/messages-2jcOA_5R.js.map +0 -1
- package/dist/openai-compat-Dy56weK8.js.map +0 -1
- package/dist/output-cap-C9zukcCj.js +0 -100
- package/dist/output-cap-C9zukcCj.js.map +0 -1
- package/dist/panels-B6gRk4bf.js +0 -789
- package/dist/panels-B6gRk4bf.js.map +0 -1
- package/dist/plugins-gbI7SBA6.js.map +0 -1
- package/dist/prompts-Pqc3JdW_.d.ts.map +0 -1
- package/dist/run-summary-BU_NgwUT.js +0 -269
- package/dist/run-summary-BU_NgwUT.js.map +0 -1
- package/dist/sandbox-BH0WQkXn.d.ts.map +0 -1
- package/dist/session/sqlite.js.map +0 -1
- package/dist/session-BqRP1hS7.js.map +0 -1
- package/dist/shell-path-DfdXrGg9.js +0 -1549
- package/dist/shell-path-DfdXrGg9.js.map +0 -1
- package/dist/skills-BC3dodWp.js.map +0 -1
- package/dist/src-CkNQKFKr.js.map +0 -1
- package/dist/todo-edits-UOwgxGKu.js +0 -528
- package/dist/todo-edits-UOwgxGKu.js.map +0 -1
- package/dist/tool-formatters-DoRLnfuD.d.ts +0 -759
- package/dist/tool-formatters-DoRLnfuD.d.ts.map +0 -1
- package/dist/transcript-anchors-BeudMbXx.d.ts +0 -1762
- package/dist/transcript-anchors-BeudMbXx.d.ts.map +0 -1
- package/dist/turn-operations-C8bMBDl_.d.ts +0 -1258
- package/dist/turn-operations-C8bMBDl_.d.ts.map +0 -1
- package/dist/turn-operations-DrbJlpq9.js.map +0 -1
- package/dist/types-CWgENVLY.js.map +0 -1
- package/dist/xai-CazjStOK.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages-BqiwcD2Q.js","names":[],"sources":["../src/session/messages-pairing.ts","../src/session/messages.ts"],"sourcesContent":["/**\n * Tool-call / tool-result pairing repair for wire-bound message lists:\n * `ensureToolResultPairing`, duplicate-id reminting, and invalid-pair drops.\n * Re-exported from `./messages`.\n */\n\nimport type { SessionContentBlock, SessionMessage } from '../types'\n\ntype ToolCallBlock = Extract<SessionContentBlock, { type: 'tool_call' }>\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\n// ---------------------------------------------------------------------------\n// dropInvalidToolPairings — load-time drop-only healing\n// ---------------------------------------------------------------------------\n\n/**\n * Classification of a single deletion {@link dropInvalidToolPairings}\n * performed. Surfaced via `onDrop` for telemetry.\n */\nexport type PairingDropKind\n = | 'duplicate-tool-call' // later instance of a reused tool_call id\n | 'duplicate-tool-result' // repeated callId within one user turn\n | 'orphan-tool-result' // tool_result with no matching tool_call in the immediately preceding assistant turn\n | 'empty-turn' // turn emptied by the drops above\n\nexport interface PairingDrop {\n kind: PairingDropKind\n /** Tool-call / tool-result id the drop concerns. Absent for empty-turn drops. */\n callId?: string\n /** Zero-based index into the INPUT `turns` array. */\n index: number\n}\n\n/**\n * Drop-only normalization of persisted history: delete structurally invalid\n * tool blocks so the transcript never re-triggers wire-time pairing repairs\n * turn after turn.\n *\n * Unlike {@link ensureToolResultPairing} — which SYNTHESIZES placeholder\n * content and must therefore stay wire-only (fabricated results would poison\n * persisted history and training data) — this pass performs pure deletions of\n * blocks that are provably invalid regardless of interpretation:\n *\n * | Corruption | Action |\n * |------------|--------|\n * | duplicate `tool_call` id within one run lane (across or within turns) | drop the later instance |\n * | duplicate `tool_result.callId` within one turn | drop the later instance |\n * | `tool_result` with no matching `tool_call` in the immediately preceding same-lane assistant turn | drop the block |\n * | turn emptied by the drops above (reasoning-only remainders count as empty) | drop the whole turn |\n *\n * Duplicate detection is scoped to the WIRE-VIEW lane: tool_call ids only\n * collide when both instances reach one model request. By default a turn's\n * lane is its raw `runId` — correct for parent/subagent interleaving, where\n * providers minting per-conversation counter ids (`call_0`, `call_1`)\n * legitimately reuse the same id across lanes. Callers that know the run\n * topology (see `createSession`) pass `laneForRun` to widen lanes to true\n * wire views: all depth-0 runs share the main conversation view, and a\n * resumed subagent shares its resume-chain ancestors' view — duplicates\n * across those runIds DO reach a single request (wire-time\n * `ensureToolResultPairing` mode 4 would strip them on every send) and are\n * healed here.\n *\n * Orphan `tool_use` blocks (a call whose result was never persisted —\n * interrupted run) are deliberately KEPT: they are authentic model output,\n * and the wire-time pass patches them with a synthetic result on every send\n * without touching history.\n *\n * Deletions cascade by design: dropping a duplicate `tool_call` orphans its\n * `tool_result`, which is then dropped too; a turn emptied by either is\n * removed entirely.\n *\n * Safe to persist: deterministic, idempotent (re-running on healed output is\n * a no-op returning the same reference), and fabricates nothing.\n *\n * Pure: does not mutate input turns or their content arrays.\n */\nexport function dropInvalidToolPairings<T extends { role: string, content: SessionContentBlock[], runId?: string }>(\n turns: T[],\n options: {\n onDrop?: (drop: PairingDrop) => void\n /**\n * Map a turn's `runId` to its wire-view lane. Turns whose lanes differ\n * never share a model request, so their tool_call ids may legitimately\n * collide. Defaults to the raw `runId` (undefined runIds share one\n * lane) — the conservative choice when run topology is unknown.\n */\n laneForRun?: (runId: string | undefined) => string\n } = {},\n): T[] {\n if (turns.length === 0)\n return turns\n\n const onDrop = options.onDrop ?? (() => {})\n const laneForRun = options.laneForRun ?? ((runId: string | undefined) => runId ?? '')\n const laneOf = (turn: T): string => laneForRun(turn.runId)\n\n // First-occurrence index of every assistant tool_call id, keyed per\n // wire-view lane — the first instance wins, matching\n // ensureToolResultPairing's mode-4 semantics. Lane-scoped because\n // counter-style ids (call_0) can legitimately repeat across parallel\n // lanes; only same-lane reuse (the retry/re-append corruption) is\n // provably invalid.\n const laneKey = (turn: T, id: string): string => `${laneOf(turn)}\\u0000${id}`\n const firstSeenAt = new Map<string, number>()\n for (let i = 0; i < turns.length; i++) {\n const turn = turns[i]\n if (turn.role !== 'assistant')\n continue\n for (const block of turn.content) {\n if (block.type === 'tool_call' && !firstSeenAt.has(laneKey(turn, block.id)))\n firstSeenAt.set(laneKey(turn, block.id), i)\n }\n }\n\n let changed = false\n const out: T[] = []\n\n for (let i = 0; i < turns.length; i++) {\n const turn = turns[i]\n\n if (turn.role === 'assistant') {\n const kept: SessionContentBlock[] = []\n const seenHere = new Set<string>()\n let dropped = false\n for (const block of turn.content) {\n if (block.type === 'tool_call') {\n if (firstSeenAt.get(laneKey(turn, block.id)) !== i || seenHere.has(block.id)) {\n dropped = true\n onDrop({ kind: 'duplicate-tool-call', callId: block.id, index: i })\n continue\n }\n seenHere.add(block.id)\n }\n kept.push(block)\n }\n if (!dropped) {\n out.push(turn)\n continue\n }\n changed = true\n // Reasoning-only remainders count as empty: the reasoning belonged to\n // the dropped duplicate call, and cross-provider sends may strip it\n // (sanitizeHistoryForProvider), leaving an empty assistant message\n // some providers 400 on.\n const onlyReasoning = kept.every(b =>\n b.type === 'thinking' || b.type === 'redacted_thinking' || b.type === 'provider_reasoning')\n if (kept.length === 0 || onlyReasoning) {\n onDrop({ kind: 'empty-turn', index: i })\n continue\n }\n out.push({ ...turn, content: kept })\n continue\n }\n\n if (turn.role !== 'user') {\n out.push(turn)\n continue\n }\n\n // tool_results are only valid against the immediately preceding EMITTED\n // assistant turn IN THE SAME WIRE-VIEW LANE — subagent turns interleave\n // in the flat session array under different runIds, so a parent's\n // tool_result can sit several turns after its tool_call with the child's\n // turns in between. Wire builders view one lane at a time, where the\n // pair IS adjacent. Display-only roles ('system', 'command') are\n // filtered from every wire builder, so they don't break adjacency\n // either. Using `out` (not the input) makes cascades work: results of a\n // dropped duplicate tool_call are orphans here.\n const lane = laneOf(turn)\n let prev: T | null = null\n for (let j = out.length - 1; j >= 0; j--) {\n const candidate = out[j]\n if (candidate.role === 'system' || candidate.role === 'command')\n continue\n if (laneOf(candidate) !== lane)\n continue\n prev = candidate\n break\n }\n const validIds = prev && prev.role === 'assistant'\n ? collectIds<ToolCallBlock>(prev.content, 'tool_call', b => b.id)\n : new Set<string>()\n\n const kept: SessionContentBlock[] = []\n const seenCallIds = new Set<string>()\n let dropped = false\n for (const block of turn.content) {\n if (block.type !== 'tool_result') {\n kept.push(block)\n continue\n }\n if (seenCallIds.has(block.callId)) {\n dropped = true\n onDrop({ kind: 'duplicate-tool-result', callId: block.callId, index: i })\n continue\n }\n seenCallIds.add(block.callId)\n if (!validIds.has(block.callId)) {\n dropped = true\n onDrop({ kind: 'orphan-tool-result', callId: block.callId, index: i })\n continue\n }\n kept.push(block)\n }\n if (!dropped) {\n out.push(turn)\n continue\n }\n changed = true\n if (kept.length === 0) {\n onDrop({ kind: 'empty-turn', index: i })\n continue\n }\n out.push({ ...turn, content: kept })\n }\n\n return changed ? out : turns\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 * 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 type { PairingRepair } from './messages-pairing'\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, isWireTurn } from '../types'\nimport { ensureToolResultPairing } from './messages-pairing'\n\ninterface ContentRefBlock {\n type: 'image' | 'document'\n ref: string\n mediaType: string\n encoding?: 'base64' | 'text'\n name?: string\n}\n\nexport {\n dropInvalidToolPairings,\n ensureToolResultPairing,\n ORPHANED_TOOL_RESULT_MARKER,\n remintDuplicateToolCallIds,\n SYNTHETIC_TOOL_RESULT_PLACEHOLDER,\n TOOL_USE_INTERRUPTED_MARKER,\n} from './messages-pairing'\nexport type {\n EnsureToolResultPairingOptions,\n PairingDrop,\n PairingDropKind,\n PairingRepair,\n PairingRepairMode,\n RemintResult,\n} from './messages-pairing'\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// Cross-provider reasoning wire policy\n// ---------------------------------------------------------------------------\n\n/** What a wire builder should do with a reasoning block for a given target. */\nexport type ReasoningWireAction = 'keep' | 'drop' | 'strip-signature'\n\n/**\n * Single source of truth for whether a signature-bearing reasoning block\n * (`thinking` / `redacted_thinking`) may be forwarded to a provider. Both\n * wire builders ({@link toAnthropic}, the OpenAI assistant-message shaper)\n * and the host-facing {@link sanitizeHistoryForProvider} apply this same\n * policy — keeping them one function means the sanitizer can never drift\n * from what the wire actually sends.\n *\n * Rules, each driven by an API constraint hit in production:\n *\n * - target `anthropic`:\n * - `redacted_thinking` → keep (native Anthropic block; must be echoed).\n * - `thinking` tagged `openai` → drop (OpenAI `encrypted_content` is not\n * a valid Anthropic HMAC; 400s).\n * - unsigned `thinking` → drop (`ThinkingBlockParam` requires a\n * signature; unsigned blocks come from non-signing producers).\n * - signed but UNTAGGED `thinking` (pre-tagging sessions) → keep: it is\n * most plausibly legacy Anthropic output, and the signature-400\n * recovery converges if it is not.\n *\n * - target `openai`:\n * - `redacted_thinking` → drop (Anthropic-only block type).\n * - `thinking` tagged `anthropic` → drop (an Anthropic HMAC is not\n * valid `encrypted_content`).\n * - signed but UNTAGGED `thinking` → strip-signature: unlike the\n * Anthropic direction there is no signature-error recovery on this\n * path, so forwarding a possibly-foreign signature risks an\n * unrecoverable 400 — the reasoning text survives, only the opaque\n * signature is withheld.\n */\nexport function reasoningWireAction(\n block: { type: 'thinking', signature?: string, signatureProducer?: 'anthropic' | 'openai' } | { type: 'redacted_thinking' },\n target: 'anthropic' | 'openai',\n): ReasoningWireAction {\n if (block.type === 'redacted_thinking')\n return target === 'anthropic' ? 'keep' : 'drop'\n if (target === 'anthropic') {\n if (block.signatureProducer === 'openai')\n return 'drop'\n if (!block.signature)\n return 'drop'\n return 'keep'\n }\n if (block.signatureProducer === 'anthropic')\n return 'drop'\n if (block.signature && block.signatureProducer !== 'openai')\n return 'strip-signature'\n return 'keep'\n}\n\n/**\n * Host-facing history sanitizer — applies {@link reasoningWireAction} to a\n * turn set BEFORE it is handed to an agent targeting `target`. The in-loop\n * wire builders already enforce the same policy per call, so this is for\n * hosts that seed history from an external durable store (or hydrate\n * pre-provenance-tagging sessions) and want the poison removed from their\n * source of truth in one call instead of re-deriving block semantics.\n *\n * Returns new turn objects only where something changed (unchanged turns\n * pass through by reference). A turn whose content empties keeps the same\n * neutral marker the wire repair uses, so role boundaries stay intact.\n */\nexport function sanitizeHistoryForProvider<T extends { content: SessionContentBlock[] }>(\n turns: readonly T[],\n target: 'anthropic' | 'openai',\n): { turns: T[], removed: number, stripped: number } {\n let removed = 0\n let stripped = 0\n const out = turns.map((turn) => {\n let changed = false\n const content: SessionContentBlock[] = []\n for (const block of turn.content) {\n if (block.type !== 'thinking' && block.type !== 'redacted_thinking') {\n content.push(block)\n continue\n }\n const action = reasoningWireAction(block, target)\n if (action === 'drop') {\n removed += 1\n changed = true\n continue\n }\n if (action === 'strip-signature' && block.type === 'thinking') {\n const { signature: _sig, signatureProducer: _producer, ...rest } = block\n content.push(rest)\n stripped += 1\n changed = true\n continue\n }\n content.push(block)\n }\n if (!changed)\n return turn\n return {\n ...turn,\n content: content.length > 0\n ? content\n : [{ type: 'text' as const, text: '[historical reasoning omitted]' }],\n }\n })\n return { turns: out, removed, stripped }\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. Signature-bearing blocks follow the\n // shared {@link reasoningWireAction} policy (see its doc for the API\n // constraints behind each rule); `provider_reasoning` is opaque\n // provider-bound state that is not an Anthropic block type at all.\n .filter((b) => {\n if (b.type === 'provider_reasoning')\n return false\n if (b.type !== 'thinking' && b.type !== 'redacted_thinking')\n return true\n return reasoningWireAction(b, 'anthropic') === 'keep'\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 // Drop strictly-empty text blocks — the API rejects `text` blocks with\n // empty text inside a content array (\"text content blocks must be\n // non-empty\"), and a provider refusal (`stop_reason: refusal`) persists\n // exactly that shape (`[{type:'text', text:''}]`, sometimes next to a\n // thinking block). Without this filter, ONE refusal turn could 400 every\n // subsequent request in the session. Whitespace-only blocks are kept\n // as-is: they shipped (and were accepted) before this filter existed, and\n // degrading them to `''` would produce the documented-invalid empty\n // content shape for non-final messages. Deterministic on message content,\n // so prompt-cache prefixes stay stable across requests.\n const nonEmpty = blocks.filter(b => !(b.type === 'text' && b.text.length === 0))\n\n // A message whose content was ONLY empty text (or unknown blocks) degrades\n // to plain-string `''` — the shape refusal turns have always produced via\n // the single-text simplification below, observed accepted by the live API.\n if (nonEmpty.length === 0)\n return { role: msg.role, content: '' }\n\n // Simplify to plain string if only one text block\n const first = nonEmpty[0]\n if (nonEmpty.length === 1 && first.type === 'text')\n return { role: msg.role, content: first.text }\n\n return { role: msg.role, content: nonEmpty }\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// 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' | 'command', content: SessionContentBlock[] }[],\n options: ToWireMessagesOptions = {},\n): Promise<SessionMessage[]> {\n const messages: SessionMessage[] = []\n for (const t of turns) {\n // Only wire turns reach the model (drops out-of-band `system` +\n // display-only `command`). Shared, fail-safe guard — see `isWireTurn`.\n if (!isWireTurn(t))\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":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { u as errorMessage } from "./errors-DKiMrOKq.js";
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { n as writeFileAtomic } from "./atomic-write-Ijgf2Mhc.js";
|
|
4
3
|
import { Buffer } from "node:buffer";
|
|
5
4
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -120,7 +120,7 @@ const XAI_SEED_ID = "xai-oauth";
|
|
|
120
120
|
* to it.
|
|
121
121
|
*/
|
|
122
122
|
async function refreshXaiTokenSeed(credentials) {
|
|
123
|
-
const { refreshXaiToken } = await import("./xai-
|
|
123
|
+
const { refreshXaiToken } = await import("./xai-CvJTeLrI.js").then((n) => n.i);
|
|
124
124
|
return refreshXaiToken(credentials);
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
@@ -135,7 +135,7 @@ const CURSOR_SEED_ID = "cursor";
|
|
|
135
135
|
* consumer that merely resolves a credential.
|
|
136
136
|
*/
|
|
137
137
|
async function refreshCursorTokenSeed(credentials) {
|
|
138
|
-
const { refreshCursorToken } = await import("./cursor-
|
|
138
|
+
const { refreshCursorToken } = await import("./cursor-CdjaQTBW.js").then((n) => n.r);
|
|
139
139
|
return refreshCursorToken(credentials);
|
|
140
140
|
}
|
|
141
141
|
/**
|
|
@@ -523,4 +523,4 @@ async function resolveOAuthApiKey(options, callbacks) {
|
|
|
523
523
|
//#endregion
|
|
524
524
|
export { readOAuthCredentials as a, withCredentialsLock as c, getOAuthApiKey as d, getOAuthProvider as f, registerOAuthProvider as h, isOAuthAccessToken as i, writeOAuthCredentials as l, refreshOpenAICodexToken as m, credentialsFromParams as n, registerCustomOAuthProvider as o, refreshAnthropicToken as p, extractRuntimeCredentials as r, resolveOAuthApiKey as s, credentialsFilePath as t, ensureBundledOAuthFlows as u };
|
|
525
525
|
|
|
526
|
-
//# sourceMappingURL=oauth-
|
|
526
|
+
//# sourceMappingURL=oauth-Px_pBB9W.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-DxHO17JE.js","names":[],"sources":["../src/providers/oauth-registry.ts","../src/providers/oauth.ts"],"sourcesContent":["/**\n * Local OAuth provider registry.\n *\n * pi-ai 0.80.8 removed its global OAuth registry (`registerOAuthProvider`,\n * `getOAuthApiKey`, `OAuthProviderInterface`) and the public low-level\n * refresh helpers in favor of canonical `Provider.auth.oauth` objects\n * resolved through a `Models` collection + `CredentialStore`. zidane's\n * credential architecture (shared `.credentials.json`, cross-process refresh\n * locks in `./oauth.ts`, themed login flows in `src/chat/oauth-page/`)\n * predates that and owns storage itself, so this module reproduces the\n * removed registry surface locally with the same semantics.\n *\n * Nothing protocol-level is duplicated here: token refresh for the built-in\n * providers delegates to pi-ai's canonical `auth.oauth.refresh` flows via the\n * public `providers/*` subpaths (lazily imported, so processes that never\n * authenticate don't load provider code).\n */\n\nimport type { OAuthAuth } from '@earendil-works/pi-ai'\nimport type { OAuthCredentials, OAuthLoginCallbacks } from '@earendil-works/pi-ai/oauth'\nimport { errorMessage } from '../errors'\n\n/**\n * Provider-owned OAuth flow. Mirrors the interface pi-ai exported from\n * `@earendil-works/pi-ai/oauth` up to 0.80.6 — kept as a local type so\n * zidane's descriptor seam (`ProviderDescriptor.oauthProvider`) and provider\n * extensions (e.g. `extensions/bolt`) stay source-compatible across the\n * upstream removal.\n */\nexport interface OAuthProviderInterface {\n readonly id: string\n readonly name: string\n\n /** Run the login flow, return credentials to persist. */\n login: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials>\n\n /** Whether login uses a local callback server and supports manual code input. */\n usesCallbackServer?: boolean\n\n /** Refresh expired credentials, return updated credentials to persist. */\n refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>\n\n /** Convert credentials to an API key string for the provider. */\n getApiKey: (credentials: OAuthCredentials) => string\n}\n\n/**\n * Run a canonical pi-ai `OAuthAuth.refresh` from a bare refresh token.\n * pi-ai's built-in refresh implementations only read `credential.refresh`,\n * so the placeholder access/expires fields are never observed.\n *\n * The canonical result is an `OAuthCredential` tagged `type: 'oauth'`. Strip\n * the tag before returning: refresh results get merged into stored\n * `.credentials.json` entries and `onOAuthRefresh` hook payloads (see\n * `resolveCredentialSource` in `./oauth.ts`), whose documented shape carries\n * zidane's own `kind` tag — letting pi-ai's `type` leak through would\n * silently drift the on-disk shape.\n */\nasync function refreshViaCanonicalFlow(\n providerId: string,\n oauth: OAuthAuth | undefined,\n refreshToken: string,\n): Promise<OAuthCredentials> {\n if (!oauth)\n throw new Error(`pi-ai provider ${providerId} exposes no OAuth flow`)\n // pi-ai 0.84 made `signal` a required second argument. These refreshers are\n // called from token-expiry paths that carry no cancellation of their own, so\n // a never-aborting signal keeps the pre-0.84 behavior exactly — same idiom as\n // `completion-core.ts`.\n const { type: _type, ...credentials }\n = await oauth.refresh(\n { type: 'oauth', access: '', refresh: refreshToken, expires: 0 },\n new AbortController().signal,\n )\n return credentials\n}\n\n/**\n * pi-ai ≥ 0.85 loads each OAuth flow module through a VARIABLE specifier\n * (`auth/oauth/load.js`: `import(rewrite(\"./anthropic.ts\"))`) precisely so\n * bundlers cannot follow it. Fine under plain Node/Bun where the file sits\n * next to `load.js`; fatal inside anything bundled — the compiled `zidane`\n * binary, the packaged Electron main — where the refresh path died with\n * `Cannot find module './anthropic.js'` and the user was told to re-run auth\n * for a token that was perfectly renewable.\n *\n * pi-ai ships the escape hatch for exactly this: `bun-oauth` statically\n * imports every flow and registers them with the loader. We call it lazily,\n * once, right before the first refresh — a static specifier the bundler DOES\n * follow — so every host (TUI binary, GUI, daemon, SDK) refreshes the same\n * way. Idempotent; failure to register falls through to the loader's own\n * behavior (works unbundled, so nothing gets worse).\n */\nlet bundledFlowsReady: Promise<void> | null = null\nexport function ensureBundledOAuthFlows(): Promise<void> {\n bundledFlowsReady ??= import('@earendil-works/pi-ai/bun-oauth')\n .then(({ registerBunOAuthFlows }) => {\n registerBunOAuthFlows()\n })\n .catch(() => {\n bundledFlowsReady = null\n })\n return bundledFlowsReady\n}\n\n/**\n * Refresh an Anthropic (Claude Pro/Max) OAuth token. Drop-in replacement for\n * the `refreshAnthropicToken` pi-ai exported until 0.80.6; delegates to the\n * canonical flow on `anthropicProvider().auth.oauth`.\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n await ensureBundledOAuthFlows()\n const { anthropicProvider } = await import('@earendil-works/pi-ai/providers/anthropic')\n return refreshViaCanonicalFlow('anthropic', anthropicProvider().auth.oauth, refreshToken)\n}\n\n/**\n * Refresh an OpenAI Codex (ChatGPT) OAuth token. Drop-in replacement for the\n * removed `refreshOpenAICodexToken`; the canonical flow still derives\n * `accountId` from the refreshed access token's JWT claims, so the returned\n * credentials keep the extra field older callers persisted.\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n await ensureBundledOAuthFlows()\n const { openaiCodexProvider } = await import('@earendil-works/pi-ai/providers/openai-codex')\n return refreshViaCanonicalFlow('openai-codex', openaiCodexProvider().auth.oauth, refreshToken)\n}\n\n/**\n * Built-in registry seeds. Every id whose stored credential can expire has to\n * be seeded here, because zidane's lazy token refresh (`getOAuthApiKey` inside\n * `resolveOAuthApiKey`) resolves through the registry — an unseeded id is not a\n * degraded path but a dead end, with no way to renew the session. That is wider\n * than the Anthropic / OpenAI Codex pair pi-ai's removed registry pre-seeded:\n * flows zidane implements itself (xAI) need seeding just as much.\n *\n * Login is intentionally unsupported here: interactive login always goes\n * through `ProviderDescriptor.oauthProvider` (the themed flows in\n * `src/chat/oauth-page/`), never through the registry.\n */\nfunction builtinProvider(\n id: string,\n name: string,\n refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>,\n): OAuthProviderInterface {\n return {\n id,\n name,\n login: async () => {\n throw new Error(`OAuth login for ${id} must run through a provider descriptor flow, not the registry`)\n },\n refreshToken,\n getApiKey: credentials => credentials.access,\n }\n}\n\n/**\n * Seed id for the xAI Grok OAuth flow. Duplicated from\n * `XAI_OAUTH_PROVIDER_ID` in `../chat/oauth-page/xai` rather than imported:\n * that module pulls in `node:http` and the PKCE helpers at module scope, and\n * this registry is loaded by every consumer that resolves a credential. The\n * ids are pinned together by a test, so the duplication cannot drift.\n */\nconst XAI_SEED_ID = 'xai-oauth'\n\n/**\n * Refresh an xAI Grok token. Imported lazily so seeding the registry does not\n * drag the login flow's `node:http` callback server into every consumer —\n * and so the static import edge back to this module stays type-only, leaving\n * no runtime cycle.\n *\n * Unlike the Anthropic / Codex seeds, this receives the FULL credential rather\n * than a bare refresh token: xAI stores its resolved `token_endpoint` (and the\n * cached discovery doc) alongside the tokens, and dropping them would force a\n * fresh OIDC discovery round-trip on every refresh — and discard the validated\n * endpoint that `refreshXaiToken` re-checks before posting the refresh token\n * to it.\n */\nasync function refreshXaiTokenSeed(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const { refreshXaiToken } = await import('../chat/oauth-page/xai')\n return refreshXaiToken(credentials)\n}\n\n/**\n * Seed id for the Cursor OAuth flow. Duplicated from\n * `CURSOR_OAUTH_PROVIDER_ID` for the same reason as {@link XAI_SEED_ID}, and\n * pinned to it by a test.\n */\nconst CURSOR_SEED_ID = 'cursor'\n\n/**\n * Refresh a Cursor token. Lazy for the same reason as the xAI seed: the login\n * module owns a callback server, and seeding must not drag it into every\n * consumer that merely resolves a credential.\n */\nasync function refreshCursorTokenSeed(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const { refreshCursorToken } = await import('../chat/oauth-page/cursor')\n return refreshCursorToken(credentials)\n}\n\n/**\n * The registry map is anchored on a process-wide `globalThis` slot rather\n * than module state. The pi-ai registry this replaces lived in pi-ai's own\n * module, so every copy of zidane code in a process shared one map — and two\n * copies is a real configuration, not a hypothetical: a host running from\n * `src` while a disk-loaded extension resolves `'zidane'` to `dist` through\n * ordinary node_modules resolution (see `import()` in\n * `src/extensions/discovery.ts`). The extension registers its flow at setup;\n * `resolveOAuthApiKey` looks it up only once a stored token expires — with\n * per-module maps that fails as a deferred \"Unknown OAuth provider\", which is\n * nasty to debug. `Symbol.for` restores the process-wide sharing. Whichever\n * copy touches the slot first seeds the built-ins, so a `dist` consumer can end\n * up refreshing through the `src` copy's closure (and vice versa). That is\n * harmless because the seeds are STATELESS refresh delegations — they close over\n * no module state and no configuration, so the two copies compute the same\n * result from the same stored credential. It is that property, not where the\n * refresh happens to be implemented, that makes first-copy-wins safe: it holds\n * equally for the seeds that delegate to pi-ai's canonical flows and for the\n * xAI one implemented in `../chat/oauth-page/xai`.\n */\n/**\n * Exported so tests that register a throwaway provider can remove it from the\n * same slot rather than re-deriving the key with a copied string: the registry\n * exposes no unregister, and a `Symbol.for` looked up from a duplicated literal\n * would silently stop matching if this key were ever renamed, leaving the test\n * provider resident in shared state. Deliberately kept out of the package's\n * public surface — `./index.ts` re-exports this module by explicit name list.\n */\nexport const REGISTRY_SLOT = Symbol.for('zidane.oauth-provider-registry')\n\nfunction oauthProviderRegistry(): Map<string, OAuthProviderInterface> {\n const slots = globalThis as { [key: symbol]: Map<string, OAuthProviderInterface> | undefined }\n slots[REGISTRY_SLOT] ??= new Map(\n [\n builtinProvider('anthropic', 'Anthropic (Claude Pro/Max)', c => refreshAnthropicToken(c.refresh)),\n builtinProvider('openai-codex', 'OpenAI (ChatGPT Plus/Pro)', c => refreshOpenAICodexToken(c.refresh)),\n // xAI is NOT built into pi-ai, so unlike the two above there is no\n // canonical flow to delegate to — `../chat/oauth-page/xai` implements it.\n // It is seeded here anyway because seeding is what makes refresh work for\n // consumers that never import `chat/providers.ts` or `auth.ts`, whose\n // module-scope `registerXaiOAuthProvider()` call was previously the ONLY\n // thing that registered it. A library consumer resolving a stored\n // credential through `getOAuthApiKey` got `Unknown OAuth provider:\n // xai-oauth` and had no way to renew a session — a dead end rather than a\n // degraded path. The interactive login half still comes from\n // `registerXaiOAuthProvider(renderPage)`, which replaces this seed.\n builtinProvider(XAI_SEED_ID, 'xAI Grok (SuperGrok / X Premium+)', refreshXaiTokenSeed),\n // Cursor has the identical shape and had the identical defect: its flow\n // lives in `../chat/oauth-page/cursor` and was registered only as a\n // module-scope side effect of importing `chat/providers.ts` / `auth.ts`.\n builtinProvider(CURSOR_SEED_ID, 'Cursor', refreshCursorTokenSeed),\n ].map(provider => [provider.id, provider]),\n )\n return slots[REGISTRY_SLOT]\n}\n\n/** Register (or replace) an OAuth provider by id. */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n oauthProviderRegistry().set(provider.id, provider)\n}\n\n/** Get a registered OAuth provider by id. */\nexport function getOAuthProvider(id: string): OAuthProviderInterface | undefined {\n return oauthProviderRegistry().get(id)\n}\n\n/**\n * Get an API key for a provider from OAuth credentials, refreshing expired\n * tokens through the registered flow. Same contract as the pi-ai original\n * (`null` when no credentials are stored for the provider, throws on unknown\n * provider or refresh failure), with two deliberate additions:\n *\n * - refresh failures keep the underlying reason — the original discarded it,\n * leaving invalid_grant (needs re-login) indistinguishable from a transient\n * network error.\n * - `options.force` rotates regardless of the local expiry, for 401 recovery\n * when the server revoked the grant before our stored timestamp said so.\n * Expressed as a flag rather than by handing this function a credential\n * with a doctored `expires`: callers persist whatever comes back, so a\n * fabricated expiry that a provider echoes through would land in the\n * credential store. See `resolveCredentialSource` in `./oauth.ts`.\n */\nexport async function getOAuthApiKey(\n providerId: string,\n credentials: Record<string, OAuthCredentials>,\n options?: { force?: boolean },\n): Promise<{ newCredentials: OAuthCredentials, apiKey: string } | null> {\n const provider = oauthProviderRegistry().get(providerId)\n if (!provider)\n throw new Error(`Unknown OAuth provider: ${providerId}`)\n\n let creds = credentials[providerId]\n if (!creds)\n return null\n\n if (options?.force || Date.now() >= creds.expires) {\n try {\n creds = await provider.refreshToken(creds)\n }\n catch (err) {\n throw new Error(`Failed to refresh OAuth token for ${providerId}: ${errorMessage(err)}`, { cause: err })\n }\n }\n\n return { newCredentials: creds, apiKey: provider.getApiKey(creds) }\n}\n","import type { OAuthCredentials } from '@earendil-works/pi-ai/oauth'\nimport type { StreamCallbacks } from '.'\nimport type { OAuthRefreshHookContext } from '../types'\nimport { Buffer } from 'node:buffer'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, resolve } from 'node:path'\nimport { isDeepStrictEqual } from 'node:util'\nimport { writeFileAtomic } from '../atomic-write'\nimport { errorMessage } from '../errors'\nimport { getOAuthApiKey, registerOAuthProvider } from './oauth-registry'\n\ntype GetOAuthApiKey = typeof 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\ninterface RefreshResult {\n apiKey: string\n event?: OAuthRefreshHookContext\n}\n\nconst refreshLocks = new Map<string, Promise<RefreshResult>>()\nconst storeIds = new WeakMap<object, number>()\nlet nextStoreId = 0\nfunction identity(value: object): number {\n let id = storeIds.get(value)\n if (id === undefined) {\n id = ++nextStoreId\n storeIds.set(value, id)\n }\n return id\n}\n\n/**\n * Register a custom OAuth flow with the local registry (`./oauth-registry`)\n * so lazy refresh through {@link resolveOAuthApiKey} can resolve `providerId`.\n *\n * Public because provider extensions need it: without registration,\n * `getOAuthApiKey('<id>', …)` throws `Unknown OAuth provider` the first time a\n * stored token expires. Idempotent per id (the registry is a keyed map). Kept\n * async for API stability — it awaited a dynamic pi-ai import before pi-ai\n * 0.80.8 removed its global registry and this moved in-repo.\n */\nexport async function registerCustomOAuthProvider(\n provider: import('./oauth-registry').OAuthProviderInterface,\n): Promise<void> {\n registerOAuthProvider(provider)\n}\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 /** Synchronous provider-local patch writer; injected stores own cross-process coordination. */\n writeCredentials?: (credentials: Record<string, OAuthCredentials | undefined>) => void\n getOAuthApiKey?: GetOAuthApiKey\n /**\n * Force a refresh even when the credential's local expiry is still in the\n * future (for one-shot 401 recovery). Reaches the refresh helper as an\n * explicit flag, so no credential with a fabricated expiry ever exists to\n * leak into the store — see {@link getOAuthApiKey}.\n */\n forceRefresh?: boolean\n /** Access token rejected by the server; a different token found under lock is reused instead of rotated again. */\n rejectedAccess?: string\n /**\n * Credentials-file key to read and persist, when it differs from\n * `providerId`. A provider holding several OAuth accounts parks the\n * non-active ones under sibling keys (`anthropic#account:1`); refreshing one\n * of those must rotate THAT slot, under its own lock, while the registered\n * flow is still looked up by `providerId`. Defaults to `providerId`.\n */\n credentialKey?: string\n /**\n * Credentials file to use instead of {@link credentialsFilePath}. Callers that\n * already know the data directory (the usage probes) pass it so the default\n * store's locking and coalescing apply to the right file without depending\n * on `ZIDANE_CREDENTIALS_PATH` having been exported.\n */\n credentialsPath?: string\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 return readOAuthCredentialsAt(credentialsFilePath())\n}\n\nfunction readOAuthCredentialsAt(path: string): Record<string, OAuthCredentials | undefined> {\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/** Never steal a lock based on age: a suspended holder may still own it. */\nfunction tryAcquireLock(lockPath: string): (() => void) | undefined {\n const owner = `${process.pid}:${randomUUID()}`\n try {\n writeFileSync(lockPath, owner, { flag: 'wx', mode: CREDENTIALS_FILE_MODE })\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST')\n return undefined\n throw err\n }\n return () => {\n try {\n if (readFileSync(lockPath, 'utf8') === owner)\n unlinkSync(lockPath)\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT')\n throw err\n }\n }\n}\n\n/** Shared by refresh persistence and interactive credential mutations. */\nexport function withCredentialsLock<T>(path: string, fn: () => T): T {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 })\n const deadline = Date.now() + 2000\n let release = tryAcquireLock(`${path}.lock`)\n while (!release) {\n if (Date.now() >= deadline)\n throw new Error('Timed out waiting for credentials lock')\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20)\n release = tryAcquireLock(`${path}.lock`)\n }\n try {\n return fn()\n }\n finally {\n release()\n }\n}\n\nasync function acquireRefreshLock(lockPath: string): Promise<() => void> {\n mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 })\n const deadline = Date.now() + 30_000\n while (true) {\n const release = tryAcquireLock(lockPath)\n if (release)\n return release\n if (Date.now() >= deadline)\n throw new Error('Timed out waiting for OAuth refresh lock')\n await new Promise(resolve => setTimeout(resolve, 50))\n }\n}\n\nfunction readRawCredentials(path: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed\n }\n catch { /* Missing or corrupt file. */ }\n return {}\n}\n\nfunction writeCredentialPatch(path: string, credentials: Record<string, unknown>): void {\n const merged = { ...readRawCredentials(path), ...credentials }\n for (const [key, value] of Object.entries(merged)) {\n if (value === undefined)\n delete merged[key]\n }\n writeFileAtomic(path, JSON.stringify(merged, null, 2), { mode: CREDENTIALS_FILE_MODE })\n}\n\n/** Incoming entries are a provider-local patch, not a read-time snapshot. */\nexport function writeOAuthCredentials(credentials: Record<string, OAuthCredentials | undefined>) {\n const path = credentialsFilePath()\n withCredentialsLock(path, () => writeCredentialPatch(path, credentials))\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'> & { onOAuthStage?: (stage: 'credentials' | 'refresh' | 'shared-refresh') => void },\n): Promise<string> {\n if (typeof options.params?.apiKey === 'string') {\n if (isOAuthAccessToken(options.params.apiKey))\n callbacks?.onOAuthStage?.('credentials')\n return options.params.apiKey\n }\n const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys)\n if (!paramsCredentials && typeof options.params?.access === 'string') {\n callbacks?.onOAuthStage?.('credentials')\n return options.params.access\n }\n\n // Capture the store before awaiting: environment changes cannot redirect writes.\n const path = resolve(options.credentialsPath ?? credentialsFilePath())\n const slot = options.credentialKey ?? options.providerId\n const usingDefaultStore = !options.readCredentials && !options.writeCredentials\n const read = options.readCredentials ?? (() => readOAuthCredentialsAt(path))\n const write = options.writeCredentials ?? (patch => withCredentialsLock(path, () => writeCredentialPatch(path, patch)))\n const envValue = options.envKey ? process.env[options.envKey] : undefined\n if (!paramsCredentials && envValue && (!isOAuthAccessToken(envValue) || !read()[slot])) {\n if (isOAuthAccessToken(envValue))\n callbacks?.onOAuthStage?.('credentials')\n return envValue\n }\n\n callbacks?.onOAuthStage?.('credentials')\n const source = paramsCredentials ? 'params' : 'file'\n const initial = paramsCredentials ?? read()[slot]\n const store = usingDefaultStore ? path : [identity(options.readCredentials ?? readOAuthCredentials), identity(options.writeCredentials ?? writeOAuthCredentials)]\n const key = JSON.stringify([source, options.providerId, slot, source === 'file' ? store : null, initial, options.forceRefresh === true, options.rejectedAccess, identity(options.getOAuthApiKey ?? getOAuthApiKey)])\n let task = refreshLocks.get(key)\n if (task)\n callbacks?.onOAuthStage?.('shared-refresh')\n if (!task) {\n task = run()\n refreshLocks.set(key, task)\n // Delete before callbacks run; a failing subscriber must not poison peers.\n void task.finally(() => refreshLocks.delete(key)).catch(() => {})\n }\n const result = await task\n if (result.event) {\n await callbacks?.onOAuthRefresh?.({\n ...structuredClone(result.event),\n provider: options.provider,\n })\n }\n return result.apiKey\n\n async function run(): Promise<RefreshResult> {\n const release = source === 'file' && usingDefaultStore\n ? await acquireRefreshLock(`${path}.refresh-${encodeURIComponent(slot)}.lock`)\n : undefined\n try {\n const current = paramsCredentials ?? read()[slot]\n if (!current)\n throw new Error(options.missingError)\n const generation = structuredClone(current)\n let result: Awaited<ReturnType<GetOAuthApiKey>>\n try {\n const stillRejected = options.rejectedAccess === undefined || current.access === options.rejectedAccess\n if (current.expires <= Date.now() || (options.forceRefresh && stillRejected))\n callbacks?.onOAuthStage?.('refresh')\n result = await (options.getOAuthApiKey ?? getOAuthApiKey)(\n options.providerId,\n { [options.providerId]: current },\n { force: options.forceRefresh === true && stillRejected },\n )\n }\n catch (err) {\n throw new Error(options.refreshError(errorMessage(err)), { cause: err })\n }\n if (!result)\n throw new Error(options.refreshError(options.missingError))\n const refreshed = { ...generation, ...result.newCredentials }\n if (source === 'file') {\n const persist = () => {\n if (!isDeepStrictEqual(read()[slot], generation))\n throw new Error('OAuth credentials changed during refresh; retry with current credentials')\n if (result.newCredentials !== current) {\n const patch = { [slot]: refreshed }\n if (usingDefaultStore)\n writeCredentialPatch(path, patch)\n else\n write(patch)\n }\n }\n if (usingDefaultStore)\n withCredentialsLock(path, persist)\n else\n persist()\n }\n return {\n apiKey: result.apiKey,\n ...(result.newCredentials !== current\n ? { event: {\n provider: options.provider,\n providerId: options.providerId,\n source,\n previousCredentials: generation,\n credentials: refreshed,\n } }\n : {}),\n }\n }\n finally {\n release?.()\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oauth-Px_pBB9W.js","names":[],"sources":["../src/providers/oauth-registry.ts","../src/providers/oauth.ts"],"sourcesContent":["/**\n * Local OAuth provider registry.\n *\n * pi-ai 0.80.8 removed its global OAuth registry (`registerOAuthProvider`,\n * `getOAuthApiKey`, `OAuthProviderInterface`) and the public low-level\n * refresh helpers in favor of canonical `Provider.auth.oauth` objects\n * resolved through a `Models` collection + `CredentialStore`. zidane's\n * credential architecture (shared `.credentials.json`, cross-process refresh\n * locks in `./oauth.ts`, themed login flows in `src/chat/oauth-page/`)\n * predates that and owns storage itself, so this module reproduces the\n * removed registry surface locally with the same semantics.\n *\n * Nothing protocol-level is duplicated here: token refresh for the built-in\n * providers delegates to pi-ai's canonical `auth.oauth.refresh` flows via the\n * public `providers/*` subpaths (lazily imported, so processes that never\n * authenticate don't load provider code).\n */\n\nimport type { OAuthAuth } from '@earendil-works/pi-ai'\nimport type { OAuthCredentials, OAuthLoginCallbacks } from '@earendil-works/pi-ai/oauth'\nimport { errorMessage } from '../errors'\n\n/**\n * Provider-owned OAuth flow. Mirrors the interface pi-ai exported from\n * `@earendil-works/pi-ai/oauth` up to 0.80.6 — kept as a local type so\n * zidane's descriptor seam (`ProviderDescriptor.oauthProvider`) and provider\n * extensions (e.g. `extensions/bolt`) stay source-compatible across the\n * upstream removal.\n */\nexport interface OAuthProviderInterface {\n readonly id: string\n readonly name: string\n\n /** Run the login flow, return credentials to persist. */\n login: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials>\n\n /** Whether login uses a local callback server and supports manual code input. */\n usesCallbackServer?: boolean\n\n /** Refresh expired credentials, return updated credentials to persist. */\n refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>\n\n /** Convert credentials to an API key string for the provider. */\n getApiKey: (credentials: OAuthCredentials) => string\n}\n\n/**\n * Run a canonical pi-ai `OAuthAuth.refresh` from a bare refresh token.\n * pi-ai's built-in refresh implementations only read `credential.refresh`,\n * so the placeholder access/expires fields are never observed.\n *\n * The canonical result is an `OAuthCredential` tagged `type: 'oauth'`. Strip\n * the tag before returning: refresh results get merged into stored\n * `.credentials.json` entries and `onOAuthRefresh` hook payloads (see\n * `resolveCredentialSource` in `./oauth.ts`), whose documented shape carries\n * zidane's own `kind` tag — letting pi-ai's `type` leak through would\n * silently drift the on-disk shape.\n */\nasync function refreshViaCanonicalFlow(\n providerId: string,\n oauth: OAuthAuth | undefined,\n refreshToken: string,\n): Promise<OAuthCredentials> {\n if (!oauth)\n throw new Error(`pi-ai provider ${providerId} exposes no OAuth flow`)\n // pi-ai 0.84 made `signal` a required second argument. These refreshers are\n // called from token-expiry paths that carry no cancellation of their own, so\n // a never-aborting signal keeps the pre-0.84 behavior exactly — same idiom as\n // `completion-core.ts`.\n const { type: _type, ...credentials }\n = await oauth.refresh(\n { type: 'oauth', access: '', refresh: refreshToken, expires: 0 },\n new AbortController().signal,\n )\n return credentials\n}\n\n/**\n * pi-ai ≥ 0.85 loads each OAuth flow module through a VARIABLE specifier\n * (`auth/oauth/load.js`: `import(rewrite(\"./anthropic.ts\"))`) precisely so\n * bundlers cannot follow it. Fine under plain Node/Bun where the file sits\n * next to `load.js`; fatal inside anything bundled — the compiled `zidane`\n * binary, the packaged Electron main — where the refresh path died with\n * `Cannot find module './anthropic.js'` and the user was told to re-run auth\n * for a token that was perfectly renewable.\n *\n * pi-ai ships the escape hatch for exactly this: `bun-oauth` statically\n * imports every flow and registers them with the loader. We call it lazily,\n * once, right before the first refresh — a static specifier the bundler DOES\n * follow — so every host (TUI binary, GUI, daemon, SDK) refreshes the same\n * way. Idempotent; failure to register falls through to the loader's own\n * behavior (works unbundled, so nothing gets worse).\n */\nlet bundledFlowsReady: Promise<void> | null = null\nexport function ensureBundledOAuthFlows(): Promise<void> {\n bundledFlowsReady ??= import('@earendil-works/pi-ai/bun-oauth')\n .then(({ registerBunOAuthFlows }) => {\n registerBunOAuthFlows()\n })\n .catch(() => {\n bundledFlowsReady = null\n })\n return bundledFlowsReady\n}\n\n/**\n * Refresh an Anthropic (Claude Pro/Max) OAuth token. Drop-in replacement for\n * the `refreshAnthropicToken` pi-ai exported until 0.80.6; delegates to the\n * canonical flow on `anthropicProvider().auth.oauth`.\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n await ensureBundledOAuthFlows()\n const { anthropicProvider } = await import('@earendil-works/pi-ai/providers/anthropic')\n return refreshViaCanonicalFlow('anthropic', anthropicProvider().auth.oauth, refreshToken)\n}\n\n/**\n * Refresh an OpenAI Codex (ChatGPT) OAuth token. Drop-in replacement for the\n * removed `refreshOpenAICodexToken`; the canonical flow still derives\n * `accountId` from the refreshed access token's JWT claims, so the returned\n * credentials keep the extra field older callers persisted.\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n await ensureBundledOAuthFlows()\n const { openaiCodexProvider } = await import('@earendil-works/pi-ai/providers/openai-codex')\n return refreshViaCanonicalFlow('openai-codex', openaiCodexProvider().auth.oauth, refreshToken)\n}\n\n/**\n * Built-in registry seeds. Every id whose stored credential can expire has to\n * be seeded here, because zidane's lazy token refresh (`getOAuthApiKey` inside\n * `resolveOAuthApiKey`) resolves through the registry — an unseeded id is not a\n * degraded path but a dead end, with no way to renew the session. That is wider\n * than the Anthropic / OpenAI Codex pair pi-ai's removed registry pre-seeded:\n * flows zidane implements itself (xAI) need seeding just as much.\n *\n * Login is intentionally unsupported here: interactive login always goes\n * through `ProviderDescriptor.oauthProvider` (the themed flows in\n * `src/chat/oauth-page/`), never through the registry.\n */\nfunction builtinProvider(\n id: string,\n name: string,\n refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>,\n): OAuthProviderInterface {\n return {\n id,\n name,\n login: async () => {\n throw new Error(`OAuth login for ${id} must run through a provider descriptor flow, not the registry`)\n },\n refreshToken,\n getApiKey: credentials => credentials.access,\n }\n}\n\n/**\n * Seed id for the xAI Grok OAuth flow. Duplicated from\n * `XAI_OAUTH_PROVIDER_ID` in `../chat/oauth-page/xai` rather than imported:\n * that module pulls in `node:http` and the PKCE helpers at module scope, and\n * this registry is loaded by every consumer that resolves a credential. The\n * ids are pinned together by a test, so the duplication cannot drift.\n */\nconst XAI_SEED_ID = 'xai-oauth'\n\n/**\n * Refresh an xAI Grok token. Imported lazily so seeding the registry does not\n * drag the login flow's `node:http` callback server into every consumer —\n * and so the static import edge back to this module stays type-only, leaving\n * no runtime cycle.\n *\n * Unlike the Anthropic / Codex seeds, this receives the FULL credential rather\n * than a bare refresh token: xAI stores its resolved `token_endpoint` (and the\n * cached discovery doc) alongside the tokens, and dropping them would force a\n * fresh OIDC discovery round-trip on every refresh — and discard the validated\n * endpoint that `refreshXaiToken` re-checks before posting the refresh token\n * to it.\n */\nasync function refreshXaiTokenSeed(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const { refreshXaiToken } = await import('../chat/oauth-page/xai')\n return refreshXaiToken(credentials)\n}\n\n/**\n * Seed id for the Cursor OAuth flow. Duplicated from\n * `CURSOR_OAUTH_PROVIDER_ID` for the same reason as {@link XAI_SEED_ID}, and\n * pinned to it by a test.\n */\nconst CURSOR_SEED_ID = 'cursor'\n\n/**\n * Refresh a Cursor token. Lazy for the same reason as the xAI seed: the login\n * module owns a callback server, and seeding must not drag it into every\n * consumer that merely resolves a credential.\n */\nasync function refreshCursorTokenSeed(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const { refreshCursorToken } = await import('../chat/oauth-page/cursor')\n return refreshCursorToken(credentials)\n}\n\n/**\n * The registry map is anchored on a process-wide `globalThis` slot rather\n * than module state. The pi-ai registry this replaces lived in pi-ai's own\n * module, so every copy of zidane code in a process shared one map — and two\n * copies is a real configuration, not a hypothetical: a host running from\n * `src` while a disk-loaded extension resolves `'zidane'` to `dist` through\n * ordinary node_modules resolution (see `import()` in\n * `src/extensions/discovery.ts`). The extension registers its flow at setup;\n * `resolveOAuthApiKey` looks it up only once a stored token expires — with\n * per-module maps that fails as a deferred \"Unknown OAuth provider\", which is\n * nasty to debug. `Symbol.for` restores the process-wide sharing. Whichever\n * copy touches the slot first seeds the built-ins, so a `dist` consumer can end\n * up refreshing through the `src` copy's closure (and vice versa). That is\n * harmless because the seeds are STATELESS refresh delegations — they close over\n * no module state and no configuration, so the two copies compute the same\n * result from the same stored credential. It is that property, not where the\n * refresh happens to be implemented, that makes first-copy-wins safe: it holds\n * equally for the seeds that delegate to pi-ai's canonical flows and for the\n * xAI one implemented in `../chat/oauth-page/xai`.\n */\n/**\n * Exported so tests that register a throwaway provider can remove it from the\n * same slot rather than re-deriving the key with a copied string: the registry\n * exposes no unregister, and a `Symbol.for` looked up from a duplicated literal\n * would silently stop matching if this key were ever renamed, leaving the test\n * provider resident in shared state. Deliberately kept out of the package's\n * public surface — `./index.ts` re-exports this module by explicit name list.\n */\nexport const REGISTRY_SLOT = Symbol.for('zidane.oauth-provider-registry')\n\nfunction oauthProviderRegistry(): Map<string, OAuthProviderInterface> {\n const slots = globalThis as { [key: symbol]: Map<string, OAuthProviderInterface> | undefined }\n slots[REGISTRY_SLOT] ??= new Map(\n [\n builtinProvider('anthropic', 'Anthropic (Claude Pro/Max)', c => refreshAnthropicToken(c.refresh)),\n builtinProvider('openai-codex', 'OpenAI (ChatGPT Plus/Pro)', c => refreshOpenAICodexToken(c.refresh)),\n // xAI is NOT built into pi-ai, so unlike the two above there is no\n // canonical flow to delegate to — `../chat/oauth-page/xai` implements it.\n // It is seeded here anyway because seeding is what makes refresh work for\n // consumers that never import `chat/providers.ts` or `auth.ts`, whose\n // module-scope `registerXaiOAuthProvider()` call was previously the ONLY\n // thing that registered it. A library consumer resolving a stored\n // credential through `getOAuthApiKey` got `Unknown OAuth provider:\n // xai-oauth` and had no way to renew a session — a dead end rather than a\n // degraded path. The interactive login half still comes from\n // `registerXaiOAuthProvider(renderPage)`, which replaces this seed.\n builtinProvider(XAI_SEED_ID, 'xAI Grok (SuperGrok / X Premium+)', refreshXaiTokenSeed),\n // Cursor has the identical shape and had the identical defect: its flow\n // lives in `../chat/oauth-page/cursor` and was registered only as a\n // module-scope side effect of importing `chat/providers.ts` / `auth.ts`.\n builtinProvider(CURSOR_SEED_ID, 'Cursor', refreshCursorTokenSeed),\n ].map(provider => [provider.id, provider]),\n )\n return slots[REGISTRY_SLOT]\n}\n\n/** Register (or replace) an OAuth provider by id. */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n oauthProviderRegistry().set(provider.id, provider)\n}\n\n/** Get a registered OAuth provider by id. */\nexport function getOAuthProvider(id: string): OAuthProviderInterface | undefined {\n return oauthProviderRegistry().get(id)\n}\n\n/**\n * Get an API key for a provider from OAuth credentials, refreshing expired\n * tokens through the registered flow. Same contract as the pi-ai original\n * (`null` when no credentials are stored for the provider, throws on unknown\n * provider or refresh failure), with two deliberate additions:\n *\n * - refresh failures keep the underlying reason — the original discarded it,\n * leaving invalid_grant (needs re-login) indistinguishable from a transient\n * network error.\n * - `options.force` rotates regardless of the local expiry, for 401 recovery\n * when the server revoked the grant before our stored timestamp said so.\n * Expressed as a flag rather than by handing this function a credential\n * with a doctored `expires`: callers persist whatever comes back, so a\n * fabricated expiry that a provider echoes through would land in the\n * credential store. See `resolveCredentialSource` in `./oauth.ts`.\n */\nexport async function getOAuthApiKey(\n providerId: string,\n credentials: Record<string, OAuthCredentials>,\n options?: { force?: boolean },\n): Promise<{ newCredentials: OAuthCredentials, apiKey: string } | null> {\n const provider = oauthProviderRegistry().get(providerId)\n if (!provider)\n throw new Error(`Unknown OAuth provider: ${providerId}`)\n\n let creds = credentials[providerId]\n if (!creds)\n return null\n\n if (options?.force || Date.now() >= creds.expires) {\n try {\n creds = await provider.refreshToken(creds)\n }\n catch (err) {\n throw new Error(`Failed to refresh OAuth token for ${providerId}: ${errorMessage(err)}`, { cause: err })\n }\n }\n\n return { newCredentials: creds, apiKey: provider.getApiKey(creds) }\n}\n","import type { OAuthCredentials } from '@earendil-works/pi-ai/oauth'\nimport type { StreamCallbacks } from '.'\nimport type { OAuthRefreshHookContext } from '../types'\nimport { Buffer } from 'node:buffer'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, resolve } from 'node:path'\nimport { isDeepStrictEqual } from 'node:util'\nimport { writeFileAtomic } from '../atomic-write'\nimport { errorMessage } from '../errors'\nimport { getOAuthApiKey, registerOAuthProvider } from './oauth-registry'\n\ntype GetOAuthApiKey = typeof 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\ninterface RefreshResult {\n apiKey: string\n event?: OAuthRefreshHookContext\n}\n\nconst refreshLocks = new Map<string, Promise<RefreshResult>>()\nconst storeIds = new WeakMap<object, number>()\nlet nextStoreId = 0\nfunction identity(value: object): number {\n let id = storeIds.get(value)\n if (id === undefined) {\n id = ++nextStoreId\n storeIds.set(value, id)\n }\n return id\n}\n\n/**\n * Register a custom OAuth flow with the local registry (`./oauth-registry`)\n * so lazy refresh through {@link resolveOAuthApiKey} can resolve `providerId`.\n *\n * Public because provider extensions need it: without registration,\n * `getOAuthApiKey('<id>', …)` throws `Unknown OAuth provider` the first time a\n * stored token expires. Idempotent per id (the registry is a keyed map). Kept\n * async for API stability — it awaited a dynamic pi-ai import before pi-ai\n * 0.80.8 removed its global registry and this moved in-repo.\n */\nexport async function registerCustomOAuthProvider(\n provider: import('./oauth-registry').OAuthProviderInterface,\n): Promise<void> {\n registerOAuthProvider(provider)\n}\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 /** Synchronous provider-local patch writer; injected stores own cross-process coordination. */\n writeCredentials?: (credentials: Record<string, OAuthCredentials | undefined>) => void\n getOAuthApiKey?: GetOAuthApiKey\n /**\n * Force a refresh even when the credential's local expiry is still in the\n * future (for one-shot 401 recovery). Reaches the refresh helper as an\n * explicit flag, so no credential with a fabricated expiry ever exists to\n * leak into the store — see {@link getOAuthApiKey}.\n */\n forceRefresh?: boolean\n /** Access token rejected by the server; a different token found under lock is reused instead of rotated again. */\n rejectedAccess?: string\n /**\n * Credentials-file key to read and persist, when it differs from\n * `providerId`. A provider holding several OAuth accounts parks the\n * non-active ones under sibling keys (`anthropic#account:1`); refreshing one\n * of those must rotate THAT slot, under its own lock, while the registered\n * flow is still looked up by `providerId`. Defaults to `providerId`.\n */\n credentialKey?: string\n /**\n * Credentials file to use instead of {@link credentialsFilePath}. Callers that\n * already know the data directory (the usage probes) pass it so the default\n * store's locking and coalescing apply to the right file without depending\n * on `ZIDANE_CREDENTIALS_PATH` having been exported.\n */\n credentialsPath?: string\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 return readOAuthCredentialsAt(credentialsFilePath())\n}\n\nfunction readOAuthCredentialsAt(path: string): Record<string, OAuthCredentials | undefined> {\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/** Never steal a lock based on age: a suspended holder may still own it. */\nfunction tryAcquireLock(lockPath: string): (() => void) | undefined {\n const owner = `${process.pid}:${randomUUID()}`\n try {\n writeFileSync(lockPath, owner, { flag: 'wx', mode: CREDENTIALS_FILE_MODE })\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST')\n return undefined\n throw err\n }\n return () => {\n try {\n if (readFileSync(lockPath, 'utf8') === owner)\n unlinkSync(lockPath)\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT')\n throw err\n }\n }\n}\n\n/** Shared by refresh persistence and interactive credential mutations. */\nexport function withCredentialsLock<T>(path: string, fn: () => T): T {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 })\n const deadline = Date.now() + 2000\n let release = tryAcquireLock(`${path}.lock`)\n while (!release) {\n if (Date.now() >= deadline)\n throw new Error('Timed out waiting for credentials lock')\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20)\n release = tryAcquireLock(`${path}.lock`)\n }\n try {\n return fn()\n }\n finally {\n release()\n }\n}\n\nasync function acquireRefreshLock(lockPath: string): Promise<() => void> {\n mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 })\n const deadline = Date.now() + 30_000\n while (true) {\n const release = tryAcquireLock(lockPath)\n if (release)\n return release\n if (Date.now() >= deadline)\n throw new Error('Timed out waiting for OAuth refresh lock')\n await new Promise(resolve => setTimeout(resolve, 50))\n }\n}\n\nfunction readRawCredentials(path: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed\n }\n catch { /* Missing or corrupt file. */ }\n return {}\n}\n\nfunction writeCredentialPatch(path: string, credentials: Record<string, unknown>): void {\n const merged = { ...readRawCredentials(path), ...credentials }\n for (const [key, value] of Object.entries(merged)) {\n if (value === undefined)\n delete merged[key]\n }\n writeFileAtomic(path, JSON.stringify(merged, null, 2), { mode: CREDENTIALS_FILE_MODE })\n}\n\n/** Incoming entries are a provider-local patch, not a read-time snapshot. */\nexport function writeOAuthCredentials(credentials: Record<string, OAuthCredentials | undefined>) {\n const path = credentialsFilePath()\n withCredentialsLock(path, () => writeCredentialPatch(path, credentials))\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'> & { onOAuthStage?: (stage: 'credentials' | 'refresh' | 'shared-refresh') => void },\n): Promise<string> {\n if (typeof options.params?.apiKey === 'string') {\n if (isOAuthAccessToken(options.params.apiKey))\n callbacks?.onOAuthStage?.('credentials')\n return options.params.apiKey\n }\n const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys)\n if (!paramsCredentials && typeof options.params?.access === 'string') {\n callbacks?.onOAuthStage?.('credentials')\n return options.params.access\n }\n\n // Capture the store before awaiting: environment changes cannot redirect writes.\n const path = resolve(options.credentialsPath ?? credentialsFilePath())\n const slot = options.credentialKey ?? options.providerId\n const usingDefaultStore = !options.readCredentials && !options.writeCredentials\n const read = options.readCredentials ?? (() => readOAuthCredentialsAt(path))\n const write = options.writeCredentials ?? (patch => withCredentialsLock(path, () => writeCredentialPatch(path, patch)))\n const envValue = options.envKey ? process.env[options.envKey] : undefined\n if (!paramsCredentials && envValue && (!isOAuthAccessToken(envValue) || !read()[slot])) {\n if (isOAuthAccessToken(envValue))\n callbacks?.onOAuthStage?.('credentials')\n return envValue\n }\n\n callbacks?.onOAuthStage?.('credentials')\n const source = paramsCredentials ? 'params' : 'file'\n const initial = paramsCredentials ?? read()[slot]\n const store = usingDefaultStore ? path : [identity(options.readCredentials ?? readOAuthCredentials), identity(options.writeCredentials ?? writeOAuthCredentials)]\n const key = JSON.stringify([source, options.providerId, slot, source === 'file' ? store : null, initial, options.forceRefresh === true, options.rejectedAccess, identity(options.getOAuthApiKey ?? getOAuthApiKey)])\n let task = refreshLocks.get(key)\n if (task)\n callbacks?.onOAuthStage?.('shared-refresh')\n if (!task) {\n task = run()\n refreshLocks.set(key, task)\n // Delete before callbacks run; a failing subscriber must not poison peers.\n void task.finally(() => refreshLocks.delete(key)).catch(() => {})\n }\n const result = await task\n if (result.event) {\n await callbacks?.onOAuthRefresh?.({\n ...structuredClone(result.event),\n provider: options.provider,\n })\n }\n return result.apiKey\n\n async function run(): Promise<RefreshResult> {\n const release = source === 'file' && usingDefaultStore\n ? await acquireRefreshLock(`${path}.refresh-${encodeURIComponent(slot)}.lock`)\n : undefined\n try {\n const current = paramsCredentials ?? read()[slot]\n if (!current)\n throw new Error(options.missingError)\n const generation = structuredClone(current)\n let result: Awaited<ReturnType<GetOAuthApiKey>>\n try {\n const stillRejected = options.rejectedAccess === undefined || current.access === options.rejectedAccess\n if (current.expires <= Date.now() || (options.forceRefresh && stillRejected))\n callbacks?.onOAuthStage?.('refresh')\n result = await (options.getOAuthApiKey ?? getOAuthApiKey)(\n options.providerId,\n { [options.providerId]: current },\n { force: options.forceRefresh === true && stillRejected },\n )\n }\n catch (err) {\n throw new Error(options.refreshError(errorMessage(err)), { cause: err })\n }\n if (!result)\n throw new Error(options.refreshError(options.missingError))\n const refreshed = { ...generation, ...result.newCredentials }\n if (source === 'file') {\n const persist = () => {\n if (!isDeepStrictEqual(read()[slot], generation))\n throw new Error('OAuth credentials changed during refresh; retry with current credentials')\n if (result.newCredentials !== current) {\n const patch = { [slot]: refreshed }\n if (usingDefaultStore)\n writeCredentialPatch(path, patch)\n else\n write(patch)\n }\n }\n if (usingDefaultStore)\n withCredentialsLock(path, persist)\n else\n persist()\n }\n return {\n apiKey: result.apiKey,\n ...(result.newCredentials !== current\n ? { event: {\n provider: options.provider,\n providerId: options.providerId,\n source,\n previousCredentials: generation,\n credentials: refreshed,\n } }\n : {}),\n }\n }\n finally {\n release?.()\n }\n }\n}\n"],"mappings":"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"}
|