chainlesschain 0.166.109 → 0.166.114
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/README.md +14 -6
- package/package.json +3 -2
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DkrNbDzS.js → AIOps-CVac3rIN.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DJjFXDhv.js → ActionButton-ChM9GxMl.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DyUbagRn.js → Analytics-CV5399Js.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-p7kJWSvn.js → AppLayout-CuonVXhg.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-C3rVh4Ak.js → Artifacts-D9qKfgb6.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bch_L_YO.js → Audit-DUvLysQc.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BNHy8zP6.js → BackgroundAgents-DfKohQeT.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BUaye_Ys.js → Backup-CohoEI8R.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D49mzAVz.js → BaseInput-BwdoA3xv.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-2MDzM_us.js → Chat-DRkuz1d2.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-v3pzeHNC.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-5p1T_KEq.js → Checkbox-B-GNGpKv.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-TKtiLaoZ.js → Codegen-Bs2lAsoZ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BVCMG9mS.js → Col-Bk1mJKDZ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D09-QczH.js → Community-B_FDSCOo.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-QuRMCnj7.js → Compact-No1qSLLH.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CN0PV7fk.js → Compliance-CcrG43-d.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-5N2zhAty.js → Cowork-DLkj2ywK.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BxT8UZ0S.js → Cron-sQHELVEz.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Dp-noQl4.js → Crosschain-CRTZ3GZg.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B7MZFkjG.js → DID-Ds2bjvxE.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DulJ3rKV.js → Dashboard-DOONG2NX.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DV4ov4zF.js → Dropdown-Br3PNA2L.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DB2-ooz6.js → EmailListRenderer-DT6UUqjZ.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-B1Rb6p7q.js → EvolutionSettings-BSLuEjlT.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-U8aHayx-.js → FamilyGuardDashboard-2aEM9avE.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BkQULBHY.js → Federation-D3B0tlQU.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CtviG260.js → FormItemContext-DTVkVVQP.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DnPirB24.js → GenericCardRenderer-BgFEgP4y.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CK57Oro6.js → Git-3t71m6Xd.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DbI-LB8-.js → Governance-DS7WvlaX.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-sEG4T1Jg.js → Inference-CeT-xWrI.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-9SLJR3h9.js → KnowledgeGraph-BQzMncp5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BIZYcISQ.js → Logs-CC5DOKhV.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-9nfWupWP.js → MarkdownRenderer-5jYM4kYG.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CRmLLwAE.js → Marketplace-CpYDyy3o.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DkOFkSO1.js → McpTools-DRdAZPMr.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-M29Kd9TU.js → Memory-CBLRr-l8.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Tl6JvPw_.js → MobileBridge-Bnj9veNN.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Bq-FPDzS.js → MobileProjects-lYFPD24p.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DkUjX1F5.js → Mtc-BMU_C9vD.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BCqr1I4q.js → MtcAudit-DQV-zumU.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-DmoqYGQ5.js → Multisig-LpqpqV1d.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DNRQeRke.js → NLProgramming-C4OMklSO.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CQSBeiY6.js → Notes-Cg0QCinP.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-gNY9bbH6.js → NotificationSettings-amoZoMna.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DW0CNjh8.js → OrderTableRenderer-CxNvhf5n.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CthnbKag.js → Organization-BetJmyX5.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-_GQ3sbXu.js → Overflow-DbeoPtVD.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BTr7ea6W.js → P2P-B5sitQWT.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-AP9SG2ic.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DMny2Cjs.js → Permissions-Bc7fgNKx.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CyYlMSoI.js → PersonalDataHub-BwdGMcWZ.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-FASu2Oc4.js → Pipeline-DVuJA9BA.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-9bC5FnsJ.js → Privacy-BGzwOYRG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BOYZwB5G.js → ProjectInit-BpHcC5br.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-45DViNG-.js → ProjectSettings-BI5G5gsc.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-C2fZzMZc.js → Projects-Ce92-l1V.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BgjvZx9F.js → Providers-CRHCcLFS.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Brw-3csB.js → QrScannerModal-qSeg5xag.js} +1 -1
- package/src/assets/web-panel/assets/QuickAsk-CA8oSceR.js +1 -0
- package/src/assets/web-panel/assets/{Recommend-DuQGxjIR.js → Recommend-DMvPodNo.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-T1cdSSRL.js → RemoteSession-BWsapEyN.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-9FT2dI4G.js → Reputation-BZJETVmC.js} +1 -1
- package/src/assets/web-panel/assets/{Row-yNIbNftX.js → Row-CUE1OSMp.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BIchNoPd.js → RssFeed-DvMazcUD.js} +2 -2
- package/src/assets/web-panel/assets/{Search-msDJ8UTZ.js → Search-e1c-NXWv.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DE46oBYT.js → Security-Dc9GZsx2.js} +3 -3
- package/src/assets/web-panel/assets/{Services-Dori7vc3.js → Services-AFz6ke-4.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-D3O3sUFz.js → Skeleton-gow6MYcY.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BV0ct8SO.js → Skills-h6AxYjUP.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DflIVOrh.js → Sla-Cr4CkJ0O.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cpbv2Zz6.js → SpeechSettings-Bd5yuBtH.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Dgg6-wbe.js → SyncSettings-1t4QZMLW.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cb6e4Klx.js → Tasks-u-DrjLRM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-GfdAATGz.js → Templates-C0yLUC9b.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-D0gecLXl.js → Tenant-ds1YT5iS.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BFRPJt-0.js → Terminal-BoIskW2m.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BobtChxX.js → TimelineRenderer-BkdVK24X.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CifeZtHV.js → Tokens-djyDgmfk.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BHBvbBCf.js → Trigger-CYefDS3H.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-6lvqjKYp.js → Trust-CE_dbep_.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CqzMc-G5.js → UkeySign-DwVs_fey.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-OIiRG582.js → VideoEditing-BTUqMHAV.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-4MeKq0q_.js → Wallet-Bu_E7Rrc.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-eHBRjGQG.js → WebAuthn-DTbre5AB.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DlzYdEqW.js → WorkflowEditor-BsBzd3gt.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CBcjOzkX.js → chat-tdifQQvu.js} +1 -1
- package/src/assets/web-panel/assets/{colors-zgwsYQSa.js → colors-D3SNW2Vx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Dcd9H5Bn.js → compact-item-UBlCjrAW.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DR1tO-8r.js → createContext-DKsnEnFt.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BBnVfJHh.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CtfbUH_p.js → hasIn-BkeySXWe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXfGHUz_.js → index-6Nvzgytx.js} +1 -1
- package/src/assets/web-panel/assets/index-B2w_3mtS.js +1 -0
- package/src/assets/web-panel/assets/{index-DYPMJ5Mb.js → index-BEiHKah0.js} +25 -25
- package/src/assets/web-panel/assets/{index-LN7Hkl1C.js → index-BIeFLaMV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DA2-Bhy8.js → index-BV2zQteq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwHgaYVH.js → index-BVvz3ntc.js} +1 -1
- package/src/assets/web-panel/assets/index-BoHurydC.js +1 -0
- package/src/assets/web-panel/assets/{index-DVpp9YSQ.js → index-Bw0k3VgD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaGNqkY0.js → index-C-KTHSUE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWuCGhHa.js → index-C9ImNw5O.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTx4FyLU.js → index-CG_b1xyX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cur8LNNn.js → index-CUjyL0ZS.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfzOYmOj.js → index-CXkn77ZN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cdhy5ckh.js → index-CZ6JUooI.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1GxtY-h.js → index-Cuz2QCFT.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0kP88Pb.js → index-D5Umd_Z-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHEiuvhA.js → index-D6es7MSG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPLLgPxA.js → index-D93iSDUw.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3QUK1ks.js → index-D9m1HQzl.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWG98orp.js → index-D9um0YVl.js} +1 -1
- package/src/assets/web-panel/assets/{index-hTcre7U9.js → index-DD6dD3LV.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5XGvJb0.js → index-DFMrIEIt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvPOpvvH.js → index-DSCiZJXb.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8WPohye.js → index-DSe_7MSy.js} +1 -1
- package/src/assets/web-panel/assets/{index-JSfldpw4.js → index-DYFjOyTH.js} +1 -1
- package/src/assets/web-panel/assets/{index-5XaB_Tjg.js → index-DdyTv6yc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoxowMYS.js → index-DfrW-iVs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvcz0RO.js → index-Dg8-Duze.js} +1 -1
- package/src/assets/web-panel/assets/{index-J-ORW1i6.js → index-DiDgwRty.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT7zIkqP.js → index-DlEIp1gJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7ZOiIKb.js → index-DlO6HdD9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeAi4CNt.js → index-DtxUSZSS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmVIM-m2.js → index-DugZRCiI.js} +1 -1
- package/src/assets/web-panel/assets/{index-oWOlF7CM.js → index-P-ailFQY.js} +1 -1
- package/src/assets/web-panel/assets/{index-dvF6C39V.js → index-PWScrdlX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSQCHpDo.js → index-SpQ26gD6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmzQzTY5.js → index-TLBpgN30.js} +1 -1
- package/src/assets/web-panel/assets/{index-z8lrkcb-.js → index-bOIcWW8F.js} +1 -1
- package/src/assets/web-panel/assets/{index-BndPtS9D.js → index-pgGKNvmm.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CVMtDrWZ.js → initDefaultProps-CVCXG2Hv.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Dye3IU8k.js → motion-Dv00jw87.js} +1 -1
- package/src/assets/web-panel/assets/{move-C251Zo34.js → move-BFz51iHf.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BDiOORoQ.js → mtc-parser-BK2woIDE.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DTDA_RHv.js → omit-D2X_Gwoo.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CqEUTPcT.js → pickAttrs-CLmFyJ1q.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BOB8QWH3.js → placementArrow-CCzPddQm.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CZDXP1d9.js → responsiveObserve-hcm_Zlxd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DPaRw3E9.js → slide-BxfZEFVp.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Ba9d7XZ7.js → statusUtils-kzB9pT-t.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-mZKU3lvW.js → styleChecker-CDFd7YCI.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-VcauLq0M.js → useFlexGapSupport-Czl7NNhS.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Cal9KRcx.js → useFs-y0SAzirG.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DISoRyQB.js → usePersonalDataHub-BvdPznrA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B58C6w6B.js → vnode-D8mNbyZ7.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BPlBAMKB.js → zoom-_hun6w9W.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agent.js +12 -5
- package/src/commands/ask.js +96 -21
- package/src/commands/complete.js +14 -10
- package/src/commands/llm.js +23 -41
- package/src/commands/session.js +2 -2
- package/src/commands/stream.js +9 -5
- package/src/commands/ui.js +30 -2
- package/src/data/changelog.json +65 -0
- package/src/gateways/ws/llm-chat-protocol.js +131 -40
- package/src/gateways/ws/llm-creds.js +29 -79
- package/src/gateways/ws/message-dispatcher.js +2 -0
- package/src/gateways/ws/ws-agent-handler.js +1 -1
- package/src/gateways/ws/ws-session-gateway.js +26 -12
- package/src/lib/__tests__/provider-stream.test.js +70 -4
- package/src/lib/chat-core.js +186 -24
- package/src/lib/cowork-adapter.js +29 -15
- package/src/lib/evolution/agent-evolution-runtime-composition.js +10 -7
- package/src/lib/evolution/evolution-deployment-config.js +36 -4
- package/src/lib/evolution/evolution-deployment-loader.js +117 -4
- package/src/lib/evolution/evolution-deployment-profile.js +2 -2
- package/src/lib/evolution/evolution-evidence-projector.js +18 -3
- package/src/lib/evolution/evolution-file-witness.js +10 -0
- package/src/lib/evolution/evolution-ledger.js +237 -28
- package/src/lib/evolution/evolution-windows-directory-durability.js +322 -0
- package/src/lib/evolution/governed-hub-llm.js +2 -1
- package/src/lib/evolution/governed-model-turn.js +4 -1
- package/src/lib/evolution/local-ui-evolution-deployment.js +768 -0
- package/src/lib/evolution/local-ui-evolution-host.mjs +44 -0
- package/src/lib/evolution/local-ui-evolution-runtime.js +624 -0
- package/src/lib/evolution/opaque-hex-id.js +28 -0
- package/src/lib/hub-llm-client.js +22 -14
- package/src/lib/image-input.js +19 -3
- package/src/lib/llm-config-defaults.js +19 -30
- package/src/lib/llm-connection-config.js +4 -0
- package/src/lib/llm-connection-probe.js +5 -1
- package/src/lib/llm-connection-resolution.js +94 -0
- package/src/lib/llm-providers.js +33 -11
- package/src/lib/ollama-base-url.js +14 -0
- package/src/lib/provider-protocol.js +104 -0
- package/src/lib/provider-stream.js +163 -4
- package/src/lib/runnable-provider.js +1 -0
- package/src/repl/chat-repl.js +58 -11
- package/src/runtime/agent-core.js +26 -43
- package/src/runtime/headless-runner.js +11 -4
- package/src/runtime/headless-stream.js +10 -4
- package/src/runtime/policies/agent-policy.js +17 -11
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DOX3Y5EJ.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +0 -7
- package/src/assets/web-panel/assets/QuickAsk-DWG-A5zB.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DYhpRtve.js +0 -1
- package/src/assets/web-panel/assets/index-DPJXFIY7.js +0 -1
- package/src/assets/web-panel/assets/index-DjBhR5M_.js +0 -1
|
@@ -37,15 +37,35 @@ import {
|
|
|
37
37
|
streamOllama,
|
|
38
38
|
streamOpenAI,
|
|
39
39
|
streamAnthropic,
|
|
40
|
+
streamGemini,
|
|
41
|
+
STREAM_STALL_MS,
|
|
40
42
|
} from "../../lib/chat-core.js";
|
|
41
43
|
import { BUILT_IN_PROVIDERS } from "../../lib/llm-providers.js";
|
|
42
44
|
import { resolveLlmCreds } from "./llm-creds.js";
|
|
43
45
|
import { prepareGovernedModelTurn } from "../../lib/evolution/governed-model-turn.js";
|
|
46
|
+
import { normalizeOllamaBaseUrl } from "../../lib/ollama-base-url.js";
|
|
47
|
+
import { normalizeProviderBaseUrl } from "../../lib/provider-protocol.js";
|
|
48
|
+
import { raceWithAbort } from "../../lib/abort-utils.js";
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
const activeChats = new WeakMap();
|
|
51
|
+
const CHAT_SETUP_TIMEOUT_MS = 60_000;
|
|
52
|
+
|
|
53
|
+
/** A request id is scoped to its originating socket, never another client. */
|
|
54
|
+
export function cancelLlmChat(ws, id) {
|
|
55
|
+
activeChats.get(ws)?.get(id)?.abort(new Error("Chat cancelled"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function waitForChatPhase(operation, controller, phase) {
|
|
59
|
+
const timer = setTimeout(
|
|
60
|
+
() => controller.abort(new Error(`Chat ${phase} timed out after 60s`)),
|
|
61
|
+
CHAT_SETUP_TIMEOUT_MS,
|
|
62
|
+
);
|
|
63
|
+
timer.unref?.();
|
|
64
|
+
try {
|
|
65
|
+
return await raceWithAbort(operation, controller.signal);
|
|
66
|
+
} finally {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
}
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
function validateMessages(messages) {
|
|
@@ -80,7 +100,17 @@ export async function handleLlmChat(server, id, ws, message) {
|
|
|
80
100
|
return;
|
|
81
101
|
}
|
|
82
102
|
|
|
83
|
-
|
|
103
|
+
let creds;
|
|
104
|
+
try {
|
|
105
|
+
creds = resolveLlmCreds(server, message);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
send({
|
|
108
|
+
type: `${topic}.result`,
|
|
109
|
+
ok: false,
|
|
110
|
+
error: error?.message || String(error),
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
84
114
|
if (!creds || !creds.provider) {
|
|
85
115
|
send({
|
|
86
116
|
type: `${topic}.result`,
|
|
@@ -103,6 +133,7 @@ export async function handleLlmChat(server, id, ws, message) {
|
|
|
103
133
|
|
|
104
134
|
let accumulator = "";
|
|
105
135
|
const onToken = (token) => {
|
|
136
|
+
controller.signal.throwIfAborted();
|
|
106
137
|
if (typeof token !== "string" || token.length === 0) return;
|
|
107
138
|
accumulator += token;
|
|
108
139
|
send({
|
|
@@ -117,68 +148,126 @@ export async function handleLlmChat(server, id, ws, message) {
|
|
|
117
148
|
};
|
|
118
149
|
|
|
119
150
|
const controller = new AbortController();
|
|
151
|
+
let requests = null;
|
|
152
|
+
if (ws && (typeof ws === "object" || typeof ws === "function")) {
|
|
153
|
+
requests = activeChats.get(ws);
|
|
154
|
+
if (!requests) activeChats.set(ws, (requests = new Map()));
|
|
155
|
+
if (requests.has(id)) {
|
|
156
|
+
send({
|
|
157
|
+
type: `${topic}.result`,
|
|
158
|
+
ok: false,
|
|
159
|
+
error: "Chat request is already active",
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
requests.set(id, controller);
|
|
164
|
+
}
|
|
120
165
|
const disconnect = () =>
|
|
121
166
|
controller.abort(new Error("Chat connection closed"));
|
|
167
|
+
const onStall = (elapsedMs, timeoutMs) => {
|
|
168
|
+
if (controller.signal.aborted) return;
|
|
169
|
+
send({
|
|
170
|
+
type: `${topic}.status`,
|
|
171
|
+
status: {
|
|
172
|
+
stage: "waiting",
|
|
173
|
+
provider: creds.provider,
|
|
174
|
+
model: creds.model,
|
|
175
|
+
elapsedMs,
|
|
176
|
+
timeoutMs,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
};
|
|
122
180
|
ws?.once?.("close", disconnect);
|
|
123
181
|
try {
|
|
124
|
-
const turn = await
|
|
125
|
-
server.evolutionCompositionFactory ?? null,
|
|
126
|
-
{
|
|
182
|
+
const turn = await waitForChatPhase(
|
|
183
|
+
prepareGovernedModelTurn(server.evolutionCompositionFactory ?? null, {
|
|
127
184
|
mode: "ws-llm-chat",
|
|
128
185
|
messages: message.messages,
|
|
129
186
|
signal: controller.signal,
|
|
130
|
-
},
|
|
187
|
+
}),
|
|
188
|
+
controller,
|
|
189
|
+
"initialization",
|
|
131
190
|
);
|
|
132
191
|
const streamOptions = {
|
|
133
192
|
requireCompletion: turn.governed,
|
|
134
193
|
signal: controller.signal,
|
|
135
194
|
};
|
|
195
|
+
// Tell the browser the configured deadline even when waiting hints are
|
|
196
|
+
// disabled, so a slow local model gets its full loading budget.
|
|
197
|
+
onStall(0, STREAM_STALL_MS);
|
|
136
198
|
const baseUrl =
|
|
137
199
|
creds.provider === "ollama"
|
|
138
200
|
? normalizeOllamaBaseUrl(creds.baseUrl)
|
|
139
|
-
: creds.baseUrl
|
|
201
|
+
: normalizeProviderBaseUrl(creds.baseUrl);
|
|
140
202
|
if (creds.provider === "ollama") {
|
|
141
|
-
await
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
203
|
+
await raceWithAbort(
|
|
204
|
+
streamOllama(
|
|
205
|
+
turn.messages,
|
|
206
|
+
creds.model,
|
|
207
|
+
baseUrl,
|
|
208
|
+
onToken,
|
|
209
|
+
onUsage,
|
|
210
|
+
onStall,
|
|
211
|
+
streamOptions,
|
|
212
|
+
),
|
|
213
|
+
controller.signal,
|
|
149
214
|
);
|
|
150
215
|
} else if (creds.provider === "anthropic") {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
216
|
+
const url = baseUrl;
|
|
217
|
+
await raceWithAbort(
|
|
218
|
+
streamAnthropic(
|
|
219
|
+
turn.messages,
|
|
220
|
+
creds.model,
|
|
221
|
+
url,
|
|
222
|
+
creds.apiKey,
|
|
223
|
+
onToken,
|
|
224
|
+
onUsage,
|
|
225
|
+
onStall,
|
|
226
|
+
streamOptions,
|
|
227
|
+
),
|
|
228
|
+
controller.signal,
|
|
229
|
+
);
|
|
230
|
+
} else if (creds.provider === "gemini") {
|
|
231
|
+
const url = baseUrl;
|
|
232
|
+
await raceWithAbort(
|
|
233
|
+
streamGemini(
|
|
234
|
+
turn.messages,
|
|
235
|
+
creds.model,
|
|
236
|
+
url,
|
|
237
|
+
creds.apiKey,
|
|
238
|
+
onToken,
|
|
239
|
+
onUsage,
|
|
240
|
+
onStall,
|
|
241
|
+
streamOptions,
|
|
242
|
+
),
|
|
243
|
+
controller.signal,
|
|
162
244
|
);
|
|
163
245
|
} else {
|
|
164
246
|
// OpenAI-compatible — covers openai / volcengine / deepseek / dashscope /
|
|
165
247
|
// kimi / minimax / mistral / custom. baseUrl from creds; default-from
|
|
166
248
|
// BUILT_IN_PROVIDERS already applied above.
|
|
167
249
|
const def = BUILT_IN_PROVIDERS[creds.provider];
|
|
168
|
-
const url = baseUrl
|
|
169
|
-
await
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
250
|
+
const url = baseUrl || def?.baseUrl;
|
|
251
|
+
await raceWithAbort(
|
|
252
|
+
streamOpenAI(
|
|
253
|
+
turn.messages,
|
|
254
|
+
creds.model,
|
|
255
|
+
url,
|
|
256
|
+
creds.apiKey,
|
|
257
|
+
onToken,
|
|
258
|
+
onUsage,
|
|
259
|
+
onStall,
|
|
260
|
+
streamOptions,
|
|
261
|
+
),
|
|
262
|
+
controller.signal,
|
|
178
263
|
);
|
|
179
264
|
}
|
|
180
265
|
|
|
181
|
-
await
|
|
266
|
+
await waitForChatPhase(
|
|
267
|
+
turn.complete(accumulator),
|
|
268
|
+
controller,
|
|
269
|
+
"completion",
|
|
270
|
+
);
|
|
182
271
|
send({
|
|
183
272
|
type: `${topic}.result`,
|
|
184
273
|
ok: true,
|
|
@@ -197,6 +286,8 @@ export async function handleLlmChat(server, id, ws, message) {
|
|
|
197
286
|
error: err?.message || String(err),
|
|
198
287
|
});
|
|
199
288
|
} finally {
|
|
289
|
+
requests?.delete(id);
|
|
290
|
+
if (requests?.size === 0) activeChats.delete(ws);
|
|
200
291
|
ws?.removeListener?.("close", disconnect);
|
|
201
292
|
}
|
|
202
293
|
}
|
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared LLM
|
|
2
|
+
* Shared LLM connection resolver for WS handlers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* a third consumer doesn't fork them again.
|
|
7
|
-
*
|
|
8
|
-
* Resolution order (first non-null wins):
|
|
9
|
-
* 1. Explicit `message.options.{provider,model,baseUrl,apiKey}` from client.
|
|
10
|
-
* 2. WS session creds via `server.sessionManager.getSession(sessionId)`.
|
|
11
|
-
* 3. Current saved `config.llm` (refreshed per request by the WS server).
|
|
12
|
-
* 4. Process env — first BUILT_IN_PROVIDERS entry whose apiKeyEnv is set.
|
|
13
|
-
*
|
|
14
|
-
* Fall-throughs:
|
|
15
|
-
* - Provider with `free: true` (ollama) doesn't need apiKey; a session
|
|
16
|
-
* entry with provider=ollama and no apiKey is valid.
|
|
17
|
-
* - For non-ollama providers, missing apiKey is a fatal — the caller
|
|
18
|
-
* should send a clean error frame instead of hanging on a 401.
|
|
19
|
-
* - When no source has any creds, returns null. Caller decides whether
|
|
20
|
-
* to fail-fast (llm.chat) or yield "LLM not configured" (chat-intent).
|
|
21
|
-
*
|
|
22
|
-
* @returns {{provider, model, baseUrl, apiKey} | null}
|
|
4
|
+
* Provider, model, endpoint, and credential are resolved together so a
|
|
5
|
+
* provider override cannot inherit a model or key from another endpoint.
|
|
23
6
|
*/
|
|
24
|
-
|
|
25
7
|
import { BUILT_IN_PROVIDERS } from "../../lib/llm-providers.js";
|
|
8
|
+
import { resolveLlmConnection } from "../../lib/llm-connection-resolution.js";
|
|
26
9
|
|
|
27
10
|
const ENV_PREFERRED_ORDER = [
|
|
28
11
|
"volcengine",
|
|
@@ -38,84 +21,51 @@ const ENV_PREFERRED_ORDER = [
|
|
|
38
21
|
|
|
39
22
|
export function resolveLlmCreds(server, message) {
|
|
40
23
|
const opts = message?.options || {};
|
|
41
|
-
const optProvider = opts.provider;
|
|
42
|
-
const optModel = opts.model;
|
|
43
|
-
const optBaseUrl = opts.baseUrl;
|
|
44
|
-
const optApiKey = opts.apiKey;
|
|
45
24
|
let cfgLlm = {};
|
|
46
25
|
if (typeof server?._loadLlmConfig === "function") {
|
|
47
26
|
try {
|
|
48
27
|
const configured = server._loadLlmConfig();
|
|
49
28
|
cfgLlm = configured?.llm || configured || {};
|
|
50
29
|
} catch {
|
|
51
|
-
//
|
|
52
|
-
// options, session credentials, or environment credentials. The
|
|
53
|
-
// session manager snapshot may contain merged defaults rather than
|
|
54
|
-
// values the user actually saved.
|
|
30
|
+
// A stale config snapshot can still be bypassed with session or env data.
|
|
55
31
|
}
|
|
56
32
|
} else {
|
|
57
33
|
cfgLlm = server?.sessionManager?.config?.llm || {};
|
|
58
34
|
}
|
|
59
35
|
|
|
60
|
-
|
|
61
|
-
const def = BUILT_IN_PROVIDERS[provider];
|
|
62
|
-
const sameConfiguredProvider = provider === cfgLlm.provider;
|
|
63
|
-
return {
|
|
64
|
-
provider,
|
|
65
|
-
model:
|
|
66
|
-
optModel ||
|
|
67
|
-
inherited.model ||
|
|
68
|
-
(sameConfiguredProvider ? cfgLlm.model : null) ||
|
|
69
|
-
def?.models?.[0] ||
|
|
70
|
-
null,
|
|
71
|
-
baseUrl:
|
|
72
|
-
optBaseUrl ||
|
|
73
|
-
inherited.baseUrl ||
|
|
74
|
-
(sameConfiguredProvider ? cfgLlm.baseUrl : null) ||
|
|
75
|
-
def?.baseUrl ||
|
|
76
|
-
(provider === "ollama" ? "http://localhost:11434" : null),
|
|
77
|
-
apiKey:
|
|
78
|
-
optApiKey ||
|
|
79
|
-
inherited.apiKey ||
|
|
80
|
-
(sameConfiguredProvider ? cfgLlm.apiKey : null) ||
|
|
81
|
-
(def?.apiKeyEnv ? process.env[def.apiKeyEnv] : null),
|
|
82
|
-
};
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
if (optProvider) {
|
|
86
|
-
return buildCreds(optProvider);
|
|
87
|
-
}
|
|
88
|
-
|
|
36
|
+
let session = null;
|
|
89
37
|
const sessionId = message?.sessionId;
|
|
90
38
|
if (sessionId && server?.sessionManager?.getSession) {
|
|
91
|
-
let session;
|
|
92
39
|
try {
|
|
93
40
|
session = server.sessionManager.getSession(sessionId);
|
|
94
41
|
} catch {
|
|
95
42
|
session = null;
|
|
96
43
|
}
|
|
97
|
-
if (session?.provider) {
|
|
98
|
-
// Don't default baseUrl to ollama — that breaks every cloud provider
|
|
99
|
-
// when the session was created without an explicit baseUrl. Fall back
|
|
100
|
-
// to the provider's own default endpoint instead.
|
|
101
|
-
return buildCreds(session.provider, {
|
|
102
|
-
model: session.model,
|
|
103
|
-
baseUrl: session.baseUrl,
|
|
104
|
-
apiKey: session.apiKey,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
44
|
}
|
|
108
45
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
46
|
+
const envProvider = ENV_PREFERRED_ORDER.find((name) => {
|
|
47
|
+
const envName = BUILT_IN_PROVIDERS[name]?.apiKeyEnv;
|
|
48
|
+
return envName && process.env[envName];
|
|
49
|
+
});
|
|
50
|
+
const defaultProvider =
|
|
51
|
+
opts.provider || session?.provider || cfgLlm.provider || envProvider || "ollama";
|
|
52
|
+
if (!opts.provider && !session?.provider && !cfgLlm.provider && !envProvider) {
|
|
53
|
+
return null;
|
|
118
54
|
}
|
|
119
55
|
|
|
120
|
-
return
|
|
56
|
+
return resolveLlmConnection(
|
|
57
|
+
opts,
|
|
58
|
+
[
|
|
59
|
+
session
|
|
60
|
+
? {
|
|
61
|
+
provider: session.provider,
|
|
62
|
+
model: session.model,
|
|
63
|
+
baseUrl: session.baseUrl,
|
|
64
|
+
apiKey: session.apiKey,
|
|
65
|
+
}
|
|
66
|
+
: null,
|
|
67
|
+
cfgLlm,
|
|
68
|
+
],
|
|
69
|
+
{ defaultProvider },
|
|
70
|
+
);
|
|
121
71
|
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
handleRemoteSessionTerminal,
|
|
31
31
|
} from "./remote-session-protocol.js";
|
|
32
32
|
import { RemoteCommandLedger } from "../../harness/remote-command-ledger.js";
|
|
33
|
+
import { cancelLlmChat } from "./llm-chat-protocol.js";
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Reconnect-safe command execution. When a message carries a stable
|
|
@@ -293,6 +294,7 @@ export function createWsMessageDispatcher(server) {
|
|
|
293
294
|
"chat.intent.classify-followup": () =>
|
|
294
295
|
server._handleChatIntentClassifyFollowup(id, ws, message),
|
|
295
296
|
"llm.chat": () => server._handleLlmChat(id, ws, message),
|
|
297
|
+
"llm.chat.cancel": () => cancelLlmChat(ws, id),
|
|
296
298
|
};
|
|
297
299
|
|
|
298
300
|
// Static core routes take precedence and are dispatched directly. The
|
|
@@ -1530,7 +1530,7 @@ export class WSAgentHandler {
|
|
|
1530
1530
|
}),
|
|
1531
1531
|
provider: session.provider,
|
|
1532
1532
|
model: activeModel,
|
|
1533
|
-
baseUrl: session.baseUrl
|
|
1533
|
+
baseUrl: session.baseUrl,
|
|
1534
1534
|
apiKey: session.apiKey,
|
|
1535
1535
|
contextEngine: session.contextEngine,
|
|
1536
1536
|
hookDb: this.db,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
deleteSession as dbDeleteSession,
|
|
37
37
|
} from "../../lib/session-manager.js";
|
|
38
38
|
import { buildSystemPrompt } from "../../runtime/agent-core.js";
|
|
39
|
+
import { resolveLlmConnection } from "../../lib/llm-connection-resolution.js";
|
|
39
40
|
import { createHostToolPolicyAuthority } from "../../lib/host-tool-policy-authority.js";
|
|
40
41
|
import { SubAgentRegistry } from "../../lib/sub-agent-registry.js";
|
|
41
42
|
import {
|
|
@@ -518,14 +519,13 @@ export class WSSessionManager {
|
|
|
518
519
|
? this._hooksV2HostWorkspaceRoot
|
|
519
520
|
: null;
|
|
520
521
|
const cfgLlm = this.config?.llm || {};
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const apiKey = options.apiKey || cfgLlm.apiKey || null;
|
|
522
|
+
const connection = resolveLlmConnection(options, [cfgLlm], {
|
|
523
|
+
defaultModel:
|
|
524
|
+
(options.provider || cfgLlm.provider || "ollama") === "ollama"
|
|
525
|
+
? "qwen2.5:7b"
|
|
526
|
+
: null,
|
|
527
|
+
});
|
|
528
|
+
const { provider, model, baseUrl, apiKey } = connection;
|
|
529
529
|
const worktreeIsolationRequested = options.worktreeIsolation === true;
|
|
530
530
|
const enabledToolNames = this._normalizeEnabledToolNames(
|
|
531
531
|
options.enabledToolNames,
|
|
@@ -839,15 +839,29 @@ export class WSSessionManager {
|
|
|
839
839
|
// Non-critical
|
|
840
840
|
}
|
|
841
841
|
|
|
842
|
+
const connection = resolveLlmConnection(
|
|
843
|
+
{
|
|
844
|
+
provider: dbSession.provider || "ollama",
|
|
845
|
+
model: dbSession.model || null,
|
|
846
|
+
baseUrl: metadata.baseUrl || null,
|
|
847
|
+
},
|
|
848
|
+
[this.config?.llm || {}],
|
|
849
|
+
{
|
|
850
|
+
defaultModel:
|
|
851
|
+
(dbSession.provider || "ollama") === "ollama"
|
|
852
|
+
? "qwen2.5:7b"
|
|
853
|
+
: null,
|
|
854
|
+
},
|
|
855
|
+
);
|
|
842
856
|
const session = {
|
|
843
857
|
id: dbSession.id,
|
|
844
858
|
type: metadata.sessionType || "agent",
|
|
845
859
|
status: "active",
|
|
846
860
|
messages,
|
|
847
|
-
provider:
|
|
848
|
-
model:
|
|
849
|
-
apiKey:
|
|
850
|
-
baseUrl:
|
|
861
|
+
provider: connection.provider,
|
|
862
|
+
model: connection.model,
|
|
863
|
+
apiKey: connection.apiKey,
|
|
864
|
+
baseUrl: connection.baseUrl,
|
|
851
865
|
mcpClient: this.mcpClient,
|
|
852
866
|
enabledToolNames: this._normalizeEnabledToolNames(
|
|
853
867
|
metadata.enabledToolNames,
|
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
* provider-stream tests (previously untested) — the token-stream parsers behind
|
|
5
5
|
* `cc stream` and the Hosted Session stream.run route. Pins the bug-prone
|
|
6
6
|
* streaming parse logic: Ollama NDJSON (yield response, stop on done, line
|
|
7
|
-
* buffering across chunk boundaries, skip malformed)
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Authorization / unsupported / missing-key). fetch is stubbed; no network.
|
|
7
|
+
* buffering across chunk boundaries, skip malformed), OpenAI-style SSE, and
|
|
8
|
+
* native Anthropic/Gemini SSE. Also pins protocol routing, completion markers,
|
|
9
|
+
* authentication headers, and URL normalization. fetch is stubbed; no network.
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
12
|
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
@@ -173,6 +172,73 @@ describe("buildProviderSource", () => {
|
|
|
173
172
|
);
|
|
174
173
|
});
|
|
175
174
|
|
|
175
|
+
it("normalizes a trailing slash on OpenAI-compatible provider bases", async () => {
|
|
176
|
+
const fetchMock = mockFetch(["data: [DONE]\n"]);
|
|
177
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
178
|
+
await collect(
|
|
179
|
+
buildProviderSource("deepseek", {
|
|
180
|
+
baseUrl: "https://api.deepseek.com/v1/",
|
|
181
|
+
apiKey: "sk-x",
|
|
182
|
+
prompt: "hi",
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
expect(fetchMock.mock.calls[0][0]).toBe(
|
|
186
|
+
"https://api.deepseek.com/v1/chat/completions",
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("routes Gemini through its native SSE API without putting the key in the URL", async () => {
|
|
191
|
+
const fetchMock = mockFetch([
|
|
192
|
+
'data: {"candidates":[{"content":{"parts":[{"thought":true,"text":"private"},{"text":"hello"}]}}]}\n',
|
|
193
|
+
'data: {"candidates":[{"finishReason":"STOP"}]}\n',
|
|
194
|
+
]);
|
|
195
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
196
|
+
const out = await collect(
|
|
197
|
+
buildProviderSource("gemini", {
|
|
198
|
+
apiKey: "secret",
|
|
199
|
+
model: "gemini-2.0-flash",
|
|
200
|
+
baseUrl: "https://generativelanguage.googleapis.com/v1beta/",
|
|
201
|
+
prompt: "hello",
|
|
202
|
+
requireCompletion: true,
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
const [url, options] = fetchMock.mock.calls[0];
|
|
206
|
+
expect(url).toBe(
|
|
207
|
+
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse",
|
|
208
|
+
);
|
|
209
|
+
expect(url).not.toContain("secret");
|
|
210
|
+
expect(options.headers["x-goog-api-key"]).toBe("secret");
|
|
211
|
+
expect(out).toEqual(["hello"]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("routes Anthropic through native Messages SSE with a trimmed base URL", async () => {
|
|
215
|
+
const fetchMock = mockFetch([
|
|
216
|
+
'data: {"type":"content_block_delta","delta":{"text":"hello"}}\n',
|
|
217
|
+
'data: {"type":"message_stop"}\n',
|
|
218
|
+
]);
|
|
219
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
220
|
+
const out = await collect(
|
|
221
|
+
buildProviderSource("anthropic", {
|
|
222
|
+
apiKey: "secret",
|
|
223
|
+
model: "claude-haiku-4-5-20251001",
|
|
224
|
+
baseUrl: "https://api.anthropic.com/v1/",
|
|
225
|
+
messages: [
|
|
226
|
+
{ role: "system", content: "be concise" },
|
|
227
|
+
{ role: "user", content: "hello" },
|
|
228
|
+
],
|
|
229
|
+
requireCompletion: true,
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
const [url, options] = fetchMock.mock.calls[0];
|
|
233
|
+
expect(url).toBe("https://api.anthropic.com/v1/messages");
|
|
234
|
+
expect(options.headers["x-api-key"]).toBe("secret");
|
|
235
|
+
expect(JSON.parse(options.body)).toMatchObject({
|
|
236
|
+
system: "be concise",
|
|
237
|
+
messages: [{ role: "user", content: "hello" }],
|
|
238
|
+
});
|
|
239
|
+
expect(out).toEqual(["hello"]);
|
|
240
|
+
});
|
|
241
|
+
|
|
176
242
|
it("throws for an unsupported provider", () => {
|
|
177
243
|
expect(() => buildProviderSource("nope", { prompt: "x" })).toThrow(
|
|
178
244
|
/Unsupported provider/,
|