chainlesschain 0.166.109 → 0.166.112
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 +6 -4
- 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/assets/{AIOps-DkrNbDzS.js → AIOps-sOceeExN.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DJjFXDhv.js → ActionButton-BQs-vRUr.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DyUbagRn.js → Analytics-sfAHHMhA.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-p7kJWSvn.js → AppLayout-0gAvE0c8.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-C3rVh4Ak.js → Artifacts-Dh9s2Qmd.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bch_L_YO.js → Audit-CITm26SU.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BNHy8zP6.js → BackgroundAgents-BiMbBOh0.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BUaye_Ys.js → Backup-CNpnhI3h.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D49mzAVz.js → BaseInput-BI_lltkp.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-2MDzM_us.js → Chat-BgfB88xY.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BDP3fGoz.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-5p1T_KEq.js → Checkbox-4xy9rS3j.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-TKtiLaoZ.js → Codegen-D93h9ihS.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BVCMG9mS.js → Col-BRf3STY-.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D09-QczH.js → Community-f187ziSL.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-QuRMCnj7.js → Compact-D4hXWuo1.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CN0PV7fk.js → Compliance-C6cYKI6H.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-5N2zhAty.js → Cowork-C1i1f8LD.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BxT8UZ0S.js → Cron-BacCE4LA.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Dp-noQl4.js → Crosschain-DXztxOoH.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B7MZFkjG.js → DID-rpf4HN8x.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DulJ3rKV.js → Dashboard-h4bgtH7L.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DV4ov4zF.js → Dropdown-CyQnoHmu.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DB2-ooz6.js → EmailListRenderer-FCtp4AcB.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-B1Rb6p7q.js → EvolutionSettings-JbISHTWR.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-U8aHayx-.js → FamilyGuardDashboard-DCIQLyfK.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BkQULBHY.js → Federation-DDbFpwja.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CtviG260.js → FormItemContext-CXF0jtXb.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DnPirB24.js → GenericCardRenderer-CUaNfAM0.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CK57Oro6.js → Git-Bhoz2gDf.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DbI-LB8-.js → Governance-FiiXHe8e.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-sEG4T1Jg.js → Inference-brpTW2Q3.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-9SLJR3h9.js → KnowledgeGraph-BCWP-f6k.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BIZYcISQ.js → Logs-CxYcduoK.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-12vD1nC1.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CRmLLwAE.js → Marketplace-DANdmW7l.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DkOFkSO1.js → McpTools-Bi_EKkb5.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-M29Kd9TU.js → Memory-DecoMbYw.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Tl6JvPw_.js → MobileBridge-DrOhFKp0.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-rRwP16dN.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-DkUjX1F5.js → Mtc-SLPII1iw.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BCqr1I4q.js → MtcAudit-DWoVORYy.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DmoqYGQ5.js → Multisig-Dxpnmq-m.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DNRQeRke.js → NLProgramming-kNTpNqCP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CQSBeiY6.js → Notes-D1uXCfVd.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-gNY9bbH6.js → NotificationSettings-D3Qt8Gkl.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DW0CNjh8.js → OrderTableRenderer-qvaah2-m.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CthnbKag.js → Organization-EhRl4JoJ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-_GQ3sbXu.js → Overflow-BTt5C_5W.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BTr7ea6W.js → P2P-CeR2jyb0.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-BzD3OHWq.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DMny2Cjs.js → Permissions-BOPyeRlz.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-CyYlMSoI.js → PersonalDataHub-DcmNNsfO.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-FASu2Oc4.js → Pipeline-DuL3kwWC.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-9bC5FnsJ.js → Privacy-CkQRdlSU.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BOYZwB5G.js → ProjectInit-moE_vQ1P.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-45DViNG-.js → ProjectSettings-PnTWi4NQ.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-C2fZzMZc.js → Projects-DZvbT3D7.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BgjvZx9F.js → Providers-CkcrHYy9.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Brw-3csB.js → QrScannerModal-nYNFGf_H.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DWG-A5zB.js → QuickAsk-53WNo7xT.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DuQGxjIR.js → Recommend-CmYN_NaS.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-T1cdSSRL.js → RemoteSession-BuOdNEjA.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-9FT2dI4G.js → Reputation-BgkheZou.js} +1 -1
- package/src/assets/web-panel/assets/{Row-yNIbNftX.js → Row-DvA3klj1.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BIchNoPd.js → RssFeed-CgkIL3PG.js} +3 -3
- package/src/assets/web-panel/assets/{Search-msDJ8UTZ.js → Search-Cv2twJdL.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DE46oBYT.js → Security-XrD2UNU6.js} +2 -2
- package/src/assets/web-panel/assets/{Services-Dori7vc3.js → Services-LujCntCK.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-D3O3sUFz.js → Skeleton-ZZCOsxPX.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BV0ct8SO.js → Skills-BEXGkOrU.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DflIVOrh.js → Sla-CIqmCgdD.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cpbv2Zz6.js → SpeechSettings-DkVbwJox.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Dgg6-wbe.js → SyncSettings-BVGgUs0p.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cb6e4Klx.js → Tasks-C1lh68l5.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-GfdAATGz.js → Templates-BG4zNAFx.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-D0gecLXl.js → Tenant-t2o6i5fY.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BFRPJt-0.js → Terminal-CH8UDdVO.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BobtChxX.js → TimelineRenderer-B5sYY1IO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CifeZtHV.js → Tokens-D0y7QkG5.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BHBvbBCf.js → Trigger-BA2nKko0.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-6lvqjKYp.js → Trust-CrFKCQIs.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CqzMc-G5.js → UkeySign-B3x_-Zun.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-OIiRG582.js → VideoEditing-TEwxbLH6.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-4MeKq0q_.js → Wallet-Larp5rrt.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-eHBRjGQG.js → WebAuthn-DfWbEIZe.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DlzYdEqW.js → WorkflowEditor-8LKiKExY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CBcjOzkX.js → chat-ByxYOvYW.js} +1 -1
- package/src/assets/web-panel/assets/{colors-zgwsYQSa.js → colors-Dh4yAClh.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Dcd9H5Bn.js → compact-item-f5n2j1WZ.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DR1tO-8r.js → createContext-DIprYuXL.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DRIge35Z.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CtfbUH_p.js → hasIn-CvYeap2I.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1GxtY-h.js → index-3ROY-lYB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT7zIkqP.js → index-6VEWxOyC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cdhy5ckh.js → index-7eG1J-GT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvPOpvvH.js → index-B0AXUhiJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cur8LNNn.js → index-BS_dpgzu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWG98orp.js → index-BaXSBxy-.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5XGvJb0.js → index-Bk8oWRk6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DA2-Bhy8.js → index-BlImvgb7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwHgaYVH.js → index-BoayvJ48.js} +1 -1
- package/src/assets/web-panel/assets/index-Bq24rTYx.js +1 -0
- package/src/assets/web-panel/assets/{index-DHEiuvhA.js → index-BsU46c7D.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvcz0RO.js → index-BvyFQF8I.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7ZOiIKb.js → index-C2RGykST.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmzQzTY5.js → index-C5aH2Pj2.js} +1 -1
- package/src/assets/web-panel/assets/{index-oWOlF7CM.js → index-CD0s5ESV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVpp9YSQ.js → index-CIZQtz8G.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWuCGhHa.js → index-CJjc8ObW.js} +1 -1
- package/src/assets/web-panel/assets/{index-JSfldpw4.js → index-CTqJNul4.js} +1 -1
- package/src/assets/web-panel/assets/{index-dvF6C39V.js → index-CcvA1nae.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmVIM-m2.js → index-CpR0zD38.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaGNqkY0.js → index-CzabQZ7I.js} +1 -1
- package/src/assets/web-panel/assets/index-D2f18WsK.js +1 -0
- package/src/assets/web-panel/assets/{index-BXfGHUz_.js → index-DGyTzElU.js} +1 -1
- package/src/assets/web-panel/assets/{index-LN7Hkl1C.js → index-DI8jC7gQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfzOYmOj.js → index-DUj-sL9U.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoxowMYS.js → index-DaZVtafQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSQCHpDo.js → index-DjvON8oh.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3QUK1ks.js → index-DlJCDpeN.js} +1 -1
- package/src/assets/web-panel/assets/{index-J-ORW1i6.js → index-DlTwZIeX.js} +1 -1
- package/src/assets/web-panel/assets/{index-z8lrkcb-.js → index-DlULGx8u.js} +1 -1
- package/src/assets/web-panel/assets/{index-hTcre7U9.js → index-DmcAL4hd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYPMJ5Mb.js → index-DshB3-Up.js} +3 -3
- package/src/assets/web-panel/assets/{index-5XaB_Tjg.js → index-Du435yzt.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0kP88Pb.js → index-bt-0zMmH.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8WPohye.js → index-c_CxUQl3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeAi4CNt.js → index-fpWv4u3Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BndPtS9D.js → index-hwbaD3Nk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTx4FyLU.js → index-uDiVjYrS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPLLgPxA.js → index-xU0eHLIH.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CVMtDrWZ.js → initDefaultProps-DRuE92W6.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Dye3IU8k.js → motion-6Sgma5Vd.js} +1 -1
- package/src/assets/web-panel/assets/{move-C251Zo34.js → move-BbQsH8sb.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BDiOORoQ.js → mtc-parser-BdC4INal.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DTDA_RHv.js → omit-DeC5m_bi.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CqEUTPcT.js → pickAttrs-DuehpBws.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BOB8QWH3.js → placementArrow-CJEhv2ij.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CZDXP1d9.js → responsiveObserve-Dyw3lDGr.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DPaRw3E9.js → slide-BMaLXTbS.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Ba9d7XZ7.js → statusUtils-c1K_SdJH.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-mZKU3lvW.js → styleChecker-CefBry8k.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-VcauLq0M.js → useFlexGapSupport-Dj2IsZpo.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Cal9KRcx.js → useFs-RLfB3yqb.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DISoRyQB.js → usePersonalDataHub-DbRVH_NA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B58C6w6B.js → vnode-DTn_QVJz.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BPlBAMKB.js → zoom-DuFR0E7i.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 +39 -0
- package/src/gateways/ws/llm-chat-protocol.js +18 -10
- package/src/gateways/ws/llm-creds.js +29 -79
- 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 +27 -4
- package/src/lib/evolution/evolution-deployment-loader.js +58 -3
- 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 +682 -0
- package/src/lib/evolution/local-ui-evolution-host.mjs +26 -0
- package/src/lib/evolution/local-ui-evolution-runtime.js +601 -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/MarkdownRenderer-9nfWupWP.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-Bq-FPDzS.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +0 -7
- 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
|
@@ -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
|
}
|
|
@@ -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/,
|
package/src/lib/chat-core.js
CHANGED
|
@@ -11,6 +11,13 @@ import { providerFetch } from "./formal-budget-provider-scope.js";
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
|
|
14
|
+
import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
|
|
15
|
+
import {
|
|
16
|
+
geminiGenerateContentUrl,
|
|
17
|
+
geminiRequestBody,
|
|
18
|
+
geminiUsage,
|
|
19
|
+
normalizeProviderBaseUrl,
|
|
20
|
+
} from "./provider-protocol.js";
|
|
14
21
|
import { appendTokenUsage } from "../harness/jsonl-session-store.js";
|
|
15
22
|
import {
|
|
16
23
|
isRetryableStreamError,
|
|
@@ -134,18 +141,21 @@ export async function streamOllama(
|
|
|
134
141
|
guard.bump();
|
|
135
142
|
let response;
|
|
136
143
|
try {
|
|
137
|
-
response = await providerFetch(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
:
|
|
148
|
-
|
|
144
|
+
response = await providerFetch(
|
|
145
|
+
`${normalizeOllamaBaseUrl(baseUrl)}/api/chat`,
|
|
146
|
+
{
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: { "Content-Type": "application/json" },
|
|
149
|
+
body: JSON.stringify({
|
|
150
|
+
model,
|
|
151
|
+
messages,
|
|
152
|
+
stream: true,
|
|
153
|
+
}),
|
|
154
|
+
signal: options.signal
|
|
155
|
+
? AbortSignal.any([guard.signal, options.signal])
|
|
156
|
+
: guard.signal,
|
|
157
|
+
},
|
|
158
|
+
);
|
|
149
159
|
} catch (e) {
|
|
150
160
|
guard.stop();
|
|
151
161
|
throw guard.stalled() && !isFormalBudgetError(e)
|
|
@@ -249,7 +259,9 @@ export async function streamOpenAI(
|
|
|
249
259
|
guard.bump();
|
|
250
260
|
let response;
|
|
251
261
|
try {
|
|
252
|
-
response = await providerFetch(
|
|
262
|
+
response = await providerFetch(
|
|
263
|
+
`${normalizeProviderBaseUrl(baseUrl)}/chat/completions`,
|
|
264
|
+
{
|
|
253
265
|
method: "POST",
|
|
254
266
|
headers: {
|
|
255
267
|
"Content-Type": "application/json",
|
|
@@ -266,7 +278,8 @@ export async function streamOpenAI(
|
|
|
266
278
|
signal: options.signal
|
|
267
279
|
? AbortSignal.any([guard.signal, options.signal])
|
|
268
280
|
: guard.signal,
|
|
269
|
-
|
|
281
|
+
},
|
|
282
|
+
);
|
|
270
283
|
} catch (e) {
|
|
271
284
|
guard.stop();
|
|
272
285
|
throw guard.stalled() && !isFormalBudgetError(e)
|
|
@@ -394,7 +407,9 @@ export async function streamAnthropic(
|
|
|
394
407
|
guard.bump();
|
|
395
408
|
let response;
|
|
396
409
|
try {
|
|
397
|
-
response = await providerFetch(
|
|
410
|
+
response = await providerFetch(
|
|
411
|
+
`${normalizeProviderBaseUrl(baseUrl)}/messages`,
|
|
412
|
+
{
|
|
398
413
|
method: "POST",
|
|
399
414
|
headers: {
|
|
400
415
|
"Content-Type": "application/json",
|
|
@@ -411,7 +426,8 @@ export async function streamAnthropic(
|
|
|
411
426
|
signal: options.signal
|
|
412
427
|
? AbortSignal.any([guard.signal, options.signal])
|
|
413
428
|
: guard.signal,
|
|
414
|
-
|
|
429
|
+
},
|
|
430
|
+
);
|
|
415
431
|
} catch (e) {
|
|
416
432
|
guard.stop();
|
|
417
433
|
throw guard.stalled() && !isFormalBudgetError(e)
|
|
@@ -517,6 +533,128 @@ export async function streamAnthropic(
|
|
|
517
533
|
return fullResponse;
|
|
518
534
|
}
|
|
519
535
|
|
|
536
|
+
/** Stream Google's native Gemini generateContent SSE API. */
|
|
537
|
+
export async function streamGemini(
|
|
538
|
+
messages,
|
|
539
|
+
model,
|
|
540
|
+
baseUrl,
|
|
541
|
+
apiKey,
|
|
542
|
+
onToken,
|
|
543
|
+
onUsage,
|
|
544
|
+
onStall,
|
|
545
|
+
options = {},
|
|
546
|
+
) {
|
|
547
|
+
const guard = makeStallGuard(STREAM_STALL_MS, { onHint: onStall });
|
|
548
|
+
guard.bump();
|
|
549
|
+
let response;
|
|
550
|
+
try {
|
|
551
|
+
response = await providerFetch(
|
|
552
|
+
geminiGenerateContentUrl(baseUrl, model, { stream: true }),
|
|
553
|
+
{
|
|
554
|
+
method: "POST",
|
|
555
|
+
headers: {
|
|
556
|
+
"Content-Type": "application/json",
|
|
557
|
+
"x-goog-api-key": apiKey,
|
|
558
|
+
},
|
|
559
|
+
body: JSON.stringify(geminiRequestBody(messages)),
|
|
560
|
+
signal: options.signal
|
|
561
|
+
? AbortSignal.any([guard.signal, options.signal])
|
|
562
|
+
: guard.signal,
|
|
563
|
+
},
|
|
564
|
+
);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
guard.stop();
|
|
567
|
+
throw guard.stalled() && !isFormalBudgetError(error)
|
|
568
|
+
? new Error(
|
|
569
|
+
`Gemini request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
|
|
570
|
+
)
|
|
571
|
+
: error;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (!response.ok || !response.body) {
|
|
575
|
+
guard.stop();
|
|
576
|
+
throw attachStreamRetryMetadata(
|
|
577
|
+
new Error(`Gemini error: ${response.status} ${response.statusText}`),
|
|
578
|
+
captureStreamRetryMetadata(response),
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const retryMetadata = captureStreamRetryMetadata(response);
|
|
583
|
+
const reader = response.body.getReader();
|
|
584
|
+
const decoder = new TextDecoder();
|
|
585
|
+
let buffer = "";
|
|
586
|
+
let fullResponse = "";
|
|
587
|
+
let completed = false;
|
|
588
|
+
let latestUsage = null;
|
|
589
|
+
|
|
590
|
+
const consumeLine = async (raw) => {
|
|
591
|
+
const line = raw.trim();
|
|
592
|
+
if (!line.startsWith("data:")) return;
|
|
593
|
+
const payload = line.slice(5).trim();
|
|
594
|
+
if (!payload || payload === "[DONE]") return;
|
|
595
|
+
const data = JSON.parse(payload);
|
|
596
|
+
if (data.error) {
|
|
597
|
+
throw new Error(
|
|
598
|
+
typeof data.error.message === "string"
|
|
599
|
+
? data.error.message
|
|
600
|
+
: "Gemini stream returned an error",
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
const candidate = data.candidates?.[0];
|
|
604
|
+
if (candidate?.finishReason || data.promptFeedback?.blockReason)
|
|
605
|
+
completed = true;
|
|
606
|
+
const usage = geminiUsage(data);
|
|
607
|
+
if (usage) latestUsage = usage;
|
|
608
|
+
for (const part of candidate?.content?.parts || []) {
|
|
609
|
+
if (part?.thought === true || typeof part?.text !== "string") continue;
|
|
610
|
+
fullResponse += part.text;
|
|
611
|
+
await _deliverToken(onToken, part.text, guard, options);
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
while (true) {
|
|
617
|
+
const { done, value } = await reader.read();
|
|
618
|
+
if (done) break;
|
|
619
|
+
guard.bump();
|
|
620
|
+
buffer += decoder.decode(value, { stream: true });
|
|
621
|
+
const lines = buffer.split("\n");
|
|
622
|
+
buffer = lines.pop() || "";
|
|
623
|
+
for (const line of lines) {
|
|
624
|
+
try {
|
|
625
|
+
await consumeLine(line);
|
|
626
|
+
} catch (error) {
|
|
627
|
+
if (
|
|
628
|
+
options.requireCompletion ||
|
|
629
|
+
error?.isOutputBackpressureFailure === true
|
|
630
|
+
)
|
|
631
|
+
throw error;
|
|
632
|
+
// Non-governed streaming keeps its historical best-effort parsing.
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
buffer += decoder.decode();
|
|
637
|
+
if (buffer.trim()) await consumeLine(buffer);
|
|
638
|
+
} catch (error) {
|
|
639
|
+
throw attachStreamRetryMetadata(
|
|
640
|
+
guard.stalled() && !isFormalBudgetError(error)
|
|
641
|
+
? new Error(
|
|
642
|
+
`Gemini stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
|
|
643
|
+
)
|
|
644
|
+
: error,
|
|
645
|
+
retryMetadata,
|
|
646
|
+
);
|
|
647
|
+
} finally {
|
|
648
|
+
guard.stop();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (latestUsage && onUsage) onUsage(latestUsage);
|
|
652
|
+
options.signal?.throwIfAborted();
|
|
653
|
+
if (options.requireCompletion && !completed)
|
|
654
|
+
throw new Error("Model stream ended before its completion marker");
|
|
655
|
+
return fullResponse;
|
|
656
|
+
}
|
|
657
|
+
|
|
520
658
|
/**
|
|
521
659
|
* Async generator that streams a chat response.
|
|
522
660
|
*
|
|
@@ -578,10 +716,9 @@ export async function* chatStream(messages, options) {
|
|
|
578
716
|
);
|
|
579
717
|
} else if (provider === "anthropic") {
|
|
580
718
|
const providerDef = BUILT_IN_PROVIDERS.anthropic;
|
|
581
|
-
const url =
|
|
582
|
-
baseUrl
|
|
583
|
-
|
|
584
|
-
: providerDef?.baseUrl || "https://api.anthropic.com/v1";
|
|
719
|
+
const url = normalizeProviderBaseUrl(
|
|
720
|
+
baseUrl || providerDef?.baseUrl || "https://api.anthropic.com/v1",
|
|
721
|
+
);
|
|
585
722
|
const key =
|
|
586
723
|
apiKey ||
|
|
587
724
|
(providerDef?.apiKeyEnv ? process.env[providerDef.apiKeyEnv] : null);
|
|
@@ -601,12 +738,37 @@ export async function* chatStream(messages, options) {
|
|
|
601
738
|
options.onStall,
|
|
602
739
|
options,
|
|
603
740
|
);
|
|
741
|
+
} else if (provider === "gemini") {
|
|
742
|
+
const providerDef = BUILT_IN_PROVIDERS.gemini;
|
|
743
|
+
const url = normalizeProviderBaseUrl(
|
|
744
|
+
baseUrl ||
|
|
745
|
+
providerDef?.baseUrl ||
|
|
746
|
+
"https://generativelanguage.googleapis.com/v1beta",
|
|
747
|
+
);
|
|
748
|
+
const key =
|
|
749
|
+
apiKey ||
|
|
750
|
+
(providerDef?.apiKeyEnv ? process.env[providerDef.apiKeyEnv] : null);
|
|
751
|
+
if (!key) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
`API key required for gemini (set ${providerDef?.apiKeyEnv || "GEMINI_API_KEY"})`,
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
providerCall = () =>
|
|
757
|
+
streamGemini(
|
|
758
|
+
messages,
|
|
759
|
+
model,
|
|
760
|
+
url,
|
|
761
|
+
key,
|
|
762
|
+
onToken,
|
|
763
|
+
onUsage,
|
|
764
|
+
options.onStall,
|
|
765
|
+
options,
|
|
766
|
+
);
|
|
604
767
|
} else {
|
|
605
768
|
const providerDef = BUILT_IN_PROVIDERS[provider];
|
|
606
|
-
const url =
|
|
607
|
-
baseUrl
|
|
608
|
-
|
|
609
|
-
: providerDef?.baseUrl || "https://api.openai.com/v1";
|
|
769
|
+
const url = normalizeProviderBaseUrl(
|
|
770
|
+
baseUrl || providerDef?.baseUrl || "https://api.openai.com/v1",
|
|
771
|
+
);
|
|
610
772
|
const key =
|
|
611
773
|
apiKey ||
|
|
612
774
|
(providerDef?.apiKeyEnv ? process.env[providerDef.apiKeyEnv] : null);
|
|
@@ -12,6 +12,11 @@ import { LLMProviderRegistry, BUILT_IN_PROVIDERS } from "./llm-providers.js";
|
|
|
12
12
|
import { loadConfig } from "./config-manager.js";
|
|
13
13
|
import { applyConfigLlmDefaults } from "./llm-config-defaults.js";
|
|
14
14
|
import { captureAgentEvolutionIngress } from "./evolution/agent-evolution-ingress.js";
|
|
15
|
+
import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
|
|
16
|
+
import {
|
|
17
|
+
normalizeProviderBaseUrl,
|
|
18
|
+
openAICompatibleBaseUrl,
|
|
19
|
+
} from "./provider-protocol.js";
|
|
15
20
|
|
|
16
21
|
function plainUsage(value) {
|
|
17
22
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
@@ -175,17 +180,20 @@ export function createChatFn(options = {}) {
|
|
|
175
180
|
currentModel,
|
|
176
181
|
async ({ signal = opts.signal || resolved.signal } = {}) => {
|
|
177
182
|
const modelMessages = await prepareMessages();
|
|
178
|
-
const res = await providerFetch(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
const res = await providerFetch(
|
|
184
|
+
`${normalizeOllamaBaseUrl(baseUrl)}/api/chat`,
|
|
185
|
+
{
|
|
186
|
+
method: "POST",
|
|
187
|
+
signal: requestSignal(signal),
|
|
188
|
+
headers: { "Content-Type": "application/json" },
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
model: currentModel,
|
|
191
|
+
messages: modelMessages,
|
|
192
|
+
stream: false,
|
|
193
|
+
options: { num_predict: maxTokens },
|
|
194
|
+
}),
|
|
195
|
+
},
|
|
196
|
+
);
|
|
189
197
|
if (!res.ok) throw new Error(`Ollama error: ${res.status}`);
|
|
190
198
|
const data = await res.json();
|
|
191
199
|
return chatEnvelope(
|
|
@@ -215,7 +223,9 @@ export function createChatFn(options = {}) {
|
|
|
215
223
|
? { system: systemMsgs.map((m) => m.content).join("\n") }
|
|
216
224
|
: {}),
|
|
217
225
|
};
|
|
218
|
-
const res = await providerFetch(
|
|
226
|
+
const res = await providerFetch(
|
|
227
|
+
`${normalizeProviderBaseUrl(baseUrl)}/messages`,
|
|
228
|
+
{
|
|
219
229
|
method: "POST",
|
|
220
230
|
signal: requestSignal(signal),
|
|
221
231
|
headers: {
|
|
@@ -224,7 +234,8 @@ export function createChatFn(options = {}) {
|
|
|
224
234
|
"anthropic-version": "2023-06-01",
|
|
225
235
|
},
|
|
226
236
|
body: JSON.stringify(body),
|
|
227
|
-
|
|
237
|
+
},
|
|
238
|
+
);
|
|
228
239
|
if (!res.ok) throw new Error(`Anthropic error: ${res.status}`);
|
|
229
240
|
const data = await res.json();
|
|
230
241
|
return chatEnvelope(
|
|
@@ -246,7 +257,9 @@ export function createChatFn(options = {}) {
|
|
|
246
257
|
currentModel,
|
|
247
258
|
async ({ signal = opts.signal || resolved.signal } = {}) => {
|
|
248
259
|
const modelMessages = await prepareMessages();
|
|
249
|
-
const res = await providerFetch(
|
|
260
|
+
const res = await providerFetch(
|
|
261
|
+
`${openAICompatibleBaseUrl(provider, baseUrl)}/chat/completions`,
|
|
262
|
+
{
|
|
250
263
|
method: "POST",
|
|
251
264
|
signal: requestSignal(signal),
|
|
252
265
|
headers: {
|
|
@@ -258,7 +271,8 @@ export function createChatFn(options = {}) {
|
|
|
258
271
|
messages: modelMessages,
|
|
259
272
|
max_tokens: maxTokens,
|
|
260
273
|
}),
|
|
261
|
-
|
|
274
|
+
},
|
|
275
|
+
);
|
|
262
276
|
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
263
277
|
const data = await res.json();
|
|
264
278
|
return chatEnvelope(
|