chainlesschain 0.166.108 → 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 +14 -7
- 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-B5HWUFaS.js → AIOps-sOceeExN.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DCIKpxoQ.js → ActionButton-BQs-vRUr.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-dsgvjCEQ.js → Analytics-sfAHHMhA.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DWDH5Qbm.js → AppLayout-0gAvE0c8.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-BD8S8kzU.js → Artifacts-Dh9s2Qmd.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CDXv4Aoj.js → Audit-CITm26SU.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BLAQ5Uy0.js → BackgroundAgents-BiMbBOh0.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-moPac6tF.js → Backup-CNpnhI3h.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C_6nzsLU.js → BaseInput-BI_lltkp.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-cYuslkFG.js → Chat-BgfB88xY.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BDP3fGoz.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-Qjb-XOX_.js → Checkbox-4xy9rS3j.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-IYRWXlMA.js → Codegen-D93h9ihS.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CsQ9jv8M.js → Col-BRf3STY-.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BbNmfTwe.js → Community-f187ziSL.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-SShDx-nr.js → Compact-D4hXWuo1.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CLWhKIJp.js → Compliance-C6cYKI6H.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-D4tK9_s3.js → Cowork-C1i1f8LD.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-D7GnfQ1z.js → Cron-BacCE4LA.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-TG86SH3X.js → Crosschain-DXztxOoH.js} +1 -1
- package/src/assets/web-panel/assets/{DID-6pue0NCO.js → DID-rpf4HN8x.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BwkJ29du.js → Dashboard-h4bgtH7L.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DEvpYDof.js → Dropdown-CyQnoHmu.js} +1 -1
- package/src/assets/web-panel/assets/EmailListRenderer-FCtp4AcB.js +1 -0
- package/src/assets/web-panel/assets/{EvolutionSettings-tpvPMM9W.js → EvolutionSettings-JbISHTWR.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CWYFW75O.js → FamilyGuardDashboard-DCIQLyfK.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-gRIi96rM.js → Federation-DDbFpwja.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-B7Nr3YkV.js → FormItemContext-CXF0jtXb.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-b1BgGgLT.js → GenericCardRenderer-CUaNfAM0.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Dis1jjO7.js → Git-Bhoz2gDf.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C4Yg99Am.js → Governance-FiiXHe8e.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-D4UQRbGu.js → Inference-brpTW2Q3.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Co_kLMAq.js → KnowledgeGraph-BCWP-f6k.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-D4BRp7bg.js → Logs-CxYcduoK.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-12vD1nC1.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-ys0j1vr9.js → Marketplace-DANdmW7l.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B3ZvRZe_.js → McpTools-Bi_EKkb5.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-BOa0jbr-.js → Memory-DecoMbYw.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CWJRmUbj.js → MobileBridge-DrOhFKp0.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-rRwP16dN.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CCcOk5m5.js → Mtc-SLPII1iw.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-vlrrScbQ.js → MtcAudit-DWoVORYy.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-C8K59S_8.js → Multisig-Dxpnmq-m.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-oTpFYVme.js → NLProgramming-kNTpNqCP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BhqAJWiz.js → Notes-D1uXCfVd.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DCnV1CJM.js → NotificationSettings-D3Qt8Gkl.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CZGnsrVW.js → OrderTableRenderer-qvaah2-m.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DX1LuQlJ.js → Organization-EhRl4JoJ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CbfKrFdT.js → Overflow-BTt5C_5W.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BEwYZuHL.js → P2P-CeR2jyb0.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-YNmWbwd7.js → PdhVaultBrowser-BzD3OHWq.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DyZ18LiA.js → Permissions-BOPyeRlz.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B2J3aw2g.js → PersonalDataHub-DcmNNsfO.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CXl2ZlhQ.js → Pipeline-DuL3kwWC.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-I5SDmgAp.js → Privacy-CkQRdlSU.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Crl9tXw3.js → ProjectInit-moE_vQ1P.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DCFtFlSK.js → ProjectSettings-PnTWi4NQ.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Do5BQq1i.js → Projects-DZvbT3D7.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Cb7OWa3y.js → Providers-CkcrHYy9.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CJmhAU-s.js → QrScannerModal-nYNFGf_H.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Cu6lEWou.js → QuickAsk-53WNo7xT.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Crip3jLB.js → Recommend-CmYN_NaS.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-KxYY-2CB.js → RemoteSession-BuOdNEjA.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D0vN-htI.js → Reputation-BgkheZou.js} +1 -1
- package/src/assets/web-panel/assets/{Row-wqzd_xCD.js → Row-DvA3klj1.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-8lsPfb0a.js → RssFeed-CgkIL3PG.js} +3 -3
- package/src/assets/web-panel/assets/{Search-C-e8qAeU.js → Search-Cv2twJdL.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CMM9WgFy.js → Security-XrD2UNU6.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CZdxjN2B.js → Services-LujCntCK.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BI3XNk35.js → Skeleton-ZZCOsxPX.js} +1 -1
- package/src/assets/web-panel/assets/Skills-BEXGkOrU.js +1 -0
- package/src/assets/web-panel/assets/{Sla-HQruznKY.js → Sla-CIqmCgdD.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Dl-MFq3w.js → SpeechSettings-DkVbwJox.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BU224vDm.js → SyncSettings-BVGgUs0p.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-C1lh68l5.js +1 -0
- package/src/assets/web-panel/assets/{Templates-Jb3TPz4Q.js → Templates-BG4zNAFx.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CQ-GTrfa.js → Tenant-t2o6i5fY.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BbMl3Rue.js → Terminal-CH8UDdVO.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CS_vstKF.js → TimelineRenderer-B5sYY1IO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BaopVl1f.js → Tokens-D0y7QkG5.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DtnD4WUN.js → Trigger-BA2nKko0.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CsvG4Tmf.js → Trust-CrFKCQIs.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C1-A-cOV.js → UkeySign-B3x_-Zun.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CNgJPdSL.js → VideoEditing-TEwxbLH6.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CdF41501.js → Wallet-Larp5rrt.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-D-JmxD5g.js → WebAuthn-DfWbEIZe.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-C0XunR13.js → WorkflowEditor-8LKiKExY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BBAZpD_G.js → chat-ByxYOvYW.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BxSxO5BX.js → colors-Dh4yAClh.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BoxL8pg5.js → compact-item-f5n2j1WZ.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-W5JAyZmY.js → createContext-DIprYuXL.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DRIge35Z.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-HpiKDPVC.js → hasIn-CvYeap2I.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9VxE1C-.js → index-3ROY-lYB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdEcQ1rp.js → index-6VEWxOyC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4uI7WCB.js → index-7eG1J-GT.js} +1 -1
- package/src/assets/web-panel/assets/{index-_38IJqoP.js → index-B0AXUhiJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7GIUWWF.js → index-BS_dpgzu.js} +1 -1
- package/src/assets/web-panel/assets/{index-ooP1pQCt.js → index-BaXSBxy-.js} +1 -1
- package/src/assets/web-panel/assets/{index-gvfIRZxJ.js → index-Bk8oWRk6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSJ0cAle.js → index-BlImvgb7.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5kqii-G.js → index-BoayvJ48.js} +1 -1
- package/src/assets/web-panel/assets/index-Bq24rTYx.js +1 -0
- package/src/assets/web-panel/assets/{index-B05_YRHZ.js → index-BsU46c7D.js} +1 -1
- package/src/assets/web-panel/assets/{index-_p4e3KfB.js → index-BvyFQF8I.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bwn6k_qw.js → index-C2RGykST.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtFAEG08.js → index-C5aH2Pj2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CehxfRUR.js → index-CD0s5ESV.js} +1 -1
- package/src/assets/web-panel/assets/{index-JrMBRi62.js → index-CIZQtz8G.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRg3aOoj.js → index-CJjc8ObW.js} +1 -1
- package/src/assets/web-panel/assets/{index-V6VIJe2N.js → index-CTqJNul4.js} +1 -1
- package/src/assets/web-panel/assets/{index-p6rNtYud.js → index-CcvA1nae.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6m6B4Wy.js → index-CpR0zD38.js} +1 -1
- package/src/assets/web-panel/assets/{index-IO2I7nzu.js → index-CzabQZ7I.js} +1 -1
- package/src/assets/web-panel/assets/index-D2f18WsK.js +1 -0
- package/src/assets/web-panel/assets/{index-9caMEwQ-.js → index-DGyTzElU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DinY7IQa.js → index-DI8jC7gQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuM9I1If.js → index-DUj-sL9U.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkSnlgK_.js → index-DaZVtafQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CM_gdohF.js → index-DjvON8oh.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcmAiq_Y.js → index-DlJCDpeN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaJk9U9V.js → index-DlTwZIeX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgMViZIY.js → index-DlULGx8u.js} +1 -1
- package/src/assets/web-panel/assets/{index-BA3WWQAT.js → index-DmcAL4hd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSyfxxQS.js → index-DshB3-Up.js} +3 -3
- package/src/assets/web-panel/assets/{index-BZAbaY64.js → index-Du435yzt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW4PP5GN.js → index-bt-0zMmH.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlPvZgv8.js → index-c_CxUQl3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ba4usRFM.js → index-fpWv4u3Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwidpDEE.js → index-hwbaD3Nk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcyzZloJ.js → index-uDiVjYrS.js} +1 -1
- package/src/assets/web-panel/assets/{index-QzlEbtRL.js → index-xU0eHLIH.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Dfn6M05J.js → initDefaultProps-DRuE92W6.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BRRwFqGO.js → motion-6Sgma5Vd.js} +1 -1
- package/src/assets/web-panel/assets/{move-DpDPN6zR.js → move-BbQsH8sb.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BHlSE87F.js → mtc-parser-BdC4INal.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CEOri_L5.js → omit-DeC5m_bi.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DqICh-ad.js → pickAttrs-DuehpBws.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DQizFFiV.js → placementArrow-CJEhv2ij.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DmFHPdvC.js → responsiveObserve-Dyw3lDGr.js} +1 -1
- package/src/assets/web-panel/assets/{slide-s2s1XS3T.js → slide-BMaLXTbS.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-IwEIbBan.js → statusUtils-c1K_SdJH.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CIFLlbXH.js → styleChecker-CefBry8k.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CFkN2Cgh.js → useFlexGapSupport-Dj2IsZpo.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BSY6O48s.js → useFs-RLfB3yqb.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-iJl3VnU0.js → usePersonalDataHub-DbRVH_NA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-vu8ReMs7.js → vnode-DTn_QVJz.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CeSrYAHp.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 +52 -0
- package/src/gateways/ws/llm-chat-protocol.js +23 -5
- package/src/gateways/ws/llm-creds.js +38 -60
- package/src/gateways/ws/ws-agent-handler.js +1 -1
- package/src/gateways/ws/ws-server.js +47 -0
- 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-Ceh3MG6M.js +0 -1
- package/src/assets/web-panel/assets/EmailListRenderer-CQj7UlKJ.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-Dgy8arQI.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-7ty8vTpj.js +0 -1
- package/src/assets/web-panel/assets/Skills-DMlsh9It.js +0 -1
- package/src/assets/web-panel/assets/Tasks-DESg3yCs.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BI5TZ7EX.js +0 -1
- package/src/assets/web-panel/assets/index-9-JLDK5n.js +0 -1
- package/src/assets/web-panel/assets/index-B7m8ioaQ.js +0 -1
|
@@ -1,27 +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. Process env — first BUILT_IN_PROVIDERS entry whose apiKeyEnv is set.
|
|
12
|
-
*
|
|
13
|
-
* Fall-throughs:
|
|
14
|
-
* - Provider with `free: true` (ollama) doesn't need apiKey; a session
|
|
15
|
-
* entry with provider=ollama and no apiKey is valid.
|
|
16
|
-
* - For non-ollama providers, missing apiKey is a fatal — the caller
|
|
17
|
-
* should send a clean error frame instead of hanging on a 401.
|
|
18
|
-
* - When no source has any creds, returns null. Caller decides whether
|
|
19
|
-
* to fail-fast (llm.chat) or yield "LLM not configured" (chat-intent).
|
|
20
|
-
*
|
|
21
|
-
* @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.
|
|
22
6
|
*/
|
|
23
|
-
|
|
24
7
|
import { BUILT_IN_PROVIDERS } from "../../lib/llm-providers.js";
|
|
8
|
+
import { resolveLlmConnection } from "../../lib/llm-connection-resolution.js";
|
|
25
9
|
|
|
26
10
|
const ENV_PREFERRED_ORDER = [
|
|
27
11
|
"volcengine",
|
|
@@ -37,57 +21,51 @@ const ENV_PREFERRED_ORDER = [
|
|
|
37
21
|
|
|
38
22
|
export function resolveLlmCreds(server, message) {
|
|
39
23
|
const opts = message?.options || {};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
baseUrl: optBaseUrl || def?.baseUrl || null,
|
|
51
|
-
apiKey: optApiKey || (def?.apiKeyEnv ? process.env[def.apiKeyEnv] : null),
|
|
52
|
-
};
|
|
24
|
+
let cfgLlm = {};
|
|
25
|
+
if (typeof server?._loadLlmConfig === "function") {
|
|
26
|
+
try {
|
|
27
|
+
const configured = server._loadLlmConfig();
|
|
28
|
+
cfgLlm = configured?.llm || configured || {};
|
|
29
|
+
} catch {
|
|
30
|
+
// A stale config snapshot can still be bypassed with session or env data.
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
cfgLlm = server?.sessionManager?.config?.llm || {};
|
|
53
34
|
}
|
|
54
35
|
|
|
36
|
+
let session = null;
|
|
55
37
|
const sessionId = message?.sessionId;
|
|
56
38
|
if (sessionId && server?.sessionManager?.getSession) {
|
|
57
|
-
let session;
|
|
58
39
|
try {
|
|
59
40
|
session = server.sessionManager.getSession(sessionId);
|
|
60
41
|
} catch {
|
|
61
42
|
session = null;
|
|
62
43
|
}
|
|
63
|
-
if (session?.provider) {
|
|
64
|
-
const def = BUILT_IN_PROVIDERS[session.provider];
|
|
65
|
-
// Don't default baseUrl to ollama — that breaks every cloud provider
|
|
66
|
-
// when the session was created without an explicit baseUrl. Fall back
|
|
67
|
-
// to the provider's own default endpoint instead.
|
|
68
|
-
const fallbackBase =
|
|
69
|
-
def?.baseUrl ||
|
|
70
|
-
(session.provider === "ollama" ? "http://localhost:11434" : null);
|
|
71
|
-
return {
|
|
72
|
-
provider: session.provider,
|
|
73
|
-
model: optModel || session.model,
|
|
74
|
-
baseUrl: session.baseUrl || fallbackBase,
|
|
75
|
-
apiKey: session.apiKey,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
44
|
}
|
|
79
45
|
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
89
|
-
}
|
|
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;
|
|
90
54
|
}
|
|
91
55
|
|
|
92
|
-
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
|
+
);
|
|
93
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,
|
|
@@ -124,6 +124,10 @@ import {
|
|
|
124
124
|
} from "./artifact-protocol.js";
|
|
125
125
|
import { executionBroker } from "../../lib/process-execution-broker/index.js";
|
|
126
126
|
import { collectWorkspacePluginBinSandboxPolicy } from "../../lib/plugin-runtime/bin.js";
|
|
127
|
+
import {
|
|
128
|
+
loadConfig,
|
|
129
|
+
loadUserConfig,
|
|
130
|
+
} from "../../lib/config-manager.js";
|
|
127
131
|
|
|
128
132
|
export const _deps = {
|
|
129
133
|
spawn: (...args) => executionBroker.spawn(...args),
|
|
@@ -195,6 +199,47 @@ function boundedInteger(value, fallback, maximum = Number.MAX_SAFE_INTEGER) {
|
|
|
195
199
|
return Math.min(parsed, maximum);
|
|
196
200
|
}
|
|
197
201
|
|
|
202
|
+
function loadCurrentLlmConfig() {
|
|
203
|
+
const savedLlm =
|
|
204
|
+
loadUserConfig({ failIfUnavailable: true }).llm || {};
|
|
205
|
+
const resolvedLlm = loadConfig({ failIfUnavailable: true }).llm || {};
|
|
206
|
+
const confirmedLlm =
|
|
207
|
+
loadUserConfig({ failIfUnavailable: true }).llm || {};
|
|
208
|
+
const provider =
|
|
209
|
+
typeof savedLlm.provider === "string" && savedLlm.provider.trim()
|
|
210
|
+
? savedLlm.provider.trim()
|
|
211
|
+
: null;
|
|
212
|
+
const routingIdentity = (llm) =>
|
|
213
|
+
JSON.stringify([
|
|
214
|
+
llm.provider ?? null,
|
|
215
|
+
llm.baseUrl ?? null,
|
|
216
|
+
llm.apiKey ?? null,
|
|
217
|
+
llm.model ?? null,
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
// Config writes are atomic, but a save can land between reads. Never pair
|
|
221
|
+
// a key or route from different saved versions in that case.
|
|
222
|
+
if (
|
|
223
|
+
routingIdentity(savedLlm) !== routingIdentity(confirmedLlm) ||
|
|
224
|
+
(provider && provider !== resolvedLlm.provider)
|
|
225
|
+
) {
|
|
226
|
+
throw new Error("LLM configuration changed while being read");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
provider,
|
|
231
|
+
model:
|
|
232
|
+
typeof savedLlm.model === "string" && savedLlm.model.trim()
|
|
233
|
+
? savedLlm.model.trim()
|
|
234
|
+
: null,
|
|
235
|
+
baseUrl:
|
|
236
|
+
typeof savedLlm.baseUrl === "string" && savedLlm.baseUrl.trim()
|
|
237
|
+
? savedLlm.baseUrl.trim()
|
|
238
|
+
: null,
|
|
239
|
+
apiKey: provider ? resolvedLlm.apiKey || null : null,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
198
243
|
function frameByteLength(data) {
|
|
199
244
|
if (typeof data === "string") return Buffer.byteLength(data, "utf8");
|
|
200
245
|
if (Buffer.isBuffer(data)) return data.byteLength;
|
|
@@ -368,6 +413,8 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
368
413
|
|
|
369
414
|
/** Session manager for stateful agent/chat sessions */
|
|
370
415
|
this.sessionManager = options.sessionManager || null;
|
|
416
|
+
/** Read current saved LLM settings for stateless chat requests. */
|
|
417
|
+
this._loadLlmConfig = options.loadLlmConfig || loadCurrentLlmConfig;
|
|
371
418
|
this.projectRoot = options.projectRoot || process.cwd();
|
|
372
419
|
|
|
373
420
|
/** Session handlers: sessionId → WSAgentHandler | WSChatHandler */
|
|
@@ -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);
|