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.
Files changed (209) hide show
  1. package/README.md +14 -7
  2. package/package.json +3 -2
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-B5HWUFaS.js → AIOps-sOceeExN.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-DCIKpxoQ.js → ActionButton-BQs-vRUr.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-dsgvjCEQ.js → Analytics-sfAHHMhA.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-DWDH5Qbm.js → AppLayout-0gAvE0c8.js} +4 -4
  9. package/src/assets/web-panel/assets/{Artifacts-BD8S8kzU.js → Artifacts-Dh9s2Qmd.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-CDXv4Aoj.js → Audit-CITm26SU.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-BLAQ5Uy0.js → BackgroundAgents-BiMbBOh0.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-moPac6tF.js → Backup-CNpnhI3h.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-C_6nzsLU.js → BaseInput-BI_lltkp.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-cYuslkFG.js → Chat-BgfB88xY.js} +6 -6
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-BDP3fGoz.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-Qjb-XOX_.js → Checkbox-4xy9rS3j.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-IYRWXlMA.js → Codegen-D93h9ihS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-CsQ9jv8M.js → Col-BRf3STY-.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-BbNmfTwe.js → Community-f187ziSL.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-SShDx-nr.js → Compact-D4hXWuo1.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-CLWhKIJp.js → Compliance-C6cYKI6H.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-D4tK9_s3.js → Cowork-C1i1f8LD.js} +2 -2
  23. package/src/assets/web-panel/assets/{Cron-D7GnfQ1z.js → Cron-BacCE4LA.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-TG86SH3X.js → Crosschain-DXztxOoH.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-6pue0NCO.js → DID-rpf4HN8x.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-BwkJ29du.js → Dashboard-h4bgtH7L.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-DEvpYDof.js → Dropdown-CyQnoHmu.js} +1 -1
  28. package/src/assets/web-panel/assets/EmailListRenderer-FCtp4AcB.js +1 -0
  29. package/src/assets/web-panel/assets/{EvolutionSettings-tpvPMM9W.js → EvolutionSettings-JbISHTWR.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CWYFW75O.js → FamilyGuardDashboard-DCIQLyfK.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-gRIi96rM.js → Federation-DDbFpwja.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-B7Nr3YkV.js → FormItemContext-CXF0jtXb.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-b1BgGgLT.js → GenericCardRenderer-CUaNfAM0.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-Dis1jjO7.js → Git-Bhoz2gDf.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-C4Yg99Am.js → Governance-FiiXHe8e.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-D4UQRbGu.js → Inference-brpTW2Q3.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-Co_kLMAq.js → KnowledgeGraph-BCWP-f6k.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-D4BRp7bg.js → Logs-CxYcduoK.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-12vD1nC1.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-ys0j1vr9.js → Marketplace-DANdmW7l.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-B3ZvRZe_.js → McpTools-Bi_EKkb5.js} +2 -2
  42. package/src/assets/web-panel/assets/{Memory-BOa0jbr-.js → Memory-DecoMbYw.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-CWJRmUbj.js → MobileBridge-DrOhFKp0.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-rRwP16dN.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-CCcOk5m5.js → Mtc-SLPII1iw.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-vlrrScbQ.js → MtcAudit-DWoVORYy.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-C8K59S_8.js → Multisig-Dxpnmq-m.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-oTpFYVme.js → NLProgramming-kNTpNqCP.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-BhqAJWiz.js → Notes-D1uXCfVd.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-DCnV1CJM.js → NotificationSettings-D3Qt8Gkl.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-CZGnsrVW.js → OrderTableRenderer-qvaah2-m.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-DX1LuQlJ.js → Organization-EhRl4JoJ.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-CbfKrFdT.js → Overflow-BTt5C_5W.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-BEwYZuHL.js → P2P-CeR2jyb0.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-YNmWbwd7.js → PdhVaultBrowser-BzD3OHWq.js} +3 -3
  56. package/src/assets/web-panel/assets/{Permissions-DyZ18LiA.js → Permissions-BOPyeRlz.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-B2J3aw2g.js → PersonalDataHub-DcmNNsfO.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-CXl2ZlhQ.js → Pipeline-DuL3kwWC.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-I5SDmgAp.js → Privacy-CkQRdlSU.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-Crl9tXw3.js → ProjectInit-moE_vQ1P.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-DCFtFlSK.js → ProjectSettings-PnTWi4NQ.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-Do5BQq1i.js → Projects-DZvbT3D7.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-Cb7OWa3y.js → Providers-CkcrHYy9.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-CJmhAU-s.js → QrScannerModal-nYNFGf_H.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-Cu6lEWou.js → QuickAsk-53WNo7xT.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-Crip3jLB.js → Recommend-CmYN_NaS.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-KxYY-2CB.js → RemoteSession-BuOdNEjA.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-D0vN-htI.js → Reputation-BgkheZou.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-wqzd_xCD.js → Row-DvA3klj1.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-8lsPfb0a.js → RssFeed-CgkIL3PG.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-C-e8qAeU.js → Search-Cv2twJdL.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-CMM9WgFy.js → Security-XrD2UNU6.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-CZdxjN2B.js → Services-LujCntCK.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-BI3XNk35.js → Skeleton-ZZCOsxPX.js} +1 -1
  75. package/src/assets/web-panel/assets/Skills-BEXGkOrU.js +1 -0
  76. package/src/assets/web-panel/assets/{Sla-HQruznKY.js → Sla-CIqmCgdD.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Dl-MFq3w.js → SpeechSettings-DkVbwJox.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BU224vDm.js → SyncSettings-BVGgUs0p.js} +2 -2
  79. package/src/assets/web-panel/assets/Tasks-C1lh68l5.js +1 -0
  80. package/src/assets/web-panel/assets/{Templates-Jb3TPz4Q.js → Templates-BG4zNAFx.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-CQ-GTrfa.js → Tenant-t2o6i5fY.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BbMl3Rue.js → Terminal-CH8UDdVO.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-CS_vstKF.js → TimelineRenderer-B5sYY1IO.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-BaopVl1f.js → Tokens-D0y7QkG5.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-DtnD4WUN.js → Trigger-BA2nKko0.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-CsvG4Tmf.js → Trust-CrFKCQIs.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-C1-A-cOV.js → UkeySign-B3x_-Zun.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-CNgJPdSL.js → VideoEditing-TEwxbLH6.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-CdF41501.js → Wallet-Larp5rrt.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-D-JmxD5g.js → WebAuthn-DfWbEIZe.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-C0XunR13.js → WorkflowEditor-8LKiKExY.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-BBAZpD_G.js → chat-ByxYOvYW.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-BxSxO5BX.js → colors-Dh4yAClh.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-BoxL8pg5.js → compact-item-f5n2j1WZ.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-W5JAyZmY.js → createContext-DIprYuXL.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-DRIge35Z.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-HpiKDPVC.js → hasIn-CvYeap2I.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B9VxE1C-.js → index-3ROY-lYB.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DdEcQ1rp.js → index-6VEWxOyC.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C4uI7WCB.js → index-7eG1J-GT.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-_38IJqoP.js → index-B0AXUhiJ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-C7GIUWWF.js → index-BS_dpgzu.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-ooP1pQCt.js → index-BaXSBxy-.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-gvfIRZxJ.js → index-Bk8oWRk6.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BSJ0cAle.js → index-BlImvgb7.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C5kqii-G.js → index-BoayvJ48.js} +1 -1
  107. package/src/assets/web-panel/assets/index-Bq24rTYx.js +1 -0
  108. package/src/assets/web-panel/assets/{index-B05_YRHZ.js → index-BsU46c7D.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-_p4e3KfB.js → index-BvyFQF8I.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Bwn6k_qw.js → index-C2RGykST.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BtFAEG08.js → index-C5aH2Pj2.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CehxfRUR.js → index-CD0s5ESV.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-JrMBRi62.js → index-CIZQtz8G.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CRg3aOoj.js → index-CJjc8ObW.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-V6VIJe2N.js → index-CTqJNul4.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-p6rNtYud.js → index-CcvA1nae.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-D6m6B4Wy.js → index-CpR0zD38.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-IO2I7nzu.js → index-CzabQZ7I.js} +1 -1
  119. package/src/assets/web-panel/assets/index-D2f18WsK.js +1 -0
  120. package/src/assets/web-panel/assets/{index-9caMEwQ-.js → index-DGyTzElU.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DinY7IQa.js → index-DI8jC7gQ.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DuM9I1If.js → index-DUj-sL9U.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DkSnlgK_.js → index-DaZVtafQ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CM_gdohF.js → index-DjvON8oh.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DcmAiq_Y.js → index-DlJCDpeN.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DaJk9U9V.js → index-DlTwZIeX.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BgMViZIY.js → index-DlULGx8u.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BA3WWQAT.js → index-DmcAL4hd.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DSyfxxQS.js → index-DshB3-Up.js} +3 -3
  130. package/src/assets/web-panel/assets/{index-BZAbaY64.js → index-Du435yzt.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BW4PP5GN.js → index-bt-0zMmH.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DlPvZgv8.js → index-c_CxUQl3.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-Ba4usRFM.js → index-fpWv4u3Z.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DwidpDEE.js → index-hwbaD3Nk.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CcyzZloJ.js → index-uDiVjYrS.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-QzlEbtRL.js → index-xU0eHLIH.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-Dfn6M05J.js → initDefaultProps-DRuE92W6.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-BRRwFqGO.js → motion-6Sgma5Vd.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-DpDPN6zR.js → move-BbQsH8sb.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-BHlSE87F.js → mtc-parser-BdC4INal.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-CEOri_L5.js → omit-DeC5m_bi.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DqICh-ad.js → pickAttrs-DuehpBws.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DQizFFiV.js → placementArrow-CJEhv2ij.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-DmFHPdvC.js → responsiveObserve-Dyw3lDGr.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-s2s1XS3T.js → slide-BMaLXTbS.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-IwEIbBan.js → statusUtils-c1K_SdJH.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-CIFLlbXH.js → styleChecker-CefBry8k.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-CFkN2Cgh.js → useFlexGapSupport-Dj2IsZpo.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-BSY6O48s.js → useFs-RLfB3yqb.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-iJl3VnU0.js → usePersonalDataHub-DbRVH_NA.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-vu8ReMs7.js → vnode-DTn_QVJz.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-CeSrYAHp.js → zoom-DuFR0E7i.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +1 -1
  155. package/src/commands/agent.js +12 -5
  156. package/src/commands/ask.js +96 -21
  157. package/src/commands/complete.js +14 -10
  158. package/src/commands/llm.js +23 -41
  159. package/src/commands/session.js +2 -2
  160. package/src/commands/stream.js +9 -5
  161. package/src/commands/ui.js +30 -2
  162. package/src/data/changelog.json +52 -0
  163. package/src/gateways/ws/llm-chat-protocol.js +23 -5
  164. package/src/gateways/ws/llm-creds.js +38 -60
  165. package/src/gateways/ws/ws-agent-handler.js +1 -1
  166. package/src/gateways/ws/ws-server.js +47 -0
  167. package/src/gateways/ws/ws-session-gateway.js +26 -12
  168. package/src/lib/__tests__/provider-stream.test.js +70 -4
  169. package/src/lib/chat-core.js +186 -24
  170. package/src/lib/cowork-adapter.js +29 -15
  171. package/src/lib/evolution/agent-evolution-runtime-composition.js +10 -7
  172. package/src/lib/evolution/evolution-deployment-config.js +27 -4
  173. package/src/lib/evolution/evolution-deployment-loader.js +58 -3
  174. package/src/lib/evolution/evolution-deployment-profile.js +2 -2
  175. package/src/lib/evolution/evolution-evidence-projector.js +18 -3
  176. package/src/lib/evolution/evolution-file-witness.js +10 -0
  177. package/src/lib/evolution/evolution-ledger.js +237 -28
  178. package/src/lib/evolution/evolution-windows-directory-durability.js +322 -0
  179. package/src/lib/evolution/governed-hub-llm.js +2 -1
  180. package/src/lib/evolution/governed-model-turn.js +4 -1
  181. package/src/lib/evolution/local-ui-evolution-deployment.js +682 -0
  182. package/src/lib/evolution/local-ui-evolution-host.mjs +26 -0
  183. package/src/lib/evolution/local-ui-evolution-runtime.js +601 -0
  184. package/src/lib/evolution/opaque-hex-id.js +28 -0
  185. package/src/lib/hub-llm-client.js +22 -14
  186. package/src/lib/image-input.js +19 -3
  187. package/src/lib/llm-config-defaults.js +19 -30
  188. package/src/lib/llm-connection-config.js +4 -0
  189. package/src/lib/llm-connection-probe.js +5 -1
  190. package/src/lib/llm-connection-resolution.js +94 -0
  191. package/src/lib/llm-providers.js +33 -11
  192. package/src/lib/ollama-base-url.js +14 -0
  193. package/src/lib/provider-protocol.js +104 -0
  194. package/src/lib/provider-stream.js +163 -4
  195. package/src/lib/runnable-provider.js +1 -0
  196. package/src/repl/chat-repl.js +58 -11
  197. package/src/runtime/agent-core.js +26 -43
  198. package/src/runtime/headless-runner.js +11 -4
  199. package/src/runtime/headless-stream.js +10 -4
  200. package/src/runtime/policies/agent-policy.js +17 -11
  201. package/src/assets/web-panel/assets/ChatBubbleRenderer-Ceh3MG6M.js +0 -1
  202. package/src/assets/web-panel/assets/EmailListRenderer-CQj7UlKJ.js +0 -1
  203. package/src/assets/web-panel/assets/MarkdownRenderer-Dgy8arQI.js +0 -1
  204. package/src/assets/web-panel/assets/MobileProjects-7ty8vTpj.js +0 -1
  205. package/src/assets/web-panel/assets/Skills-DMlsh9It.js +0 -1
  206. package/src/assets/web-panel/assets/Tasks-DESg3yCs.js +0 -1
  207. package/src/assets/web-panel/assets/devWarning-BI5TZ7EX.js +0 -1
  208. package/src/assets/web-panel/assets/index-9-JLDK5n.js +0 -1
  209. package/src/assets/web-panel/assets/index-B7m8ioaQ.js +0 -1
@@ -1,27 +1,11 @@
1
1
  /**
2
- * Shared LLM credential resolver for WS handlers.
2
+ * Shared LLM connection resolver for WS handlers.
3
3
  *
4
- * Used by both `llm.chat` (single-shot streaming) and `chat.intent.*`
5
- * (intent-understanding flow). Encapsulates the precedence rules so adding
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
- const optProvider = opts.provider;
41
- const optModel = opts.model;
42
- const optBaseUrl = opts.baseUrl;
43
- const optApiKey = opts.apiKey;
44
-
45
- if (optProvider) {
46
- const def = BUILT_IN_PROVIDERS[optProvider];
47
- return {
48
- provider: optProvider,
49
- model: optModel || def?.models?.[0] || null,
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
- for (const name of ENV_PREFERRED_ORDER) {
81
- const def = BUILT_IN_PROVIDERS[name];
82
- if (def?.apiKeyEnv && process.env[def.apiKeyEnv]) {
83
- return {
84
- provider: name,
85
- model: optModel || def.models?.[0] || null,
86
- baseUrl: def.baseUrl,
87
- apiKey: process.env[def.apiKeyEnv],
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 null;
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 || "http://localhost:11434",
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 provider = options.provider || cfgLlm.provider || "ollama";
522
- const model =
523
- options.model ||
524
- cfgLlm.model ||
525
- (provider === "ollama" ? "qwen2.5:7b" : null);
526
- const baseUrl =
527
- options.baseUrl || cfgLlm.baseUrl || "http://localhost:11434";
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: dbSession.provider || "ollama",
848
- model: dbSession.model || null,
849
- apiKey: null,
850
- baseUrl: metadata.baseUrl || "http://localhost:11434",
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) and OpenAI-style SSE
8
- * (yield delta.content, stop on [DONE], skip non-data/empty), error on non-ok,
9
- * plus buildProviderSource routing (ollama defaults / known-provider endpoint +
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/,
@@ -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(`${baseUrl}/api/chat`, {
138
- method: "POST",
139
- headers: { "Content-Type": "application/json" },
140
- body: JSON.stringify({
141
- model,
142
- messages,
143
- stream: true,
144
- }),
145
- signal: options.signal
146
- ? AbortSignal.any([guard.signal, options.signal])
147
- : guard.signal,
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(`${baseUrl}/chat/completions`, {
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(`${baseUrl}/messages`, {
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 && baseUrl !== "http://localhost:11434"
583
- ? baseUrl
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 !== "http://localhost:11434"
608
- ? baseUrl
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);