shadow-claw 1.32.0 → 1.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.well-known/mcp/server-card.json +1 -1
- package/.well-known/mcp.json +1 -1
- package/dist/electron/main.cjs +1 -1
- package/dist/lib/components/index.js +26 -26
- package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/settings/shadow-claw-git.js +1 -1
- package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
- package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
- package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
- package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw-a2ui.js +1 -1
- package/dist/lib/components/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/shadow-claw-channels.js +1 -1
- package/dist/lib/components/shadow-claw-chat.js +1 -1
- package/dist/lib/components/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/shadow-claw-conversations.js +1 -1
- package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
- package/dist/lib/components/shadow-claw-files.js +1 -1
- package/dist/lib/components/shadow-claw-git.js +1 -1
- package/dist/lib/components/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/shadow-claw-llm.js +1 -1
- package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/shadow-claw-networking.js +1 -1
- package/dist/lib/components/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/shadow-claw-page-header.js +1 -1
- package/dist/lib/components/shadow-claw-pages.js +1 -1
- package/dist/lib/components/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/shadow-claw-settings.js +1 -1
- package/dist/lib/components/shadow-claw-storage.js +1 -1
- package/dist/lib/components/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/shadow-claw-tasks.js +1 -1
- package/dist/lib/components/shadow-claw-terminal.js +1 -1
- package/dist/lib/components/shadow-claw-tools.js +1 -1
- package/dist/lib/components/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw.js +1 -1
- package/dist/lib/{config-DjZ9QWc4.js → config-BcFbkaQv.js} +21 -4
- package/dist/lib/config-BcFbkaQv.js.map +1 -0
- package/dist/lib/{configurePeerJs-DD3G3Pms.js → configurePeerJs-CBMPHbwa.js} +3 -3
- package/dist/lib/{configurePeerJs-DD3G3Pms.js.map → configurePeerJs-CBMPHbwa.js.map} +1 -1
- package/dist/lib/{connections-BURqMIUv.js → connections-PZrUDf49.js} +2 -2
- package/dist/lib/{connections-BURqMIUv.js.map → connections-PZrUDf49.js.map} +1 -1
- package/dist/lib/{downloadGroupFile-B8J4up2a.js → downloadGroupFile-DSRUa9Au.js} +2 -2
- package/dist/lib/{downloadGroupFile-B8J4up2a.js.map → downloadGroupFile-DSRUa9Au.js.map} +1 -1
- package/dist/lib/{file-viewer-DoHM75Jz.js → file-viewer-11VVOpJv.js} +5 -5
- package/dist/lib/{file-viewer-DoHM75Jz.js.map → file-viewer-11VVOpJv.js.map} +1 -1
- package/dist/lib/{getGroupDir-QrN2aTo8.js → getGroupDir-BxKMYPPm.js} +3 -3
- package/dist/lib/{getGroupDir-QrN2aTo8.js.map → getGroupDir-BxKMYPPm.js.map} +1 -1
- package/dist/lib/{git-Brr7JIbs.js → git-BppibnJW.js} +3 -3
- package/dist/lib/{git-Brr7JIbs.js.map → git-BppibnJW.js.map} +1 -1
- package/dist/lib/{iframe-sanitizer-Bujgq14T.js → iframe-sanitizer-Dp8EYffI.js} +2 -2
- package/dist/lib/{iframe-sanitizer-Bujgq14T.js.map → iframe-sanitizer-Dp8EYffI.js.map} +1 -1
- package/dist/lib/index.js +27 -27
- package/dist/lib/{initChatSplitResize-BjiWoqC_.js → initChatSplitResize-CE_kWaLf.js} +2 -2
- package/dist/lib/{initChatSplitResize-BjiWoqC_.js.map → initChatSplitResize-CE_kWaLf.js.map} +1 -1
- package/dist/lib/{initControlPlane-3TUdhRBM.js → initControlPlane-BRIIOzoH.js} +18 -17
- package/dist/lib/{initControlPlane-3TUdhRBM.js.map → initControlPlane-BRIIOzoH.js.map} +1 -1
- package/dist/lib/{markdown-BFUTPQoO.js → markdown-Cq4sVwUT.js} +4 -4
- package/dist/lib/{markdown-BFUTPQoO.js.map → markdown-Cq4sVwUT.js.map} +1 -1
- package/dist/lib/{mcp-reconnect-BxJ77WSb.js → mcp-reconnect-CoHDMjmY.js} +2 -2
- package/dist/lib/{mcp-reconnect-BxJ77WSb.js.map → mcp-reconnect-CoHDMjmY.js.map} +1 -1
- package/dist/lib/{memoryStorage-D3FBwO9T.js → memoryStorage-DcFWrn_y.js} +2 -2
- package/dist/lib/{memoryStorage-D3FBwO9T.js.map → memoryStorage-DcFWrn_y.js.map} +1 -1
- package/dist/lib/{orchestrator-DW5l2bbW.js → orchestrator-C3TgDZvq.js} +81 -18
- package/dist/lib/{orchestrator-DW5l2bbW.js.map → orchestrator-C3TgDZvq.js.map} +1 -1
- package/dist/lib/{orchestrator-DLnCmeTZ.js → orchestrator-Ce9_lmL2.js} +12 -12
- package/dist/lib/{orchestrator-DLnCmeTZ.js.map → orchestrator-Ce9_lmL2.js.map} +1 -1
- package/dist/lib/{peerjs-BXlMuIQn.js → peerjs-PTYQ7yEG.js} +6 -6
- package/dist/lib/{peerjs-BXlMuIQn.js.map → peerjs-PTYQ7yEG.js.map} +1 -1
- package/dist/lib/{push-client-B419nn-i.js → push-client-DDV5Rcg6.js} +3 -3
- package/dist/lib/{push-client-B419nn-i.js.map → push-client-DDV5Rcg6.js.map} +1 -1
- package/dist/lib/{readGroupFileBytes-CSA7u-nJ.js → readGroupFileBytes-7C8G-yps.js} +2 -2
- package/dist/lib/{readGroupFileBytes-CSA7u-nJ.js.map → readGroupFileBytes-7C8G-yps.js.map} +1 -1
- package/dist/lib/{shadow-claw-DmMO4vVG.js → shadow-claw-WrWoPqFx.js} +10 -10
- package/dist/lib/{shadow-claw-DmMO4vVG.js.map → shadow-claw-WrWoPqFx.js.map} +1 -1
- package/dist/lib/{shadow-claw-a2ui-2I5nffIn.js → shadow-claw-a2ui-CcmJNXQP.js} +2 -2
- package/dist/lib/{shadow-claw-a2ui-2I5nffIn.js.map → shadow-claw-a2ui-CcmJNXQP.js.map} +1 -1
- package/dist/lib/{shadow-claw-accounts-DMb0y4ca.js → shadow-claw-accounts-CcB2429E.js} +3 -3
- package/dist/lib/{shadow-claw-accounts-DMb0y4ca.js.map → shadow-claw-accounts-CcB2429E.js.map} +1 -1
- package/dist/lib/{shadow-claw-channel-config-BhxHf6wZ.js → shadow-claw-channel-config-C4v0h8oa.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-BhxHf6wZ.js.map → shadow-claw-channel-config-C4v0h8oa.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-B5vzE9Z2.js → shadow-claw-channels-CyV88k9T.js} +3 -3
- package/dist/lib/{shadow-claw-channels-B5vzE9Z2.js.map → shadow-claw-channels-CyV88k9T.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-BsYnJ4-G.js → shadow-claw-chat-DSYtuZod.js} +11 -11
- package/dist/lib/{shadow-claw-chat-BsYnJ4-G.js.map → shadow-claw-chat-DSYtuZod.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-DqcgQsun.js → shadow-claw-control-plane-DBSGuDT1.js} +7 -7
- package/dist/lib/{shadow-claw-control-plane-DqcgQsun.js.map → shadow-claw-control-plane-DBSGuDT1.js.map} +1 -1
- package/dist/lib/{shadow-claw-conversations-B9Pn58g3.js → shadow-claw-conversations-Cp8pK4PM.js} +5 -5
- package/dist/lib/{shadow-claw-conversations-B9Pn58g3.js.map → shadow-claw-conversations-Cp8pK4PM.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-DL6c4oPK.js → shadow-claw-file-viewer-B8G8yo48.js} +8 -8
- package/dist/lib/{shadow-claw-file-viewer-DL6c4oPK.js.map → shadow-claw-file-viewer-B8G8yo48.js.map} +1 -1
- package/dist/lib/{shadow-claw-files-C_ksPb8W.js → shadow-claw-files-D1COKzuC.js} +9 -9
- package/dist/lib/{shadow-claw-files-C_ksPb8W.js.map → shadow-claw-files-D1COKzuC.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-jwsY7L1N.js → shadow-claw-git-60tWU4Ee.js} +6 -6
- package/dist/lib/{shadow-claw-git-jwsY7L1N.js.map → shadow-claw-git-60tWU4Ee.js.map} +1 -1
- package/dist/lib/{shadow-claw-integrations-yT7j07TM.js → shadow-claw-integrations-Bw1cG1ze.js} +4 -4
- package/dist/lib/{shadow-claw-integrations-yT7j07TM.js.map → shadow-claw-integrations-Bw1cG1ze.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-CEPJh1Jh.js → shadow-claw-llm-By5CvsFJ.js} +6 -6
- package/dist/lib/{shadow-claw-llm-CEPJh1Jh.js.map → shadow-claw-llm-By5CvsFJ.js.map} +1 -1
- package/dist/lib/{shadow-claw-mcp-remote-DGDWjByQ.js → shadow-claw-mcp-remote-BaNODd1v.js} +4 -4
- package/dist/lib/{shadow-claw-mcp-remote-DGDWjByQ.js.map → shadow-claw-mcp-remote-BaNODd1v.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-Bi4yvc73.js → shadow-claw-networking-DtZ5bsTz.js} +2 -2
- package/dist/lib/{shadow-claw-networking-Bi4yvc73.js.map → shadow-claw-networking-DtZ5bsTz.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-C-JgGqQx.js → shadow-claw-notifications-C3VEba_A.js} +3 -3
- package/dist/lib/{shadow-claw-notifications-C-JgGqQx.js.map → shadow-claw-notifications-C3VEba_A.js.map} +1 -1
- package/dist/lib/{shadow-claw-page-header-DrAcWCZ9.js → shadow-claw-page-header-BLowJopA.js} +2 -2
- package/dist/lib/{shadow-claw-page-header-DrAcWCZ9.js.map → shadow-claw-page-header-BLowJopA.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-OEOClgA8.js → shadow-claw-pages-CpeUV-hw.js} +9 -9
- package/dist/lib/{shadow-claw-pages-OEOClgA8.js.map → shadow-claw-pages-CpeUV-hw.js.map} +1 -1
- package/dist/lib/{shadow-claw-peerjs-DmC2CV-9.js → shadow-claw-peerjs-DYAzJbTy.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-DmC2CV-9.js.map → shadow-claw-peerjs-DYAzJbTy.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-xAtFog0T.js → shadow-claw-settings-ByxwW3WK.js} +9 -9
- package/dist/lib/{shadow-claw-settings-xAtFog0T.js.map → shadow-claw-settings-ByxwW3WK.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-B4UHl-po.js → shadow-claw-storage-CsSPYVDJ.js} +5 -5
- package/dist/lib/{shadow-claw-storage-B4UHl-po.js.map → shadow-claw-storage-CsSPYVDJ.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-B0eWzg57.js → shadow-claw-task-server-De2WCEw4.js} +2 -2
- package/dist/lib/{shadow-claw-task-server-B0eWzg57.js.map → shadow-claw-task-server-De2WCEw4.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-HHU_8Gyg.js → shadow-claw-tasks-zHWT4GaE.js} +7 -7
- package/dist/lib/{shadow-claw-tasks-HHU_8Gyg.js.map → shadow-claw-tasks-zHWT4GaE.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-CdC2G6Zz.js → shadow-claw-terminal-Bpawfv4N.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-CdC2G6Zz.js.map → shadow-claw-terminal-Bpawfv4N.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-2AICxVXA.js → shadow-claw-tools-BbKdWcps.js} +7 -7
- package/dist/lib/{shadow-claw-tools-2AICxVXA.js.map → shadow-claw-tools-BbKdWcps.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-Cy-Ia2vs.js → shadow-claw-webvm-DLqK0AxW.js} +3 -3
- package/dist/lib/{shadow-claw-webvm-Cy-Ia2vs.js.map → shadow-claw-webvm-DLqK0AxW.js.map} +1 -1
- package/dist/lib/subsystems/mcp/webmcp.d.ts +37 -0
- package/dist/lib/subsystems/providers/model-registry.d.ts +4 -1
- package/dist/lib/subsystems/providers/providers.d.ts +10 -0
- package/dist/lib/{syncWebMcpRegistration-CaAPPg2V.js → syncWebMcpRegistration-mlbbvcdm.js} +5 -5
- package/dist/lib/{syncWebMcpRegistration-CaAPPg2V.js.map → syncWebMcpRegistration-mlbbvcdm.js.map} +1 -1
- package/dist/lib/{tools-f1WPNDSS.js → tools-Bb2ujM2E.js} +4 -4
- package/dist/lib/{tools-f1WPNDSS.js.map → tools-Bb2ujM2E.js.map} +1 -1
- package/dist/lib/utils/index.js +1 -1
- package/dist/lib/utils/utils.js +1 -1
- package/dist/lib/{utils-B99XP70b.js → utils-mpsYPfS7.js} +3 -3
- package/dist/lib/{utils-B99XP70b.js.map → utils-mpsYPfS7.js.map} +1 -1
- package/dist/lib/webmcp-BVR2lBVo.js +1544 -0
- package/dist/lib/webmcp-BVR2lBVo.js.map +1 -0
- package/dist/public/.well-known/mcp/server-card.json +1 -1
- package/dist/public/.well-known/mcp.json +1 -1
- package/dist/public/agent.worker.js +44 -44
- package/dist/public/config-BqecTktB.js +1 -0
- package/dist/public/{configurePeerJs--JGpJVti.js → configurePeerJs-BPKfWuEt.js} +1 -1
- package/dist/public/{connections-6575KDGE.js → connections-C_3tYsmX.js} +1 -1
- package/dist/public/{custom-element-security-Da_6HQ6X.js → custom-element-security-DXwU3Lae.js} +1 -1
- package/dist/public/docs/example/article/index.html +2 -2
- package/dist/public/docs/publishing/index.html +2 -2
- package/dist/public/docs/skill-creator/index.html +2 -2
- package/dist/public/docs/subsystems/tools.md +4 -3
- package/dist/public/docs/subsystems/webmcp.md +58 -13
- package/dist/public/{downloadGroupFile-Dj-AMNsd.js → downloadGroupFile-DTyf5DIC.js} +1 -1
- package/dist/public/{e2e-bridge-CbYqdMp-.js → e2e-bridge-DXwWCQKw.js} +1 -1
- package/dist/public/{file-viewer-vHJ5Zsgk.js → file-viewer-ConEyDDG.js} +1 -1
- package/dist/public/{getGroupDir-BwprrdrQ.js → getGroupDir-ZfZsiq6d.js} +1 -1
- package/dist/public/{git-8F7wvoUB.js → git-BtBaLC1-.js} +2 -2
- package/dist/public/{iframe-sanitizer-viROQkRn.js → iframe-sanitizer-Corkvo_d.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-BA18wLQQ.js → initChatSplitResize-CUc-TEmT.js} +1 -1
- package/dist/public/{initControlPlane-C5v0FtJE.js → initControlPlane-B_uYS8a4.js} +1 -1
- package/dist/public/main/index.html +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{markdown-eFKxxpVP.js → markdown-Cfa6GuJs.js} +1 -1
- package/dist/public/{mcp-reconnect-CBHuaYdF.js → mcp-reconnect-CUOkdPEz.js} +1 -1
- package/dist/public/{memoryStorage-C1llaYus.js → memoryStorage-Bh8fjmip.js} +1 -1
- package/dist/public/{orchestrator-DNr2HDI2.js → orchestrator-BcSzrg6E.js} +2 -2
- package/dist/public/{orchestrator-BeJfwtvz.js → orchestrator-Bi5OdVZD.js} +11 -11
- package/dist/public/{peerjs-mG30TAJ_.js → peerjs-CzCXG-NW.js} +1 -1
- package/dist/public/{push-client-C2zMT3Sf.js → push-client-Cr3Ruahp.js} +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Ble6l0Iw.js → shadow-claw-CzUYuuOa.js} +2 -2
- package/dist/public/{shadow-claw-accounts-D1y-zbMs.js → shadow-claw-accounts-BgIHB_aP.js} +1 -1
- package/dist/public/{shadow-claw-channels-D_xfBoWp.js → shadow-claw-channels-Ce_trJ5D.js} +1 -1
- package/dist/public/{shadow-claw-chat-D_Lbi5oH.js → shadow-claw-chat-CdECLLwL.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-CrnA5lM0.js → shadow-claw-control-plane-DM62UmOz.js} +2 -2
- package/dist/public/{shadow-claw-conversations-BN-hFmN1.js → shadow-claw-conversations-AM5g3C00.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-BKEpzpbx.js → shadow-claw-file-viewer-BP7gWS5g.js} +1 -1
- package/dist/public/{shadow-claw-files-DDZMk5AA.js → shadow-claw-files-RPPGszcK.js} +1 -1
- package/dist/public/{shadow-claw-git-CqOO97Re.js → shadow-claw-git-BsHEvLrb.js} +2 -2
- package/dist/public/{shadow-claw-integrations-CRGL78Fy.js → shadow-claw-integrations-B0vbcOqg.js} +1 -1
- package/dist/public/{shadow-claw-llm-DoGxEHQL.js → shadow-claw-llm-FNNamrc_.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-DyGnK4S_.js → shadow-claw-mcp-remote-7mDgniQu.js} +1 -1
- package/dist/public/{shadow-claw-networking-DJeCdb7e.js → shadow-claw-networking-CGsetaLp.js} +1 -1
- package/dist/public/{shadow-claw-notifications-hSKJeMXb.js → shadow-claw-notifications-pnwRcR-4.js} +1 -1
- package/dist/public/{shadow-claw-page-header-DA-FG08G.js → shadow-claw-page-header-BUy15Poz.js} +1 -1
- package/dist/public/{shadow-claw-pages-DTS98Zbi.js → shadow-claw-pages-ILAjGWNk.js} +1 -1
- package/dist/public/{shadow-claw-settings-5w0ce8SP.js → shadow-claw-settings-Bx5IgVqN.js} +3 -3
- package/dist/public/{shadow-claw-storage-DqlVt27X.js → shadow-claw-storage-PAtNykaF.js} +1 -1
- package/dist/public/{shadow-claw-task-server-ByZwKI6S.js → shadow-claw-task-server-Cbbi_2OO.js} +1 -1
- package/dist/public/{shadow-claw-tasks-Dc4G4zBu.js → shadow-claw-tasks-B-jKYWZJ.js} +1 -1
- package/dist/public/{shadow-claw-terminal-DvusP7wg.js → shadow-claw-terminal-BRejND-Z.js} +1 -1
- package/dist/public/{shadow-claw-tools-BEsid8q-.js → shadow-claw-tools-DNQe3oa-.js} +1 -1
- package/dist/public/{shadow-claw-webvm-BWlIeK78.js → shadow-claw-webvm-BFuwRifU.js} +1 -1
- package/dist/public/{syncWebMcpRegistration-CqVCB6m8.js → syncWebMcpRegistration-FxKAPwaz.js} +1 -1
- package/dist/public/theme-init.js +1 -1
- package/dist/public/{tools-Cp1gequ8.js → tools-BwQKGsro.js} +1 -1
- package/dist/public/transformers-js.worker.js +1 -1
- package/dist/public/webmcp-C28PAlfy.js +1 -0
- package/dist/server.js +1 -1
- package/package.json +3 -3
- package/src/core/utils/initControlPlane.test.ts +122 -1
- package/src/core/utils/initControlPlane.ts +12 -8
- package/src/subsystems/mcp/webmcp.test.ts +253 -1
- package/src/subsystems/mcp/webmcp.ts +147 -4
- package/src/subsystems/providers/model-registry.test.ts +139 -0
- package/src/subsystems/providers/model-registry.ts +54 -2
- package/src/subsystems/providers/providers.test.ts +329 -0
- package/src/subsystems/providers/providers.ts +135 -11
- package/dist/lib/config-DjZ9QWc4.js.map +0 -1
- package/dist/lib/webmcp-BC8_WafM.js +0 -1710
- package/dist/lib/webmcp-BC8_WafM.js.map +0 -1
- package/dist/public/config-Bnwx4EfS.js +0 -1
- package/dist/public/webmcp-DBONjQWd.js +0 -2
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getContextLimit,
|
|
8
8
|
normalizeMeshLlmResult,
|
|
9
9
|
parseResponse,
|
|
10
|
+
supportsPromptCaching,
|
|
10
11
|
} from "./providers.js";
|
|
11
12
|
|
|
12
13
|
describe("providers.js", () => {
|
|
@@ -935,6 +936,334 @@ describe("providers.js", () => {
|
|
|
935
936
|
expect(result.tools[0].cache_control).toBeUndefined();
|
|
936
937
|
expect(result.system).toBe("System prompt");
|
|
937
938
|
});
|
|
939
|
+
|
|
940
|
+
it("should allow prompt caching to be enabled explicitly for any non-legacy model", () => {
|
|
941
|
+
const tools = [
|
|
942
|
+
{
|
|
943
|
+
name: "test_tool",
|
|
944
|
+
description: "A tool",
|
|
945
|
+
input_schema: { type: "object" },
|
|
946
|
+
},
|
|
947
|
+
];
|
|
948
|
+
|
|
949
|
+
const result = formatRequest(
|
|
950
|
+
anthropicProvider,
|
|
951
|
+
[{ role: "user", content: "hello" }],
|
|
952
|
+
tools,
|
|
953
|
+
{
|
|
954
|
+
model: "custom-unlisted-model",
|
|
955
|
+
maxTokens: 1000,
|
|
956
|
+
system: "System prompt",
|
|
957
|
+
promptCaching: true,
|
|
958
|
+
} as any,
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
expect(result.tools[0].cache_control).toEqual({ type: "ephemeral" });
|
|
962
|
+
expect(result.system).toEqual([
|
|
963
|
+
{
|
|
964
|
+
type: "text",
|
|
965
|
+
text: "System prompt",
|
|
966
|
+
cache_control: { type: "ephemeral" },
|
|
967
|
+
},
|
|
968
|
+
]);
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it("should guard against prompt caching for legacy models even when promptCaching: true is set", () => {
|
|
972
|
+
const tools = [
|
|
973
|
+
{
|
|
974
|
+
name: "test_tool",
|
|
975
|
+
description: "A tool",
|
|
976
|
+
input_schema: { type: "object" },
|
|
977
|
+
},
|
|
978
|
+
];
|
|
979
|
+
|
|
980
|
+
const result = formatRequest(
|
|
981
|
+
anthropicProvider,
|
|
982
|
+
[{ role: "user", content: "hello" }],
|
|
983
|
+
tools,
|
|
984
|
+
{
|
|
985
|
+
model: "claude-2.1",
|
|
986
|
+
maxTokens: 1000,
|
|
987
|
+
system: "System prompt",
|
|
988
|
+
promptCaching: true,
|
|
989
|
+
} as any,
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
expect(result.tools[0].cache_control).toBeUndefined();
|
|
993
|
+
expect(result.system).toBe("System prompt");
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it("should guard against prompt caching for dot-delimited legacy model IDs when promptCaching: true is set", () => {
|
|
997
|
+
const tools = [
|
|
998
|
+
{
|
|
999
|
+
name: "test_tool",
|
|
1000
|
+
description: "A tool",
|
|
1001
|
+
input_schema: { type: "object" },
|
|
1002
|
+
},
|
|
1003
|
+
];
|
|
1004
|
+
|
|
1005
|
+
const result = formatRequest(
|
|
1006
|
+
anthropicProvider,
|
|
1007
|
+
[{ role: "user", content: "hello" }],
|
|
1008
|
+
tools,
|
|
1009
|
+
{
|
|
1010
|
+
model: "claude.2.1",
|
|
1011
|
+
maxTokens: 1000,
|
|
1012
|
+
system: "System prompt",
|
|
1013
|
+
promptCaching: true,
|
|
1014
|
+
} as any,
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
expect(result.tools[0].cache_control).toBeUndefined();
|
|
1018
|
+
expect(result.system).toBe("System prompt");
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it("should place cache_control on the last user turn in multi-turn conversations", () => {
|
|
1022
|
+
const tools = [
|
|
1023
|
+
{
|
|
1024
|
+
name: "test_tool",
|
|
1025
|
+
description: "A tool",
|
|
1026
|
+
input_schema: { type: "object" },
|
|
1027
|
+
},
|
|
1028
|
+
];
|
|
1029
|
+
|
|
1030
|
+
const messages = [
|
|
1031
|
+
{ role: "user", content: "First turn" },
|
|
1032
|
+
{ role: "assistant", content: "Assistant response" },
|
|
1033
|
+
{ role: "user", content: "Second turn" },
|
|
1034
|
+
];
|
|
1035
|
+
|
|
1036
|
+
const result = formatRequest(anthropicProvider, messages, tools, {
|
|
1037
|
+
model: "claude-3-5-sonnet-20241022",
|
|
1038
|
+
maxTokens: 1000,
|
|
1039
|
+
system: "System prompt",
|
|
1040
|
+
} as any);
|
|
1041
|
+
|
|
1042
|
+
expect(result.messages[0].content).toBe("First turn");
|
|
1043
|
+
expect(result.messages[2].content).toEqual([
|
|
1044
|
+
{
|
|
1045
|
+
type: "text",
|
|
1046
|
+
text: "Second turn",
|
|
1047
|
+
cache_control: { type: "ephemeral" },
|
|
1048
|
+
},
|
|
1049
|
+
]);
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
it("should never mutate caller input message objects or arrays", () => {
|
|
1053
|
+
const originalUserTurn = { role: "user", content: "First turn" };
|
|
1054
|
+
const originalAssistantTurn = {
|
|
1055
|
+
role: "assistant",
|
|
1056
|
+
content: "Assistant response",
|
|
1057
|
+
};
|
|
1058
|
+
const originalSecondUserTurn = { role: "user", content: "Second turn" };
|
|
1059
|
+
const callerMessages = [
|
|
1060
|
+
originalUserTurn,
|
|
1061
|
+
originalAssistantTurn,
|
|
1062
|
+
originalSecondUserTurn,
|
|
1063
|
+
];
|
|
1064
|
+
|
|
1065
|
+
const result = formatRequest(anthropicProvider, callerMessages, [], {
|
|
1066
|
+
model: "claude-3-5-sonnet-20241022",
|
|
1067
|
+
maxTokens: 1000,
|
|
1068
|
+
system: "System prompt",
|
|
1069
|
+
} as any);
|
|
1070
|
+
|
|
1071
|
+
// Caller objects must be unchanged
|
|
1072
|
+
expect(originalUserTurn.content).toBe("First turn");
|
|
1073
|
+
expect(originalSecondUserTurn.content).toBe("Second turn");
|
|
1074
|
+
expect((originalSecondUserTurn as any).cache_control).toBeUndefined();
|
|
1075
|
+
expect(callerMessages[0]).toBe(originalUserTurn);
|
|
1076
|
+
expect(callerMessages[2]).toBe(originalSecondUserTurn);
|
|
1077
|
+
|
|
1078
|
+
// Formatted result gets the cache breakpoint
|
|
1079
|
+
expect(result.messages[2].content).toEqual([
|
|
1080
|
+
{
|
|
1081
|
+
type: "text",
|
|
1082
|
+
text: "Second turn",
|
|
1083
|
+
cache_control: { type: "ephemeral" },
|
|
1084
|
+
},
|
|
1085
|
+
]);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
it("should dynamically enable prompt caching when ModelRegistry metadata specifies support", () => {
|
|
1089
|
+
modelRegistry.registerModelInfo("arbitrary-proxy-model", {
|
|
1090
|
+
contextWindow: 100000,
|
|
1091
|
+
maxOutput: 4096,
|
|
1092
|
+
supportsPromptCaching: true,
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
const tools = [
|
|
1096
|
+
{
|
|
1097
|
+
name: "test_tool",
|
|
1098
|
+
description: "A tool",
|
|
1099
|
+
input_schema: { type: "object" },
|
|
1100
|
+
},
|
|
1101
|
+
];
|
|
1102
|
+
|
|
1103
|
+
const result = formatRequest(
|
|
1104
|
+
anthropicProvider,
|
|
1105
|
+
[{ role: "user", content: "hello" }],
|
|
1106
|
+
tools,
|
|
1107
|
+
{
|
|
1108
|
+
model: "arbitrary-proxy-model",
|
|
1109
|
+
maxTokens: 1000,
|
|
1110
|
+
system: "System prompt",
|
|
1111
|
+
} as any,
|
|
1112
|
+
);
|
|
1113
|
+
|
|
1114
|
+
expect(result.tools[0].cache_control).toEqual({ type: "ephemeral" });
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
it("should dynamically disable prompt caching when ModelRegistry metadata specifies false", () => {
|
|
1118
|
+
modelRegistry.registerModelInfo("claude-3-5-sonnet-cached", {
|
|
1119
|
+
contextWindow: 200000,
|
|
1120
|
+
maxOutput: 8192,
|
|
1121
|
+
supportsPromptCaching: false,
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
const tools = [
|
|
1125
|
+
{
|
|
1126
|
+
name: "test_tool",
|
|
1127
|
+
description: "A tool",
|
|
1128
|
+
input_schema: { type: "object" },
|
|
1129
|
+
},
|
|
1130
|
+
];
|
|
1131
|
+
|
|
1132
|
+
const result = formatRequest(
|
|
1133
|
+
anthropicProvider,
|
|
1134
|
+
[{ role: "user", content: "hello" }],
|
|
1135
|
+
tools,
|
|
1136
|
+
{
|
|
1137
|
+
model: "claude-3-5-sonnet-cached",
|
|
1138
|
+
maxTokens: 1000,
|
|
1139
|
+
system: "System prompt",
|
|
1140
|
+
} as any,
|
|
1141
|
+
);
|
|
1142
|
+
|
|
1143
|
+
expect(result.tools[0].cache_control).toBeUndefined();
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
it("should guard against prompt caching when ModelRegistry explicitly specifies false even if promptCaching: true is set", () => {
|
|
1147
|
+
modelRegistry.registerModelInfo("explicit-no-cache-model", {
|
|
1148
|
+
contextWindow: 128000,
|
|
1149
|
+
maxOutput: 4096,
|
|
1150
|
+
supportsPromptCaching: false,
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
const tools = [
|
|
1154
|
+
{
|
|
1155
|
+
name: "test_tool",
|
|
1156
|
+
description: "A tool",
|
|
1157
|
+
input_schema: { type: "object" },
|
|
1158
|
+
},
|
|
1159
|
+
];
|
|
1160
|
+
|
|
1161
|
+
const result = formatRequest(
|
|
1162
|
+
anthropicProvider,
|
|
1163
|
+
[{ role: "user", content: "hello" }],
|
|
1164
|
+
tools,
|
|
1165
|
+
{
|
|
1166
|
+
model: "explicit-no-cache-model",
|
|
1167
|
+
maxTokens: 1000,
|
|
1168
|
+
system: "System prompt",
|
|
1169
|
+
promptCaching: true,
|
|
1170
|
+
} as any,
|
|
1171
|
+
);
|
|
1172
|
+
|
|
1173
|
+
expect(result.tools[0].cache_control).toBeUndefined();
|
|
1174
|
+
expect(result.system).toBe("System prompt");
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it("should preserve pre-existing cache_control on content blocks during mapAnthropicContent", () => {
|
|
1178
|
+
const messages = [
|
|
1179
|
+
{
|
|
1180
|
+
role: "user",
|
|
1181
|
+
content: [
|
|
1182
|
+
{
|
|
1183
|
+
type: "text",
|
|
1184
|
+
text: "Explicitly tagged block",
|
|
1185
|
+
cache_control: { type: "ephemeral" },
|
|
1186
|
+
},
|
|
1187
|
+
],
|
|
1188
|
+
},
|
|
1189
|
+
];
|
|
1190
|
+
|
|
1191
|
+
const result = formatRequest(anthropicProvider, messages, [], {
|
|
1192
|
+
model: "claude-3-5-sonnet-20241022",
|
|
1193
|
+
maxTokens: 1000,
|
|
1194
|
+
system: "",
|
|
1195
|
+
} as any);
|
|
1196
|
+
|
|
1197
|
+
expect(result.messages[0].content[0]).toEqual({
|
|
1198
|
+
type: "text",
|
|
1199
|
+
text: "Explicitly tagged block",
|
|
1200
|
+
cache_control: { type: "ephemeral" },
|
|
1201
|
+
});
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
describe("supportsPromptCaching", () => {
|
|
1206
|
+
it("returns false for empty or non-string inputs", () => {
|
|
1207
|
+
expect(supportsPromptCaching("")).toBe(false);
|
|
1208
|
+
expect(supportsPromptCaching(" ")).toBe(false);
|
|
1209
|
+
expect(supportsPromptCaching(null as any)).toBe(false);
|
|
1210
|
+
expect(supportsPromptCaching(undefined as any)).toBe(false);
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
it("identifies Claude 3.5 models (with dashes and dots)", () => {
|
|
1214
|
+
expect(supportsPromptCaching("claude-3-5-sonnet-20241022")).toBe(true);
|
|
1215
|
+
expect(supportsPromptCaching("claude-3-5-sonnet-latest")).toBe(true);
|
|
1216
|
+
expect(supportsPromptCaching("claude-3.5-sonnet")).toBe(true);
|
|
1217
|
+
expect(supportsPromptCaching("claude-3-5-haiku-20241022")).toBe(true);
|
|
1218
|
+
expect(supportsPromptCaching("claude-3.5-haiku")).toBe(true);
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
it("identifies Claude 3.7 models (with dashes and dots)", () => {
|
|
1222
|
+
expect(supportsPromptCaching("claude-3-7-sonnet-20250219")).toBe(true);
|
|
1223
|
+
expect(supportsPromptCaching("claude-3.7-sonnet")).toBe(true);
|
|
1224
|
+
expect(
|
|
1225
|
+
supportsPromptCaching("anthropic/claude-3.7-sonnet:thinking"),
|
|
1226
|
+
).toBe(true);
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
it("identifies Claude 3 Haiku and Claude 3 Opus", () => {
|
|
1230
|
+
expect(supportsPromptCaching("claude-3-haiku-20240307")).toBe(true);
|
|
1231
|
+
expect(supportsPromptCaching("claude-3-haiku")).toBe(true);
|
|
1232
|
+
expect(supportsPromptCaching("claude-3-opus-20240229")).toBe(true);
|
|
1233
|
+
expect(supportsPromptCaching("claude-3-opus")).toBe(true);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
it("identifies Claude 4+ and Claude 5+ standard families", () => {
|
|
1237
|
+
expect(supportsPromptCaching("claude-sonnet-4")).toBe(true);
|
|
1238
|
+
expect(supportsPromptCaching("claude-opus-4")).toBe(true);
|
|
1239
|
+
expect(supportsPromptCaching("claude-haiku-4")).toBe(true);
|
|
1240
|
+
expect(supportsPromptCaching("claude-4-sonnet")).toBe(true);
|
|
1241
|
+
expect(supportsPromptCaching("claude-sonnet-5")).toBe(true);
|
|
1242
|
+
expect(supportsPromptCaching("claude-opus-5")).toBe(true);
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
it("identifies AWS Bedrock inference profile IDs", () => {
|
|
1246
|
+
expect(
|
|
1247
|
+
supportsPromptCaching("anthropic.claude-3-5-sonnet-20241022-v2:0"),
|
|
1248
|
+
).toBe(true);
|
|
1249
|
+
expect(
|
|
1250
|
+
supportsPromptCaching("us.anthropic.claude-3-7-sonnet-20250219-v1:0"),
|
|
1251
|
+
).toBe(true);
|
|
1252
|
+
expect(
|
|
1253
|
+
supportsPromptCaching("eu.anthropic.claude-3-5-haiku-20241022-v1:0"),
|
|
1254
|
+
).toBe(true);
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
it("rejects non-Anthropic models, legacy Claude models, and unspecified families", () => {
|
|
1258
|
+
expect(supportsPromptCaching("amazon.nova-pro-v1:0")).toBe(false);
|
|
1259
|
+
expect(supportsPromptCaching("claude-2.1")).toBe(false);
|
|
1260
|
+
expect(supportsPromptCaching("claude-2.0")).toBe(false);
|
|
1261
|
+
expect(supportsPromptCaching("claude-2")).toBe(false);
|
|
1262
|
+
expect(supportsPromptCaching("anthropic.claude-v2")).toBe(false);
|
|
1263
|
+
expect(supportsPromptCaching("claude-instant-1.2")).toBe(false);
|
|
1264
|
+
expect(supportsPromptCaching("claude-1.3")).toBe(false);
|
|
1265
|
+
expect(supportsPromptCaching("gpt-4o")).toBe(false);
|
|
1266
|
+
});
|
|
938
1267
|
});
|
|
939
1268
|
|
|
940
1269
|
describe("Anthropic response parsing", () => {
|
|
@@ -183,22 +183,92 @@ function mapOpenAiUserContent(blocks: any[], model: string): any[] {
|
|
|
183
183
|
return content;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
function
|
|
186
|
+
function normalizeModelId(model: string): string {
|
|
187
187
|
if (typeof model !== "string" || model.trim().length === 0) {
|
|
188
|
-
return
|
|
188
|
+
return "";
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
return model
|
|
192
|
+
.toLowerCase()
|
|
193
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
194
|
+
.replace(/-+/g, "-")
|
|
195
|
+
.replace(/^-|-$/g, "");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function isLegacyClaudeModel(model: string): boolean {
|
|
199
|
+
const id = normalizeModelId(model);
|
|
200
|
+
if (!id) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
192
203
|
|
|
193
204
|
return (
|
|
194
|
-
id.includes("claude-
|
|
195
|
-
id.includes("claude-
|
|
196
|
-
id.includes("claude-
|
|
197
|
-
id.includes("claude-
|
|
198
|
-
id.includes("claude-
|
|
205
|
+
id.includes("claude-1") ||
|
|
206
|
+
id.includes("claude-2") ||
|
|
207
|
+
id.includes("claude-v1") ||
|
|
208
|
+
id.includes("claude-v2") ||
|
|
209
|
+
id.includes("claude-instant")
|
|
199
210
|
);
|
|
200
211
|
}
|
|
201
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Check if a model supports Anthropic/Bedrock prompt caching.
|
|
215
|
+
*
|
|
216
|
+
* 1. Checks ModelRegistry for dynamic capability metadata (e.g. from OpenRouter
|
|
217
|
+
* `supported_parameters` or explicit registration).
|
|
218
|
+
* 2. Normalizes model identifiers (handling dots, slashes, uppercase).
|
|
219
|
+
* 3. Rejects legacy models (Claude 1, 2, instant).
|
|
220
|
+
* 4. Matches modern Claude 3, 3.5, 3.7, 4+, 5+ families.
|
|
221
|
+
*/
|
|
222
|
+
export function supportsPromptCaching(model: string): boolean {
|
|
223
|
+
if (typeof model !== "string" || model.trim().length === 0) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 1. Dynamic metadata lookup (avoids hardcoded strings when provider supplies capabilities)
|
|
228
|
+
const modelInfo = modelRegistry.getModelInfo(model);
|
|
229
|
+
if (typeof modelInfo?.supportsPromptCaching === "boolean") {
|
|
230
|
+
return modelInfo.supportsPromptCaching;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 2. Normalize model identifier (lowercased, non-alphanumeric converted to hyphens)
|
|
234
|
+
const id = normalizeModelId(model);
|
|
235
|
+
|
|
236
|
+
// Legacy Claude models that explicitly do NOT support prompt caching
|
|
237
|
+
if (isLegacyClaudeModel(id)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Claude 3.5 & 3.7 families (Sonnet, Haiku)
|
|
242
|
+
if (
|
|
243
|
+
id.includes("claude-3-5-sonnet") ||
|
|
244
|
+
id.includes("claude-3-5-haiku") ||
|
|
245
|
+
id.includes("claude-3-7-sonnet")
|
|
246
|
+
) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Claude 3 Haiku & Claude 3 Opus
|
|
251
|
+
if (id.includes("claude-3-haiku") || id.includes("claude-3-opus")) {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Claude 4+ & 5+ families (Sonnet 4+, Opus 4+, Haiku 4+, Sonnet 5, Opus 5, etc.)
|
|
256
|
+
// Matches "claude-sonnet-4", "claude-opus-4", "claude-haiku-4", "claude-sonnet-5", "claude-4-sonnet", etc.
|
|
257
|
+
if (
|
|
258
|
+
/claude-(?:sonnet|opus|haiku)-[4-9]/.test(id) ||
|
|
259
|
+
/claude-[4-9]-(?:sonnet|opus|haiku)/.test(id)
|
|
260
|
+
) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Maps incoming content blocks to Anthropic Messages format.
|
|
269
|
+
* Preserves caller-specified `cache_control` blocks if already present on incoming blocks
|
|
270
|
+
* (e.g. from orchestrators or replayed conversation history).
|
|
271
|
+
*/
|
|
202
272
|
function mapAnthropicContent(blocks: any[], model: string): any[] {
|
|
203
273
|
const content: any[] = [];
|
|
204
274
|
const canSendImages = canSendNativeImage(model);
|
|
@@ -210,7 +280,11 @@ function mapAnthropicContent(blocks: any[], model: string): any[] {
|
|
|
210
280
|
continue;
|
|
211
281
|
}
|
|
212
282
|
|
|
213
|
-
content.push({
|
|
283
|
+
content.push({
|
|
284
|
+
type: "text",
|
|
285
|
+
text,
|
|
286
|
+
...(block.cache_control && { cache_control: block.cache_control }),
|
|
287
|
+
});
|
|
214
288
|
|
|
215
289
|
continue;
|
|
216
290
|
}
|
|
@@ -269,6 +343,7 @@ function mapAnthropicContent(blocks: any[], model: string): any[] {
|
|
|
269
343
|
media_type: block.mimeType,
|
|
270
344
|
data: block.data,
|
|
271
345
|
},
|
|
346
|
+
...(block.cache_control && { cache_control: block.cache_control }),
|
|
272
347
|
});
|
|
273
348
|
} else if (
|
|
274
349
|
block.mediaType === "document" &&
|
|
@@ -283,11 +358,13 @@ function mapAnthropicContent(blocks: any[], model: string): any[] {
|
|
|
283
358
|
media_type: "application/pdf",
|
|
284
359
|
data: block.data,
|
|
285
360
|
},
|
|
361
|
+
...(block.cache_control && { cache_control: block.cache_control }),
|
|
286
362
|
});
|
|
287
363
|
} else {
|
|
288
364
|
content.push({
|
|
289
365
|
type: "text",
|
|
290
366
|
text: formatAttachmentFallbackText(block),
|
|
367
|
+
...(block.cache_control && { cache_control: block.cache_control }),
|
|
291
368
|
});
|
|
292
369
|
}
|
|
293
370
|
}
|
|
@@ -600,16 +677,22 @@ class AnthropicAdapter extends BaseAdapter {
|
|
|
600
677
|
},
|
|
601
678
|
): any {
|
|
602
679
|
const { model, maxTokens, system, reasoning } = options;
|
|
680
|
+
const modelInfo = modelRegistry.getModelInfo(model);
|
|
681
|
+
const explicitlyUnsupported =
|
|
682
|
+
isLegacyClaudeModel(model) || modelInfo?.supportsPromptCaching === false;
|
|
603
683
|
const cacheEnabled =
|
|
604
|
-
|
|
684
|
+
!explicitlyUnsupported &&
|
|
685
|
+
(options.promptCaching === true ||
|
|
686
|
+
(options.promptCaching !== false && supportsPromptCaching(model)));
|
|
605
687
|
|
|
606
688
|
// Messages are already in Anthropic format internally.
|
|
607
689
|
// Filter out system messages (system is passed separately).
|
|
690
|
+
// Always clone message objects so formatting never mutates the caller's array.
|
|
608
691
|
const filteredMessages = messages
|
|
609
692
|
.filter((msg) => msg.role !== "system")
|
|
610
693
|
.map((msg) => {
|
|
611
694
|
if (!Array.isArray(msg.content)) {
|
|
612
|
-
return msg;
|
|
695
|
+
return { ...msg };
|
|
613
696
|
}
|
|
614
697
|
|
|
615
698
|
return {
|
|
@@ -635,6 +718,47 @@ class AnthropicAdapter extends BaseAdapter {
|
|
|
635
718
|
? [{ type: "text", text: system, cache_control: { type: "ephemeral" } }]
|
|
636
719
|
: system;
|
|
637
720
|
|
|
721
|
+
// For multi-turn conversations, place a cache breakpoint on the last user turn (up to 4 total checkpoints).
|
|
722
|
+
// Mutates only the newly cloned message objects in filteredMessages, leaving caller references untouched.
|
|
723
|
+
if (cacheEnabled && filteredMessages.length > 1) {
|
|
724
|
+
for (let i = filteredMessages.length - 1; i >= 0; i--) {
|
|
725
|
+
const msg = filteredMessages[i];
|
|
726
|
+
if (msg.role === "user") {
|
|
727
|
+
if (typeof msg.content === "string") {
|
|
728
|
+
filteredMessages[i] = {
|
|
729
|
+
...msg,
|
|
730
|
+
content: [
|
|
731
|
+
{
|
|
732
|
+
type: "text",
|
|
733
|
+
text: msg.content,
|
|
734
|
+
cache_control: { type: "ephemeral" },
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
};
|
|
738
|
+
} else if (Array.isArray(msg.content) && msg.content.length > 0) {
|
|
739
|
+
const updated = [...msg.content];
|
|
740
|
+
const lastIdx = updated.length - 1;
|
|
741
|
+
const lastBlock = updated[lastIdx];
|
|
742
|
+
if (
|
|
743
|
+
lastBlock &&
|
|
744
|
+
typeof lastBlock === "object" &&
|
|
745
|
+
!lastBlock.cache_control
|
|
746
|
+
) {
|
|
747
|
+
updated[lastIdx] = {
|
|
748
|
+
...lastBlock,
|
|
749
|
+
cache_control: { type: "ephemeral" },
|
|
750
|
+
};
|
|
751
|
+
filteredMessages[i] = {
|
|
752
|
+
...msg,
|
|
753
|
+
content: updated,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
638
762
|
const request: any = {
|
|
639
763
|
model,
|
|
640
764
|
max_tokens: maxTokens,
|