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.
Files changed (206) hide show
  1. package/README.md +6 -4
  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-DkrNbDzS.js → AIOps-sOceeExN.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-DJjFXDhv.js → ActionButton-BQs-vRUr.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-DyUbagRn.js → Analytics-sfAHHMhA.js} +2 -2
  8. package/src/assets/web-panel/assets/{AppLayout-p7kJWSvn.js → AppLayout-0gAvE0c8.js} +4 -4
  9. package/src/assets/web-panel/assets/{Artifacts-C3rVh4Ak.js → Artifacts-Dh9s2Qmd.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-Bch_L_YO.js → Audit-CITm26SU.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-BNHy8zP6.js → BackgroundAgents-BiMbBOh0.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-BUaye_Ys.js → Backup-CNpnhI3h.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-D49mzAVz.js → BaseInput-BI_lltkp.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-2MDzM_us.js → Chat-BgfB88xY.js} +5 -5
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-BDP3fGoz.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-5p1T_KEq.js → Checkbox-4xy9rS3j.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-TKtiLaoZ.js → Codegen-D93h9ihS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-BVCMG9mS.js → Col-BRf3STY-.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-D09-QczH.js → Community-f187ziSL.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-QuRMCnj7.js → Compact-D4hXWuo1.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-CN0PV7fk.js → Compliance-C6cYKI6H.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-5N2zhAty.js → Cowork-C1i1f8LD.js} +2 -2
  23. package/src/assets/web-panel/assets/{Cron-BxT8UZ0S.js → Cron-BacCE4LA.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-Dp-noQl4.js → Crosschain-DXztxOoH.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-B7MZFkjG.js → DID-rpf4HN8x.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-DulJ3rKV.js → Dashboard-h4bgtH7L.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-DV4ov4zF.js → Dropdown-CyQnoHmu.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-DB2-ooz6.js → EmailListRenderer-FCtp4AcB.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-B1Rb6p7q.js → EvolutionSettings-JbISHTWR.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-U8aHayx-.js → FamilyGuardDashboard-DCIQLyfK.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-BkQULBHY.js → Federation-DDbFpwja.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-CtviG260.js → FormItemContext-CXF0jtXb.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-DnPirB24.js → GenericCardRenderer-CUaNfAM0.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-CK57Oro6.js → Git-Bhoz2gDf.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-DbI-LB8-.js → Governance-FiiXHe8e.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-sEG4T1Jg.js → Inference-brpTW2Q3.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-9SLJR3h9.js → KnowledgeGraph-BCWP-f6k.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BIZYcISQ.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-CRmLLwAE.js → Marketplace-DANdmW7l.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-DkOFkSO1.js → McpTools-Bi_EKkb5.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-M29Kd9TU.js → Memory-DecoMbYw.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Tl6JvPw_.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-DkUjX1F5.js → Mtc-SLPII1iw.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-BCqr1I4q.js → MtcAudit-DWoVORYy.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-DmoqYGQ5.js → Multisig-Dxpnmq-m.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DNRQeRke.js → NLProgramming-kNTpNqCP.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-CQSBeiY6.js → Notes-D1uXCfVd.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-gNY9bbH6.js → NotificationSettings-D3Qt8Gkl.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-DW0CNjh8.js → OrderTableRenderer-qvaah2-m.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-CthnbKag.js → Organization-EhRl4JoJ.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-_GQ3sbXu.js → Overflow-BTt5C_5W.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-BTr7ea6W.js → P2P-CeR2jyb0.js} +2 -2
  55. package/src/assets/web-panel/assets/PdhVaultBrowser-BzD3OHWq.js +7 -0
  56. package/src/assets/web-panel/assets/{Permissions-DMny2Cjs.js → Permissions-BOPyeRlz.js} +3 -3
  57. package/src/assets/web-panel/assets/{PersonalDataHub-CyYlMSoI.js → PersonalDataHub-DcmNNsfO.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-FASu2Oc4.js → Pipeline-DuL3kwWC.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-9bC5FnsJ.js → Privacy-CkQRdlSU.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-BOYZwB5G.js → ProjectInit-moE_vQ1P.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-45DViNG-.js → ProjectSettings-PnTWi4NQ.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-C2fZzMZc.js → Projects-DZvbT3D7.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-BgjvZx9F.js → Providers-CkcrHYy9.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-Brw-3csB.js → QrScannerModal-nYNFGf_H.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-DWG-A5zB.js → QuickAsk-53WNo7xT.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DuQGxjIR.js → Recommend-CmYN_NaS.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-T1cdSSRL.js → RemoteSession-BuOdNEjA.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-9FT2dI4G.js → Reputation-BgkheZou.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-yNIbNftX.js → Row-DvA3klj1.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-BIchNoPd.js → RssFeed-CgkIL3PG.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-msDJ8UTZ.js → Search-Cv2twJdL.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-DE46oBYT.js → Security-XrD2UNU6.js} +2 -2
  73. package/src/assets/web-panel/assets/{Services-Dori7vc3.js → Services-LujCntCK.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-D3O3sUFz.js → Skeleton-ZZCOsxPX.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-BV0ct8SO.js → Skills-BEXGkOrU.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-DflIVOrh.js → Sla-CIqmCgdD.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Cpbv2Zz6.js → SpeechSettings-DkVbwJox.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-Dgg6-wbe.js → SyncSettings-BVGgUs0p.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-Cb6e4Klx.js → Tasks-C1lh68l5.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-GfdAATGz.js → Templates-BG4zNAFx.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-D0gecLXl.js → Tenant-t2o6i5fY.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BFRPJt-0.js → Terminal-CH8UDdVO.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-BobtChxX.js → TimelineRenderer-B5sYY1IO.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-CifeZtHV.js → Tokens-D0y7QkG5.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-BHBvbBCf.js → Trigger-BA2nKko0.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-6lvqjKYp.js → Trust-CrFKCQIs.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CqzMc-G5.js → UkeySign-B3x_-Zun.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-OIiRG582.js → VideoEditing-TEwxbLH6.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-4MeKq0q_.js → Wallet-Larp5rrt.js} +2 -2
  90. package/src/assets/web-panel/assets/{WebAuthn-eHBRjGQG.js → WebAuthn-DfWbEIZe.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-DlzYdEqW.js → WorkflowEditor-8LKiKExY.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CBcjOzkX.js → chat-ByxYOvYW.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-zgwsYQSa.js → colors-Dh4yAClh.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-Dcd9H5Bn.js → compact-item-f5n2j1WZ.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-DR1tO-8r.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-CtfbUH_p.js → hasIn-CvYeap2I.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-D1GxtY-h.js → index-3ROY-lYB.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CT7zIkqP.js → index-6VEWxOyC.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Cdhy5ckh.js → index-7eG1J-GT.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DvPOpvvH.js → index-B0AXUhiJ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cur8LNNn.js → index-BS_dpgzu.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DWG98orp.js → index-BaXSBxy-.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-C5XGvJb0.js → index-Bk8oWRk6.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DA2-Bhy8.js → index-BlImvgb7.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CwHgaYVH.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-DHEiuvhA.js → index-BsU46c7D.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DSvcz0RO.js → index-BvyFQF8I.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D7ZOiIKb.js → index-C2RGykST.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CmzQzTY5.js → index-C5aH2Pj2.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-oWOlF7CM.js → index-CD0s5ESV.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DVpp9YSQ.js → index-CIZQtz8G.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DWuCGhHa.js → index-CJjc8ObW.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-JSfldpw4.js → index-CTqJNul4.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-dvF6C39V.js → index-CcvA1nae.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CmVIM-m2.js → index-CpR0zD38.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CaGNqkY0.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-BXfGHUz_.js → index-DGyTzElU.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-LN7Hkl1C.js → index-DI8jC7gQ.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DfzOYmOj.js → index-DUj-sL9U.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BoxowMYS.js → index-DaZVtafQ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CSQCHpDo.js → index-DjvON8oh.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B3QUK1ks.js → index-DlJCDpeN.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-J-ORW1i6.js → index-DlTwZIeX.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-z8lrkcb-.js → index-DlULGx8u.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-hTcre7U9.js → index-DmcAL4hd.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DYPMJ5Mb.js → index-DshB3-Up.js} +3 -3
  130. package/src/assets/web-panel/assets/{index-5XaB_Tjg.js → index-Du435yzt.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C0kP88Pb.js → index-bt-0zMmH.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D8WPohye.js → index-c_CxUQl3.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DeAi4CNt.js → index-fpWv4u3Z.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BndPtS9D.js → index-hwbaD3Nk.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DTx4FyLU.js → index-uDiVjYrS.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CPLLgPxA.js → index-xU0eHLIH.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-CVMtDrWZ.js → initDefaultProps-DRuE92W6.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-Dye3IU8k.js → motion-6Sgma5Vd.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-C251Zo34.js → move-BbQsH8sb.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-BDiOORoQ.js → mtc-parser-BdC4INal.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-DTDA_RHv.js → omit-DeC5m_bi.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-CqEUTPcT.js → pickAttrs-DuehpBws.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-BOB8QWH3.js → placementArrow-CJEhv2ij.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-CZDXP1d9.js → responsiveObserve-Dyw3lDGr.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-DPaRw3E9.js → slide-BMaLXTbS.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-Ba9d7XZ7.js → statusUtils-c1K_SdJH.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-mZKU3lvW.js → styleChecker-CefBry8k.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-VcauLq0M.js → useFlexGapSupport-Dj2IsZpo.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-Cal9KRcx.js → useFs-RLfB3yqb.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-DISoRyQB.js → usePersonalDataHub-DbRVH_NA.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-B58C6w6B.js → vnode-DTn_QVJz.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-BPlBAMKB.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 +39 -0
  163. package/src/gateways/ws/llm-chat-protocol.js +18 -10
  164. package/src/gateways/ws/llm-creds.js +29 -79
  165. package/src/gateways/ws/ws-agent-handler.js +1 -1
  166. package/src/gateways/ws/ws-session-gateway.js +26 -12
  167. package/src/lib/__tests__/provider-stream.test.js +70 -4
  168. package/src/lib/chat-core.js +186 -24
  169. package/src/lib/cowork-adapter.js +29 -15
  170. package/src/lib/evolution/agent-evolution-runtime-composition.js +10 -7
  171. package/src/lib/evolution/evolution-deployment-config.js +27 -4
  172. package/src/lib/evolution/evolution-deployment-loader.js +58 -3
  173. package/src/lib/evolution/evolution-deployment-profile.js +2 -2
  174. package/src/lib/evolution/evolution-evidence-projector.js +18 -3
  175. package/src/lib/evolution/evolution-file-witness.js +10 -0
  176. package/src/lib/evolution/evolution-ledger.js +237 -28
  177. package/src/lib/evolution/evolution-windows-directory-durability.js +322 -0
  178. package/src/lib/evolution/governed-hub-llm.js +2 -1
  179. package/src/lib/evolution/governed-model-turn.js +4 -1
  180. package/src/lib/evolution/local-ui-evolution-deployment.js +682 -0
  181. package/src/lib/evolution/local-ui-evolution-host.mjs +26 -0
  182. package/src/lib/evolution/local-ui-evolution-runtime.js +601 -0
  183. package/src/lib/evolution/opaque-hex-id.js +28 -0
  184. package/src/lib/hub-llm-client.js +22 -14
  185. package/src/lib/image-input.js +19 -3
  186. package/src/lib/llm-config-defaults.js +19 -30
  187. package/src/lib/llm-connection-config.js +4 -0
  188. package/src/lib/llm-connection-probe.js +5 -1
  189. package/src/lib/llm-connection-resolution.js +94 -0
  190. package/src/lib/llm-providers.js +33 -11
  191. package/src/lib/ollama-base-url.js +14 -0
  192. package/src/lib/provider-protocol.js +104 -0
  193. package/src/lib/provider-stream.js +163 -4
  194. package/src/lib/runnable-provider.js +1 -0
  195. package/src/repl/chat-repl.js +58 -11
  196. package/src/runtime/agent-core.js +26 -43
  197. package/src/runtime/headless-runner.js +11 -4
  198. package/src/runtime/headless-stream.js +10 -4
  199. package/src/runtime/policies/agent-policy.js +17 -11
  200. package/src/assets/web-panel/assets/ChatBubbleRenderer-DOX3Y5EJ.js +0 -1
  201. package/src/assets/web-panel/assets/MarkdownRenderer-9nfWupWP.js +0 -1
  202. package/src/assets/web-panel/assets/MobileProjects-Bq-FPDzS.js +0 -1
  203. package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +0 -7
  204. package/src/assets/web-panel/assets/devWarning-DYhpRtve.js +0 -1
  205. package/src/assets/web-panel/assets/index-DPJXFIY7.js +0 -1
  206. package/src/assets/web-panel/assets/index-DjBhR5M_.js +0 -1
@@ -1,28 +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. 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
- // If the saved config cannot be refreshed, continue with explicit
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
- const buildCreds = (provider, inherited = {}) => {
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
- if (cfgLlm.provider) {
110
- return buildCreds(cfgLlm.provider);
111
- }
112
-
113
- for (const name of ENV_PREFERRED_ORDER) {
114
- const def = BUILT_IN_PROVIDERS[name];
115
- if (def?.apiKeyEnv && process.env[def.apiKeyEnv]) {
116
- return buildCreds(name);
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 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
+ );
121
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,
@@ -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);
@@ -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(`${baseUrl}/api/chat`, {
179
- method: "POST",
180
- signal: requestSignal(signal),
181
- headers: { "Content-Type": "application/json" },
182
- body: JSON.stringify({
183
- model: currentModel,
184
- messages: modelMessages,
185
- stream: false,
186
- options: { num_predict: maxTokens },
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(`${baseUrl}/messages`, {
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(`${baseUrl}/chat/completions`, {
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(