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
|
@@ -1,104 +1,14 @@
|
|
|
1
|
-
import { F as unsupportedMediaError, S as resolvePromptCacheKey, b as sanitizeToolSpecs, k as lookupModel, v as createToolNameBoundary, w as fillEstimatedCost } from "./openai-compat-Dy56weK8.js";
|
|
2
|
-
import { t as lazyAsync } from "./lazy-DLOurOC_.js";
|
|
3
1
|
import { f as isRetryableHttpStatus, h as matchesToolPairingError, l as classifyErrorPrelude, m as matchesContextExceeded, u as errorMessage } from "./errors-DKiMrOKq.js";
|
|
2
|
+
import { A as lookupModel, C as createToolJsonEscaper, I as unsupportedMediaError, T as fillEstimatedCost, b as resolvePromptCacheKey, g as createToolNameBoundary, v as sanitizeToolSpecs } from "./openai-compat-CFrpH7uq.js";
|
|
4
3
|
import { renderSystemForWire, splitSystemPrompt } from "./prompt.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { a as fromAnthropic, u as toAnthropic } from "./messages-BqiwcD2Q.js";
|
|
5
|
+
import { t as lazyAsync } from "./lazy-DLOurOC_.js";
|
|
6
|
+
import { a as readOAuthCredentials, r as extractRuntimeCredentials, s as resolveOAuthApiKey } from "./oauth-Px_pBB9W.js";
|
|
7
7
|
import { Buffer } from "node:buffer";
|
|
8
8
|
import { arch, homedir, hostname, platform, userInfo } from "node:os";
|
|
9
9
|
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
-
//#region src/chat/anthropic-models.ts
|
|
11
|
-
/**
|
|
12
|
-
* Anthropic "fast mode" — `speed: "fast"` on the Messages API. ~2.5× output
|
|
13
|
-
* tokens/sec at a per-model price multiplier. Supported on Opus 4.6 / 4.7 /
|
|
14
|
-
* 4.8 / 5 / 5.5 only; the API errors if sent on any other model, so it's
|
|
15
|
-
* attached just to those entries (see {@link FAST_MODE_OPTIONS}).
|
|
16
|
-
*
|
|
17
|
-
* The multiplier scales ALL rate components (input, output, and both cache
|
|
18
|
-
* rates) uniformly: per Anthropic, "prompt caching multipliers apply on top of
|
|
19
|
-
* fast mode pricing" across the full context window. Opus 5.5 ($4/$20 →
|
|
20
|
-
* $8/$40), 5 and 4.8 ($5/$25 → $10/$50) are 2×; Opus 4.6 / 4.7 are a steeper
|
|
21
|
-
* 6× ($5/$25 → $30/$150).
|
|
22
|
-
* See https://platform.claude.com/docs/en/build-with-claude/fast-mode.
|
|
23
|
-
*/
|
|
24
|
-
const FAST_MODE_OPTION = {
|
|
25
|
-
id: "fast",
|
|
26
|
-
label: "Fast mode",
|
|
27
|
-
description: "~2.5× faster output at premium pricing",
|
|
28
|
-
costMultiplier: {
|
|
29
|
-
input: 2,
|
|
30
|
-
output: 2,
|
|
31
|
-
cacheRead: 2,
|
|
32
|
-
cacheWrite: 2
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const FAST_MODE_OPTION_LEGACY = {
|
|
36
|
-
...FAST_MODE_OPTION,
|
|
37
|
-
description: "~2.5× faster output at 6× pricing",
|
|
38
|
-
costMultiplier: {
|
|
39
|
-
input: 6,
|
|
40
|
-
output: 6,
|
|
41
|
-
cacheRead: 6,
|
|
42
|
-
cacheWrite: 6
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
/** Model ids that accept `speed: "fast"`, mapped to their fast-mode option. */
|
|
46
|
-
const FAST_MODE_OPTIONS = {
|
|
47
|
-
"claude-opus-5-5": FAST_MODE_OPTION,
|
|
48
|
-
"claude-opus-5": FAST_MODE_OPTION,
|
|
49
|
-
"claude-opus-4-8": FAST_MODE_OPTION,
|
|
50
|
-
"claude-opus-4-7": FAST_MODE_OPTION_LEGACY,
|
|
51
|
-
"claude-opus-4-6": FAST_MODE_OPTION_LEGACY
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* The price multiplier to apply to a model's base rates when fast mode is
|
|
55
|
-
* active, or `undefined` when the model has no fast-mode pricing (i.e. doesn't
|
|
56
|
-
* support it). Mirrors {@link FAST_MODE_OPTIONS} — kept as a separate accessor
|
|
57
|
-
* so the cost layer can stay decoupled from the picker-facing option objects.
|
|
58
|
-
*/
|
|
59
|
-
function fastModeCostMultiplier(modelId) {
|
|
60
|
-
return FAST_MODE_OPTIONS[modelId]?.costMultiplier;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Currently empty: pi-ai 0.87.1 bundles every GA Anthropic model up to and
|
|
64
|
-
* including Claude Opus 5.5 (2026-09-22) — Opus 4.8 / 5 / 5.5, Fable 5 / 5.1,
|
|
65
|
-
* Sonnet 4.6 / 5, Haiku 4.5 — with published rates, 1M context and 128k output.
|
|
66
|
-
*
|
|
67
|
-
* When adding a stopgap entry, mirror pi-ai's `ModelInfo` shape and set
|
|
68
|
-
* `provider: 'anthropic'`. Known caveats worth carrying over in the comment:
|
|
69
|
-
* Fable 5 / 5.1 REJECT forced tool use (`tool_choice` of `any` or `tool`
|
|
70
|
-
* returns 400), which breaks zidane's structured-output paths (`loop.ts`,
|
|
71
|
-
* `generate-data.ts`, the eval judge) — Opus 5.5 rejects it too, handled
|
|
72
|
-
* generically by the learned `MODELS_REJECTING_FORCED_TOOL_CHOICE` retry in
|
|
73
|
-
* providers/anthropic.ts. Opus 5.5 also REJECTS non-adaptive thinking:
|
|
74
|
-
* `thinking: {type:'enabled', budget_tokens}` and `{type:'disabled'}` both 400
|
|
75
|
-
* with `"thinking.type.enabled/disabled" is not supported for this model`;
|
|
76
|
-
* zidane's explicit-budget path (`behavior.thinkingBudget`, the Bedrock-only
|
|
77
|
-
* shape) emits `enabled`, so the provider catches that 400 and retries as
|
|
78
|
-
* adaptive (`MODELS_REJECTING_ENABLED_THINKING`). Claude Mythos 5 / 5.1 are
|
|
79
|
-
* deliberately absent: invitation-only (Project Glasswing), so they would be
|
|
80
|
-
* dead picker entries for virtually all users.
|
|
81
|
-
*
|
|
82
|
-
* See https://platform.claude.com/docs/en/about-claude/models/overview.
|
|
83
|
-
*/
|
|
84
|
-
const ANTHROPIC_EXTRA_MODELS = [];
|
|
85
|
-
//#endregion
|
|
86
10
|
//#region src/providers/anthropic-claude-code.ts
|
|
87
11
|
/**
|
|
88
|
-
* Claude Code client alignment for Anthropic subscription OAuth.
|
|
89
|
-
*
|
|
90
|
-
* Anthropic's subscription endpoint serves the Claude Code CLI. Requests that
|
|
91
|
-
* do not look like that client are, at best, treated as a different product; at
|
|
92
|
-
* worst they miss features the CLI relies on. This module holds every surface
|
|
93
|
-
* that has to match, in one file, so a client bump is one edit rather than a
|
|
94
|
-
* scavenger hunt through the provider.
|
|
95
|
-
*
|
|
96
|
-
* Modelled on `@pi-plugins/claude-oauth`, which maintains the same alignment
|
|
97
|
-
* for pi and verifies its pins against a live client trace. Everything here is
|
|
98
|
-
* scoped to the OAuth path — API-key, gateway-bearer and Bedrock requests never
|
|
99
|
-
* reach it.
|
|
100
|
-
*/
|
|
101
|
-
/**
|
|
102
12
|
* Pinned Claude Code client versions.
|
|
103
13
|
*
|
|
104
14
|
* These travel together: the billing fingerprint below hashes
|
|
@@ -169,6 +79,42 @@ function claudeCodeToolAliases(names) {
|
|
|
169
79
|
return aliases;
|
|
170
80
|
}
|
|
171
81
|
/**
|
|
82
|
+
* The wire `system` block and `messages` for one request.
|
|
83
|
+
*
|
|
84
|
+
* Claude Code OAuth requires the system block to be the canonical Claude Code
|
|
85
|
+
* prompt, so the real system prompt is folded into a leading user/assistant
|
|
86
|
+
* pair ahead of the conversation. Every other auth shape sends the rendered
|
|
87
|
+
* system prompt as the system block. Shared by `stream()` and `countTokens()`
|
|
88
|
+
* so the count measures what is actually sent.
|
|
89
|
+
*/
|
|
90
|
+
function foldSystemForWire(system, messages, claudeCode) {
|
|
91
|
+
const wireSystem = renderSystemForWire(system);
|
|
92
|
+
if (!claudeCode) return {
|
|
93
|
+
system: wireSystem,
|
|
94
|
+
messages: [...messages]
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
system: `You are Claude Code, Anthropic's official CLI for Claude.`,
|
|
98
|
+
messages: system ? [
|
|
99
|
+
{
|
|
100
|
+
role: "user",
|
|
101
|
+
content: [{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: wireSystem
|
|
104
|
+
}]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
role: "assistant",
|
|
108
|
+
content: [{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: "Understood. I will proceed with these instructions above the rest of my system prompt."
|
|
111
|
+
}]
|
|
112
|
+
},
|
|
113
|
+
...messages
|
|
114
|
+
] : [...messages]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
172
118
|
* Beta set the Claude Code agent sends, in client order.
|
|
173
119
|
*
|
|
174
120
|
* Sent as one unit: the endpoint is configured for this combination, and
|
|
@@ -486,133 +432,7 @@ function wrapFetchForAnthropicRateLimit(fetch) {
|
|
|
486
432
|
}, fetch);
|
|
487
433
|
}
|
|
488
434
|
//#endregion
|
|
489
|
-
//#region src/providers/
|
|
490
|
-
const NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
491
|
-
"ECONNREFUSED",
|
|
492
|
-
"ECONNRESET",
|
|
493
|
-
"ETIMEDOUT",
|
|
494
|
-
"ENOTFOUND",
|
|
495
|
-
"EAI_AGAIN",
|
|
496
|
-
"ENETUNREACH",
|
|
497
|
-
"EHOSTUNREACH",
|
|
498
|
-
"EPIPE",
|
|
499
|
-
"UND_ERR_CONNECT_TIMEOUT",
|
|
500
|
-
"UND_ERR_HEADERS_TIMEOUT",
|
|
501
|
-
"UND_ERR_SOCKET",
|
|
502
|
-
"CERT_HAS_EXPIRED",
|
|
503
|
-
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
504
|
-
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
505
|
-
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
506
|
-
"AbortError",
|
|
507
|
-
"TimeoutError"
|
|
508
|
-
]);
|
|
509
|
-
function sanitizeProviderDiagnosticMetadata(metadata) {
|
|
510
|
-
const safe = {};
|
|
511
|
-
if (typeof metadata.transportAttempt === "number" && Number.isSafeInteger(metadata.transportAttempt) && metadata.transportAttempt > 0) safe.transportAttempt = metadata.transportAttempt;
|
|
512
|
-
if (typeof metadata.networkCode === "string" && NETWORK_CODES.has(metadata.networkCode)) safe.networkCode = metadata.networkCode;
|
|
513
|
-
if (typeof metadata.httpStatus === "number" && Number.isInteger(metadata.httpStatus) && metadata.httpStatus >= 100 && metadata.httpStatus <= 599) safe.httpStatus = metadata.httpStatus;
|
|
514
|
-
if (typeof metadata.upstreamRequestId === "string" && metadata.upstreamRequestId.length <= 128 && /^req_[\w-]+$/.test(metadata.upstreamRequestId)) safe.upstreamRequestId = metadata.upstreamRequestId;
|
|
515
|
-
const retryAfter = metadata.retryAfter;
|
|
516
|
-
if (typeof retryAfter === "string" && retryAfter.length <= 32 && (/^\d+(?:\.\d+)?$/.test(retryAfter) || retryAfter.length === 29 && new Date(retryAfter).toUTCString() === retryAfter)) safe.retryAfter = retryAfter;
|
|
517
|
-
return safe;
|
|
518
|
-
}
|
|
519
|
-
/** No request or error objects cross this boundary. Diagnostics cannot fail a request. */
|
|
520
|
-
function createProviderDiagnosticTracker(emit, signal) {
|
|
521
|
-
const requestId = crypto.randomUUID();
|
|
522
|
-
const started = performance.now();
|
|
523
|
-
let stageStarted = started;
|
|
524
|
-
let current;
|
|
525
|
-
let attempt = 0;
|
|
526
|
-
let transportAttempt = 0;
|
|
527
|
-
let finished = false;
|
|
528
|
-
let timer;
|
|
529
|
-
let abort = () => {};
|
|
530
|
-
function send(event, metadata = {}) {
|
|
531
|
-
if (!current) return;
|
|
532
|
-
const now = performance.now();
|
|
533
|
-
try {
|
|
534
|
-
emit?.({
|
|
535
|
-
requestId,
|
|
536
|
-
stage: current,
|
|
537
|
-
event,
|
|
538
|
-
elapsedMs: Math.round(now - started),
|
|
539
|
-
stageElapsedMs: Math.round(now - stageStarted),
|
|
540
|
-
attempt,
|
|
541
|
-
...sanitizeProviderDiagnosticMetadata(metadata)
|
|
542
|
-
});
|
|
543
|
-
} catch {}
|
|
544
|
-
}
|
|
545
|
-
const tracker = {
|
|
546
|
-
wrapFetch(fetch) {
|
|
547
|
-
if (!emit) return fetch;
|
|
548
|
-
return Object.assign(async (...args) => {
|
|
549
|
-
const number = ++transportAttempt;
|
|
550
|
-
const began = performance.now();
|
|
551
|
-
const report = (event, metadata = {}) => {
|
|
552
|
-
try {
|
|
553
|
-
emit({
|
|
554
|
-
requestId,
|
|
555
|
-
stage: "transport",
|
|
556
|
-
event,
|
|
557
|
-
elapsedMs: Math.round(performance.now() - started),
|
|
558
|
-
stageElapsedMs: Math.round(performance.now() - began),
|
|
559
|
-
attempt,
|
|
560
|
-
...sanitizeProviderDiagnosticMetadata({
|
|
561
|
-
...metadata,
|
|
562
|
-
transportAttempt: number
|
|
563
|
-
})
|
|
564
|
-
});
|
|
565
|
-
} catch {}
|
|
566
|
-
};
|
|
567
|
-
report("start");
|
|
568
|
-
try {
|
|
569
|
-
const response = await fetch(...args);
|
|
570
|
-
report("end", {
|
|
571
|
-
httpStatus: response.status,
|
|
572
|
-
upstreamRequestId: response.headers.get("request-id"),
|
|
573
|
-
retryAfter: response.headers.get("retry-after")
|
|
574
|
-
});
|
|
575
|
-
return response;
|
|
576
|
-
} catch (error) {
|
|
577
|
-
const err = error;
|
|
578
|
-
report("failure", { networkCode: [
|
|
579
|
-
err?.cause?.code,
|
|
580
|
-
err?.code,
|
|
581
|
-
err?.name
|
|
582
|
-
].find((value) => typeof value === "string" && NETWORK_CODES.has(value)) });
|
|
583
|
-
throw error;
|
|
584
|
-
}
|
|
585
|
-
}, fetch);
|
|
586
|
-
},
|
|
587
|
-
stage(stage, metadata = {}) {
|
|
588
|
-
if (!emit || finished) return;
|
|
589
|
-
if (current) send("end");
|
|
590
|
-
current = stage;
|
|
591
|
-
stageStarted = performance.now();
|
|
592
|
-
if (stage === "dispatch") attempt++;
|
|
593
|
-
send("start", metadata);
|
|
594
|
-
if (timer) clearInterval(timer);
|
|
595
|
-
timer = setInterval(send, 3e4, "waiting");
|
|
596
|
-
timer.unref?.();
|
|
597
|
-
if (signal?.aborted) abort();
|
|
598
|
-
},
|
|
599
|
-
finish(event, metadata = {}) {
|
|
600
|
-
if (!emit || finished) return;
|
|
601
|
-
finished = true;
|
|
602
|
-
clearInterval(timer);
|
|
603
|
-
signal?.removeEventListener("abort", abort);
|
|
604
|
-
if (current) {
|
|
605
|
-
send("end");
|
|
606
|
-
send(event, metadata);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
};
|
|
610
|
-
abort = () => tracker.finish("abort");
|
|
611
|
-
if (emit) signal?.addEventListener("abort", abort, { once: true });
|
|
612
|
-
return tracker;
|
|
613
|
-
}
|
|
614
|
-
//#endregion
|
|
615
|
-
//#region src/providers/anthropic.ts
|
|
435
|
+
//#region src/providers/anthropic-client.ts
|
|
616
436
|
const loadAnthropicSdk = lazyAsync(async () => {
|
|
617
437
|
try {
|
|
618
438
|
return (await import("@anthropic-ai/sdk")).default;
|
|
@@ -664,76 +484,33 @@ const OAUTH_DEFAULT_BETAS = CLAUDE_CODE_BETAS;
|
|
|
664
484
|
* See: https://platform.claude.com/docs/en/build-with-claude/fast-mode
|
|
665
485
|
*/
|
|
666
486
|
const FAST_MODE_BETA = "fast-mode-2026-02-01";
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
"
|
|
671
|
-
"
|
|
672
|
-
"
|
|
487
|
+
const API_KEY_RESERVED_EXTRA_HEADER_NAMES = /* @__PURE__ */ new Set(["anthropic-beta"]);
|
|
488
|
+
const OAUTH_RESERVED_EXTRA_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
489
|
+
"accept",
|
|
490
|
+
"anthropic-beta",
|
|
491
|
+
"anthropic-client-platform",
|
|
492
|
+
"anthropic-client-version",
|
|
493
|
+
"x-stainless-arch",
|
|
494
|
+
"x-stainless-lang",
|
|
495
|
+
"x-stainless-os",
|
|
496
|
+
"x-stainless-package-version",
|
|
497
|
+
"x-stainless-retry-count",
|
|
498
|
+
"x-stainless-runtime",
|
|
499
|
+
"x-stainless-runtime-version",
|
|
500
|
+
"x-stainless-timeout",
|
|
501
|
+
"anthropic-dangerous-direct-browser-access",
|
|
502
|
+
"user-agent",
|
|
503
|
+
"x-app"
|
|
673
504
|
]);
|
|
505
|
+
function filterReservedExtraHeaders(extraHeaders, reservedNames) {
|
|
506
|
+
const out = {};
|
|
507
|
+
for (const [name, value] of Object.entries(extraHeaders ?? {})) if (!reservedNames.has(name.toLowerCase())) out[name] = value;
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
674
510
|
/**
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
* an earlier host's setting write for a shell value.
|
|
679
|
-
*/
|
|
680
|
-
const THINKING_BINDING_SETTING_ENV = "ZIDANE_THINKING_BINDING_SETTING";
|
|
681
|
-
/**
|
|
682
|
-
* Effective {@link AnthropicParams.thinkingBinding}: the explicit param wins,
|
|
683
|
-
* then the shell's `ZIDANE_THINKING_BINDING`, then the chat host's setting
|
|
684
|
-
* channel ({@link THINKING_BINDING_SETTING_ENV}), then `'drop_block'`. Read
|
|
685
|
-
* per request so a flip lands on the next call without rebuilding the
|
|
686
|
-
* provider (the factory is no-arg in the chat host). Unrecognized values are
|
|
687
|
-
* ignored at each level.
|
|
688
|
-
*/
|
|
689
|
-
function resolveThinkingBinding(param, env = process.env) {
|
|
690
|
-
if (param !== void 0) return param;
|
|
691
|
-
for (const key of ["ZIDANE_THINKING_BINDING", THINKING_BINDING_SETTING_ENV]) {
|
|
692
|
-
const value = env[key]?.trim();
|
|
693
|
-
if (value && THINKING_BINDING_BEHAVIORS.has(value)) return value;
|
|
694
|
-
}
|
|
695
|
-
return "drop_block";
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* The 400 the API returns under `prefix_mismatch_behavior: "error"` for a
|
|
699
|
-
* replayed block whose prefix changed. Distinct from a corrupt/tampered
|
|
700
|
-
* signature: this one names the binding and the remedy.
|
|
701
|
-
*/
|
|
702
|
-
function isPrefixMismatchError(err) {
|
|
703
|
-
return /bound to a different conversation|prefix_mismatch_behavior/i.test(errorText(err));
|
|
704
|
-
}
|
|
705
|
-
function isInputTransformation(value) {
|
|
706
|
-
if (typeof value !== "object" || value === null) return false;
|
|
707
|
-
const v = value;
|
|
708
|
-
return typeof v.type === "string" && typeof v.path === "string" && typeof v.reason === "string";
|
|
709
|
-
}
|
|
710
|
-
const API_KEY_RESERVED_EXTRA_HEADER_NAMES = /* @__PURE__ */ new Set(["anthropic-beta"]);
|
|
711
|
-
const OAUTH_RESERVED_EXTRA_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
712
|
-
"accept",
|
|
713
|
-
"anthropic-beta",
|
|
714
|
-
"anthropic-client-platform",
|
|
715
|
-
"anthropic-client-version",
|
|
716
|
-
"x-stainless-arch",
|
|
717
|
-
"x-stainless-lang",
|
|
718
|
-
"x-stainless-os",
|
|
719
|
-
"x-stainless-package-version",
|
|
720
|
-
"x-stainless-retry-count",
|
|
721
|
-
"x-stainless-runtime",
|
|
722
|
-
"x-stainless-runtime-version",
|
|
723
|
-
"x-stainless-timeout",
|
|
724
|
-
"anthropic-dangerous-direct-browser-access",
|
|
725
|
-
"user-agent",
|
|
726
|
-
"x-app"
|
|
727
|
-
]);
|
|
728
|
-
function filterReservedExtraHeaders(extraHeaders, reservedNames) {
|
|
729
|
-
const out = {};
|
|
730
|
-
for (const [name, value] of Object.entries(extraHeaders ?? {})) if (!reservedNames.has(name.toLowerCase())) out[name] = value;
|
|
731
|
-
return out;
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Build the `anthropic-beta` header value — OAuth defaults plus caller-supplied
|
|
735
|
-
* extras, de-duped while preserving order. Returns `undefined` when no betas
|
|
736
|
-
* apply (non-OAuth, no extras).
|
|
511
|
+
* Build the `anthropic-beta` header value — OAuth defaults plus caller-supplied
|
|
512
|
+
* extras, de-duped while preserving order. Returns `undefined` when no betas
|
|
513
|
+
* apply (non-OAuth, no extras).
|
|
737
514
|
*/
|
|
738
515
|
function resolveAnthropicBetas(isOAuth, extraBetas) {
|
|
739
516
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -863,424 +640,84 @@ function createClient(SDK, apiKey, auth, baseURL, extraBetas, extraHeaders, wrap
|
|
|
863
640
|
if (wrapFetch) options.fetch = wrapFetch(options.fetch ?? globalThis.fetch);
|
|
864
641
|
return new SDK(options);
|
|
865
642
|
}
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region src/providers/anthropic-errors.ts
|
|
866
645
|
/**
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
|
|
871
|
-
/**
|
|
872
|
-
* Explicit thinking budgets for Bedrock, per effort tier. Bedrock's
|
|
873
|
-
* anthropic-native path only accepts the legacy
|
|
874
|
-
* `thinking: { type: 'enabled', budget_tokens }` shape (the 4.6 surface —
|
|
875
|
-
* `output_config.effort`, adaptive, `display` — is rejected by its
|
|
876
|
-
* translation layer), so effort tiers degrade to token budgets. Floor is
|
|
877
|
-
* Anthropic's 1024 minimum; ceilings stay well under haiku/sonnet output
|
|
878
|
-
* caps — the request builder clamps against the model ceiling regardless.
|
|
879
|
-
*/
|
|
880
|
-
/**
|
|
881
|
-
* Output tokens reserved for the visible response when an explicit thinking
|
|
882
|
-
* budget must be shrunk to fit under the model's `max_tokens` ceiling — see
|
|
883
|
-
* the budget clamp in `stream()`.
|
|
884
|
-
*/
|
|
885
|
-
const THINKING_RESPONSE_RESERVE_TOKENS = 1024;
|
|
886
|
-
const BEDROCK_THINKING_BUDGETS = {
|
|
887
|
-
low: 4096,
|
|
888
|
-
medium: 12288,
|
|
889
|
-
high: 24576,
|
|
890
|
-
xhigh: 32768,
|
|
891
|
-
max: 32768
|
|
892
|
-
};
|
|
893
|
-
const EFFORT_FOR_LEVEL = {
|
|
894
|
-
minimal: "low",
|
|
895
|
-
low: "low",
|
|
896
|
-
medium: "medium",
|
|
897
|
-
high: "high",
|
|
898
|
-
xhigh: "xhigh",
|
|
899
|
-
max: "max"
|
|
900
|
-
};
|
|
901
|
-
/**
|
|
902
|
-
* Decide how to translate a `ThinkingLevel` into Anthropic's request shape.
|
|
903
|
-
* Pure / synchronous — exported so tests can assert routing without standing
|
|
904
|
-
* up the SDK.
|
|
646
|
+
* Duck-type check for an Anthropic SDK `APIError` — avoids a runtime dependency
|
|
647
|
+
* on `@anthropic-ai/sdk` so `classifyError` stays usable even when the optional
|
|
648
|
+
* peer dep isn't loaded (e.g. host code calling it on an unrelated provider's
|
|
649
|
+
* error).
|
|
905
650
|
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
* - `'adaptive'` → adaptive thinking with no effort hint (model decides
|
|
909
|
-
* everything). When `taskBudget` is set, it is carried as
|
|
910
|
-
* `taskBudgetTokens` and emitted as `output_config.task_budget` — the
|
|
911
|
-
* native whole-task thinking cap. LEGACY: when `customBudget` is set, it
|
|
912
|
-
* is carried as `maxTokensCap` so the request builder caps `max_tokens`
|
|
913
|
-
* accordingly — this clamps the whole response envelope (text and
|
|
914
|
-
* tool-call JSON included), which is the wrong knob for bounding thinking
|
|
915
|
-
* now that `taskBudget` exists; kept only for callers who already depend
|
|
916
|
-
* on it. Prefer `taskBudget`.
|
|
917
|
-
* - Budgeted levels → adaptive thinking with an `effort` hint (plus
|
|
918
|
-
* `taskBudgetTokens` when `taskBudget` is set), unless `customBudget` is
|
|
919
|
-
* provided.
|
|
920
|
-
* - Any level + `customBudget` → explicit-budget `enabled` path, carrying
|
|
921
|
-
* the level's `effort` hint alongside the budget. The caller has opted
|
|
922
|
-
* into precise budget control and accepts the Anthropic deprecation
|
|
923
|
-
* warning that comes with it on opus 4.6+. `'adaptive'` is the sole
|
|
924
|
-
* exception: it never falls back to enabled, since adaptive is the
|
|
925
|
-
* current Anthropic API surface for self-budgeted thinking. `taskBudget`
|
|
926
|
-
* is adaptive-only and ignored on this path.
|
|
927
|
-
* - `effortHint: false` (default `true`) strips the `effort` hint from
|
|
928
|
-
* every shape — budgeted levels plan plain adaptive and the
|
|
929
|
-
* `customBudget` path plans `enabled` with no `output_config.effort` —
|
|
930
|
-
* for Anthropic models that reject the effort field.
|
|
651
|
+
* Anthropic's APIError shape: `.status: number` + `.error` (parsed body, object
|
|
652
|
+
* or null). Plain `Error`s don't have `.status` + `.error`.
|
|
931
653
|
*/
|
|
932
|
-
function
|
|
933
|
-
if (
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
kind: "adaptive",
|
|
937
|
-
...typeof customBudget === "number" && customBudget > 0 ? { maxTokensCap: customBudget } : {},
|
|
938
|
-
...taskBudgetTokens !== void 0 ? { taskBudgetTokens } : {}
|
|
939
|
-
};
|
|
940
|
-
if (customBudget !== void 0) return {
|
|
941
|
-
kind: "enabled",
|
|
942
|
-
budgetTokens: customBudget,
|
|
943
|
-
maxTokensBump: customBudget,
|
|
944
|
-
...effortHint ? { effort: EFFORT_FOR_LEVEL[level] } : {}
|
|
945
|
-
};
|
|
946
|
-
return {
|
|
947
|
-
kind: "adaptive",
|
|
948
|
-
...effortHint ? { effort: EFFORT_FOR_LEVEL[level] } : {},
|
|
949
|
-
...taskBudgetTokens !== void 0 ? { taskBudgetTokens } : {}
|
|
950
|
-
};
|
|
654
|
+
function looksLikeAnthropicApiError(err) {
|
|
655
|
+
if (!err || typeof err !== "object") return false;
|
|
656
|
+
const e = err;
|
|
657
|
+
return typeof e.status === "number" && "error" in e;
|
|
951
658
|
}
|
|
952
659
|
/**
|
|
953
|
-
*
|
|
660
|
+
* Anthropic SSE `error` event types that warrant a retry. `overloaded_error`
|
|
661
|
+
* is the headline case (capacity event mid-stream); `api_error` and
|
|
662
|
+
* `timeout_error` are both server-side transient failures. `invalid_request_error`,
|
|
663
|
+
* `authentication_error`, `permission_error`, `not_found_error`, `billing_error`
|
|
664
|
+
* are terminal — retrying them would just hammer a bad request.
|
|
954
665
|
*
|
|
955
|
-
* `
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
* adjusts the `done` flag so the loop can recover.
|
|
959
|
-
*/
|
|
960
|
-
function mapStopReason(stopReason) {
|
|
961
|
-
if (!stopReason) return void 0;
|
|
962
|
-
switch (stopReason) {
|
|
963
|
-
case "end_turn":
|
|
964
|
-
case "stop_sequence": return "stop";
|
|
965
|
-
case "tool_use": return "tool-calls";
|
|
966
|
-
case "max_tokens":
|
|
967
|
-
case "model_context_window_exceeded": return "length";
|
|
968
|
-
case "refusal": return "content-filter";
|
|
969
|
-
case "pause_turn": return "pause";
|
|
970
|
-
default: return "other";
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
/**
|
|
974
|
-
* Conservative per-family output ceiling, used ONLY as a fallback when pi-ai's
|
|
975
|
-
* model registry doesn't know the model (or pi-ai is externalized) — see
|
|
976
|
-
* {@link resolveAnthropicMaxOutput}, which is the primary path. These are
|
|
977
|
-
* non-beta floors we can always send without a 400; the registry reports the
|
|
978
|
-
* real (often much higher, e.g. Opus 4.x = 128K native) max when available.
|
|
666
|
+
* `rate_limit_error` here would be redundant: the SDK's pre-stream retry
|
|
667
|
+
* handles 429, and a mid-stream `rate_limit_error` is exceedingly rare. We
|
|
668
|
+
* include it anyway for symmetry with the HTTP-level retry policy.
|
|
979
669
|
*/
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
670
|
+
const SSE_RETRYABLE_ERROR_TYPES = /* @__PURE__ */ new Set([
|
|
671
|
+
"overloaded_error",
|
|
672
|
+
"api_error",
|
|
673
|
+
"timeout_error",
|
|
674
|
+
"rate_limit_error"
|
|
675
|
+
]);
|
|
986
676
|
/**
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
677
|
+
* Try to parse an Anthropic SSE `error` event payload out of a thrown error's
|
|
678
|
+
* message. The SDK's `MessageStream` surfaces mid-stream error events by
|
|
679
|
+
* throwing an `AnthropicError` whose `.message` contains the raw JSON shape:
|
|
680
|
+
*
|
|
681
|
+
* {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"},"request_id":"req_..."}
|
|
682
|
+
*
|
|
683
|
+
* Returns the inner `{ type, message }` or `null` when the message isn't
|
|
684
|
+
* the canonical SSE error shape. Tolerant: silently returns `null` for
|
|
685
|
+
* non-string input, non-JSON content, or any shape mismatch.
|
|
990
686
|
*/
|
|
991
|
-
|
|
687
|
+
function matchSseErrorEvent(message) {
|
|
688
|
+
if (typeof message !== "string" || message.length === 0) return null;
|
|
689
|
+
if (!message.includes("\"type\":\"error\"") && !message.includes("\"type\": \"error\"")) return null;
|
|
690
|
+
const start = message.indexOf("{");
|
|
691
|
+
if (start < 0) return null;
|
|
992
692
|
try {
|
|
993
|
-
|
|
693
|
+
const parsed = JSON.parse(message.slice(start));
|
|
694
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
695
|
+
const outer = parsed;
|
|
696
|
+
if (outer.type !== "error" || !outer.error || typeof outer.error !== "object") return null;
|
|
697
|
+
const inner = outer.error;
|
|
698
|
+
if (typeof inner.type !== "string") return null;
|
|
699
|
+
return {
|
|
700
|
+
type: inner.type,
|
|
701
|
+
message: typeof inner.message === "string" ? inner.message : inner.type
|
|
702
|
+
};
|
|
994
703
|
} catch {
|
|
995
704
|
return null;
|
|
996
705
|
}
|
|
997
|
-
}
|
|
706
|
+
}
|
|
998
707
|
/**
|
|
999
|
-
*
|
|
1000
|
-
* authoritative source the SDK itself uses to build requests, so e.g. Opus 4.x
|
|
1001
|
-
* reports its full native 128K (no extended-output beta required) — and falls
|
|
1002
|
-
* back to {@link defaultAnthropicMaxTokens} only when the registry lacks the
|
|
1003
|
-
* model. Used as BOTH the default (when the caller leaves `maxTokens` unset)
|
|
1004
|
-
* and a hard clamp, since a `max_tokens` above the model's max would 400.
|
|
708
|
+
* Classify an Anthropic SDK / HTTP error for typed-error wrapping.
|
|
1005
709
|
*
|
|
1006
|
-
*
|
|
1007
|
-
* `
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
* VISIBLE response, already enforced as `max_tokens` itself, whereas this
|
|
1018
|
-
* value is the model's physical ceiling. Letting it lower the ceiling would
|
|
1019
|
-
* break the `enabled` thinking path's invariant that the budget sits ON TOP
|
|
1020
|
-
* of the response cap — the budget would be shrunk to share the caller's cap,
|
|
1021
|
-
* or dropped entirely once no satisfiable budget fits.
|
|
1022
|
-
*
|
|
1023
|
-
* A registry-backed max always wins over the caller — above it the provider
|
|
1024
|
-
* 400s. A CATALOG entry (`behavior.modelCatalog` / `setModelCatalog`) outranks
|
|
1025
|
-
* even that: it's an explicit claim about a model whose registry entry may be
|
|
1026
|
-
* absent, stale, or an alias for something else entirely.
|
|
1027
|
-
*/
|
|
1028
|
-
async function resolveAnthropicMaxOutput(modelId, explicitMaxTokens, catalog) {
|
|
1029
|
-
const override = lookupModel("anthropic", modelId, catalog)?.maxTokens;
|
|
1030
|
-
if (typeof override === "number" && override > 0) return override;
|
|
1031
|
-
const registryMax = (await loadPiModelGetter())?.("anthropic", modelId)?.maxTokens;
|
|
1032
|
-
if (typeof registryMax === "number" && registryMax > 0) return registryMax;
|
|
1033
|
-
const fallback = defaultAnthropicMaxTokens(modelId);
|
|
1034
|
-
return typeof explicitMaxTokens === "number" && explicitMaxTokens > 0 ? Math.max(fallback, explicitMaxTokens) : fallback;
|
|
1035
|
-
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Anthropic bills a 1h-TTL cache write at 2× base input against 1.25× for the
|
|
1038
|
-
* default 5m TTL, but `cache_creation_input_tokens` (what `TurnUsage.
|
|
1039
|
-
* cacheCreation` carries) is the SUM of both buckets. Registry `cacheWrite`
|
|
1040
|
-
* rates are the 5m rate, so a turn that wrote under `ttl: '1h'` needs its
|
|
1041
|
-
* write component scaled by `(5m·1.25 + 1h·2) / (total·1.25)`. `1` when the
|
|
1042
|
-
* response carries no split (Bedrock, older API versions) or no 1h writes.
|
|
1043
|
-
*/
|
|
1044
|
-
const CACHE_WRITE_1H_OVER_5M = 2 / 1.25;
|
|
1045
|
-
function cacheWriteTtlMultiplier(usage) {
|
|
1046
|
-
const split = usage.cache_creation;
|
|
1047
|
-
const oneHour = split?.ephemeral_1h_input_tokens ?? 0;
|
|
1048
|
-
if (oneHour <= 0) return 1;
|
|
1049
|
-
const fiveMin = split?.ephemeral_5m_input_tokens ?? 0;
|
|
1050
|
-
const total = usage.cache_creation_input_tokens ?? fiveMin + oneHour;
|
|
1051
|
-
if (total <= 0) return 1;
|
|
1052
|
-
return (fiveMin + oneHour * CACHE_WRITE_1H_OVER_5M) / total;
|
|
1053
|
-
}
|
|
1054
|
-
/** Per-rate product of two multipliers; a missing key on either side is `1`. */
|
|
1055
|
-
function composeCostMultipliers(a, b) {
|
|
1056
|
-
if (!a) return b;
|
|
1057
|
-
if (!b) return a;
|
|
1058
|
-
return {
|
|
1059
|
-
input: (a.input ?? 1) * (b.input ?? 1),
|
|
1060
|
-
output: (a.output ?? 1) * (b.output ?? 1),
|
|
1061
|
-
cacheRead: (a.cacheRead ?? 1) * (b.cacheRead ?? 1),
|
|
1062
|
-
cacheWrite: (a.cacheWrite ?? 1) * (b.cacheWrite ?? 1)
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Price an API-key (non-OAuth) turn.
|
|
1067
|
-
*
|
|
1068
|
-
* - Bedrock traffic is looked up under pi-ai's `amazon-bedrock` registry: the
|
|
1069
|
-
* echoed ids (`global.anthropic.claude-…`, `eu.anthropic.…`) never appear
|
|
1070
|
-
* under `anthropic`, and Bedrock's regional uplifts only exist there.
|
|
1071
|
-
* - The API may echo a dated snapshot for an alias request (`claude-haiku-4-5`
|
|
1072
|
-
* → `claude-haiku-4-5-20251001`). Rates and the fast-mode multiplier are
|
|
1073
|
-
* resolved by the echoed id first, then by the REQUESTED id, so a snapshot
|
|
1074
|
-
* the registry doesn't list yet is still priced (and a fast-mode Opus turn
|
|
1075
|
-
* keeps its 2× instead of silently billing at base). The echoed id stays on
|
|
1076
|
-
* the record — only the price falls back.
|
|
1077
|
-
* - 1h cache writes are scaled to their real rate (see
|
|
1078
|
-
* {@link cacheWriteTtlMultiplier}); fast mode composes on top.
|
|
1079
|
-
*/
|
|
1080
|
-
async function priceAnthropicUsage(raw, response, requestedModelId, fastRequested, bedrock, catalog) {
|
|
1081
|
-
const providerKey = bedrock ? "amazon-bedrock" : "anthropic";
|
|
1082
|
-
const echoedModelId = raw.modelId ?? requestedModelId;
|
|
1083
|
-
const fast = fastRequested ? fastModeCostMultiplier(echoedModelId) ?? fastModeCostMultiplier(requestedModelId) : void 0;
|
|
1084
|
-
const ttl = cacheWriteTtlMultiplier(response.usage);
|
|
1085
|
-
const multiplier = composeCostMultipliers(fast, ttl === 1 ? void 0 : { cacheWrite: ttl });
|
|
1086
|
-
const priced = await fillEstimatedCost(raw, providerKey, multiplier, catalog);
|
|
1087
|
-
if (priced.cost !== void 0 || echoedModelId === requestedModelId) return priced;
|
|
1088
|
-
const byRequested = await fillEstimatedCost({
|
|
1089
|
-
...raw,
|
|
1090
|
-
modelId: requestedModelId
|
|
1091
|
-
}, providerKey, multiplier, catalog);
|
|
1092
|
-
return byRequested.cost !== void 0 ? {
|
|
1093
|
-
...raw,
|
|
1094
|
-
cost: byRequested.cost
|
|
1095
|
-
} : raw;
|
|
1096
|
-
}
|
|
1097
|
-
const EPHEMERAL = { type: "ephemeral" };
|
|
1098
|
-
/**
|
|
1099
|
-
* `cache_control` for the requested TTL. `'5m'` stays the bare ephemeral shape
|
|
1100
|
-
* (no `ttl` field) so default-TTL requests are byte-identical to what every
|
|
1101
|
-
* zidane version has ever sent — adding `ttl: '5m'` would be semantically
|
|
1102
|
-
* equal but is gratuitous churn against proxies that pattern-match the body.
|
|
1103
|
-
* `'1h'` also requires the `extended-cache-ttl-2025-04-11` beta header, which
|
|
1104
|
-
* the request path adds — see {@link EXTENDED_CACHE_TTL_BETA}.
|
|
1105
|
-
*/
|
|
1106
|
-
function cacheControlFor(ttl) {
|
|
1107
|
-
return ttl === "1h" ? {
|
|
1108
|
-
type: "ephemeral",
|
|
1109
|
-
ttl: "1h"
|
|
1110
|
-
} : EPHEMERAL;
|
|
1111
|
-
}
|
|
1112
|
-
/** Beta flag Anthropic requires before it honors `ttl: '1h'` on `cache_control`. */
|
|
1113
|
-
const EXTENDED_CACHE_TTL_BETA = "extended-cache-ttl-2025-04-11";
|
|
1114
|
-
/**
|
|
1115
|
-
* Mutate an Anthropic request in place to add cache breakpoints on the three
|
|
1116
|
-
* stable prefixes:
|
|
1117
|
-
* 1. System prompt — `cache_control` on the static prefix only (see below).
|
|
1118
|
-
* 2. Tool definitions — last tool.
|
|
1119
|
-
* 3. Conversation — last content block of the last message.
|
|
1120
|
-
*
|
|
1121
|
-
* Each breakpoint tells Anthropic to cache the prefix ending at that block;
|
|
1122
|
-
* subsequent turns reuse the cached prefix and pay only for the delta. Safe
|
|
1123
|
-
* no-op when any prefix is empty (no tools, empty system, etc.).
|
|
1124
|
-
*
|
|
1125
|
-
* System-prompt boundary handling (`SYSTEM_PROMPT_BOUNDARY`):
|
|
1126
|
-
*
|
|
1127
|
-
* - When `originalSystem` is provided AND carries the marker, the system
|
|
1128
|
-
* block is rewritten as a 2-block array: static (cached) then dynamic
|
|
1129
|
-
* (uncached). The caller is expected to have already stripped the marker
|
|
1130
|
-
* from `params.system` via {@link renderSystemForWire} — this helper
|
|
1131
|
-
* only re-splits to apply `cache_control` cleanly.
|
|
1132
|
-
* - When `originalSystem` is omitted or marker-free, the existing single-
|
|
1133
|
-
* block treatment applies: `cache_control` on the whole system string.
|
|
1134
|
-
* - Array system prompts: existing semantics — `cache_control` lands on the
|
|
1135
|
-
* last block. Callers building their own multi-block layout own the
|
|
1136
|
-
* breakpoint placement.
|
|
1137
|
-
*/
|
|
1138
|
-
function applyAnthropicCacheBreakpoints(params, originalSystem, ttl) {
|
|
1139
|
-
const cacheControl = cacheControlFor(ttl);
|
|
1140
|
-
if (typeof params.system === "string") {
|
|
1141
|
-
if (params.system.length > 0) {
|
|
1142
|
-
const parts = splitSystemPrompt(originalSystem && originalSystem.length > 0 ? originalSystem : params.system);
|
|
1143
|
-
if (parts.dynamic.length > 0) {
|
|
1144
|
-
const blocks = [];
|
|
1145
|
-
if (parts.static.length > 0) blocks.push({
|
|
1146
|
-
type: "text",
|
|
1147
|
-
text: parts.static,
|
|
1148
|
-
cache_control: cacheControl
|
|
1149
|
-
});
|
|
1150
|
-
blocks.push({
|
|
1151
|
-
type: "text",
|
|
1152
|
-
text: parts.dynamic
|
|
1153
|
-
});
|
|
1154
|
-
params.system = blocks;
|
|
1155
|
-
} else params.system = [{
|
|
1156
|
-
type: "text",
|
|
1157
|
-
text: parts.static,
|
|
1158
|
-
cache_control: cacheControl
|
|
1159
|
-
}];
|
|
1160
|
-
}
|
|
1161
|
-
} else if (Array.isArray(params.system) && params.system.length > 0) {
|
|
1162
|
-
const lastIdx = params.system.length - 1;
|
|
1163
|
-
params.system = params.system.map((block, i) => i === lastIdx ? {
|
|
1164
|
-
...block,
|
|
1165
|
-
cache_control: cacheControl
|
|
1166
|
-
} : block);
|
|
1167
|
-
}
|
|
1168
|
-
if (params.tools && params.tools.length > 0) {
|
|
1169
|
-
const lastIdx = params.tools.length - 1;
|
|
1170
|
-
params.tools = params.tools.map((tool, i) => i === lastIdx ? {
|
|
1171
|
-
...tool,
|
|
1172
|
-
cache_control: cacheControl
|
|
1173
|
-
} : tool);
|
|
1174
|
-
}
|
|
1175
|
-
if (params.messages.length === 0) return;
|
|
1176
|
-
const lastMsgIdx = params.messages.length - 1;
|
|
1177
|
-
const lastMsg = params.messages[lastMsgIdx];
|
|
1178
|
-
if (typeof lastMsg.content === "string") {
|
|
1179
|
-
if (lastMsg.content.length === 0) return;
|
|
1180
|
-
params.messages[lastMsgIdx] = {
|
|
1181
|
-
...lastMsg,
|
|
1182
|
-
content: [{
|
|
1183
|
-
type: "text",
|
|
1184
|
-
text: lastMsg.content,
|
|
1185
|
-
cache_control: cacheControl
|
|
1186
|
-
}]
|
|
1187
|
-
};
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
if (!Array.isArray(lastMsg.content) || lastMsg.content.length === 0) return;
|
|
1191
|
-
const blocks = lastMsg.content;
|
|
1192
|
-
let targetIdx = blocks.length - 1;
|
|
1193
|
-
while (targetIdx >= 0 && isThinkingBlock(blocks[targetIdx])) targetIdx -= 1;
|
|
1194
|
-
if (targetIdx < 0) return;
|
|
1195
|
-
const nextBlocks = blocks.slice();
|
|
1196
|
-
nextBlocks[targetIdx] = {
|
|
1197
|
-
...nextBlocks[targetIdx],
|
|
1198
|
-
cache_control: cacheControl
|
|
1199
|
-
};
|
|
1200
|
-
params.messages[lastMsgIdx] = {
|
|
1201
|
-
...lastMsg,
|
|
1202
|
-
content: nextBlocks
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
function isThinkingBlock(block) {
|
|
1206
|
-
return block.type === "thinking" || block.type === "redacted_thinking";
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Duck-type check for an Anthropic SDK `APIError` — avoids a runtime dependency
|
|
1210
|
-
* on `@anthropic-ai/sdk` so `classifyError` stays usable even when the optional
|
|
1211
|
-
* peer dep isn't loaded (e.g. host code calling it on an unrelated provider's
|
|
1212
|
-
* error).
|
|
1213
|
-
*
|
|
1214
|
-
* Anthropic's APIError shape: `.status: number` + `.error` (parsed body, object
|
|
1215
|
-
* or null). Plain `Error`s don't have `.status` + `.error`.
|
|
1216
|
-
*/
|
|
1217
|
-
function looksLikeAnthropicApiError(err) {
|
|
1218
|
-
if (!err || typeof err !== "object") return false;
|
|
1219
|
-
const e = err;
|
|
1220
|
-
return typeof e.status === "number" && "error" in e;
|
|
1221
|
-
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Anthropic SSE `error` event types that warrant a retry. `overloaded_error`
|
|
1224
|
-
* is the headline case (capacity event mid-stream); `api_error` and
|
|
1225
|
-
* `timeout_error` are both server-side transient failures. `invalid_request_error`,
|
|
1226
|
-
* `authentication_error`, `permission_error`, `not_found_error`, `billing_error`
|
|
1227
|
-
* are terminal — retrying them would just hammer a bad request.
|
|
1228
|
-
*
|
|
1229
|
-
* `rate_limit_error` here would be redundant: the SDK's pre-stream retry
|
|
1230
|
-
* handles 429, and a mid-stream `rate_limit_error` is exceedingly rare. We
|
|
1231
|
-
* include it anyway for symmetry with the HTTP-level retry policy.
|
|
1232
|
-
*/
|
|
1233
|
-
const SSE_RETRYABLE_ERROR_TYPES = /* @__PURE__ */ new Set([
|
|
1234
|
-
"overloaded_error",
|
|
1235
|
-
"api_error",
|
|
1236
|
-
"timeout_error",
|
|
1237
|
-
"rate_limit_error"
|
|
1238
|
-
]);
|
|
1239
|
-
/**
|
|
1240
|
-
* Try to parse an Anthropic SSE `error` event payload out of a thrown error's
|
|
1241
|
-
* message. The SDK's `MessageStream` surfaces mid-stream error events by
|
|
1242
|
-
* throwing an `AnthropicError` whose `.message` contains the raw JSON shape:
|
|
1243
|
-
*
|
|
1244
|
-
* {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"},"request_id":"req_..."}
|
|
1245
|
-
*
|
|
1246
|
-
* Returns the inner `{ type, message }` or `null` when the message isn't
|
|
1247
|
-
* the canonical SSE error shape. Tolerant: silently returns `null` for
|
|
1248
|
-
* non-string input, non-JSON content, or any shape mismatch.
|
|
1249
|
-
*/
|
|
1250
|
-
function matchSseErrorEvent(message) {
|
|
1251
|
-
if (typeof message !== "string" || message.length === 0) return null;
|
|
1252
|
-
if (!message.includes("\"type\":\"error\"") && !message.includes("\"type\": \"error\"")) return null;
|
|
1253
|
-
const start = message.indexOf("{");
|
|
1254
|
-
if (start < 0) return null;
|
|
1255
|
-
try {
|
|
1256
|
-
const parsed = JSON.parse(message.slice(start));
|
|
1257
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
1258
|
-
const outer = parsed;
|
|
1259
|
-
if (outer.type !== "error" || !outer.error || typeof outer.error !== "object") return null;
|
|
1260
|
-
const inner = outer.error;
|
|
1261
|
-
if (typeof inner.type !== "string") return null;
|
|
1262
|
-
return {
|
|
1263
|
-
type: inner.type,
|
|
1264
|
-
message: typeof inner.message === "string" ? inner.message : inner.type
|
|
1265
|
-
};
|
|
1266
|
-
} catch {
|
|
1267
|
-
return null;
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
/**
|
|
1271
|
-
* Classify an Anthropic SDK / HTTP error for typed-error wrapping.
|
|
1272
|
-
*
|
|
1273
|
-
* - `prompt is too long` (400 invalid_request_error) → `context_exceeded`.
|
|
1274
|
-
* - `'tool_use' ids were found without 'tool_result' blocks` /
|
|
1275
|
-
* `'tool_result must be preceded by a tool_call'` (400) →
|
|
1276
|
-
* `tool_pairing_corruption`. The local repair pass should have caught
|
|
1277
|
-
* this — the typed error tells consumers their pre-send pipeline is
|
|
1278
|
-
* shipping orphaned blocks somehow (custom `context:transform` hook,
|
|
1279
|
-
* bypassed loop, mid-stream session edit) so they can patch the root
|
|
1280
|
-
* cause instead of chasing 400s.
|
|
1281
|
-
* - Any other Anthropic `APIError`-shaped value → `provider_error` with the
|
|
1282
|
-
* native status/type code.
|
|
1283
|
-
* - Unknown errors → `null` (loop wraps in `AgentProviderError` generically).
|
|
710
|
+
* - `prompt is too long` (400 invalid_request_error) → `context_exceeded`.
|
|
711
|
+
* - `'tool_use' ids were found without 'tool_result' blocks` /
|
|
712
|
+
* `'tool_result must be preceded by a tool_call'` (400) →
|
|
713
|
+
* `tool_pairing_corruption`. The local repair pass should have caught
|
|
714
|
+
* this — the typed error tells consumers their pre-send pipeline is
|
|
715
|
+
* shipping orphaned blocks somehow (custom `context:transform` hook,
|
|
716
|
+
* bypassed loop, mid-stream session edit) so they can patch the root
|
|
717
|
+
* cause instead of chasing 400s.
|
|
718
|
+
* - Any other Anthropic `APIError`-shaped value → `provider_error` with the
|
|
719
|
+
* native status/type code.
|
|
720
|
+
* - Unknown errors → `null` (loop wraps in `AgentProviderError` generically).
|
|
1284
721
|
*
|
|
1285
722
|
* Anthropic's wire error shape is `{ type: 'error', error: { type, message } }` — the
|
|
1286
723
|
* SDK stores the parsed body on `err.error`. We walk both levels so callers see the
|
|
@@ -1344,7 +781,15 @@ function shouldFallbackFromFastMode(err) {
|
|
|
1344
781
|
return /\b(?:fast|speed)\b/i.test(message) && /capacity|overload|rate|unavailable|unsupported|not supported|not permitted|extra inputs|invalid/i.test(message);
|
|
1345
782
|
}
|
|
1346
783
|
/**
|
|
1347
|
-
*
|
|
784
|
+
* The 400 the API returns under `prefix_mismatch_behavior: "error"` for a
|
|
785
|
+
* replayed block whose prefix changed. Distinct from a corrupt/tampered
|
|
786
|
+
* signature: this one names the binding and the remedy.
|
|
787
|
+
*/
|
|
788
|
+
function isPrefixMismatchError(err) {
|
|
789
|
+
return /bound to a different conversation|prefix_mismatch_behavior/i.test(errorText(err));
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Anthropic signs thinking blocks so they can be echoed through tool-use
|
|
1348
793
|
* chains without exposing raw reasoning. A signature can become
|
|
1349
794
|
* unverifiable after a model/route/account switch, a legacy session edit,
|
|
1350
795
|
* or a provider-side signing-key rotation. We cannot validate the opaque
|
|
@@ -1442,24 +887,6 @@ function parseFailingMessageIndex(err) {
|
|
|
1442
887
|
return Number.isSafeInteger(index) ? index : void 0;
|
|
1443
888
|
}
|
|
1444
889
|
/**
|
|
1445
|
-
* Hard cap on signature-400 scrub retries within one `stream()` call. Two
|
|
1446
|
-
* passes resolve every real-world shape (bounded pass, then the
|
|
1447
|
-
* everything-but-active-chain pass); the third is margin. Beyond that the
|
|
1448
|
-
* error is not signature poisoning and must surface.
|
|
1449
|
-
*/
|
|
1450
|
-
const MAX_THINKING_SCRUB_PASSES = 3;
|
|
1451
|
-
/**
|
|
1452
|
-
* Models observed rejecting forced tool use, so later calls skip straight to
|
|
1453
|
-
* the downgraded shape instead of paying the failing round-trip every time.
|
|
1454
|
-
*
|
|
1455
|
-
* Learned, not declared: Claude Fable 5.1 is the first model where
|
|
1456
|
-
* `tool_choice` of `tool` / `any` returns 400, and a catalog flag would need
|
|
1457
|
-
* upkeep for every successor that follows suit. Process-wide and keyed by
|
|
1458
|
-
* model id only — whether a model accepts forcing is a property of the model,
|
|
1459
|
-
* not of the endpoint or credential serving it.
|
|
1460
|
-
*/
|
|
1461
|
-
const MODELS_REJECTING_FORCED_TOOL_CHOICE = /* @__PURE__ */ new Set();
|
|
1462
|
-
/**
|
|
1463
890
|
* True for the 400 Anthropic returns when a model does not support forcing a
|
|
1464
891
|
* tool: `tool_choice: type "tool" and "any" are not supported for this model.`
|
|
1465
892
|
* Anchored on the status/type gate plus the field name and the "not supported
|
|
@@ -1493,174 +920,1313 @@ function isThinkingBindingRejectedError(err) {
|
|
|
1493
920
|
const text = errorText(err);
|
|
1494
921
|
return /block_binding|prefix_mismatch_behavior|thinking-binding-controls/i.test(text) && !/invalid\s+`?signature`?|bound to a different conversation/i.test(text);
|
|
1495
922
|
}
|
|
1496
|
-
/**
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
923
|
+
/**
|
|
924
|
+
* True for the 400 Anthropic returns when a model does not support the manual
|
|
925
|
+
* extended-thinking shape: `"thinking.type.enabled" is not supported for this
|
|
926
|
+
* model. Use "thinking.type.adaptive" and "output_config.effort" ...`. Anchored
|
|
927
|
+
* on the validation-400 gate plus the dotted field name and the "not supported
|
|
928
|
+
* for this model" tail, so a reworded middle still matches while an unrelated
|
|
929
|
+
* `thinking` validation error (bad budget, wrong type) does not. Only the
|
|
930
|
+
* `enabled` variant is matched: zidane omits the field for the `'off'` level
|
|
931
|
+
* (adaptive on these models) and never emits `thinking.type.disabled`.
|
|
932
|
+
*/
|
|
933
|
+
function isEnabledThinkingUnsupportedError(err) {
|
|
934
|
+
return isRequestValidationError(err) && /\bthinking\.type\.enabled\b[^.]+not supported for this model\b/i.test(errorText(err));
|
|
935
|
+
}
|
|
936
|
+
//#endregion
|
|
937
|
+
//#region src/providers/anthropic-cache.ts
|
|
938
|
+
const EPHEMERAL = { type: "ephemeral" };
|
|
939
|
+
/**
|
|
940
|
+
* `cache_control` for the requested TTL. `'5m'` stays the bare ephemeral shape
|
|
941
|
+
* (no `ttl` field) so default-TTL requests are byte-identical to what every
|
|
942
|
+
* zidane version has ever sent — adding `ttl: '5m'` would be semantically
|
|
943
|
+
* equal but is gratuitous churn against proxies that pattern-match the body.
|
|
944
|
+
* `'1h'` also requires the `extended-cache-ttl-2025-04-11` beta header, which
|
|
945
|
+
* the request path adds — see {@link EXTENDED_CACHE_TTL_BETA}.
|
|
946
|
+
*/
|
|
947
|
+
function cacheControlFor(ttl) {
|
|
948
|
+
return ttl === "1h" ? {
|
|
949
|
+
type: "ephemeral",
|
|
950
|
+
ttl: "1h"
|
|
951
|
+
} : EPHEMERAL;
|
|
952
|
+
}
|
|
953
|
+
/** Beta flag Anthropic requires before it honors `ttl: '1h'` on `cache_control`. */
|
|
954
|
+
const EXTENDED_CACHE_TTL_BETA = "extended-cache-ttl-2025-04-11";
|
|
955
|
+
/** Anthropic's per-request limit on `cache_control` markers. */
|
|
956
|
+
const MAX_CACHE_BREAKPOINTS = 4;
|
|
957
|
+
/**
|
|
958
|
+
* Mutate an Anthropic request in place to add cache breakpoints on the three
|
|
959
|
+
* stable prefixes:
|
|
960
|
+
* 1. System prompt — `cache_control` on the static prefix only (see below).
|
|
961
|
+
* 2. Tool definitions — last tool.
|
|
962
|
+
* 3. Conversation — last content block of the last message.
|
|
963
|
+
*
|
|
964
|
+
* Each breakpoint tells Anthropic to cache the prefix ending at that block;
|
|
965
|
+
* subsequent turns reuse the cached prefix and pay only for the delta. Safe
|
|
966
|
+
* no-op when any prefix is empty (no tools, empty system, etc.).
|
|
967
|
+
*
|
|
968
|
+
* System-prompt boundary handling (`SYSTEM_PROMPT_BOUNDARY`):
|
|
969
|
+
*
|
|
970
|
+
* - When `originalSystem` is provided AND carries the marker, the system
|
|
971
|
+
* block is rewritten as a 2-block array: static (cached) then dynamic
|
|
972
|
+
* (uncached). The caller is expected to have already stripped the marker
|
|
973
|
+
* from `params.system` via {@link renderSystemForWire} — this helper
|
|
974
|
+
* only re-splits to apply `cache_control` cleanly.
|
|
975
|
+
* - When `originalSystem` is omitted or marker-free, the existing single-
|
|
976
|
+
* block treatment applies: `cache_control` on the whole system string.
|
|
977
|
+
* - Array system prompts: existing semantics — `cache_control` lands on the
|
|
978
|
+
* last block. Callers building their own multi-block layout own the
|
|
979
|
+
* breakpoint placement.
|
|
980
|
+
*/
|
|
981
|
+
function applyAnthropicCacheBreakpoints(params, originalSystem, ttl) {
|
|
982
|
+
const cacheControl = cacheControlFor(ttl);
|
|
983
|
+
if (typeof params.system === "string") {
|
|
984
|
+
if (params.system.length > 0) {
|
|
985
|
+
const splitSource = originalSystem && originalSystem.length > 0 ? originalSystem : params.system;
|
|
986
|
+
const parts = splitSystemPrompt(splitSource);
|
|
987
|
+
if (parts.dynamic.length > 0) {
|
|
988
|
+
const blocks = [];
|
|
989
|
+
if (parts.static.length > 0) blocks.push({
|
|
990
|
+
type: "text",
|
|
991
|
+
text: parts.static,
|
|
992
|
+
cache_control: cacheControl
|
|
993
|
+
});
|
|
994
|
+
blocks.push({
|
|
995
|
+
type: "text",
|
|
996
|
+
text: parts.dynamic
|
|
997
|
+
});
|
|
998
|
+
params.system = blocks;
|
|
999
|
+
} else params.system = [{
|
|
1000
|
+
type: "text",
|
|
1001
|
+
text: parts.static,
|
|
1002
|
+
cache_control: cacheControl
|
|
1003
|
+
}];
|
|
1004
|
+
}
|
|
1005
|
+
} else if (Array.isArray(params.system) && params.system.length > 0) {
|
|
1006
|
+
const lastIdx = params.system.length - 1;
|
|
1007
|
+
params.system = params.system.map((block, i) => i === lastIdx ? {
|
|
1008
|
+
...block,
|
|
1009
|
+
cache_control: cacheControl
|
|
1010
|
+
} : block);
|
|
1011
|
+
}
|
|
1012
|
+
if (params.tools && params.tools.length > 0) {
|
|
1013
|
+
const lastIdx = params.tools.length - 1;
|
|
1014
|
+
params.tools = params.tools.map((tool, i) => i === lastIdx ? {
|
|
1015
|
+
...tool,
|
|
1016
|
+
cache_control: cacheControl
|
|
1017
|
+
} : tool);
|
|
1018
|
+
}
|
|
1019
|
+
if (params.messages.length === 0) return;
|
|
1020
|
+
const lastMsgIdx = params.messages.length - 1;
|
|
1021
|
+
markMessageBreakpoint(params.messages, lastMsgIdx, cacheControl);
|
|
1022
|
+
if (countCacheBreakpoints(params) >= MAX_CACHE_BREAKPOINTS) return;
|
|
1023
|
+
for (let i = lastMsgIdx - 1; i >= 0; i--) {
|
|
1024
|
+
if (params.messages[i].role !== "user") continue;
|
|
1025
|
+
if (i < lastMsgIdx - 1) markMessageBreakpoint(params.messages, i, cacheControl);
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
/** Put `cache_control` on the last cache-eligible block of `messages[idx]`. */
|
|
1030
|
+
function markMessageBreakpoint(messages, idx, cacheControl) {
|
|
1031
|
+
const msg = messages[idx];
|
|
1032
|
+
if (typeof msg.content === "string") {
|
|
1033
|
+
if (msg.content.length === 0) return;
|
|
1034
|
+
messages[idx] = {
|
|
1035
|
+
...msg,
|
|
1036
|
+
content: [{
|
|
1037
|
+
type: "text",
|
|
1038
|
+
text: msg.content,
|
|
1039
|
+
cache_control: cacheControl
|
|
1040
|
+
}]
|
|
1041
|
+
};
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
if (!Array.isArray(msg.content) || msg.content.length === 0) return;
|
|
1045
|
+
const blocks = msg.content;
|
|
1046
|
+
let targetIdx = blocks.length - 1;
|
|
1047
|
+
while (targetIdx >= 0 && isThinkingBlock(blocks[targetIdx])) targetIdx -= 1;
|
|
1048
|
+
if (targetIdx < 0) return;
|
|
1049
|
+
const nextBlocks = blocks.slice();
|
|
1050
|
+
nextBlocks[targetIdx] = {
|
|
1051
|
+
...nextBlocks[targetIdx],
|
|
1052
|
+
cache_control: cacheControl
|
|
1053
|
+
};
|
|
1054
|
+
messages[idx] = {
|
|
1055
|
+
...msg,
|
|
1056
|
+
content: nextBlocks
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function hasCacheControl(value) {
|
|
1060
|
+
return typeof value === "object" && value !== null && value.cache_control != null;
|
|
1061
|
+
}
|
|
1062
|
+
function countCacheBreakpoints(params) {
|
|
1063
|
+
let count = 0;
|
|
1064
|
+
if (Array.isArray(params.system)) {
|
|
1065
|
+
for (const block of params.system) if (hasCacheControl(block)) count++;
|
|
1066
|
+
}
|
|
1067
|
+
for (const tool of params.tools ?? []) if (hasCacheControl(tool)) count++;
|
|
1068
|
+
for (const msg of params.messages) {
|
|
1069
|
+
if (!Array.isArray(msg.content)) continue;
|
|
1070
|
+
for (const block of msg.content) if (hasCacheControl(block)) count++;
|
|
1071
|
+
else if (block.type === "tool_result" && Array.isArray(block.content)) {
|
|
1072
|
+
for (const part of block.content) if (hasCacheControl(part)) count++;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return count;
|
|
1076
|
+
}
|
|
1077
|
+
function isThinkingBlock(block) {
|
|
1078
|
+
return block.type === "thinking" || block.type === "redacted_thinking";
|
|
1079
|
+
}
|
|
1080
|
+
//#endregion
|
|
1081
|
+
//#region src/providers/anthropic-thinking.ts
|
|
1082
|
+
/** Gates `thinking.block_binding` and the `input_transformations` response field. */
|
|
1083
|
+
const THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
|
|
1084
|
+
const THINKING_BINDING_BEHAVIORS = /* @__PURE__ */ new Set([
|
|
1085
|
+
"drop_block",
|
|
1086
|
+
"error",
|
|
1087
|
+
"off"
|
|
1088
|
+
]);
|
|
1089
|
+
/**
|
|
1090
|
+
* Env channel the chat host writes the user's `thinkingBinding` SETTING to.
|
|
1091
|
+
* Distinct from `ZIDANE_THINKING_BINDING` (the shell override) so the two
|
|
1092
|
+
* never read each other back: a host recreated in-process must not mistake
|
|
1093
|
+
* an earlier host's setting write for a shell value.
|
|
1094
|
+
*/
|
|
1095
|
+
const THINKING_BINDING_SETTING_ENV = "ZIDANE_THINKING_BINDING_SETTING";
|
|
1096
|
+
/**
|
|
1097
|
+
* Effective {@link AnthropicParams.thinkingBinding}: the explicit param wins,
|
|
1098
|
+
* then the shell's `ZIDANE_THINKING_BINDING`, then the chat host's setting
|
|
1099
|
+
* channel ({@link THINKING_BINDING_SETTING_ENV}), then `'drop_block'`. Read
|
|
1100
|
+
* per request so a flip lands on the next call without rebuilding the
|
|
1101
|
+
* provider (the factory is no-arg in the chat host). Unrecognized values are
|
|
1102
|
+
* ignored at each level.
|
|
1103
|
+
*/
|
|
1104
|
+
function resolveThinkingBinding(param, env = process.env) {
|
|
1105
|
+
if (param !== void 0) return param;
|
|
1106
|
+
for (const key of ["ZIDANE_THINKING_BINDING", THINKING_BINDING_SETTING_ENV]) {
|
|
1107
|
+
const value = env[key]?.trim();
|
|
1108
|
+
if (value && THINKING_BINDING_BEHAVIORS.has(value)) return value;
|
|
1109
|
+
}
|
|
1110
|
+
return "drop_block";
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Output tokens reserved for the visible response when an explicit thinking
|
|
1114
|
+
* budget must be shrunk to fit under the model's `max_tokens` ceiling — see
|
|
1115
|
+
* the budget clamp in `buildAnthropicStreamRequest` (`./anthropic-request`).
|
|
1116
|
+
*/
|
|
1117
|
+
const THINKING_RESPONSE_RESERVE_TOKENS = 1024;
|
|
1118
|
+
/**
|
|
1119
|
+
* Explicit thinking budgets for Bedrock, per effort tier. Bedrock's
|
|
1120
|
+
* anthropic-native path only accepts the legacy
|
|
1121
|
+
* `thinking: { type: 'enabled', budget_tokens }` shape (the 4.6 surface —
|
|
1122
|
+
* `output_config.effort`, adaptive, `display` — is rejected by its
|
|
1123
|
+
* translation layer), so effort tiers degrade to token budgets. Floor is
|
|
1124
|
+
* Anthropic's 1024 minimum; ceilings stay well under haiku/sonnet output
|
|
1125
|
+
* caps — the request builder clamps against the model ceiling regardless.
|
|
1126
|
+
*/
|
|
1127
|
+
const BEDROCK_THINKING_BUDGETS = {
|
|
1128
|
+
low: 4096,
|
|
1129
|
+
medium: 12288,
|
|
1130
|
+
high: 24576,
|
|
1131
|
+
xhigh: 32768,
|
|
1132
|
+
max: 32768
|
|
1133
|
+
};
|
|
1134
|
+
/**
|
|
1135
|
+
* Map `ThinkingLevel` budgeted tiers to Anthropic's `output_config.effort`
|
|
1136
|
+
* enum. `minimal` collapses to `low` (the closest equivalent — Anthropic does
|
|
1137
|
+
* not have a sub-`low` tier).
|
|
1138
|
+
*/
|
|
1139
|
+
const EFFORT_FOR_LEVEL = {
|
|
1140
|
+
minimal: "low",
|
|
1141
|
+
low: "low",
|
|
1142
|
+
medium: "medium",
|
|
1143
|
+
high: "high",
|
|
1144
|
+
xhigh: "xhigh",
|
|
1145
|
+
max: "max"
|
|
1146
|
+
};
|
|
1147
|
+
/**
|
|
1148
|
+
* Decide how to translate a `ThinkingLevel` into Anthropic's request shape.
|
|
1149
|
+
* Pure / synchronous — exported so tests can assert routing without standing
|
|
1150
|
+
* up the SDK.
|
|
1151
|
+
*
|
|
1152
|
+
* Routing rules:
|
|
1153
|
+
* - `'off'` → `null` (no thinking field, no effort hint).
|
|
1154
|
+
* - `'adaptive'` → adaptive thinking with no effort hint (model decides
|
|
1155
|
+
* everything). When `taskBudget` is set, it is carried as
|
|
1156
|
+
* `taskBudgetTokens` and emitted as `output_config.task_budget` — the
|
|
1157
|
+
* native whole-task thinking cap. LEGACY: when `customBudget` is set, it
|
|
1158
|
+
* is carried as `maxTokensCap` so the request builder caps `max_tokens`
|
|
1159
|
+
* accordingly — this clamps the whole response envelope (text and
|
|
1160
|
+
* tool-call JSON included), which is the wrong knob for bounding thinking
|
|
1161
|
+
* now that `taskBudget` exists; kept only for callers who already depend
|
|
1162
|
+
* on it. Prefer `taskBudget`.
|
|
1163
|
+
* - Budgeted levels → adaptive thinking with an `effort` hint (plus
|
|
1164
|
+
* `taskBudgetTokens` when `taskBudget` is set), unless `customBudget` is
|
|
1165
|
+
* provided.
|
|
1166
|
+
* - Any level + `customBudget` → explicit-budget `enabled` path, carrying
|
|
1167
|
+
* the level's `effort` hint alongside the budget. The caller has opted
|
|
1168
|
+
* into precise budget control and accepts the Anthropic deprecation
|
|
1169
|
+
* warning that comes with it on opus 4.6+. `'adaptive'` is the sole
|
|
1170
|
+
* exception: it never falls back to enabled, since adaptive is the
|
|
1171
|
+
* current Anthropic API surface for self-budgeted thinking. `taskBudget`
|
|
1172
|
+
* is adaptive-only and ignored on this path.
|
|
1173
|
+
* - `effortHint: false` (default `true`) strips the `effort` hint from
|
|
1174
|
+
* every shape — budgeted levels plan plain adaptive and the
|
|
1175
|
+
* `customBudget` path plans `enabled` with no `output_config.effort` —
|
|
1176
|
+
* for Anthropic models that reject the effort field.
|
|
1177
|
+
*/
|
|
1178
|
+
function planAnthropicThinking(level, customBudget, taskBudget, effortHint = true) {
|
|
1179
|
+
if (level === "off") return null;
|
|
1180
|
+
const taskBudgetTokens = typeof taskBudget === "number" && taskBudget > 0 ? taskBudget : void 0;
|
|
1181
|
+
if (level === "adaptive") return {
|
|
1182
|
+
kind: "adaptive",
|
|
1183
|
+
...typeof customBudget === "number" && customBudget > 0 ? { maxTokensCap: customBudget } : {},
|
|
1184
|
+
...taskBudgetTokens !== void 0 ? { taskBudgetTokens } : {}
|
|
1185
|
+
};
|
|
1186
|
+
if (customBudget !== void 0) return {
|
|
1187
|
+
kind: "enabled",
|
|
1188
|
+
budgetTokens: customBudget,
|
|
1189
|
+
maxTokensBump: customBudget,
|
|
1190
|
+
...effortHint ? { effort: EFFORT_FOR_LEVEL[level] } : {}
|
|
1191
|
+
};
|
|
1192
|
+
return {
|
|
1193
|
+
kind: "adaptive",
|
|
1194
|
+
...effortHint ? { effort: EFFORT_FOR_LEVEL[level] } : {},
|
|
1195
|
+
...taskBudgetTokens !== void 0 ? { taskBudgetTokens } : {}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
//#endregion
|
|
1199
|
+
//#region src/providers/anthropic-request.ts
|
|
1200
|
+
/**
|
|
1201
|
+
* Conservative per-family output ceiling, used ONLY as a fallback when pi-ai's
|
|
1202
|
+
* model registry doesn't know the model (or pi-ai is externalized) — see
|
|
1203
|
+
* {@link resolveAnthropicMaxOutput}, which is the primary path. These are
|
|
1204
|
+
* non-beta floors we can always send without a 400; the registry reports the
|
|
1205
|
+
* real (often much higher, e.g. Opus 4.x = 128K native) max when available.
|
|
1206
|
+
*/
|
|
1207
|
+
function defaultAnthropicMaxTokens(modelId) {
|
|
1208
|
+
const id = modelId.toLowerCase();
|
|
1209
|
+
if (id.includes("haiku")) return 8192;
|
|
1210
|
+
if (id.includes("sonnet")) return 64e3;
|
|
1211
|
+
return 32e3;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Lazily-loaded pi-ai model-registry getter (cached; keeps the multi-MB
|
|
1215
|
+
* registry out of the static bundle, mirroring `cost.ts`). Returns `null` when
|
|
1216
|
+
* pi-ai is externalized, in which case callers fall back to the family map.
|
|
1217
|
+
*/
|
|
1218
|
+
const loadPiModelGetter = lazyAsync(async () => {
|
|
1219
|
+
try {
|
|
1220
|
+
return (await import("@earendil-works/pi-ai/providers/all")).getBuiltinModel;
|
|
1221
|
+
} catch {
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
/**
|
|
1226
|
+
* Resolve a model's max output tokens. Prefers pi-ai's bundled registry — the
|
|
1227
|
+
* authoritative source the SDK itself uses to build requests, so e.g. Opus 4.x
|
|
1228
|
+
* reports its full native 128K (no extended-output beta required) — and falls
|
|
1229
|
+
* back to {@link defaultAnthropicMaxTokens} only when the registry lacks the
|
|
1230
|
+
* model. Used as BOTH the default (when the caller leaves `maxTokens` unset)
|
|
1231
|
+
* and a hard clamp, since a `max_tokens` above the model's max would 400.
|
|
1232
|
+
*
|
|
1233
|
+
* The clamp is only as good as the ceiling's provenance, so an explicit caller
|
|
1234
|
+
* `maxTokens` can RAISE the fallback: models the registry doesn't know are
|
|
1235
|
+
* mostly ids that never appear in pi-ai's anthropic registry — Bedrock ids
|
|
1236
|
+
* (`global.anthropic.claude-*`) and non-Anthropic models served over the
|
|
1237
|
+
* Anthropic wire (`openai.gpt-*` on Bedrock, gateway aliases). For the
|
|
1238
|
+
* latter the fallback degrades to the 32000 catch-all; for the former the
|
|
1239
|
+
* family match yields a deliberately conservative non-beta floor. Clamping a
|
|
1240
|
+
* caller who knows the real ceiling down to either silently truncates long
|
|
1241
|
+
* generations mid-write (observed as turns pinned at exactly 32000 tokens).
|
|
1242
|
+
*
|
|
1243
|
+
* It can only raise, never lower: the caller's `maxTokens` is a cap on the
|
|
1244
|
+
* VISIBLE response, already enforced as `max_tokens` itself, whereas this
|
|
1245
|
+
* value is the model's physical ceiling. Letting it lower the ceiling would
|
|
1246
|
+
* break the `enabled` thinking path's invariant that the budget sits ON TOP
|
|
1247
|
+
* of the response cap — the budget would be shrunk to share the caller's cap,
|
|
1248
|
+
* or dropped entirely once no satisfiable budget fits.
|
|
1249
|
+
*
|
|
1250
|
+
* A registry-backed max always wins over the caller — above it the provider
|
|
1251
|
+
* 400s. A CATALOG entry (`behavior.modelCatalog` / `setModelCatalog`) outranks
|
|
1252
|
+
* even that: it's an explicit claim about a model whose registry entry may be
|
|
1253
|
+
* absent, stale, or an alias for something else entirely.
|
|
1254
|
+
*/
|
|
1255
|
+
async function resolveAnthropicMaxOutput(modelId, explicitMaxTokens, catalog) {
|
|
1256
|
+
const override = lookupModel("anthropic", modelId, catalog)?.maxTokens;
|
|
1257
|
+
if (typeof override === "number" && override > 0) return override;
|
|
1258
|
+
const registryMax = (await loadPiModelGetter())?.("anthropic", modelId)?.maxTokens;
|
|
1259
|
+
if (typeof registryMax === "number" && registryMax > 0) return registryMax;
|
|
1260
|
+
const fallback = defaultAnthropicMaxTokens(modelId);
|
|
1261
|
+
return typeof explicitMaxTokens === "number" && explicitMaxTokens > 0 ? Math.max(fallback, explicitMaxTokens) : fallback;
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Build the Messages request body for one `stream()` call: system block (and
|
|
1265
|
+
* the OAuth persona fold), tool-name boundary, max-output resolution, cache
|
|
1266
|
+
* breakpoints, thinking shape, Claude Code alignment, fast mode, tool choice,
|
|
1267
|
+
* and the prompt-cache affinity key/header.
|
|
1268
|
+
*/
|
|
1269
|
+
async function buildAnthropicStreamRequest(input) {
|
|
1270
|
+
const { anthropicParams, bedrockConfig, options, callIsOAuth, effectiveCacheTtl, extendedTtl, plan, thinkingBinding, bindingActive, resolvedExtraHeaders } = input;
|
|
1271
|
+
const { system, messages } = foldSystemForWire(options.system, options.messages, callIsOAuth);
|
|
1272
|
+
const inputTools = options.tools ?? [];
|
|
1273
|
+
const nameBoundary = createToolNameBoundary(messages, inputTools.map((t) => t.name), callIsOAuth ? claudeCodeToolAliases : void 0);
|
|
1274
|
+
const wireTools = nameBoundary.changed ? inputTools.map((t) => t.name === nameBoundary.toWire(t.name) ? t : {
|
|
1275
|
+
...t,
|
|
1276
|
+
name: nameBoundary.toWire(t.name)
|
|
1277
|
+
}) : inputTools;
|
|
1278
|
+
const modelId = options.model;
|
|
1279
|
+
const modelMaxOutput = await resolveAnthropicMaxOutput(modelId, options.maxTokens, options.modelCatalog);
|
|
1280
|
+
const params = {
|
|
1281
|
+
...anthropicParams?.extraBodyParams ?? {},
|
|
1282
|
+
model: modelId,
|
|
1283
|
+
max_tokens: options.maxTokens ?? modelMaxOutput,
|
|
1284
|
+
system,
|
|
1285
|
+
tools: wireTools,
|
|
1286
|
+
messages: nameBoundary.messages.map((m) => toAnthropic(m)),
|
|
1287
|
+
stream: true
|
|
1288
|
+
};
|
|
1289
|
+
if (anthropicParams?.contextManagement && !bedrockConfig) params.context_management = anthropicParams.contextManagement;
|
|
1290
|
+
if (options.cache !== false) applyAnthropicCacheBreakpoints(params, callIsOAuth ? void 0 : options.system, extendedTtl ? effectiveCacheTtl : void 0);
|
|
1291
|
+
applyAnthropicThinkingPlan(params, plan, bedrockConfig, bindingActive, thinkingBinding);
|
|
1292
|
+
params.max_tokens = Math.min(params.max_tokens, modelMaxOutput);
|
|
1293
|
+
if (callIsOAuth) alignClaudeCodePayload(params);
|
|
1294
|
+
fitThinkingBudgetUnderEnvelope(params);
|
|
1295
|
+
if (options.modelOptions?.fast && !callIsOAuth) params.speed = "fast";
|
|
1296
|
+
if (options.toolChoice) {
|
|
1297
|
+
if (options.toolChoice.type === "tool" && options.toolChoice.name) params.tool_choice = {
|
|
1298
|
+
type: "tool",
|
|
1299
|
+
name: nameBoundary.toWire(options.toolChoice.name)
|
|
1300
|
+
};
|
|
1301
|
+
else if (options.toolChoice.type === "required") params.tool_choice = { type: "any" };
|
|
1302
|
+
else params.tool_choice = { type: "auto" };
|
|
1303
|
+
}
|
|
1304
|
+
const promptCacheKey = resolvePromptCacheKey(anthropicParams?.promptCacheKey, {
|
|
1305
|
+
model: modelId,
|
|
1306
|
+
system: options.system,
|
|
1307
|
+
messages: options.messages
|
|
1308
|
+
});
|
|
1309
|
+
if (promptCacheKey !== void 0 && params.metadata?.user_id === void 0) params.metadata = {
|
|
1310
|
+
...params.metadata,
|
|
1311
|
+
user_id: promptCacheKey
|
|
1312
|
+
};
|
|
1313
|
+
const affinityHeaderName = anthropicParams?.promptCacheKeyHeader?.toLowerCase();
|
|
1314
|
+
return {
|
|
1315
|
+
params,
|
|
1316
|
+
nameBoundary,
|
|
1317
|
+
modelId,
|
|
1318
|
+
affinityHeader: anthropicParams?.promptCacheKeyHeader !== void 0 && promptCacheKey !== void 0 && !Object.keys(resolvedExtraHeaders ?? {}).some((k) => k.toLowerCase() === affinityHeaderName) ? { [anthropicParams.promptCacheKeyHeader]: promptCacheKey } : void 0
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
/** Set the thinking fields (and the max-tokens/temperature they imply) from the plan. */
|
|
1322
|
+
function applyAnthropicThinkingPlan(params, plan, bedrockConfig, bindingActive, thinkingBinding) {
|
|
1323
|
+
if (plan && bedrockConfig) {
|
|
1324
|
+
const budget = plan.kind === "enabled" ? plan.budgetTokens : BEDROCK_THINKING_BUDGETS[plan.effort ?? "medium"];
|
|
1325
|
+
params.thinking = {
|
|
1326
|
+
type: "enabled",
|
|
1327
|
+
budget_tokens: budget
|
|
1328
|
+
};
|
|
1329
|
+
params.max_tokens = budget + params.max_tokens;
|
|
1330
|
+
params.temperature = 1;
|
|
1331
|
+
} else if (plan) {
|
|
1332
|
+
if (plan.kind === "enabled") {
|
|
1333
|
+
params.thinking = {
|
|
1334
|
+
type: "enabled",
|
|
1335
|
+
budget_tokens: plan.budgetTokens,
|
|
1336
|
+
display: "summarized"
|
|
1337
|
+
};
|
|
1338
|
+
params.max_tokens = plan.maxTokensBump + params.max_tokens;
|
|
1339
|
+
if (plan.effort) params.output_config = {
|
|
1340
|
+
...params.output_config,
|
|
1341
|
+
effort: plan.effort
|
|
1342
|
+
};
|
|
1343
|
+
} else {
|
|
1344
|
+
params.thinking = {
|
|
1345
|
+
type: "adaptive",
|
|
1346
|
+
display: "summarized"
|
|
1347
|
+
};
|
|
1348
|
+
const outputConfig = {};
|
|
1349
|
+
if (plan.effort) outputConfig.effort = plan.effort;
|
|
1350
|
+
if (typeof plan.taskBudgetTokens === "number" && plan.taskBudgetTokens > 0) outputConfig.task_budget = {
|
|
1351
|
+
type: "tokens",
|
|
1352
|
+
total: plan.taskBudgetTokens
|
|
1353
|
+
};
|
|
1354
|
+
if (Object.keys(outputConfig).length > 0) params.output_config = {
|
|
1355
|
+
...params.output_config,
|
|
1356
|
+
...outputConfig
|
|
1357
|
+
};
|
|
1358
|
+
if (typeof plan.maxTokensCap === "number" && plan.maxTokensCap > 0) params.max_tokens = Math.min(params.max_tokens, plan.maxTokensCap);
|
|
1359
|
+
}
|
|
1360
|
+
params.temperature = 1;
|
|
1361
|
+
if (bindingActive) params.thinking.block_binding = { prefix_mismatch_behavior: thinkingBinding };
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
/** Claude Code payload alignment for the OAuth persona (output cap, billing block, `user_id`). */
|
|
1365
|
+
function alignClaudeCodePayload(params) {
|
|
1366
|
+
params.max_tokens = Math.min(params.max_tokens, CLAUDE_CODE_MAX_OUTPUT_TOKENS);
|
|
1367
|
+
if (Array.isArray(params.system)) params.system = [{
|
|
1368
|
+
type: "text",
|
|
1369
|
+
text: claudeCodeBillingText(firstUserMessageText(params.messages))
|
|
1370
|
+
}, ...params.system];
|
|
1371
|
+
if (typeof params.metadata?.user_id !== "string") params.metadata = {
|
|
1372
|
+
...params.metadata,
|
|
1373
|
+
user_id: claudeCodeUserId()
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
function fitThinkingBudgetUnderEnvelope(params) {
|
|
1377
|
+
if (params.thinking?.type === "enabled" && params.thinking.budget_tokens >= params.max_tokens) {
|
|
1378
|
+
const fitted = params.max_tokens - THINKING_RESPONSE_RESERVE_TOKENS;
|
|
1379
|
+
if (fitted >= 1024) params.thinking.budget_tokens = fitted;
|
|
1380
|
+
else {
|
|
1381
|
+
delete params.thinking;
|
|
1382
|
+
delete params.temperature;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
//#endregion
|
|
1387
|
+
//#region src/providers/anthropic-retry.ts
|
|
1388
|
+
/**
|
|
1389
|
+
* Models observed rejecting forced tool use, so later calls skip straight to
|
|
1390
|
+
* the downgraded shape instead of paying the failing round-trip every time.
|
|
1391
|
+
*
|
|
1392
|
+
* Learned, not declared: Claude Fable 5.1 is the first model where
|
|
1393
|
+
* `tool_choice` of `tool` / `any` returns 400, and a catalog flag would need
|
|
1394
|
+
* upkeep for every successor that follows suit. Process-wide and keyed by
|
|
1395
|
+
* model id only — whether a model accepts forcing is a property of the model,
|
|
1396
|
+
* not of the endpoint or credential serving it.
|
|
1397
|
+
*/
|
|
1398
|
+
const MODELS_REJECTING_FORCED_TOOL_CHOICE = /* @__PURE__ */ new Set();
|
|
1399
|
+
/** The same request with `thinking.block_binding` removed. */
|
|
1400
|
+
function withoutThinkingBinding(params) {
|
|
1401
|
+
if (!params.thinking) return params;
|
|
1402
|
+
const { block_binding: _dropped, ...thinking } = params.thinking;
|
|
1403
|
+
return {
|
|
1404
|
+
...params,
|
|
1405
|
+
thinking
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
function isForcedToolChoice(choice) {
|
|
1409
|
+
return choice?.type === "tool" || choice?.type === "any";
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Models observed rejecting the explicit-budget thinking shape
|
|
1413
|
+
* (`thinking: { type: 'enabled', budget_tokens }`), so later calls skip the
|
|
1414
|
+
* failing round-trip and downgrade to adaptive up front.
|
|
1415
|
+
*
|
|
1416
|
+
* Learned, not declared — same rationale as
|
|
1417
|
+
* {@link MODELS_REJECTING_FORCED_TOOL_CHOICE}. Claude Opus 5.5 is the first
|
|
1418
|
+
* model where the `enabled` shape 400s (adaptive-only, thinking always on);
|
|
1419
|
+
* successors that follow suit are caught without a catalog edit.
|
|
1420
|
+
*/
|
|
1421
|
+
const MODELS_REJECTING_ENABLED_THINKING = /* @__PURE__ */ new Set();
|
|
1422
|
+
function isEnabledThinking(thinking) {
|
|
1423
|
+
return thinking?.type === "enabled";
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* The same request with the explicit budget dropped: `thinking.type` flipped to
|
|
1427
|
+
* `adaptive`, `budget_tokens` removed, every other thinking field
|
|
1428
|
+
* (`display`, `block_binding`) kept. `temperature` stays pinned to 1 (adaptive
|
|
1429
|
+
* requires it, same as the enabled shape) and the budget-inflated `max_tokens`
|
|
1430
|
+
* is left as-is — it is already clamped to the model ceiling and an oversized
|
|
1431
|
+
* envelope is harmless for adaptive, which thinks and replies inside it. Any
|
|
1432
|
+
* `output_config.effort` the enabled path set survives on `params.output_config`.
|
|
1433
|
+
*/
|
|
1434
|
+
function withoutEnabledThinking(params) {
|
|
1435
|
+
const thinking = params.thinking;
|
|
1436
|
+
if (!thinking || thinking.type !== "enabled") return params;
|
|
1437
|
+
const { budget_tokens: _dropped, type: _type, ...rest } = thinking;
|
|
1438
|
+
return {
|
|
1439
|
+
...params,
|
|
1440
|
+
thinking: {
|
|
1441
|
+
...rest,
|
|
1442
|
+
type: "adaptive"
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* The same request with forcing removed: `tool_choice: auto` plus one trailing
|
|
1448
|
+
* system sentence naming what the model was being forced into.
|
|
1449
|
+
*
|
|
1450
|
+
* The sentence is load-bearing, not decoration. `enforceSchemaOutput` sends the
|
|
1451
|
+
* agent's own system prompt, which says nothing about `__output__` — forcing
|
|
1452
|
+
* was the whole instruction — so without it a model on `auto` can legitimately
|
|
1453
|
+
* answer in prose and the caller's "no structured payload" guard fires. It is
|
|
1454
|
+
* appended as a final uncached block: everything before it keeps its cache
|
|
1455
|
+
* prefix, and the downgrade adds no new breakpoint.
|
|
1456
|
+
*/
|
|
1457
|
+
function withoutForcedToolChoice(params) {
|
|
1458
|
+
const choice = params.tool_choice;
|
|
1459
|
+
const nudge = choice?.type === "tool" ? `Respond only by calling the \`${choice.name}\` tool. Do not reply with prose.` : "Respond only by calling one of the provided tools. Do not reply with prose.";
|
|
1460
|
+
const system = typeof params.system === "string" ? params.system.length > 0 ? `${params.system}\n\n${nudge}` : nudge : [...params.system ?? [], {
|
|
1461
|
+
type: "text",
|
|
1462
|
+
text: nudge
|
|
1463
|
+
}];
|
|
1464
|
+
return {
|
|
1465
|
+
...params,
|
|
1466
|
+
system,
|
|
1467
|
+
tool_choice: { type: "auto" }
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Wire index where the ACTIVE chain begins: the last user message that
|
|
1472
|
+
* carries real user content (string content, or any non-tool_result
|
|
1473
|
+
* block). Everything from there onward is the current prompt plus the
|
|
1474
|
+
* in-flight assistant/tool exchange — its thinking blocks were minted by
|
|
1475
|
+
* this very call chain and Anthropic requires them to be echoed back, so
|
|
1476
|
+
* escalated scrubs must never cross this boundary. `undefined` when no
|
|
1477
|
+
* such message exists (degenerate resume shapes) — callers treat that as
|
|
1478
|
+
* "no protected region".
|
|
1479
|
+
*/
|
|
1480
|
+
function activeChainScrubBoundary(messages) {
|
|
1481
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1482
|
+
const message = messages[i];
|
|
1483
|
+
if (message?.role !== "user") continue;
|
|
1484
|
+
if (typeof message.content === "string") return i;
|
|
1485
|
+
if (message.content.some((block) => block.type !== "tool_result")) return i;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Decide what a scrub pass may touch. The bound NEVER crosses into the
|
|
1490
|
+
* active chain wholesale — not even on pass 0 with no parseable locator
|
|
1491
|
+
* (an unbounded first pass would strip the in-flight chain's thinking and
|
|
1492
|
+
* trade a recoverable 400 for an "expected thinking block" one). When the
|
|
1493
|
+
* API-reported failing index lies INSIDE the chain, only that single
|
|
1494
|
+
* message is scrubbed on top of the pre-chain region: the block the API
|
|
1495
|
+
* itself rejected fails whether or not it is echoed, but sibling chain
|
|
1496
|
+
* messages below it may hold still-valid thinking the exchange requires.
|
|
1497
|
+
*
|
|
1498
|
+
* - pass 0 with a pre-chain locator → bounded at the locator (preserves
|
|
1499
|
+
* later, still-valid historical thinking).
|
|
1500
|
+
* - pass 0 without a locator, and every escalated pass → the full
|
|
1501
|
+
* pre-chain history.
|
|
1502
|
+
* - no active chain at all (degenerate resume shapes) → locator bound on
|
|
1503
|
+
* pass 0, full history afterwards.
|
|
1504
|
+
*/
|
|
1505
|
+
function planThinkingScrub(scrubPass, failingIndex, messages) {
|
|
1506
|
+
const chainStart = activeChainScrubBoundary(messages);
|
|
1507
|
+
if (chainStart === void 0) return scrubPass === 0 && failingIndex !== void 0 ? { upToIndex: failingIndex } : {};
|
|
1508
|
+
const beforeChain = chainStart - 1;
|
|
1509
|
+
const inChain = failingIndex !== void 0 && failingIndex >= chainStart;
|
|
1510
|
+
const upToIndex = scrubPass === 0 && failingIndex !== void 0 && !inChain ? failingIndex : beforeChain;
|
|
1511
|
+
return inChain ? {
|
|
1512
|
+
upToIndex,
|
|
1513
|
+
alsoMessageIndex: failingIndex
|
|
1514
|
+
} : { upToIndex };
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Remove opaque historical Anthropic reasoning from a retry payload. Text,
|
|
1518
|
+
* tool calls/results, images, and every other block are preserved exactly.
|
|
1519
|
+
* If a message consisted only of reasoning, leave a neutral text marker so
|
|
1520
|
+
* the wire never receives an empty content array and conversation role
|
|
1521
|
+
* boundaries stay intact.
|
|
1522
|
+
*
|
|
1523
|
+
* `upToIndex` bounds the scrub to messages at/before the API-reported
|
|
1524
|
+
* failing index (see {@link parseFailingMessageIndex}); `undefined` strips
|
|
1525
|
+
* everywhere. `alsoMessageIndex` additionally scrubs that single message
|
|
1526
|
+
* beyond the bound (see {@link planThinkingScrub}). A later stale block
|
|
1527
|
+
* simply triggers the same recovery again — each pass removes at least
|
|
1528
|
+
* one block, so the recursion is bounded.
|
|
1529
|
+
*/
|
|
1530
|
+
function withoutHistoricalThinking(messages, upToIndex, alsoMessageIndex) {
|
|
1531
|
+
let removed = 0;
|
|
1532
|
+
return {
|
|
1533
|
+
messages: messages.map((message, index) => {
|
|
1534
|
+
if (upToIndex !== void 0 && index > upToIndex && index !== alsoMessageIndex) return message;
|
|
1535
|
+
if (!Array.isArray(message.content)) return message;
|
|
1536
|
+
const content = message.content.filter((block) => {
|
|
1537
|
+
if (block.type === "thinking" || block.type === "redacted_thinking") {
|
|
1538
|
+
removed += 1;
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
return true;
|
|
1542
|
+
});
|
|
1543
|
+
return {
|
|
1544
|
+
...message,
|
|
1545
|
+
content: content.length > 0 ? content : [{
|
|
1546
|
+
type: "text",
|
|
1547
|
+
text: "[historical reasoning omitted]"
|
|
1548
|
+
}]
|
|
1549
|
+
};
|
|
1550
|
+
}),
|
|
1551
|
+
removed
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
//#endregion
|
|
1555
|
+
//#region src/providers/anthropic-tool-json.ts
|
|
1556
|
+
/**
|
|
1557
|
+
* Raw control characters in streamed tool input.
|
|
1558
|
+
*
|
|
1559
|
+
* With fine-grained tool streaming (one of the Claude Code subscription
|
|
1560
|
+
* betas) the API streams a tool call's input JSON without validating it, so a
|
|
1561
|
+
* string value can carry a literal newline or tab. The SDK re-parses the
|
|
1562
|
+
* accumulated input on every `input_json_delta` with a strict `JSON.parse`,
|
|
1563
|
+
* which throws "Bad control character in string literal" and fails the whole
|
|
1564
|
+
* turn. Escaping those characters inside strings before the SDK sees the delta
|
|
1565
|
+
* yields the value the model meant.
|
|
1566
|
+
*/
|
|
1567
|
+
/** The stream with every `input_json_delta` escaped; same object, so the SDK keeps its controller. */
|
|
1568
|
+
function escapingStream(stream) {
|
|
1569
|
+
const escape = createToolJsonEscaper();
|
|
1570
|
+
const events = stream[Symbol.asyncIterator].bind(stream);
|
|
1571
|
+
Object.defineProperty(stream, Symbol.asyncIterator, {
|
|
1572
|
+
configurable: true,
|
|
1573
|
+
async *value() {
|
|
1574
|
+
for await (const event of { [Symbol.asyncIterator]: events }) {
|
|
1575
|
+
const e = event;
|
|
1576
|
+
if (e.type === "content_block_delta" && e.delta?.type === "input_json_delta" && typeof e.delta.partial_json === "string") yield {
|
|
1577
|
+
...e,
|
|
1578
|
+
delta: {
|
|
1579
|
+
...e.delta,
|
|
1580
|
+
partial_json: escape(e.index ?? 0, e.delta.partial_json)
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
else yield event;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
return stream;
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* `messages` whose `stream()` escapes tool-input control characters. The
|
|
1591
|
+
* SDK's `stream()` builds its `MessageStream` from `this.create(...)
|
|
1592
|
+
* .withResponse()`, so only `create` is replaced; everything else (client,
|
|
1593
|
+
* logger, headers, abort, `connect`) is the real one, through the prototype.
|
|
1594
|
+
* Pinned by a test against the real SDK, which fails if that changes.
|
|
1595
|
+
*/
|
|
1596
|
+
function withToolJsonEscaping(messages) {
|
|
1597
|
+
const create = (...args) => {
|
|
1598
|
+
const pending = messages.create(...args);
|
|
1599
|
+
if (!args[0]?.stream) return pending;
|
|
1600
|
+
return {
|
|
1601
|
+
...pending,
|
|
1602
|
+
withResponse: async () => {
|
|
1603
|
+
const result = await pending.withResponse();
|
|
1604
|
+
return {
|
|
1605
|
+
...result,
|
|
1606
|
+
data: escapingStream(result.data)
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
};
|
|
1611
|
+
return Object.create(messages, { create: { value: create } });
|
|
1612
|
+
}
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region src/chat/anthropic-models.ts
|
|
1615
|
+
/**
|
|
1616
|
+
* Anthropic "fast mode" — `speed: "fast"` on the Messages API. ~2.5× output
|
|
1617
|
+
* tokens/sec at a per-model price multiplier. Supported on Opus 4.6 / 4.7 /
|
|
1618
|
+
* 4.8 / 5 / 5.5 only; the API errors if sent on any other model, so it's
|
|
1619
|
+
* attached just to those entries (see {@link FAST_MODE_OPTIONS}).
|
|
1620
|
+
*
|
|
1621
|
+
* The multiplier scales ALL rate components (input, output, and both cache
|
|
1622
|
+
* rates) uniformly: per Anthropic, "prompt caching multipliers apply on top of
|
|
1623
|
+
* fast mode pricing" across the full context window. Opus 5.5 ($4/$20 →
|
|
1624
|
+
* $8/$40), 5 and 4.8 ($5/$25 → $10/$50) are 2×; Opus 4.6 / 4.7 are a steeper
|
|
1625
|
+
* 6× ($5/$25 → $30/$150).
|
|
1626
|
+
* See https://platform.claude.com/docs/en/build-with-claude/fast-mode.
|
|
1627
|
+
*/
|
|
1628
|
+
const FAST_MODE_OPTION = {
|
|
1629
|
+
id: "fast",
|
|
1630
|
+
label: "Fast mode",
|
|
1631
|
+
description: "~2.5× faster output at premium pricing",
|
|
1632
|
+
costMultiplier: {
|
|
1633
|
+
input: 2,
|
|
1634
|
+
output: 2,
|
|
1635
|
+
cacheRead: 2,
|
|
1636
|
+
cacheWrite: 2
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
const FAST_MODE_OPTION_LEGACY = {
|
|
1640
|
+
...FAST_MODE_OPTION,
|
|
1641
|
+
description: "~2.5× faster output at 6× pricing",
|
|
1642
|
+
costMultiplier: {
|
|
1643
|
+
input: 6,
|
|
1644
|
+
output: 6,
|
|
1645
|
+
cacheRead: 6,
|
|
1646
|
+
cacheWrite: 6
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
/** Model ids that accept `speed: "fast"`, mapped to their fast-mode option. */
|
|
1650
|
+
const FAST_MODE_OPTIONS = {
|
|
1651
|
+
"claude-opus-5-5": FAST_MODE_OPTION,
|
|
1652
|
+
"claude-opus-5": FAST_MODE_OPTION,
|
|
1653
|
+
"claude-opus-4-8": FAST_MODE_OPTION,
|
|
1654
|
+
"claude-opus-4-7": FAST_MODE_OPTION_LEGACY,
|
|
1655
|
+
"claude-opus-4-6": FAST_MODE_OPTION_LEGACY
|
|
1656
|
+
};
|
|
1657
|
+
/**
|
|
1658
|
+
* The price multiplier to apply to a model's base rates when fast mode is
|
|
1659
|
+
* active, or `undefined` when the model has no fast-mode pricing (i.e. doesn't
|
|
1660
|
+
* support it). Mirrors {@link FAST_MODE_OPTIONS} — kept as a separate accessor
|
|
1661
|
+
* so the cost layer can stay decoupled from the picker-facing option objects.
|
|
1662
|
+
*/
|
|
1663
|
+
function fastModeCostMultiplier(modelId) {
|
|
1664
|
+
return FAST_MODE_OPTIONS[modelId]?.costMultiplier;
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Currently empty: pi-ai 1.0.4 bundles every GA Anthropic model up to and
|
|
1668
|
+
* including Claude Sonnet 5.5 — Opus 4.8 / 5 / 5.5, Fable 5 / 5.1,
|
|
1669
|
+
* Sonnet 4.6 / 5 / 5.5, Haiku 4.5 — with published rates, 1M context and 128k output.
|
|
1670
|
+
*
|
|
1671
|
+
* When adding a stopgap entry, mirror pi-ai's `ModelInfo` shape and set
|
|
1672
|
+
* `provider: 'anthropic'`. Known caveats worth carrying over in the comment:
|
|
1673
|
+
* Fable 5 / 5.1 REJECT forced tool use (`tool_choice` of `any` or `tool`
|
|
1674
|
+
* returns 400), which breaks zidane's structured-output paths (`loop.ts`,
|
|
1675
|
+
* `generate-data.ts`, the eval judge) — Opus 5.5 rejects it too, handled
|
|
1676
|
+
* generically by the learned `MODELS_REJECTING_FORCED_TOOL_CHOICE` retry in
|
|
1677
|
+
* providers/anthropic.ts. Opus 5.5 also REJECTS non-adaptive thinking:
|
|
1678
|
+
* `thinking: {type:'enabled', budget_tokens}` and `{type:'disabled'}` both 400
|
|
1679
|
+
* with `"thinking.type.enabled/disabled" is not supported for this model`;
|
|
1680
|
+
* zidane's explicit-budget path (`behavior.thinkingBudget`, the Bedrock-only
|
|
1681
|
+
* shape) emits `enabled`, so the provider catches that 400 and retries as
|
|
1682
|
+
* adaptive (`MODELS_REJECTING_ENABLED_THINKING`). Claude Mythos 5 / 5.1 are
|
|
1683
|
+
* deliberately absent: invitation-only (Project Glasswing), so they would be
|
|
1684
|
+
* dead picker entries for virtually all users.
|
|
1685
|
+
*
|
|
1686
|
+
* See https://platform.claude.com/docs/en/about-claude/models/overview.
|
|
1687
|
+
*/
|
|
1688
|
+
const ANTHROPIC_EXTRA_MODELS = [];
|
|
1689
|
+
//#endregion
|
|
1690
|
+
//#region src/providers/anthropic-usage.ts
|
|
1691
|
+
/**
|
|
1692
|
+
* Map Anthropic's native `stop_reason` to the zidane `TurnFinishReason` union.
|
|
1693
|
+
*
|
|
1694
|
+
* `pause_turn` and `model_context_window_exceeded` are 4.6+ stop reasons that
|
|
1695
|
+
* pre-Z21 collapsed to `'other'` and silently terminated the run. They now
|
|
1696
|
+
* map to `'pause'` and `'length'` respectively, and the surrounding caller
|
|
1697
|
+
* adjusts the `done` flag so the loop can recover.
|
|
1698
|
+
*/
|
|
1699
|
+
function mapStopReason(stopReason) {
|
|
1700
|
+
if (!stopReason) return void 0;
|
|
1701
|
+
switch (stopReason) {
|
|
1702
|
+
case "end_turn":
|
|
1703
|
+
case "stop_sequence": return "stop";
|
|
1704
|
+
case "tool_use": return "tool-calls";
|
|
1705
|
+
case "max_tokens":
|
|
1706
|
+
case "model_context_window_exceeded": return "length";
|
|
1707
|
+
case "refusal": return "content-filter";
|
|
1708
|
+
case "pause_turn": return "pause";
|
|
1709
|
+
default: return "other";
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Anthropic bills a 1h-TTL cache write at 2× base input against 1.25× for the
|
|
1714
|
+
* default 5m TTL, but `cache_creation_input_tokens` (what `TurnUsage.
|
|
1715
|
+
* cacheCreation` carries) is the SUM of both buckets. Registry `cacheWrite`
|
|
1716
|
+
* rates are the 5m rate, so a turn that wrote under `ttl: '1h'` needs its
|
|
1717
|
+
* write component scaled by `(5m·1.25 + 1h·2) / (total·1.25)`. `1` when the
|
|
1718
|
+
* response carries no split (Bedrock, older API versions) or no 1h writes.
|
|
1719
|
+
*/
|
|
1720
|
+
const CACHE_WRITE_1H_OVER_5M = 2 / 1.25;
|
|
1721
|
+
function cacheWriteTtlMultiplier(usage) {
|
|
1722
|
+
const split = usage.cache_creation;
|
|
1723
|
+
const oneHour = split?.ephemeral_1h_input_tokens ?? 0;
|
|
1724
|
+
if (oneHour <= 0) return 1;
|
|
1725
|
+
const fiveMin = split?.ephemeral_5m_input_tokens ?? 0;
|
|
1726
|
+
const total = usage.cache_creation_input_tokens ?? fiveMin + oneHour;
|
|
1727
|
+
if (total <= 0) return 1;
|
|
1728
|
+
return (fiveMin + oneHour * CACHE_WRITE_1H_OVER_5M) / total;
|
|
1729
|
+
}
|
|
1730
|
+
/** Per-rate product of two multipliers; a missing key on either side is `1`. */
|
|
1731
|
+
function composeCostMultipliers(a, b) {
|
|
1732
|
+
if (!a) return b;
|
|
1733
|
+
if (!b) return a;
|
|
1734
|
+
return {
|
|
1735
|
+
input: (a.input ?? 1) * (b.input ?? 1),
|
|
1736
|
+
output: (a.output ?? 1) * (b.output ?? 1),
|
|
1737
|
+
cacheRead: (a.cacheRead ?? 1) * (b.cacheRead ?? 1),
|
|
1738
|
+
cacheWrite: (a.cacheWrite ?? 1) * (b.cacheWrite ?? 1)
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Price an API-key (non-OAuth) turn.
|
|
1743
|
+
*
|
|
1744
|
+
* - Bedrock traffic is looked up under pi-ai's `amazon-bedrock` registry: the
|
|
1745
|
+
* echoed ids (`global.anthropic.claude-…`, `eu.anthropic.…`) never appear
|
|
1746
|
+
* under `anthropic`, and Bedrock's regional uplifts only exist there.
|
|
1747
|
+
* - The API may echo a dated snapshot for an alias request (`claude-haiku-4-5`
|
|
1748
|
+
* → `claude-haiku-4-5-20251001`). Rates and the fast-mode multiplier are
|
|
1749
|
+
* resolved by the echoed id first, then by the REQUESTED id, so a snapshot
|
|
1750
|
+
* the registry doesn't list yet is still priced (and a fast-mode Opus turn
|
|
1751
|
+
* keeps its 2× instead of silently billing at base). The echoed id stays on
|
|
1752
|
+
* the record — only the price falls back.
|
|
1753
|
+
* - 1h cache writes are scaled to their real rate (see
|
|
1754
|
+
* {@link cacheWriteTtlMultiplier}); fast mode composes on top.
|
|
1755
|
+
*/
|
|
1756
|
+
async function priceAnthropicUsage(raw, response, requestedModelId, fastRequested, bedrock, catalog) {
|
|
1757
|
+
const providerKey = bedrock ? "amazon-bedrock" : "anthropic";
|
|
1758
|
+
const echoedModelId = raw.modelId ?? requestedModelId;
|
|
1759
|
+
const fast = fastRequested ? fastModeCostMultiplier(echoedModelId) ?? fastModeCostMultiplier(requestedModelId) : void 0;
|
|
1760
|
+
const ttl = cacheWriteTtlMultiplier(response.usage);
|
|
1761
|
+
const multiplier = composeCostMultipliers(fast, ttl === 1 ? void 0 : { cacheWrite: ttl });
|
|
1762
|
+
const priced = await fillEstimatedCost(raw, providerKey, multiplier, catalog);
|
|
1763
|
+
if (priced.cost !== void 0 || echoedModelId === requestedModelId) return priced;
|
|
1764
|
+
const byRequested = await fillEstimatedCost({
|
|
1765
|
+
...raw,
|
|
1766
|
+
modelId: requestedModelId
|
|
1767
|
+
}, providerKey, multiplier, catalog);
|
|
1768
|
+
return byRequested.cost !== void 0 ? {
|
|
1769
|
+
...raw,
|
|
1770
|
+
cost: byRequested.cost
|
|
1771
|
+
} : raw;
|
|
1772
|
+
}
|
|
1773
|
+
//#endregion
|
|
1774
|
+
//#region src/providers/diagnostic.ts
|
|
1775
|
+
const NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
1776
|
+
"ECONNREFUSED",
|
|
1777
|
+
"ECONNRESET",
|
|
1778
|
+
"ETIMEDOUT",
|
|
1779
|
+
"ENOTFOUND",
|
|
1780
|
+
"EAI_AGAIN",
|
|
1781
|
+
"ENETUNREACH",
|
|
1782
|
+
"EHOSTUNREACH",
|
|
1783
|
+
"EPIPE",
|
|
1784
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
1785
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
1786
|
+
"UND_ERR_SOCKET",
|
|
1787
|
+
"CERT_HAS_EXPIRED",
|
|
1788
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
1789
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
1790
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
1791
|
+
"AbortError",
|
|
1792
|
+
"TimeoutError"
|
|
1793
|
+
]);
|
|
1794
|
+
function sanitizeProviderDiagnosticMetadata(metadata) {
|
|
1795
|
+
const safe = {};
|
|
1796
|
+
if (typeof metadata.transportAttempt === "number" && Number.isSafeInteger(metadata.transportAttempt) && metadata.transportAttempt > 0) safe.transportAttempt = metadata.transportAttempt;
|
|
1797
|
+
if (typeof metadata.networkCode === "string" && NETWORK_CODES.has(metadata.networkCode)) safe.networkCode = metadata.networkCode;
|
|
1798
|
+
if (typeof metadata.httpStatus === "number" && Number.isInteger(metadata.httpStatus) && metadata.httpStatus >= 100 && metadata.httpStatus <= 599) safe.httpStatus = metadata.httpStatus;
|
|
1799
|
+
if (typeof metadata.upstreamRequestId === "string" && metadata.upstreamRequestId.length <= 128 && /^req_[\w-]+$/.test(metadata.upstreamRequestId)) safe.upstreamRequestId = metadata.upstreamRequestId;
|
|
1800
|
+
const retryAfter = metadata.retryAfter;
|
|
1801
|
+
if (typeof retryAfter === "string" && retryAfter.length <= 32 && (/^\d+(?:\.\d+)?$/.test(retryAfter) || retryAfter.length === 29 && new Date(retryAfter).toUTCString() === retryAfter)) safe.retryAfter = retryAfter;
|
|
1802
|
+
return safe;
|
|
1803
|
+
}
|
|
1804
|
+
/** No request or error objects cross this boundary. Diagnostics cannot fail a request. */
|
|
1805
|
+
function createProviderDiagnosticTracker(emit, signal) {
|
|
1806
|
+
const requestId = crypto.randomUUID();
|
|
1807
|
+
const started = performance.now();
|
|
1808
|
+
let stageStarted = started;
|
|
1809
|
+
let current;
|
|
1810
|
+
let attempt = 0;
|
|
1811
|
+
let transportAttempt = 0;
|
|
1812
|
+
let finished = false;
|
|
1813
|
+
let timer;
|
|
1814
|
+
let abort = () => {};
|
|
1815
|
+
function send(event, metadata = {}) {
|
|
1816
|
+
if (!current) return;
|
|
1817
|
+
const now = performance.now();
|
|
1818
|
+
try {
|
|
1819
|
+
emit?.({
|
|
1820
|
+
requestId,
|
|
1821
|
+
stage: current,
|
|
1822
|
+
event,
|
|
1823
|
+
elapsedMs: Math.round(now - started),
|
|
1824
|
+
stageElapsedMs: Math.round(now - stageStarted),
|
|
1825
|
+
attempt,
|
|
1826
|
+
...sanitizeProviderDiagnosticMetadata(metadata)
|
|
1827
|
+
});
|
|
1828
|
+
} catch {}
|
|
1829
|
+
}
|
|
1830
|
+
const tracker = {
|
|
1831
|
+
wrapFetch(fetch) {
|
|
1832
|
+
if (!emit) return fetch;
|
|
1833
|
+
return Object.assign(async (...args) => {
|
|
1834
|
+
const number = ++transportAttempt;
|
|
1835
|
+
const began = performance.now();
|
|
1836
|
+
const report = (event, metadata = {}) => {
|
|
1837
|
+
try {
|
|
1838
|
+
emit({
|
|
1839
|
+
requestId,
|
|
1840
|
+
stage: "transport",
|
|
1841
|
+
event,
|
|
1842
|
+
elapsedMs: Math.round(performance.now() - started),
|
|
1843
|
+
stageElapsedMs: Math.round(performance.now() - began),
|
|
1844
|
+
attempt,
|
|
1845
|
+
...sanitizeProviderDiagnosticMetadata({
|
|
1846
|
+
...metadata,
|
|
1847
|
+
transportAttempt: number
|
|
1848
|
+
})
|
|
1849
|
+
});
|
|
1850
|
+
} catch {}
|
|
1851
|
+
};
|
|
1852
|
+
report("start");
|
|
1853
|
+
try {
|
|
1854
|
+
const response = await fetch(...args);
|
|
1855
|
+
report("end", {
|
|
1856
|
+
httpStatus: response.status,
|
|
1857
|
+
upstreamRequestId: response.headers.get("request-id"),
|
|
1858
|
+
retryAfter: response.headers.get("retry-after")
|
|
1859
|
+
});
|
|
1860
|
+
return response;
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
const err = error;
|
|
1863
|
+
report("failure", { networkCode: [
|
|
1864
|
+
err?.cause?.code,
|
|
1865
|
+
err?.code,
|
|
1866
|
+
err?.name
|
|
1867
|
+
].find((value) => typeof value === "string" && NETWORK_CODES.has(value)) });
|
|
1868
|
+
throw error;
|
|
1869
|
+
}
|
|
1870
|
+
}, fetch);
|
|
1871
|
+
},
|
|
1872
|
+
stage(stage, metadata = {}) {
|
|
1873
|
+
if (!emit || finished) return;
|
|
1874
|
+
if (current) send("end");
|
|
1875
|
+
current = stage;
|
|
1876
|
+
stageStarted = performance.now();
|
|
1877
|
+
if (stage === "dispatch") attempt++;
|
|
1878
|
+
send("start", metadata);
|
|
1879
|
+
if (timer) clearInterval(timer);
|
|
1880
|
+
timer = setInterval(send, 3e4, "waiting");
|
|
1881
|
+
timer.unref?.();
|
|
1882
|
+
if (signal?.aborted) abort();
|
|
1883
|
+
},
|
|
1884
|
+
finish(event, metadata = {}) {
|
|
1885
|
+
if (!emit || finished) return;
|
|
1886
|
+
finished = true;
|
|
1887
|
+
clearInterval(timer);
|
|
1888
|
+
signal?.removeEventListener("abort", abort);
|
|
1889
|
+
if (current) {
|
|
1890
|
+
send("end");
|
|
1891
|
+
send(event, metadata);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1503
1894
|
};
|
|
1895
|
+
abort = () => tracker.finish("abort");
|
|
1896
|
+
if (emit) signal?.addEventListener("abort", abort, { once: true });
|
|
1897
|
+
return tracker;
|
|
1504
1898
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1899
|
+
//#endregion
|
|
1900
|
+
//#region src/providers/anthropic-stream.ts
|
|
1901
|
+
function isInputTransformation(value) {
|
|
1902
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1903
|
+
const v = value;
|
|
1904
|
+
return typeof v.type === "string" && typeof v.path === "string" && typeof v.reason === "string";
|
|
1507
1905
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
* (`thinking: { type: 'enabled', budget_tokens }`), so later calls skip the
|
|
1511
|
-
* failing round-trip and downgrade to adaptive up front.
|
|
1512
|
-
*
|
|
1513
|
-
* Learned, not declared — same rationale as
|
|
1514
|
-
* {@link MODELS_REJECTING_FORCED_TOOL_CHOICE}. Claude Opus 5.5 is the first
|
|
1515
|
-
* model where the `enabled` shape 400s (adaptive-only, thinking always on);
|
|
1516
|
-
* successors that follow suit are caught without a catalog edit.
|
|
1517
|
-
*/
|
|
1518
|
-
const MODELS_REJECTING_ENABLED_THINKING = /* @__PURE__ */ new Set();
|
|
1519
|
-
/**
|
|
1520
|
-
* True for the 400 Anthropic returns when a model does not support the manual
|
|
1521
|
-
* extended-thinking shape: `"thinking.type.enabled" is not supported for this
|
|
1522
|
-
* model. Use "thinking.type.adaptive" and "output_config.effort" ...`. Anchored
|
|
1523
|
-
* on the validation-400 gate plus the dotted field name and the "not supported
|
|
1524
|
-
* for this model" tail, so a reworded middle still matches while an unrelated
|
|
1525
|
-
* `thinking` validation error (bad budget, wrong type) does not. Only the
|
|
1526
|
-
* `enabled` variant is matched: zidane omits the field for the `'off'` level
|
|
1527
|
-
* (adaptive on these models) and never emits `thinking.type.disabled`.
|
|
1528
|
-
*/
|
|
1529
|
-
function isEnabledThinkingUnsupportedError(err) {
|
|
1530
|
-
return isRequestValidationError(err) && /\bthinking\.type\.enabled\b[^.]+not supported for this model\b/i.test(errorText(err));
|
|
1906
|
+
function betasFor(anthropicParams, conditional) {
|
|
1907
|
+
return conditional.length > 0 ? [...anthropicParams?.extraBetas ?? [], ...conditional] : anthropicParams?.extraBetas;
|
|
1531
1908
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1909
|
+
/** The `anthropic()` provider's `stream()`: prepare auth + client, build the request, run it. */
|
|
1910
|
+
async function streamAnthropic(provider, options, callbacks) {
|
|
1911
|
+
const diagnostic = createProviderDiagnosticTracker(callbacks.onDiagnostic, options.signal);
|
|
1912
|
+
const live = {
|
|
1913
|
+
diagnosticActive: false,
|
|
1914
|
+
apiKey: ""
|
|
1915
|
+
};
|
|
1916
|
+
try {
|
|
1917
|
+
const prepared = await prepareAnthropicCall(provider, options, callbacks, diagnostic, live);
|
|
1918
|
+
const { params, nameBoundary, modelId, affinityHeader } = await buildAnthropicStreamRequest(prepared);
|
|
1919
|
+
const call = {
|
|
1920
|
+
...prepared,
|
|
1921
|
+
nameBoundary,
|
|
1922
|
+
modelId,
|
|
1923
|
+
affinityHeader
|
|
1924
|
+
};
|
|
1925
|
+
const { callIsOAuth } = call;
|
|
1926
|
+
let initialParams = isForcedToolChoice(params.tool_choice) && MODELS_REJECTING_FORCED_TOOL_CHOICE.has(modelId) ? withoutForcedToolChoice(params) : params;
|
|
1927
|
+
if (isEnabledThinking(initialParams.thinking) && MODELS_REJECTING_ENABLED_THINKING.has(modelId)) initialParams = withoutEnabledThinking(initialParams);
|
|
1928
|
+
const result = await runAnthropicStream(call, initialParams, options.modelOptions?.fast === true && !callIsOAuth);
|
|
1929
|
+
diagnostic.finish("success");
|
|
1930
|
+
return result;
|
|
1931
|
+
} catch (err) {
|
|
1932
|
+
diagnostic.finish(options.signal?.aborted ? "abort" : "failure", { httpStatus: err && typeof err === "object" && "status" in err ? err.status : void 0 });
|
|
1933
|
+
throw err;
|
|
1934
|
+
} finally {
|
|
1935
|
+
diagnostic.finish("failure");
|
|
1936
|
+
}
|
|
1534
1937
|
}
|
|
1535
|
-
/**
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1938
|
+
/** Resolve (or force-refresh) this call's credential through the shared OAuth machinery. */
|
|
1939
|
+
async function resolveAnthropicCallApiKey(call, forceRefresh = false, rejectedAccess) {
|
|
1940
|
+
const identity = credentialIdentity(call.provider.anthropicParams);
|
|
1941
|
+
return await resolveOAuthApiKey({
|
|
1942
|
+
provider: identity.providerId,
|
|
1943
|
+
providerId: identity.providerId,
|
|
1944
|
+
params: call.provider.runtimeCredentials ? {
|
|
1945
|
+
...call.provider.anthropicParams,
|
|
1946
|
+
...call.provider.runtimeCredentials
|
|
1947
|
+
} : call.provider.anthropicParams,
|
|
1948
|
+
envKey: identity.envKey,
|
|
1949
|
+
missingError: identity.missingError,
|
|
1950
|
+
refreshError: identity.refreshError,
|
|
1951
|
+
forceRefresh,
|
|
1952
|
+
rejectedAccess
|
|
1953
|
+
}, {
|
|
1954
|
+
...call.callbacks,
|
|
1955
|
+
onOAuthStage(stage) {
|
|
1956
|
+
if (!call.callbacks.onDiagnostic || call.provider.anthropicParams?.authMode === "api-key" || call.provider.anthropicParams?.authMode === "bearer") return;
|
|
1957
|
+
call.live.diagnosticActive = true;
|
|
1958
|
+
call.diagnostic.stage(stage);
|
|
1959
|
+
},
|
|
1960
|
+
async onOAuthRefresh(ctx) {
|
|
1961
|
+
if (ctx.source === "params") call.provider.runtimeCredentials = {
|
|
1962
|
+
access: ctx.credentials.access,
|
|
1963
|
+
refresh: ctx.credentials.refresh,
|
|
1964
|
+
expires: ctx.credentials.expires
|
|
1965
|
+
};
|
|
1966
|
+
await call.callbacks.onOAuthRefresh?.(ctx);
|
|
1553
1967
|
}
|
|
1554
|
-
};
|
|
1968
|
+
});
|
|
1555
1969
|
}
|
|
1556
1970
|
/**
|
|
1557
|
-
*
|
|
1558
|
-
*
|
|
1559
|
-
*
|
|
1560
|
-
* The sentence is load-bearing, not decoration. `enforceSchemaOutput` sends the
|
|
1561
|
-
* agent's own system prompt, which says nothing about `__output__` — forcing
|
|
1562
|
-
* was the whole instruction — so without it a model on `auto` can legitimately
|
|
1563
|
-
* answer in prose and the caller's "no structured payload" guard fires. It is
|
|
1564
|
-
* appended as a final uncached block: everything before it keeps its cache
|
|
1565
|
-
* prefix, and the downgrade adds no new breakpoint.
|
|
1971
|
+
* Auth, betas, extra headers, and client for one call — everything decided
|
|
1972
|
+
* before the request body.
|
|
1566
1973
|
*/
|
|
1567
|
-
function
|
|
1568
|
-
const
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1974
|
+
async function prepareAnthropicCall(provider, options, callbacks, diagnostic, live) {
|
|
1975
|
+
const { anthropicParams, bedrockConfig } = provider;
|
|
1976
|
+
if (!bedrockConfig) live.apiKey = await resolveAnthropicCallApiKey({
|
|
1977
|
+
provider,
|
|
1978
|
+
callbacks,
|
|
1979
|
+
diagnostic,
|
|
1980
|
+
live
|
|
1981
|
+
});
|
|
1982
|
+
const callAuth = resolveAnthropicAuthShape(live.apiKey, bedrockConfig ? "api-key" : anthropicParams?.authMode);
|
|
1983
|
+
const callIsOAuth = callAuth.claudeCode;
|
|
1984
|
+
if (callIsOAuth && callbacks.onDiagnostic) {
|
|
1985
|
+
live.diagnosticActive = true;
|
|
1986
|
+
diagnostic.stage("prepare");
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Whether a provider auth rejection on this call is worth ATTEMPTING one
|
|
1990
|
+
* forced credential rotation. A bearer-authenticated call MAY resolve its
|
|
1991
|
+
* token through the refreshable OAuth store — subscription OAuth and
|
|
1992
|
+
* `authMode: 'bearer'` gateways (`extensions/bolt`) both do — so a 401
|
|
1993
|
+
* arriving before the local expiry is recoverable for them. Deliberately
|
|
1994
|
+
* WIDER than {@link callIsOAuth}, which additionally means "wear the
|
|
1995
|
+
* Claude Code persona"; a gateway is not Claude Code but its token still
|
|
1996
|
+
* rotates.
|
|
1997
|
+
*
|
|
1998
|
+
* The ones that cannot rotate stop at the identical-credential guard in
|
|
1999
|
+
* the recovery below rather than being excluded here: a static bearer
|
|
2000
|
+
* token supplied as `apiKey` short-circuits `resolveOAuthApiKey` and never
|
|
2001
|
+
* reaches the store, so the re-resolve returns the same value and costs
|
|
2002
|
+
* nothing but a function call. API-key and Bedrock calls never get that
|
|
2003
|
+
* far — `callAuth.bearer` is false for both.
|
|
2004
|
+
*/
|
|
2005
|
+
const callCanRotateAuth = callAuth.bearer;
|
|
2006
|
+
const effectiveCacheTtl = options.cacheTtl ?? (callIsOAuth ? "1h" : void 0);
|
|
2007
|
+
const extendedTtl = options.cache !== false && effectiveCacheTtl === "1h" && !bedrockConfig;
|
|
2008
|
+
const plan = planAnthropicThinking(options.thinking ?? "off", options.thinkingBudget, options.taskBudget, options.effortHint !== false);
|
|
2009
|
+
const thinkingBinding = resolveThinkingBinding(anthropicParams?.thinkingBinding);
|
|
2010
|
+
const bindingActive = plan !== null && !bedrockConfig && thinkingBinding !== "off" && !provider.thinkingBindingRejected;
|
|
2011
|
+
const baseConditionalBetas = [...options.modelOptions?.fast && !callIsOAuth ? [FAST_MODE_BETA] : [], ...extendedTtl ? [EXTENDED_CACHE_TTL_BETA] : []];
|
|
2012
|
+
const requestBetas = betasFor(anthropicParams, [...baseConditionalBetas, ...bindingActive ? [THINKING_BINDING_BETA] : []]);
|
|
2013
|
+
const resolvedExtraHeaders = await resolveExtraHeaders(anthropicParams?.extraHeaders);
|
|
1574
2014
|
return {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
2015
|
+
provider,
|
|
2016
|
+
callbacks,
|
|
2017
|
+
diagnostic,
|
|
2018
|
+
live,
|
|
2019
|
+
anthropicParams,
|
|
2020
|
+
bedrockConfig,
|
|
2021
|
+
options,
|
|
2022
|
+
callIsOAuth,
|
|
2023
|
+
effectiveCacheTtl,
|
|
2024
|
+
extendedTtl,
|
|
2025
|
+
plan,
|
|
2026
|
+
thinkingBinding,
|
|
2027
|
+
bindingActive,
|
|
2028
|
+
resolvedExtraHeaders,
|
|
2029
|
+
client: bedrockConfig ? await createBedrockClient(bedrockConfig, anthropicParams, resolvedExtraHeaders) : createClient(await loadAnthropicSdk(), live.apiKey, callAuth, anthropicParams?.baseURL, requestBetas, resolvedExtraHeaders, callIsOAuth ? diagnostic.wrapFetch : void 0),
|
|
2030
|
+
callAuth,
|
|
2031
|
+
callCanRotateAuth,
|
|
2032
|
+
baseConditionalBetas,
|
|
2033
|
+
requestBetas
|
|
1578
2034
|
};
|
|
1579
2035
|
}
|
|
1580
|
-
/**
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
if (message?.role !== "user") continue;
|
|
1594
|
-
if (typeof message.content === "string") return i;
|
|
1595
|
-
if (message.content.some((block) => block.type !== "tool_result")) return i;
|
|
2036
|
+
/** Dispatch one request and settle it, recovering through the retry ladder on failure. */
|
|
2037
|
+
async function runAnthropicStream(call, requestParams, fastRequested, scrubPass = 0, authPass = 0) {
|
|
2038
|
+
if (call.live.diagnosticActive) call.diagnostic.stage("dispatch");
|
|
2039
|
+
const s = withToolJsonEscaping(call.client.messages).stream(requestParams, {
|
|
2040
|
+
signal: call.options.signal,
|
|
2041
|
+
...call.affinityHeader ? { headers: call.affinityHeader } : {}
|
|
2042
|
+
});
|
|
2043
|
+
const turn = attachAnthropicStreamListeners(call, s);
|
|
2044
|
+
let response;
|
|
2045
|
+
try {
|
|
2046
|
+
response = await s.finalMessage();
|
|
2047
|
+
} catch (err) {
|
|
2048
|
+
return recoverAnthropicStreamFailure(call, turn, err, requestParams, fastRequested, scrubPass, authPass);
|
|
1596
2049
|
}
|
|
2050
|
+
return settleAnthropicStream(call, turn, response, fastRequested);
|
|
2051
|
+
}
|
|
2052
|
+
/** Wire diagnostics, output deltas, live usage, input transformations, and server-tool callbacks. */
|
|
2053
|
+
function attachAnthropicStreamListeners(call, s) {
|
|
2054
|
+
if (call.live.diagnosticActive) {
|
|
2055
|
+
s.on("connect", () => call.diagnostic.stage("response-headers", {
|
|
2056
|
+
httpStatus: s.response?.status,
|
|
2057
|
+
upstreamRequestId: s.request_id
|
|
2058
|
+
}));
|
|
2059
|
+
let firstActivity = true;
|
|
2060
|
+
s.on("streamEvent", () => {
|
|
2061
|
+
if (!firstActivity) return;
|
|
2062
|
+
firstActivity = false;
|
|
2063
|
+
call.diagnostic.stage("first-activity");
|
|
2064
|
+
call.diagnostic.stage("stream");
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
const turn = {
|
|
2068
|
+
text: "",
|
|
2069
|
+
observedOutput: false,
|
|
2070
|
+
inputTransformations: void 0
|
|
2071
|
+
};
|
|
2072
|
+
s.on("text", (delta) => {
|
|
2073
|
+
turn.observedOutput = true;
|
|
2074
|
+
turn.text += delta;
|
|
2075
|
+
call.callbacks.onText(delta);
|
|
2076
|
+
});
|
|
2077
|
+
if (call.callbacks.onThinking) s.on("thinking", (delta) => {
|
|
2078
|
+
turn.observedOutput = true;
|
|
2079
|
+
call.callbacks.onThinking(delta);
|
|
2080
|
+
});
|
|
2081
|
+
if (call.callbacks.onUsage) {
|
|
2082
|
+
let live;
|
|
2083
|
+
s.on("streamEvent", (event) => {
|
|
2084
|
+
if (event.type === "message_start") {
|
|
2085
|
+
const u = event.message.usage;
|
|
2086
|
+
live = {
|
|
2087
|
+
inputTokens: u.input_tokens ?? 0,
|
|
2088
|
+
...u.cache_read_input_tokens != null ? { cacheRead: u.cache_read_input_tokens } : {},
|
|
2089
|
+
...u.cache_creation_input_tokens != null ? { cacheCreation: u.cache_creation_input_tokens } : {},
|
|
2090
|
+
...call.callIsOAuth ? { billable: false } : {}
|
|
2091
|
+
};
|
|
2092
|
+
} else if (event.type === "message_delta") live = {
|
|
2093
|
+
...live ?? {
|
|
2094
|
+
inputTokens: 0,
|
|
2095
|
+
...call.callIsOAuth ? { billable: false } : {}
|
|
2096
|
+
},
|
|
2097
|
+
outputTokens: event.usage.output_tokens
|
|
2098
|
+
};
|
|
2099
|
+
else return;
|
|
2100
|
+
call.callbacks.onUsage(live);
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
if (call.callbacks.onInputTransformations) s.on("streamEvent", (event) => {
|
|
2104
|
+
const list = (event.type === "message_start" ? event.message : event.type === "message_delta" ? event : void 0)?.input_transformations;
|
|
2105
|
+
if (Array.isArray(list) && list.length > 0) turn.inputTransformations = list.filter(isInputTransformation);
|
|
2106
|
+
});
|
|
2107
|
+
if (call.callbacks.onToolCallDelta) s.on("inputJson", () => {
|
|
2108
|
+
turn.observedOutput = true;
|
|
2109
|
+
call.callbacks.onToolCallDelta();
|
|
2110
|
+
});
|
|
2111
|
+
if (call.callbacks.onServerToolUse || call.callbacks.onServerToolResult) s.on("contentBlock", (block) => {
|
|
2112
|
+
turn.observedOutput = true;
|
|
2113
|
+
if (block.type === "server_tool_use") call.callbacks.onServerToolUse?.({
|
|
2114
|
+
id: block.id,
|
|
2115
|
+
name: block.name,
|
|
2116
|
+
input: block.input ?? {}
|
|
2117
|
+
});
|
|
2118
|
+
else if (block.type === "web_search_tool_result") call.callbacks.onServerToolResult?.({
|
|
2119
|
+
toolUseId: block.tool_use_id,
|
|
2120
|
+
toolName: "web_search",
|
|
2121
|
+
content: block.content
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
return turn;
|
|
1597
2125
|
}
|
|
1598
2126
|
/**
|
|
1599
|
-
*
|
|
1600
|
-
*
|
|
1601
|
-
*
|
|
1602
|
-
* trade a recoverable 400 for an "expected thinking block" one). When the
|
|
1603
|
-
* API-reported failing index lies INSIDE the chain, only that single
|
|
1604
|
-
* message is scrubbed on top of the pre-chain region: the block the API
|
|
1605
|
-
* itself rejected fails whether or not it is echoed, but sibling chain
|
|
1606
|
-
* messages below it may hold still-valid thinking the exchange requires.
|
|
1607
|
-
*
|
|
1608
|
-
* - pass 0 with a pre-chain locator → bounded at the locator (preserves
|
|
1609
|
-
* later, still-valid historical thinking).
|
|
1610
|
-
* - pass 0 without a locator, and every escalated pass → the full
|
|
1611
|
-
* pre-chain history.
|
|
1612
|
-
* - no active chain at all (degenerate resume shapes) → locator bound on
|
|
1613
|
-
* pass 0, full history afterwards.
|
|
1614
|
-
*/
|
|
1615
|
-
function planThinkingScrub(scrubPass, failingIndex, messages) {
|
|
1616
|
-
const chainStart = activeChainScrubBoundary(messages);
|
|
1617
|
-
if (chainStart === void 0) return scrubPass === 0 && failingIndex !== void 0 ? { upToIndex: failingIndex } : {};
|
|
1618
|
-
const beforeChain = chainStart - 1;
|
|
1619
|
-
const inChain = failingIndex !== void 0 && failingIndex >= chainStart;
|
|
1620
|
-
const upToIndex = scrubPass === 0 && failingIndex !== void 0 && !inChain ? failingIndex : beforeChain;
|
|
1621
|
-
return inChain ? {
|
|
1622
|
-
upToIndex,
|
|
1623
|
-
alsoMessageIndex: failingIndex
|
|
1624
|
-
} : { upToIndex };
|
|
1625
|
-
}
|
|
1626
|
-
/**
|
|
1627
|
-
* Remove opaque historical Anthropic reasoning from a retry payload. Text,
|
|
1628
|
-
* tool calls/results, images, and every other block are preserved exactly.
|
|
1629
|
-
* If a message consisted only of reasoning, leave a neutral text marker so
|
|
1630
|
-
* the wire never receives an empty content array and conversation role
|
|
1631
|
-
* boundaries stay intact.
|
|
1632
|
-
*
|
|
1633
|
-
* `upToIndex` bounds the scrub to messages at/before the API-reported
|
|
1634
|
-
* failing index (see {@link parseFailingMessageIndex}); `undefined` strips
|
|
1635
|
-
* everywhere. `alsoMessageIndex` additionally scrubs that single message
|
|
1636
|
-
* beyond the bound (see {@link planThinkingScrub}). A later stale block
|
|
1637
|
-
* simply triggers the same recovery again — each pass removes at least
|
|
1638
|
-
* one block, so the recursion is bounded.
|
|
2127
|
+
* The retry ladder for a failed dispatch: thinking-signature scrub, forced
|
|
2128
|
+
* OAuth rotation, fast-mode fallback, binding downgrade, forced-tool-choice
|
|
2129
|
+
* and enabled-thinking downgrades. Rethrows when nothing applies.
|
|
1639
2130
|
*/
|
|
1640
|
-
function
|
|
1641
|
-
|
|
2131
|
+
async function recoverAnthropicStreamFailure(call, turn, err, requestParams, fastRequested, scrubPass, authPass) {
|
|
2132
|
+
if (!(call.bindingActive && call.thinkingBinding === "error" && isPrefixMismatchError(err)) && !turn.observedOutput && scrubPass < 3 && isRecoverableThinkingBlockError(err, requestParams.messages)) {
|
|
2133
|
+
const failingIndex = parseFailingMessageIndex(err);
|
|
2134
|
+
const plan = planThinkingScrub(scrubPass, failingIndex, requestParams.messages);
|
|
2135
|
+
const scrubbed = withoutHistoricalThinking(requestParams.messages, plan.upToIndex, plan.alsoMessageIndex);
|
|
2136
|
+
if (scrubbed.removed > 0) {
|
|
2137
|
+
try {
|
|
2138
|
+
await call.callbacks.onHistoryRepair?.({
|
|
2139
|
+
type: "invalid-anthropic-thinking-signature",
|
|
2140
|
+
...failingIndex !== void 0 ? { wireMessageIndex: failingIndex } : {}
|
|
2141
|
+
});
|
|
2142
|
+
} catch (repairErr) {
|
|
2143
|
+
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/anthropic] history repair callback failed: ${errorMessage(repairErr)}\n`);
|
|
2144
|
+
}
|
|
2145
|
+
return await runAnthropicStream(call, {
|
|
2146
|
+
...requestParams,
|
|
2147
|
+
messages: scrubbed.messages
|
|
2148
|
+
}, fastRequested, scrubPass + 1, authPass);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
if (call.callCanRotateAuth && authPass === 0 && !turn.observedOutput && isAnthropicAuthenticationError(err)) {
|
|
2152
|
+
const rejectedAccess = call.live.apiKey;
|
|
2153
|
+
let refreshedApiKey;
|
|
2154
|
+
try {
|
|
2155
|
+
refreshedApiKey = await resolveAnthropicCallApiKey(call, true, rejectedAccess);
|
|
2156
|
+
} catch (refreshErr) {
|
|
2157
|
+
if (err !== null && typeof err === "object" && err.cause === void 0) try {
|
|
2158
|
+
err.cause = refreshErr;
|
|
2159
|
+
} catch {}
|
|
2160
|
+
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/anthropic] forced OAuth refresh after 401 failed: ${errorMessage(refreshErr)}\n`);
|
|
2161
|
+
throw err;
|
|
2162
|
+
}
|
|
2163
|
+
if (refreshedApiKey === rejectedAccess) throw err;
|
|
2164
|
+
call.live.apiKey = refreshedApiKey;
|
|
2165
|
+
call.client = createClient(await loadAnthropicSdk(), call.live.apiKey, call.callAuth, call.anthropicParams?.baseURL, call.requestBetas, call.resolvedExtraHeaders, call.callIsOAuth ? call.diagnostic.wrapFetch : void 0);
|
|
2166
|
+
return await runAnthropicStream(call, requestParams, fastRequested, scrubPass, authPass + 1);
|
|
2167
|
+
}
|
|
2168
|
+
if (fastRequested && !turn.observedOutput && shouldFallbackFromFastMode(err)) {
|
|
2169
|
+
const standardParams = { ...requestParams };
|
|
2170
|
+
delete standardParams.speed;
|
|
2171
|
+
return await runAnthropicStream(call, standardParams, false, scrubPass, authPass);
|
|
2172
|
+
}
|
|
2173
|
+
if (call.bindingActive && !call.provider.thinkingBindingRejected && !turn.observedOutput && isThinkingBindingRejectedError(err)) {
|
|
2174
|
+
call.provider.thinkingBindingRejected = true;
|
|
2175
|
+
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/anthropic] endpoint rejected thinking.block_binding; retrying without it: ${errorMessage(err)}\n`);
|
|
2176
|
+
call.client = createClient(await loadAnthropicSdk(), call.live.apiKey, call.callAuth, call.anthropicParams?.baseURL, betasFor(call.anthropicParams, call.baseConditionalBetas), call.resolvedExtraHeaders, call.callIsOAuth ? call.diagnostic.wrapFetch : void 0);
|
|
2177
|
+
return await runAnthropicStream(call, withoutThinkingBinding(requestParams), fastRequested, scrubPass, authPass);
|
|
2178
|
+
}
|
|
2179
|
+
if (!turn.observedOutput && isForcedToolChoice(requestParams.tool_choice) && isForcedToolChoiceUnsupportedError(err)) {
|
|
2180
|
+
MODELS_REJECTING_FORCED_TOOL_CHOICE.add(call.modelId);
|
|
2181
|
+
return await runAnthropicStream(call, withoutForcedToolChoice(requestParams), fastRequested, scrubPass, authPass);
|
|
2182
|
+
}
|
|
2183
|
+
if (!turn.observedOutput && isEnabledThinking(requestParams.thinking) && isEnabledThinkingUnsupportedError(err)) {
|
|
2184
|
+
MODELS_REJECTING_ENABLED_THINKING.add(call.modelId);
|
|
2185
|
+
return await runAnthropicStream(call, withoutEnabledThinking(requestParams), fastRequested, scrubPass, authPass);
|
|
2186
|
+
}
|
|
2187
|
+
throw err;
|
|
2188
|
+
}
|
|
2189
|
+
/** Fire the settled-stream hooks and map the final message to a `TurnResult`. */
|
|
2190
|
+
async function settleAnthropicStream(call, turn, response, fastRequested) {
|
|
2191
|
+
if (turn.inputTransformations && turn.inputTransformations.length > 0) try {
|
|
2192
|
+
call.callbacks.onInputTransformations?.(turn.inputTransformations);
|
|
2193
|
+
} catch (hookErr) {
|
|
2194
|
+
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/anthropic] onInputTransformations threw: ${errorMessage(hookErr)}\n`);
|
|
2195
|
+
}
|
|
2196
|
+
const toolCalls = response.content.filter((b) => b.type === "tool_use").map((b) => ({
|
|
2197
|
+
id: b.id,
|
|
2198
|
+
name: call.nameBoundary.fromWire(b.name),
|
|
2199
|
+
input: b.input
|
|
2200
|
+
}));
|
|
2201
|
+
const finishReason = mapStopReason(response.stop_reason);
|
|
2202
|
+
const isPause = response.stop_reason === "pause_turn";
|
|
2203
|
+
const rawUsage = {
|
|
2204
|
+
input: response.usage.input_tokens,
|
|
2205
|
+
output: response.usage.output_tokens,
|
|
2206
|
+
cacheCreation: response.usage.cache_creation_input_tokens ?? void 0,
|
|
2207
|
+
cacheRead: response.usage.cache_read_input_tokens ?? void 0,
|
|
2208
|
+
...finishReason ? { finishReason } : {},
|
|
2209
|
+
modelId: response.model ?? call.options.model
|
|
2210
|
+
};
|
|
2211
|
+
const usage = call.callIsOAuth ? rawUsage : await priceAnthropicUsage(rawUsage, response, call.modelId, fastRequested, Boolean(call.bedrockConfig), call.options.modelCatalog);
|
|
2212
|
+
const assistantMessage = fromAnthropic({
|
|
2213
|
+
role: "assistant",
|
|
2214
|
+
content: response.content
|
|
2215
|
+
});
|
|
2216
|
+
if (call.nameBoundary.changed) assistantMessage.content = assistantMessage.content.map((block) => block.type === "tool_call" ? {
|
|
2217
|
+
...block,
|
|
2218
|
+
name: call.nameBoundary.fromWire(block.name)
|
|
2219
|
+
} : block);
|
|
1642
2220
|
return {
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
removed += 1;
|
|
1649
|
-
return false;
|
|
1650
|
-
}
|
|
1651
|
-
return true;
|
|
1652
|
-
});
|
|
1653
|
-
return {
|
|
1654
|
-
...message,
|
|
1655
|
-
content: content.length > 0 ? content : [{
|
|
1656
|
-
type: "text",
|
|
1657
|
-
text: "[historical reasoning omitted]"
|
|
1658
|
-
}]
|
|
1659
|
-
};
|
|
1660
|
-
}),
|
|
1661
|
-
removed
|
|
2221
|
+
assistantMessage,
|
|
2222
|
+
text: turn.text,
|
|
2223
|
+
toolCalls,
|
|
2224
|
+
done: !isPause && (response.stop_reason === "end_turn" || toolCalls.length === 0),
|
|
2225
|
+
usage
|
|
1662
2226
|
};
|
|
1663
2227
|
}
|
|
2228
|
+
//#endregion
|
|
2229
|
+
//#region src/providers/anthropic.ts
|
|
1664
2230
|
/**
|
|
1665
2231
|
* Build a user `SessionMessage` from multimodal prompt parts.
|
|
1666
2232
|
*
|
|
@@ -1711,15 +2277,12 @@ function anthropic(anthropicParams) {
|
|
|
1711
2277
|
const bedrockConfig = normalizeBedrock(anthropicParams?.bedrock);
|
|
1712
2278
|
const isOAuth = resolveAnthropicAuthShape(bedrockConfig ? "" : getConfiguredApiKey(anthropicParams), anthropicParams?.authMode).bearer;
|
|
1713
2279
|
const defaultModel = anthropicParams?.defaultModel || "claude-fable-5-1";
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
* endpoint + credential, and one process may hold several.
|
|
1721
|
-
*/
|
|
1722
|
-
let thinkingBindingRejected = false;
|
|
2280
|
+
const state = {
|
|
2281
|
+
anthropicParams,
|
|
2282
|
+
bedrockConfig,
|
|
2283
|
+
runtimeCredentials: extractRuntimeCredentials(anthropicParams),
|
|
2284
|
+
thinkingBindingRejected: false
|
|
2285
|
+
};
|
|
1723
2286
|
return {
|
|
1724
2287
|
name: "anthropic",
|
|
1725
2288
|
meta: {
|
|
@@ -1733,6 +2296,7 @@ function anthropic(anthropicParams) {
|
|
|
1733
2296
|
audio: false,
|
|
1734
2297
|
video: false,
|
|
1735
2298
|
...bedrockConfig ? {} : { nativeWebSearch: { maxUses: 5 } },
|
|
2299
|
+
...bedrockConfig ? { maxMediaPerRequest: 20 } : {},
|
|
1736
2300
|
adaptiveBudgetIsEnvelope: true,
|
|
1737
2301
|
...anthropicParams?.capabilities
|
|
1738
2302
|
}
|
|
@@ -1740,7 +2304,8 @@ function anthropic(anthropicParams) {
|
|
|
1740
2304
|
formatTools(tools) {
|
|
1741
2305
|
const nativeWebSearch = this.meta.capabilities?.nativeWebSearch;
|
|
1742
2306
|
const hasWebSearch = !!nativeWebSearch && tools.some((t) => t.name === "web_search");
|
|
1743
|
-
const
|
|
2307
|
+
const fnTools = hasWebSearch ? tools.filter((t) => t.name !== "web_search") : tools;
|
|
2308
|
+
const out = sanitizeToolSpecs(fnTools, { profile: "anthropic" }).map((t) => ({
|
|
1744
2309
|
name: t.name,
|
|
1745
2310
|
description: t.description ?? "",
|
|
1746
2311
|
input_schema: t.inputSchema
|
|
@@ -1795,9 +2360,9 @@ function anthropic(anthropicParams) {
|
|
|
1795
2360
|
const apiKey = await resolveOAuthApiKey({
|
|
1796
2361
|
provider: identity.providerId,
|
|
1797
2362
|
providerId: identity.providerId,
|
|
1798
|
-
params: runtimeCredentials ? {
|
|
2363
|
+
params: state.runtimeCredentials ? {
|
|
1799
2364
|
...anthropicParams,
|
|
1800
|
-
...runtimeCredentials
|
|
2365
|
+
...state.runtimeCredentials
|
|
1801
2366
|
} : anthropicParams,
|
|
1802
2367
|
envKey: identity.envKey,
|
|
1803
2368
|
missingError: identity.missingError,
|
|
@@ -1806,25 +2371,7 @@ function anthropic(anthropicParams) {
|
|
|
1806
2371
|
const callAuth = resolveAnthropicAuthShape(apiKey, anthropicParams?.authMode);
|
|
1807
2372
|
const callIsOAuth = callAuth.claudeCode;
|
|
1808
2373
|
const client = createClient(SDK, apiKey, callAuth, anthropicParams?.baseURL, anthropicParams?.extraBetas, await resolveExtraHeaders(anthropicParams?.extraHeaders));
|
|
1809
|
-
const
|
|
1810
|
-
const system = callIsOAuth ? `You are Claude Code, Anthropic's official CLI for Claude.` : wireSystem;
|
|
1811
|
-
const messages = callIsOAuth && payload.system ? [
|
|
1812
|
-
{
|
|
1813
|
-
role: "user",
|
|
1814
|
-
content: [{
|
|
1815
|
-
type: "text",
|
|
1816
|
-
text: wireSystem
|
|
1817
|
-
}]
|
|
1818
|
-
},
|
|
1819
|
-
{
|
|
1820
|
-
role: "assistant",
|
|
1821
|
-
content: [{
|
|
1822
|
-
type: "text",
|
|
1823
|
-
text: "Understood. I will proceed with these instructions above the rest of my system prompt."
|
|
1824
|
-
}]
|
|
1825
|
-
},
|
|
1826
|
-
...payload.messages
|
|
1827
|
-
] : [...payload.messages];
|
|
2374
|
+
const { system, messages } = foldSystemForWire(payload.system, payload.messages, callIsOAuth);
|
|
1828
2375
|
const countTools = payload.tools;
|
|
1829
2376
|
const countBoundary = createToolNameBoundary(messages, countTools.map((t) => t.name), callIsOAuth ? claudeCodeToolAliases : void 0);
|
|
1830
2377
|
return (await client.messages.countTokens({
|
|
@@ -1841,381 +2388,12 @@ function anthropic(anthropicParams) {
|
|
|
1841
2388
|
return null;
|
|
1842
2389
|
}
|
|
1843
2390
|
},
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
let diagnosticActive = false;
|
|
1847
|
-
try {
|
|
1848
|
-
let apiKey = "";
|
|
1849
|
-
const resolveCallApiKey = async (forceRefresh = false, rejectedAccess) => {
|
|
1850
|
-
const identity = credentialIdentity(anthropicParams);
|
|
1851
|
-
return await resolveOAuthApiKey({
|
|
1852
|
-
provider: identity.providerId,
|
|
1853
|
-
providerId: identity.providerId,
|
|
1854
|
-
params: runtimeCredentials ? {
|
|
1855
|
-
...anthropicParams,
|
|
1856
|
-
...runtimeCredentials
|
|
1857
|
-
} : anthropicParams,
|
|
1858
|
-
envKey: identity.envKey,
|
|
1859
|
-
missingError: identity.missingError,
|
|
1860
|
-
refreshError: identity.refreshError,
|
|
1861
|
-
forceRefresh,
|
|
1862
|
-
rejectedAccess
|
|
1863
|
-
}, {
|
|
1864
|
-
...callbacks,
|
|
1865
|
-
onOAuthStage(stage) {
|
|
1866
|
-
if (!callbacks.onDiagnostic || anthropicParams?.authMode === "api-key" || anthropicParams?.authMode === "bearer") return;
|
|
1867
|
-
diagnosticActive = true;
|
|
1868
|
-
diagnostic.stage(stage);
|
|
1869
|
-
},
|
|
1870
|
-
async onOAuthRefresh(ctx) {
|
|
1871
|
-
if (ctx.source === "params") runtimeCredentials = {
|
|
1872
|
-
access: ctx.credentials.access,
|
|
1873
|
-
refresh: ctx.credentials.refresh,
|
|
1874
|
-
expires: ctx.credentials.expires
|
|
1875
|
-
};
|
|
1876
|
-
await callbacks.onOAuthRefresh?.(ctx);
|
|
1877
|
-
}
|
|
1878
|
-
});
|
|
1879
|
-
};
|
|
1880
|
-
if (!bedrockConfig) apiKey = await resolveCallApiKey();
|
|
1881
|
-
const callAuth = resolveAnthropicAuthShape(apiKey, bedrockConfig ? "api-key" : anthropicParams?.authMode);
|
|
1882
|
-
const callIsOAuth = callAuth.claudeCode;
|
|
1883
|
-
if (callIsOAuth && callbacks.onDiagnostic) {
|
|
1884
|
-
diagnosticActive = true;
|
|
1885
|
-
diagnostic.stage("prepare");
|
|
1886
|
-
}
|
|
1887
|
-
/**
|
|
1888
|
-
* Whether a provider auth rejection on this call is worth ATTEMPTING one
|
|
1889
|
-
* forced credential rotation. A bearer-authenticated call MAY resolve its
|
|
1890
|
-
* token through the refreshable OAuth store — subscription OAuth and
|
|
1891
|
-
* `authMode: 'bearer'` gateways (`extensions/bolt`) both do — so a 401
|
|
1892
|
-
* arriving before the local expiry is recoverable for them. Deliberately
|
|
1893
|
-
* WIDER than {@link callIsOAuth}, which additionally means "wear the
|
|
1894
|
-
* Claude Code persona"; a gateway is not Claude Code but its token still
|
|
1895
|
-
* rotates.
|
|
1896
|
-
*
|
|
1897
|
-
* The ones that cannot rotate stop at the identical-credential guard in
|
|
1898
|
-
* the recovery below rather than being excluded here: a static bearer
|
|
1899
|
-
* token supplied as `apiKey` short-circuits `resolveOAuthApiKey` and never
|
|
1900
|
-
* reaches the store, so the re-resolve returns the same value and costs
|
|
1901
|
-
* nothing but a function call. API-key and Bedrock calls never get that
|
|
1902
|
-
* far — `callAuth.bearer` is false for both.
|
|
1903
|
-
*/
|
|
1904
|
-
const callCanRotateAuth = callAuth.bearer;
|
|
1905
|
-
const effectiveCacheTtl = options.cacheTtl ?? (callIsOAuth ? "1h" : void 0);
|
|
1906
|
-
const extendedTtl = options.cache !== false && effectiveCacheTtl === "1h" && !bedrockConfig;
|
|
1907
|
-
const plan = planAnthropicThinking(options.thinking ?? "off", options.thinkingBudget, options.taskBudget, options.effortHint !== false);
|
|
1908
|
-
const thinkingBinding = resolveThinkingBinding(anthropicParams?.thinkingBinding);
|
|
1909
|
-
const bindingActive = plan !== null && !bedrockConfig && thinkingBinding !== "off" && !thinkingBindingRejected;
|
|
1910
|
-
const baseConditionalBetas = [...options.modelOptions?.fast && !callIsOAuth ? [FAST_MODE_BETA] : [], ...extendedTtl ? [EXTENDED_CACHE_TTL_BETA] : []];
|
|
1911
|
-
const conditionalBetas = [...baseConditionalBetas, ...bindingActive ? [THINKING_BINDING_BETA] : []];
|
|
1912
|
-
const betasFor = (conditional) => conditional.length > 0 ? [...anthropicParams?.extraBetas ?? [], ...conditional] : anthropicParams?.extraBetas;
|
|
1913
|
-
const requestBetas = betasFor(conditionalBetas);
|
|
1914
|
-
const resolvedExtraHeaders = await resolveExtraHeaders(anthropicParams?.extraHeaders);
|
|
1915
|
-
let client = bedrockConfig ? await createBedrockClient(bedrockConfig, anthropicParams, resolvedExtraHeaders) : createClient(await loadAnthropicSdk(), apiKey, callAuth, anthropicParams?.baseURL, requestBetas, resolvedExtraHeaders, callIsOAuth ? diagnostic.wrapFetch : void 0);
|
|
1916
|
-
const wireSystem = renderSystemForWire(options.system);
|
|
1917
|
-
const system = callIsOAuth ? `You are Claude Code, Anthropic's official CLI for Claude.` : wireSystem;
|
|
1918
|
-
const messages = callIsOAuth && options.system ? [
|
|
1919
|
-
{
|
|
1920
|
-
role: "user",
|
|
1921
|
-
content: [{
|
|
1922
|
-
type: "text",
|
|
1923
|
-
text: wireSystem
|
|
1924
|
-
}]
|
|
1925
|
-
},
|
|
1926
|
-
{
|
|
1927
|
-
role: "assistant",
|
|
1928
|
-
content: [{
|
|
1929
|
-
type: "text",
|
|
1930
|
-
text: "Understood. I will proceed with these instructions above the rest of my system prompt."
|
|
1931
|
-
}]
|
|
1932
|
-
},
|
|
1933
|
-
...options.messages
|
|
1934
|
-
] : [...options.messages];
|
|
1935
|
-
const inputTools = options.tools ?? [];
|
|
1936
|
-
const nameBoundary = createToolNameBoundary(messages, inputTools.map((t) => t.name), callIsOAuth ? claudeCodeToolAliases : void 0);
|
|
1937
|
-
const wireTools = nameBoundary.changed ? inputTools.map((t) => t.name === nameBoundary.toWire(t.name) ? t : {
|
|
1938
|
-
...t,
|
|
1939
|
-
name: nameBoundary.toWire(t.name)
|
|
1940
|
-
}) : inputTools;
|
|
1941
|
-
const modelId = options.model;
|
|
1942
|
-
const modelMaxOutput = await resolveAnthropicMaxOutput(modelId, options.maxTokens, options.modelCatalog);
|
|
1943
|
-
const params = {
|
|
1944
|
-
...anthropicParams?.extraBodyParams ?? {},
|
|
1945
|
-
model: modelId,
|
|
1946
|
-
max_tokens: options.maxTokens ?? modelMaxOutput,
|
|
1947
|
-
system,
|
|
1948
|
-
tools: wireTools,
|
|
1949
|
-
messages: nameBoundary.messages.map((m) => toAnthropic(m)),
|
|
1950
|
-
stream: true
|
|
1951
|
-
};
|
|
1952
|
-
if (anthropicParams?.contextManagement && !bedrockConfig) params.context_management = anthropicParams.contextManagement;
|
|
1953
|
-
if (options.cache !== false) applyAnthropicCacheBreakpoints(params, callIsOAuth ? void 0 : options.system, extendedTtl ? effectiveCacheTtl : void 0);
|
|
1954
|
-
if (plan && bedrockConfig) {
|
|
1955
|
-
const budget = plan.kind === "enabled" ? plan.budgetTokens : BEDROCK_THINKING_BUDGETS[plan.effort ?? "medium"];
|
|
1956
|
-
params.thinking = {
|
|
1957
|
-
type: "enabled",
|
|
1958
|
-
budget_tokens: budget
|
|
1959
|
-
};
|
|
1960
|
-
params.max_tokens = budget + params.max_tokens;
|
|
1961
|
-
params.temperature = 1;
|
|
1962
|
-
} else if (plan) {
|
|
1963
|
-
if (plan.kind === "enabled") {
|
|
1964
|
-
params.thinking = {
|
|
1965
|
-
type: "enabled",
|
|
1966
|
-
budget_tokens: plan.budgetTokens,
|
|
1967
|
-
display: "summarized"
|
|
1968
|
-
};
|
|
1969
|
-
params.max_tokens = plan.maxTokensBump + params.max_tokens;
|
|
1970
|
-
if (plan.effort) params.output_config = {
|
|
1971
|
-
...params.output_config,
|
|
1972
|
-
effort: plan.effort
|
|
1973
|
-
};
|
|
1974
|
-
} else {
|
|
1975
|
-
params.thinking = {
|
|
1976
|
-
type: "adaptive",
|
|
1977
|
-
display: "summarized"
|
|
1978
|
-
};
|
|
1979
|
-
const outputConfig = {};
|
|
1980
|
-
if (plan.effort) outputConfig.effort = plan.effort;
|
|
1981
|
-
if (typeof plan.taskBudgetTokens === "number" && plan.taskBudgetTokens > 0) outputConfig.task_budget = {
|
|
1982
|
-
type: "tokens",
|
|
1983
|
-
total: plan.taskBudgetTokens
|
|
1984
|
-
};
|
|
1985
|
-
if (Object.keys(outputConfig).length > 0) params.output_config = {
|
|
1986
|
-
...params.output_config,
|
|
1987
|
-
...outputConfig
|
|
1988
|
-
};
|
|
1989
|
-
if (typeof plan.maxTokensCap === "number" && plan.maxTokensCap > 0) params.max_tokens = Math.min(params.max_tokens, plan.maxTokensCap);
|
|
1990
|
-
}
|
|
1991
|
-
params.temperature = 1;
|
|
1992
|
-
if (bindingActive) params.thinking.block_binding = { prefix_mismatch_behavior: thinkingBinding };
|
|
1993
|
-
}
|
|
1994
|
-
params.max_tokens = Math.min(params.max_tokens, modelMaxOutput);
|
|
1995
|
-
if (callIsOAuth) {
|
|
1996
|
-
params.max_tokens = Math.min(params.max_tokens, CLAUDE_CODE_MAX_OUTPUT_TOKENS);
|
|
1997
|
-
if (Array.isArray(params.system)) params.system = [{
|
|
1998
|
-
type: "text",
|
|
1999
|
-
text: claudeCodeBillingText(firstUserMessageText(params.messages))
|
|
2000
|
-
}, ...params.system];
|
|
2001
|
-
if (typeof params.metadata?.user_id !== "string") params.metadata = {
|
|
2002
|
-
...params.metadata,
|
|
2003
|
-
user_id: claudeCodeUserId()
|
|
2004
|
-
};
|
|
2005
|
-
}
|
|
2006
|
-
if (params.thinking?.type === "enabled" && params.thinking.budget_tokens >= params.max_tokens) {
|
|
2007
|
-
const fitted = params.max_tokens - THINKING_RESPONSE_RESERVE_TOKENS;
|
|
2008
|
-
if (fitted >= 1024) params.thinking.budget_tokens = fitted;
|
|
2009
|
-
else {
|
|
2010
|
-
delete params.thinking;
|
|
2011
|
-
delete params.temperature;
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
if (options.modelOptions?.fast && !callIsOAuth) params.speed = "fast";
|
|
2015
|
-
if (options.toolChoice) if (options.toolChoice.type === "tool" && options.toolChoice.name) params.tool_choice = {
|
|
2016
|
-
type: "tool",
|
|
2017
|
-
name: nameBoundary.toWire(options.toolChoice.name)
|
|
2018
|
-
};
|
|
2019
|
-
else if (options.toolChoice.type === "required") params.tool_choice = { type: "any" };
|
|
2020
|
-
else params.tool_choice = { type: "auto" };
|
|
2021
|
-
const promptCacheKey = resolvePromptCacheKey(anthropicParams?.promptCacheKey, {
|
|
2022
|
-
model: modelId,
|
|
2023
|
-
system: options.system,
|
|
2024
|
-
messages: options.messages
|
|
2025
|
-
});
|
|
2026
|
-
if (promptCacheKey !== void 0 && params.metadata?.user_id === void 0) params.metadata = {
|
|
2027
|
-
...params.metadata,
|
|
2028
|
-
user_id: promptCacheKey
|
|
2029
|
-
};
|
|
2030
|
-
const affinityHeaderName = anthropicParams?.promptCacheKeyHeader?.toLowerCase();
|
|
2031
|
-
const affinityHeader = anthropicParams?.promptCacheKeyHeader !== void 0 && promptCacheKey !== void 0 && !Object.keys(resolvedExtraHeaders ?? {}).some((k) => k.toLowerCase() === affinityHeaderName) ? { [anthropicParams.promptCacheKeyHeader]: promptCacheKey } : void 0;
|
|
2032
|
-
const runStream = async (requestParams, fastRequested, scrubPass = 0, authPass = 0) => {
|
|
2033
|
-
if (diagnosticActive) diagnostic.stage("dispatch");
|
|
2034
|
-
const s = client.messages.stream(requestParams, {
|
|
2035
|
-
signal: options.signal,
|
|
2036
|
-
...affinityHeader ? { headers: affinityHeader } : {}
|
|
2037
|
-
});
|
|
2038
|
-
if (diagnosticActive) {
|
|
2039
|
-
s.on("connect", () => diagnostic.stage("response-headers", {
|
|
2040
|
-
httpStatus: s.response?.status,
|
|
2041
|
-
upstreamRequestId: s.request_id
|
|
2042
|
-
}));
|
|
2043
|
-
let firstActivity = true;
|
|
2044
|
-
s.on("streamEvent", () => {
|
|
2045
|
-
if (!firstActivity) return;
|
|
2046
|
-
firstActivity = false;
|
|
2047
|
-
diagnostic.stage("first-activity");
|
|
2048
|
-
diagnostic.stage("stream");
|
|
2049
|
-
});
|
|
2050
|
-
}
|
|
2051
|
-
let text = "";
|
|
2052
|
-
let observedOutput = false;
|
|
2053
|
-
s.on("text", (delta) => {
|
|
2054
|
-
observedOutput = true;
|
|
2055
|
-
text += delta;
|
|
2056
|
-
callbacks.onText(delta);
|
|
2057
|
-
});
|
|
2058
|
-
if (callbacks.onThinking) s.on("thinking", (delta) => {
|
|
2059
|
-
observedOutput = true;
|
|
2060
|
-
callbacks.onThinking(delta);
|
|
2061
|
-
});
|
|
2062
|
-
if (callbacks.onUsage) {
|
|
2063
|
-
let live;
|
|
2064
|
-
s.on("streamEvent", (event) => {
|
|
2065
|
-
if (event.type === "message_start") {
|
|
2066
|
-
const u = event.message.usage;
|
|
2067
|
-
live = {
|
|
2068
|
-
inputTokens: u.input_tokens ?? 0,
|
|
2069
|
-
...u.cache_read_input_tokens != null ? { cacheRead: u.cache_read_input_tokens } : {},
|
|
2070
|
-
...u.cache_creation_input_tokens != null ? { cacheCreation: u.cache_creation_input_tokens } : {},
|
|
2071
|
-
...callIsOAuth ? { billable: false } : {}
|
|
2072
|
-
};
|
|
2073
|
-
} else if (event.type === "message_delta") live = {
|
|
2074
|
-
...live ?? {
|
|
2075
|
-
inputTokens: 0,
|
|
2076
|
-
...callIsOAuth ? { billable: false } : {}
|
|
2077
|
-
},
|
|
2078
|
-
outputTokens: event.usage.output_tokens
|
|
2079
|
-
};
|
|
2080
|
-
else return;
|
|
2081
|
-
callbacks.onUsage(live);
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2084
|
-
let inputTransformations;
|
|
2085
|
-
if (callbacks.onInputTransformations) s.on("streamEvent", (event) => {
|
|
2086
|
-
const list = (event.type === "message_start" ? event.message : event.type === "message_delta" ? event : void 0)?.input_transformations;
|
|
2087
|
-
if (Array.isArray(list) && list.length > 0) inputTransformations = list.filter(isInputTransformation);
|
|
2088
|
-
});
|
|
2089
|
-
if (callbacks.onToolCallDelta) s.on("inputJson", () => {
|
|
2090
|
-
observedOutput = true;
|
|
2091
|
-
callbacks.onToolCallDelta();
|
|
2092
|
-
});
|
|
2093
|
-
if (callbacks.onServerToolUse || callbacks.onServerToolResult) s.on("contentBlock", (block) => {
|
|
2094
|
-
observedOutput = true;
|
|
2095
|
-
if (block.type === "server_tool_use") callbacks.onServerToolUse?.({
|
|
2096
|
-
id: block.id,
|
|
2097
|
-
name: block.name,
|
|
2098
|
-
input: block.input ?? {}
|
|
2099
|
-
});
|
|
2100
|
-
else if (block.type === "web_search_tool_result") callbacks.onServerToolResult?.({
|
|
2101
|
-
toolUseId: block.tool_use_id,
|
|
2102
|
-
toolName: "web_search",
|
|
2103
|
-
content: block.content
|
|
2104
|
-
});
|
|
2105
|
-
});
|
|
2106
|
-
let response;
|
|
2107
|
-
try {
|
|
2108
|
-
response = await s.finalMessage();
|
|
2109
|
-
} catch (err) {
|
|
2110
|
-
if (!(bindingActive && thinkingBinding === "error" && isPrefixMismatchError(err)) && !observedOutput && scrubPass < MAX_THINKING_SCRUB_PASSES && isRecoverableThinkingBlockError(err, requestParams.messages)) {
|
|
2111
|
-
const failingIndex = parseFailingMessageIndex(err);
|
|
2112
|
-
const plan = planThinkingScrub(scrubPass, failingIndex, requestParams.messages);
|
|
2113
|
-
const scrubbed = withoutHistoricalThinking(requestParams.messages, plan.upToIndex, plan.alsoMessageIndex);
|
|
2114
|
-
if (scrubbed.removed > 0) {
|
|
2115
|
-
try {
|
|
2116
|
-
await callbacks.onHistoryRepair?.({
|
|
2117
|
-
type: "invalid-anthropic-thinking-signature",
|
|
2118
|
-
...failingIndex !== void 0 ? { wireMessageIndex: failingIndex } : {}
|
|
2119
|
-
});
|
|
2120
|
-
} catch (repairErr) {
|
|
2121
|
-
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/anthropic] history repair callback failed: ${errorMessage(repairErr)}\n`);
|
|
2122
|
-
}
|
|
2123
|
-
return await runStream({
|
|
2124
|
-
...requestParams,
|
|
2125
|
-
messages: scrubbed.messages
|
|
2126
|
-
}, fastRequested, scrubPass + 1, authPass);
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
if (callCanRotateAuth && authPass === 0 && !observedOutput && isAnthropicAuthenticationError(err)) {
|
|
2130
|
-
const rejectedAccess = apiKey;
|
|
2131
|
-
let refreshedApiKey;
|
|
2132
|
-
try {
|
|
2133
|
-
refreshedApiKey = await resolveCallApiKey(true, rejectedAccess);
|
|
2134
|
-
} catch (refreshErr) {
|
|
2135
|
-
if (err !== null && typeof err === "object" && err.cause === void 0) try {
|
|
2136
|
-
err.cause = refreshErr;
|
|
2137
|
-
} catch {}
|
|
2138
|
-
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/anthropic] forced OAuth refresh after 401 failed: ${errorMessage(refreshErr)}\n`);
|
|
2139
|
-
throw err;
|
|
2140
|
-
}
|
|
2141
|
-
if (refreshedApiKey === rejectedAccess) throw err;
|
|
2142
|
-
apiKey = refreshedApiKey;
|
|
2143
|
-
client = createClient(await loadAnthropicSdk(), apiKey, callAuth, anthropicParams?.baseURL, requestBetas, resolvedExtraHeaders, callIsOAuth ? diagnostic.wrapFetch : void 0);
|
|
2144
|
-
return await runStream(requestParams, fastRequested, scrubPass, authPass + 1);
|
|
2145
|
-
}
|
|
2146
|
-
if (fastRequested && !observedOutput && shouldFallbackFromFastMode(err)) {
|
|
2147
|
-
const standardParams = { ...requestParams };
|
|
2148
|
-
delete standardParams.speed;
|
|
2149
|
-
return await runStream(standardParams, false, scrubPass, authPass);
|
|
2150
|
-
}
|
|
2151
|
-
if (bindingActive && !thinkingBindingRejected && !observedOutput && isThinkingBindingRejectedError(err)) {
|
|
2152
|
-
thinkingBindingRejected = true;
|
|
2153
|
-
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/anthropic] endpoint rejected thinking.block_binding; retrying without it: ${errorMessage(err)}\n`);
|
|
2154
|
-
client = createClient(await loadAnthropicSdk(), apiKey, callAuth, anthropicParams?.baseURL, betasFor(baseConditionalBetas), resolvedExtraHeaders, callIsOAuth ? diagnostic.wrapFetch : void 0);
|
|
2155
|
-
return await runStream(withoutThinkingBinding(requestParams), fastRequested, scrubPass, authPass);
|
|
2156
|
-
}
|
|
2157
|
-
if (!observedOutput && isForcedToolChoice(requestParams.tool_choice) && isForcedToolChoiceUnsupportedError(err)) {
|
|
2158
|
-
MODELS_REJECTING_FORCED_TOOL_CHOICE.add(modelId);
|
|
2159
|
-
return await runStream(withoutForcedToolChoice(requestParams), fastRequested, scrubPass, authPass);
|
|
2160
|
-
}
|
|
2161
|
-
if (!observedOutput && isEnabledThinking(requestParams.thinking) && isEnabledThinkingUnsupportedError(err)) {
|
|
2162
|
-
MODELS_REJECTING_ENABLED_THINKING.add(modelId);
|
|
2163
|
-
return await runStream(withoutEnabledThinking(requestParams), fastRequested, scrubPass, authPass);
|
|
2164
|
-
}
|
|
2165
|
-
throw err;
|
|
2166
|
-
}
|
|
2167
|
-
if (inputTransformations && inputTransformations.length > 0) try {
|
|
2168
|
-
callbacks.onInputTransformations?.(inputTransformations);
|
|
2169
|
-
} catch (hookErr) {
|
|
2170
|
-
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/anthropic] onInputTransformations threw: ${errorMessage(hookErr)}\n`);
|
|
2171
|
-
}
|
|
2172
|
-
const toolCalls = response.content.filter((b) => b.type === "tool_use").map((b) => ({
|
|
2173
|
-
id: b.id,
|
|
2174
|
-
name: nameBoundary.fromWire(b.name),
|
|
2175
|
-
input: b.input
|
|
2176
|
-
}));
|
|
2177
|
-
const finishReason = mapStopReason(response.stop_reason);
|
|
2178
|
-
const isPause = response.stop_reason === "pause_turn";
|
|
2179
|
-
const rawUsage = {
|
|
2180
|
-
input: response.usage.input_tokens,
|
|
2181
|
-
output: response.usage.output_tokens,
|
|
2182
|
-
cacheCreation: response.usage.cache_creation_input_tokens ?? void 0,
|
|
2183
|
-
cacheRead: response.usage.cache_read_input_tokens ?? void 0,
|
|
2184
|
-
...finishReason ? { finishReason } : {},
|
|
2185
|
-
modelId: response.model ?? options.model
|
|
2186
|
-
};
|
|
2187
|
-
const usage = callIsOAuth ? rawUsage : await priceAnthropicUsage(rawUsage, response, modelId, fastRequested, Boolean(bedrockConfig), options.modelCatalog);
|
|
2188
|
-
const assistantMessage = fromAnthropic({
|
|
2189
|
-
role: "assistant",
|
|
2190
|
-
content: response.content
|
|
2191
|
-
});
|
|
2192
|
-
if (nameBoundary.changed) assistantMessage.content = assistantMessage.content.map((block) => block.type === "tool_call" ? {
|
|
2193
|
-
...block,
|
|
2194
|
-
name: nameBoundary.fromWire(block.name)
|
|
2195
|
-
} : block);
|
|
2196
|
-
return {
|
|
2197
|
-
assistantMessage,
|
|
2198
|
-
text,
|
|
2199
|
-
toolCalls,
|
|
2200
|
-
done: !isPause && (response.stop_reason === "end_turn" || toolCalls.length === 0),
|
|
2201
|
-
usage
|
|
2202
|
-
};
|
|
2203
|
-
};
|
|
2204
|
-
let initialParams = isForcedToolChoice(params.tool_choice) && MODELS_REJECTING_FORCED_TOOL_CHOICE.has(modelId) ? withoutForcedToolChoice(params) : params;
|
|
2205
|
-
if (isEnabledThinking(initialParams.thinking) && MODELS_REJECTING_ENABLED_THINKING.has(modelId)) initialParams = withoutEnabledThinking(initialParams);
|
|
2206
|
-
const result = await runStream(initialParams, options.modelOptions?.fast === true && !callIsOAuth);
|
|
2207
|
-
diagnostic.finish("success");
|
|
2208
|
-
return result;
|
|
2209
|
-
} catch (err) {
|
|
2210
|
-
diagnostic.finish(options.signal?.aborted ? "abort" : "failure", { httpStatus: err && typeof err === "object" && "status" in err ? err.status : void 0 });
|
|
2211
|
-
throw err;
|
|
2212
|
-
} finally {
|
|
2213
|
-
diagnostic.finish("failure");
|
|
2214
|
-
}
|
|
2391
|
+
stream(options, callbacks) {
|
|
2392
|
+
return streamAnthropic(state, options, callbacks);
|
|
2215
2393
|
}
|
|
2216
2394
|
};
|
|
2217
2395
|
}
|
|
2218
2396
|
//#endregion
|
|
2219
|
-
export {
|
|
2397
|
+
export { resolveAnthropicBetas as _, FAST_MODE_OPTIONS as a, THINKING_BINDING_SETTING_ENV as c, EXTENDED_CACHE_TTL_BETA as d, applyAnthropicCacheBreakpoints as f, resolveAnthropicAuthShape as g, credentialIdentity as h, ANTHROPIC_EXTRA_MODELS as i, planAnthropicThinking as l, buildAnthropicClientOptions as m, sanitizeProviderDiagnosticMetadata as n, defaultAnthropicMaxTokens as o, classifyAnthropicError as p, cacheWriteTtlMultiplier as r, resolveAnthropicMaxOutput as s, anthropic as t, resolveThinkingBinding as u };
|
|
2220
2398
|
|
|
2221
|
-
//# sourceMappingURL=anthropic-
|
|
2399
|
+
//# sourceMappingURL=anthropic-CD00LhUP.js.map
|