chainlesschain 0.166.109 → 0.166.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +14 -6
  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/.build-hash +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-DkrNbDzS.js → AIOps-CVac3rIN.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-DJjFXDhv.js → ActionButton-ChM9GxMl.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DyUbagRn.js → Analytics-CV5399Js.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-p7kJWSvn.js → AppLayout-CuonVXhg.js} +3 -3
  10. package/src/assets/web-panel/assets/{Artifacts-C3rVh4Ak.js → Artifacts-D9qKfgb6.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-Bch_L_YO.js → Audit-DUvLysQc.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-BNHy8zP6.js → BackgroundAgents-DfKohQeT.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-BUaye_Ys.js → Backup-CohoEI8R.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-D49mzAVz.js → BaseInput-BwdoA3xv.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-2MDzM_us.js → Chat-DRkuz1d2.js} +5 -5
  16. package/src/assets/web-panel/assets/ChatBubbleRenderer-v3pzeHNC.js +1 -0
  17. package/src/assets/web-panel/assets/{Checkbox-5p1T_KEq.js → Checkbox-B-GNGpKv.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-TKtiLaoZ.js → Codegen-Bs2lAsoZ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-BVCMG9mS.js → Col-Bk1mJKDZ.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-D09-QczH.js → Community-B_FDSCOo.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-QuRMCnj7.js → Compact-No1qSLLH.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-CN0PV7fk.js → Compliance-CcrG43-d.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-5N2zhAty.js → Cowork-DLkj2ywK.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-BxT8UZ0S.js → Cron-sQHELVEz.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-Dp-noQl4.js → Crosschain-CRTZ3GZg.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-B7MZFkjG.js → DID-Ds2bjvxE.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-DulJ3rKV.js → Dashboard-DOONG2NX.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-DV4ov4zF.js → Dropdown-Br3PNA2L.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-DB2-ooz6.js → EmailListRenderer-DT6UUqjZ.js} +1 -1
  30. package/src/assets/web-panel/assets/{EvolutionSettings-B1Rb6p7q.js → EvolutionSettings-BSLuEjlT.js} +1 -1
  31. package/src/assets/web-panel/assets/{FamilyGuardDashboard-U8aHayx-.js → FamilyGuardDashboard-2aEM9avE.js} +1 -1
  32. package/src/assets/web-panel/assets/{Federation-BkQULBHY.js → Federation-D3B0tlQU.js} +1 -1
  33. package/src/assets/web-panel/assets/{FormItemContext-CtviG260.js → FormItemContext-DTVkVVQP.js} +1 -1
  34. package/src/assets/web-panel/assets/{GenericCardRenderer-DnPirB24.js → GenericCardRenderer-BgFEgP4y.js} +1 -1
  35. package/src/assets/web-panel/assets/{Git-CK57Oro6.js → Git-3t71m6Xd.js} +2 -2
  36. package/src/assets/web-panel/assets/{Governance-DbI-LB8-.js → Governance-DS7WvlaX.js} +1 -1
  37. package/src/assets/web-panel/assets/{Inference-sEG4T1Jg.js → Inference-CeT-xWrI.js} +1 -1
  38. package/src/assets/web-panel/assets/{KnowledgeGraph-9SLJR3h9.js → KnowledgeGraph-BQzMncp5.js} +1 -1
  39. package/src/assets/web-panel/assets/{Logs-BIZYcISQ.js → Logs-CC5DOKhV.js} +2 -2
  40. package/src/assets/web-panel/assets/{MarkdownRenderer-9nfWupWP.js → MarkdownRenderer-5jYM4kYG.js} +1 -1
  41. package/src/assets/web-panel/assets/{Marketplace-CRmLLwAE.js → Marketplace-CpYDyy3o.js} +1 -1
  42. package/src/assets/web-panel/assets/{McpTools-DkOFkSO1.js → McpTools-DRdAZPMr.js} +3 -3
  43. package/src/assets/web-panel/assets/{Memory-M29Kd9TU.js → Memory-CBLRr-l8.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-Tl6JvPw_.js → MobileBridge-Bnj9veNN.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-Bq-FPDzS.js → MobileProjects-lYFPD24p.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-DkUjX1F5.js → Mtc-BMU_C9vD.js} +2 -2
  47. package/src/assets/web-panel/assets/{MtcAudit-BCqr1I4q.js → MtcAudit-DQV-zumU.js} +4 -4
  48. package/src/assets/web-panel/assets/{Multisig-DmoqYGQ5.js → Multisig-LpqpqV1d.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-DNRQeRke.js → NLProgramming-C4OMklSO.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-CQSBeiY6.js → Notes-Cg0QCinP.js} +3 -3
  51. package/src/assets/web-panel/assets/{NotificationSettings-gNY9bbH6.js → NotificationSettings-amoZoMna.js} +1 -1
  52. package/src/assets/web-panel/assets/{OrderTableRenderer-DW0CNjh8.js → OrderTableRenderer-CxNvhf5n.js} +1 -1
  53. package/src/assets/web-panel/assets/{Organization-CthnbKag.js → Organization-BetJmyX5.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-_GQ3sbXu.js → Overflow-DbeoPtVD.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-BTr7ea6W.js → P2P-B5sitQWT.js} +2 -2
  56. package/src/assets/web-panel/assets/PdhVaultBrowser-AP9SG2ic.js +7 -0
  57. package/src/assets/web-panel/assets/{Permissions-DMny2Cjs.js → Permissions-Bc7fgNKx.js} +4 -4
  58. package/src/assets/web-panel/assets/{PersonalDataHub-CyYlMSoI.js → PersonalDataHub-BwdGMcWZ.js} +3 -3
  59. package/src/assets/web-panel/assets/{Pipeline-FASu2Oc4.js → Pipeline-DVuJA9BA.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-9bC5FnsJ.js → Privacy-BGzwOYRG.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-BOYZwB5G.js → ProjectInit-BpHcC5br.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-45DViNG-.js → ProjectSettings-BI5G5gsc.js} +2 -2
  63. package/src/assets/web-panel/assets/{Projects-C2fZzMZc.js → Projects-Ce92-l1V.js} +1 -1
  64. package/src/assets/web-panel/assets/{Providers-BgjvZx9F.js → Providers-CRHCcLFS.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-Brw-3csB.js → QrScannerModal-qSeg5xag.js} +1 -1
  66. package/src/assets/web-panel/assets/QuickAsk-CA8oSceR.js +1 -0
  67. package/src/assets/web-panel/assets/{Recommend-DuQGxjIR.js → Recommend-DMvPodNo.js} +1 -1
  68. package/src/assets/web-panel/assets/{RemoteSession-T1cdSSRL.js → RemoteSession-BWsapEyN.js} +2 -2
  69. package/src/assets/web-panel/assets/{Reputation-9FT2dI4G.js → Reputation-BZJETVmC.js} +1 -1
  70. package/src/assets/web-panel/assets/{Row-yNIbNftX.js → Row-CUE1OSMp.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-BIchNoPd.js → RssFeed-DvMazcUD.js} +2 -2
  72. package/src/assets/web-panel/assets/{Search-msDJ8UTZ.js → Search-e1c-NXWv.js} +1 -1
  73. package/src/assets/web-panel/assets/{Security-DE46oBYT.js → Security-Dc9GZsx2.js} +3 -3
  74. package/src/assets/web-panel/assets/{Services-Dori7vc3.js → Services-AFz6ke-4.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-D3O3sUFz.js → Skeleton-gow6MYcY.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-BV0ct8SO.js → Skills-h6AxYjUP.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-DflIVOrh.js → Sla-Cr4CkJ0O.js} +1 -1
  78. package/src/assets/web-panel/assets/{SpeechSettings-Cpbv2Zz6.js → SpeechSettings-Bd5yuBtH.js} +1 -1
  79. package/src/assets/web-panel/assets/{SyncSettings-Dgg6-wbe.js → SyncSettings-1t4QZMLW.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-Cb6e4Klx.js → Tasks-u-DrjLRM.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-GfdAATGz.js → Templates-C0yLUC9b.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-D0gecLXl.js → Tenant-ds1YT5iS.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-BFRPJt-0.js → Terminal-BoIskW2m.js} +2 -2
  84. package/src/assets/web-panel/assets/{TimelineRenderer-BobtChxX.js → TimelineRenderer-BkdVK24X.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-CifeZtHV.js → Tokens-djyDgmfk.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-BHBvbBCf.js → Trigger-CYefDS3H.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-6lvqjKYp.js → Trust-CE_dbep_.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-CqzMc-G5.js → UkeySign-DwVs_fey.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-OIiRG582.js → VideoEditing-BTUqMHAV.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-4MeKq0q_.js → Wallet-Bu_E7Rrc.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-eHBRjGQG.js → WebAuthn-DTbre5AB.js} +4 -4
  92. package/src/assets/web-panel/assets/{WorkflowEditor-DlzYdEqW.js → WorkflowEditor-BsBzd3gt.js} +1 -1
  93. package/src/assets/web-panel/assets/{chat-CBcjOzkX.js → chat-tdifQQvu.js} +1 -1
  94. package/src/assets/web-panel/assets/{colors-zgwsYQSa.js → colors-D3SNW2Vx.js} +1 -1
  95. package/src/assets/web-panel/assets/{compact-item-Dcd9H5Bn.js → compact-item-UBlCjrAW.js} +1 -1
  96. package/src/assets/web-panel/assets/{createContext-DR1tO-8r.js → createContext-DKsnEnFt.js} +1 -1
  97. package/src/assets/web-panel/assets/devWarning-BBnVfJHh.js +1 -0
  98. package/src/assets/web-panel/assets/{hasIn-CtfbUH_p.js → hasIn-BkeySXWe.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BXfGHUz_.js → index-6Nvzgytx.js} +1 -1
  100. package/src/assets/web-panel/assets/index-B2w_3mtS.js +1 -0
  101. package/src/assets/web-panel/assets/{index-DYPMJ5Mb.js → index-BEiHKah0.js} +25 -25
  102. package/src/assets/web-panel/assets/{index-LN7Hkl1C.js → index-BIeFLaMV.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DA2-Bhy8.js → index-BV2zQteq.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CwHgaYVH.js → index-BVvz3ntc.js} +1 -1
  105. package/src/assets/web-panel/assets/index-BoHurydC.js +1 -0
  106. package/src/assets/web-panel/assets/{index-DVpp9YSQ.js → index-Bw0k3VgD.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CaGNqkY0.js → index-C-KTHSUE.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DWuCGhHa.js → index-C9ImNw5O.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DTx4FyLU.js → index-CG_b1xyX.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cur8LNNn.js → index-CUjyL0ZS.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DfzOYmOj.js → index-CXkn77ZN.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Cdhy5ckh.js → index-CZ6JUooI.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D1GxtY-h.js → index-Cuz2QCFT.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C0kP88Pb.js → index-D5Umd_Z-.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DHEiuvhA.js → index-D6es7MSG.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CPLLgPxA.js → index-D93iSDUw.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-B3QUK1ks.js → index-D9m1HQzl.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DWG98orp.js → index-D9um0YVl.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-hTcre7U9.js → index-DD6dD3LV.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-C5XGvJb0.js → index-DFMrIEIt.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DvPOpvvH.js → index-DSCiZJXb.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D8WPohye.js → index-DSe_7MSy.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-JSfldpw4.js → index-DYFjOyTH.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-5XaB_Tjg.js → index-DdyTv6yc.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BoxowMYS.js → index-DfrW-iVs.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DSvcz0RO.js → index-Dg8-Duze.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-J-ORW1i6.js → index-DiDgwRty.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CT7zIkqP.js → index-DlEIp1gJ.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-D7ZOiIKb.js → index-DlO6HdD9.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DeAi4CNt.js → index-DtxUSZSS.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CmVIM-m2.js → index-DugZRCiI.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-oWOlF7CM.js → index-P-ailFQY.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-dvF6C39V.js → index-PWScrdlX.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CSQCHpDo.js → index-SpQ26gD6.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CmzQzTY5.js → index-TLBpgN30.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-z8lrkcb-.js → index-bOIcWW8F.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-BndPtS9D.js → index-pgGKNvmm.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-CVMtDrWZ.js → initDefaultProps-CVCXG2Hv.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-Dye3IU8k.js → motion-Dv00jw87.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-C251Zo34.js → move-BFz51iHf.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BDiOORoQ.js → mtc-parser-BK2woIDE.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-DTDA_RHv.js → omit-D2X_Gwoo.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-CqEUTPcT.js → pickAttrs-CLmFyJ1q.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BOB8QWH3.js → placementArrow-CCzPddQm.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-CZDXP1d9.js → responsiveObserve-hcm_Zlxd.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-DPaRw3E9.js → slide-BxfZEFVp.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-Ba9d7XZ7.js → statusUtils-kzB9pT-t.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-mZKU3lvW.js → styleChecker-CDFd7YCI.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFlexGapSupport-VcauLq0M.js → useFlexGapSupport-Czl7NNhS.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFs-Cal9KRcx.js → useFs-y0SAzirG.js} +1 -1
  151. package/src/assets/web-panel/assets/{usePersonalDataHub-DISoRyQB.js → usePersonalDataHub-BvdPznrA.js} +1 -1
  152. package/src/assets/web-panel/assets/{vnode-B58C6w6B.js → vnode-D8mNbyZ7.js} +1 -1
  153. package/src/assets/web-panel/assets/{zoom-BPlBAMKB.js → zoom-_hun6w9W.js} +1 -1
  154. package/src/assets/web-panel/index.html +1 -1
  155. package/src/command-help-index.json +1 -1
  156. package/src/commands/agent.js +12 -5
  157. package/src/commands/ask.js +96 -21
  158. package/src/commands/complete.js +14 -10
  159. package/src/commands/llm.js +23 -41
  160. package/src/commands/session.js +2 -2
  161. package/src/commands/stream.js +9 -5
  162. package/src/commands/ui.js +30 -2
  163. package/src/data/changelog.json +65 -0
  164. package/src/gateways/ws/llm-chat-protocol.js +131 -40
  165. package/src/gateways/ws/llm-creds.js +29 -79
  166. package/src/gateways/ws/message-dispatcher.js +2 -0
  167. package/src/gateways/ws/ws-agent-handler.js +1 -1
  168. package/src/gateways/ws/ws-session-gateway.js +26 -12
  169. package/src/lib/__tests__/provider-stream.test.js +70 -4
  170. package/src/lib/chat-core.js +186 -24
  171. package/src/lib/cowork-adapter.js +29 -15
  172. package/src/lib/evolution/agent-evolution-runtime-composition.js +10 -7
  173. package/src/lib/evolution/evolution-deployment-config.js +36 -4
  174. package/src/lib/evolution/evolution-deployment-loader.js +117 -4
  175. package/src/lib/evolution/evolution-deployment-profile.js +2 -2
  176. package/src/lib/evolution/evolution-evidence-projector.js +18 -3
  177. package/src/lib/evolution/evolution-file-witness.js +10 -0
  178. package/src/lib/evolution/evolution-ledger.js +237 -28
  179. package/src/lib/evolution/evolution-windows-directory-durability.js +322 -0
  180. package/src/lib/evolution/governed-hub-llm.js +2 -1
  181. package/src/lib/evolution/governed-model-turn.js +4 -1
  182. package/src/lib/evolution/local-ui-evolution-deployment.js +768 -0
  183. package/src/lib/evolution/local-ui-evolution-host.mjs +44 -0
  184. package/src/lib/evolution/local-ui-evolution-runtime.js +624 -0
  185. package/src/lib/evolution/opaque-hex-id.js +28 -0
  186. package/src/lib/hub-llm-client.js +22 -14
  187. package/src/lib/image-input.js +19 -3
  188. package/src/lib/llm-config-defaults.js +19 -30
  189. package/src/lib/llm-connection-config.js +4 -0
  190. package/src/lib/llm-connection-probe.js +5 -1
  191. package/src/lib/llm-connection-resolution.js +94 -0
  192. package/src/lib/llm-providers.js +33 -11
  193. package/src/lib/ollama-base-url.js +14 -0
  194. package/src/lib/provider-protocol.js +104 -0
  195. package/src/lib/provider-stream.js +163 -4
  196. package/src/lib/runnable-provider.js +1 -0
  197. package/src/repl/chat-repl.js +58 -11
  198. package/src/runtime/agent-core.js +26 -43
  199. package/src/runtime/headless-runner.js +11 -4
  200. package/src/runtime/headless-stream.js +10 -4
  201. package/src/runtime/policies/agent-policy.js +17 -11
  202. package/src/assets/web-panel/assets/ChatBubbleRenderer-DOX3Y5EJ.js +0 -1
  203. package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +0 -7
  204. package/src/assets/web-panel/assets/QuickAsk-DWG-A5zB.js +0 -1
  205. package/src/assets/web-panel/assets/devWarning-DYhpRtve.js +0 -1
  206. package/src/assets/web-panel/assets/index-DPJXFIY7.js +0 -1
  207. package/src/assets/web-panel/assets/index-DjBhR5M_.js +0 -1
@@ -44,6 +44,8 @@ import { providerFetch } from "./formal-budget-provider-scope.js";
44
44
  ("use strict");
45
45
 
46
46
  import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
47
+ import { openAICompatibleBaseUrl } from "./provider-protocol.js";
48
+ import { resolveLlmConnection } from "./llm-connection-resolution.js";
47
49
 
48
50
  const LOCAL_SENTINELS = new Set([
49
51
  "",
@@ -87,7 +89,6 @@ export function buildCliHubLLM({ config = {}, env = process.env } = {}) {
87
89
  if (!spec) return null;
88
90
 
89
91
  const cfgLlm = (config && config.llm) || {};
90
- const sameAs = (p) => String(cfgLlm.provider || "").toLowerCase() === p;
91
92
  let provider, model, baseUrl, apiKey;
92
93
 
93
94
  if (spec.kind === "object") {
@@ -99,34 +100,41 @@ export function buildCliHubLLM({ config = {}, env = process.env } = {}) {
99
100
  apiKey =
100
101
  o.apiKey ||
101
102
  (o.apiKeyEnv ? env[o.apiKeyEnv] : null) ||
102
- (sameAs(provider) ? cfgLlm.apiKey : null);
103
- model = o.model || (sameAs(provider) ? cfgLlm.model : null);
104
- baseUrl = o.baseUrl || (sameAs(provider) ? cfgLlm.baseUrl : null);
103
+ null;
104
+ model = o.model || null;
105
+ baseUrl = o.baseUrl || null;
105
106
  } else {
106
107
  const raw = spec.value;
107
108
  if (LOCAL_SENTINELS.has(raw)) return null;
108
109
  if (CONFIG_SENTINELS.has(raw)) {
109
110
  provider = String(cfgLlm.provider || "").toLowerCase();
110
- model = cfgLlm.model;
111
- baseUrl = cfgLlm.baseUrl;
112
- apiKey = cfgLlm.apiKey;
111
+ model = null;
112
+ baseUrl = null;
113
+ apiKey = null;
113
114
  } else {
114
115
  provider = raw;
115
116
  // Reuse the saved credentials only when they belong to the requested
116
117
  // provider; otherwise fall back to provider defaults + env key.
117
- model = sameAs(provider) ? cfgLlm.model : null;
118
- baseUrl = sameAs(provider) ? cfgLlm.baseUrl : null;
119
- apiKey = sameAs(provider) ? cfgLlm.apiKey : null;
118
+ model = null;
119
+ baseUrl = null;
120
+ apiKey = null;
120
121
  }
121
122
  }
122
123
 
123
124
  if (!provider || provider === "ollama") return null;
124
125
 
126
+ const connection = resolveLlmConnection(
127
+ { provider, model, baseUrl, apiKey },
128
+ [cfgLlm],
129
+ { env },
130
+ );
125
131
  const def = BUILT_IN_PROVIDERS[provider] || null;
126
- const resolvedBase = baseUrl || (def && def.baseUrl) || null;
127
- const resolvedModel = model || (def && def.models && def.models[0]) || null;
128
- const resolvedKey =
129
- apiKey || (def && def.apiKeyEnv ? env[def.apiKeyEnv] : null) || null;
132
+ const resolvedBase = openAICompatibleBaseUrl(
133
+ provider,
134
+ connection.baseUrl || (def && def.baseUrl) || null,
135
+ );
136
+ const resolvedModel = connection.model || null;
137
+ const resolvedKey = connection.apiKey || null;
130
138
 
131
139
  if (!resolvedBase || !resolvedModel) return null;
132
140
  if (!resolvedKey) return null; // every supported cloud provider needs a key
@@ -276,6 +276,17 @@ export function imageUrlBlockToAnthropic(block) {
276
276
  * to pin a different snapshot.
277
277
  */
278
278
  export const DEFAULT_VISION_MODEL = "doubao-seed-2-0-lite-260215";
279
+ const DEFAULT_VISION_MODELS = Object.freeze({
280
+ ollama: "qwen2.5vl:7b",
281
+ openai: "gpt-4o-mini",
282
+ anthropic: "claude-sonnet-4-6",
283
+ gemini: "gemini-2.5-flash",
284
+ volcengine: DEFAULT_VISION_MODEL,
285
+ dashscope: "qwen-vl-plus",
286
+ kimi: "kimi-k2.5",
287
+ minimax: "MiniMax-VL-01",
288
+ mistral: "pixtral-12b-2409",
289
+ });
279
290
 
280
291
  // Recognize image-input families, including dated snapshots and local tags.
281
292
  // Unknown models keep using the configured vision fallback. Do not infer
@@ -327,15 +338,20 @@ export function resolveVisionLlm({ hasImage, flags = {}, llm = {} } = {}) {
327
338
  apiKey: undefined,
328
339
  };
329
340
  }
341
+ const provider = flags.provider || llm.provider;
342
+ const sameProvider = !flags.provider || !llm.provider || flags.provider === llm.provider;
330
343
  const mainModel =
331
- !flags.provider || !llm.provider || flags.provider === llm.provider
344
+ sameProvider
332
345
  ? llm.model
333
346
  : undefined;
334
347
  const visionModel = supportsVisionModel(mainModel)
335
348
  ? mainModel
336
- : llm.visionModel || DEFAULT_VISION_MODEL;
349
+ : (sameProvider ? llm.visionModel : null) ||
350
+ DEFAULT_VISION_MODELS[provider] ||
351
+ mainModel ||
352
+ DEFAULT_VISION_MODEL;
337
353
  return {
338
- provider: flags.provider || llm.provider,
354
+ provider,
339
355
  model: flags.model || flags.visionModel || visionModel,
340
356
  baseUrl: flags.baseUrl || llm.baseUrl,
341
357
  apiKey: flags.apiKey || llm.apiKey,
@@ -56,26 +56,12 @@ export async function reconcileConfigLlmProvider(cfgLlm = {}) {
56
56
  return { llm: corrected, changed: true };
57
57
  }
58
58
 
59
- import { resolveApiKeyFromHelper } from "./api-key-helper.js";
60
59
  import { modelForeignToProvider } from "./runnable-provider.js";
61
-
62
- /**
63
- * Key backfill order: config llm.apiKey (plaintext, legacy) first, then
64
- * llm.apiKeyHelper (external command → OS credential store). The helper only
65
- * runs when no key is otherwise present, so configured setups never pay a
66
- * subprocess spawn.
67
- */
68
- function backfillApiKey(options, cfgLlm) {
69
- if (options.apiKey) return;
70
- if (cfgLlm.apiKey) {
71
- options.apiKey = cfgLlm.apiKey;
72
- } else if (cfgLlm.apiKeyHelper) {
73
- const key = resolveApiKeyFromHelper(cfgLlm.apiKeyHelper);
74
- if (key) options.apiKey = key;
75
- }
76
- }
60
+ import { resolveLlmConnection } from "./llm-connection-resolution.js";
77
61
 
78
62
  export function applyConfigLlmDefaults(options = {}, cfgLlm = {}, opts = {}) {
63
+ const connection = resolveLlmConnection(options, [cfgLlm]);
64
+ const configMatchesTarget = connection.endpointSources.includes(cfgLlm);
79
65
  // The IDE chat panel pins --provider/--model (read from config) but DROPS
80
66
  // --base-url/--api-key. With an explicit --provider the block below bails, so
81
67
  // a cloud provider would lose its endpoint + key → a 401, or a silent
@@ -84,31 +70,34 @@ export function applyConfigLlmDefaults(options = {}, cfgLlm = {}, opts = {}) {
84
70
  // baseUrl/apiKey (and model) from config — it's the SAME provider, so its
85
71
  // credentials are exactly right. Mixing config into a DIFFERENT explicit
86
72
  // provider is still avoided (handled by the early return below).
87
- if (
88
- options.provider &&
89
- cfgLlm.provider &&
90
- options.provider === cfgLlm.provider
91
- ) {
92
- if (!options.baseUrl && cfgLlm.baseUrl) options.baseUrl = cfgLlm.baseUrl;
73
+ if (options.provider && configMatchesTarget) {
74
+ if (!options.baseUrl && cfgLlm.baseUrl) options.baseUrl = connection.baseUrl;
93
75
  if (options.serviceTier == null && cfgLlm.serviceTier != null)
94
76
  options.serviceTier = cfgLlm.serviceTier;
95
- backfillApiKey(options, cfgLlm);
77
+ if (!options.apiKey && connection.apiKey) options.apiKey = connection.apiKey;
96
78
  if (!options.model && !opts.explicitModel && cfgLlm.model) {
97
79
  options.model = cfgLlm.model;
98
80
  }
99
81
  return options;
100
82
  }
101
- if (options.provider || !cfgLlm.provider) return options;
102
- options.provider = cfgLlm.provider;
103
- if (options.serviceTier == null && cfgLlm.serviceTier != null)
83
+ if (options.provider) {
84
+ if (!options.apiKey && connection.apiKey) options.apiKey = connection.apiKey;
85
+ return options;
86
+ }
87
+ if (!cfgLlm.provider) return options;
88
+ options.provider = connection.provider;
89
+ if (configMatchesTarget && options.serviceTier == null && cfgLlm.serviceTier != null)
104
90
  options.serviceTier = cfgLlm.serviceTier;
105
91
  if (opts.explicitModel) {
106
92
  options.model = opts.explicitModel;
107
- } else if (cfgLlm.model) {
93
+ } else if (configMatchesTarget && cfgLlm.model) {
108
94
  options.model = cfgLlm.model;
95
+ } else if (!configMatchesTarget) {
96
+ options.model = connection.model;
109
97
  }
110
- if (!options.baseUrl && cfgLlm.baseUrl) options.baseUrl = cfgLlm.baseUrl;
111
- backfillApiKey(options, cfgLlm);
98
+ if (configMatchesTarget && !options.baseUrl && cfgLlm.baseUrl)
99
+ options.baseUrl = connection.baseUrl;
100
+ if (!options.apiKey && connection.apiKey) options.apiKey = connection.apiKey;
112
101
  return options;
113
102
  }
114
103
 
@@ -1,4 +1,5 @@
1
1
  import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
2
+ import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
2
3
  import {
3
4
  setSecretConfigValue,
4
5
  updateConfigAtomically,
@@ -65,6 +66,9 @@ export function normalizeLlmConnection(input) {
65
66
  "Use the API base path, not a completion/messages endpoint",
66
67
  );
67
68
  value.baseUrl = url.href.replace(/\/+$/, "");
69
+ if (value.provider === "ollama") {
70
+ value.baseUrl = normalizeOllamaBaseUrl(value.baseUrl);
71
+ }
68
72
  return value;
69
73
  }
70
74
 
@@ -2,6 +2,7 @@ import { formatProviderResponseError } from "./provider-http-error.js";
2
2
  import { estimateCost } from "./llm-pricing.js";
3
3
  import { providerFetch } from "./formal-budget-provider-scope.js";
4
4
  import { isFormalBudgetError } from "./formal-budget-errors.js";
5
+ import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
5
6
 
6
7
  // Reasoning models spend the same output allowance on thinking before the
7
8
  // final answer. A 16-token probe can therefore receive HTTP 200 with only
@@ -139,7 +140,10 @@ export async function probeLlmConnection(
139
140
  target,
140
141
  { fetchImpl = fetch, timeoutMs = 20_000, includeUsage = false } = {},
141
142
  ) {
142
- const base = target.baseUrl.replace(/\/+$/, "");
143
+ const base =
144
+ target.provider === "ollama"
145
+ ? normalizeOllamaBaseUrl(target.baseUrl)
146
+ : target.baseUrl.replace(/\/+$/, "");
143
147
  const headers = { "Content-Type": "application/json" };
144
148
  let url, body;
145
149
  if (target.provider === "ollama") {
@@ -0,0 +1,94 @@
1
+ import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
2
+ import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
3
+ import { normalizeProviderBaseUrl } from "./provider-protocol.js";
4
+ import { resolveApiKeyFromHelper } from "./api-key-helper.js";
5
+
6
+ const DEFAULT_PROVIDER = "ollama";
7
+ const DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
8
+
9
+ function nonEmpty(value) {
10
+ return typeof value === "string" && value.trim().length > 0;
11
+ }
12
+
13
+ export function normalizeLlmEndpoint(provider, baseUrl) {
14
+ if (!nonEmpty(baseUrl)) return "";
15
+ return provider === "ollama"
16
+ ? normalizeOllamaBaseUrl(baseUrl)
17
+ : normalizeProviderBaseUrl(baseUrl);
18
+ }
19
+
20
+ /**
21
+ * Resolve provider, model, endpoint, and credential as one configuration.
22
+ * Saved models and credentials are reusable only for the same provider and
23
+ * normalized endpoint. An explicit key always belongs to the explicit target.
24
+ */
25
+ export function resolveLlmConnection(
26
+ options = {},
27
+ configuredSources = [],
28
+ {
29
+ env = process.env,
30
+ builtIns = BUILT_IN_PROVIDERS,
31
+ defaultProvider = DEFAULT_PROVIDER,
32
+ defaultModel = null,
33
+ defaultBaseUrl = null,
34
+ } = {},
35
+ ) {
36
+ const sources = (Array.isArray(configuredSources)
37
+ ? configuredSources
38
+ : [configuredSources]
39
+ ).filter((source) => source && typeof source === "object");
40
+ const provider =
41
+ (nonEmpty(options.provider) && options.provider) ||
42
+ sources.find((source) => nonEmpty(source.provider))?.provider ||
43
+ defaultProvider;
44
+ const definition = builtIns[provider];
45
+ const matchingSources = sources.filter(
46
+ (source) => source.provider === provider,
47
+ );
48
+ const providerBaseUrl =
49
+ definition?.baseUrl ||
50
+ defaultBaseUrl ||
51
+ (provider === "ollama" ? DEFAULT_OLLAMA_BASE_URL : null);
52
+ const baseUrl =
53
+ (nonEmpty(options.baseUrl) && options.baseUrl) ||
54
+ matchingSources.find((source) => nonEmpty(source.baseUrl))?.baseUrl ||
55
+ providerBaseUrl;
56
+ const normalizedBaseUrl = normalizeLlmEndpoint(provider, baseUrl);
57
+ const endpointSources = matchingSources.filter((source) => {
58
+ const sourceBaseUrl = source.baseUrl || providerBaseUrl;
59
+ return (
60
+ normalizedBaseUrl !== "" &&
61
+ normalizeLlmEndpoint(provider, sourceBaseUrl) === normalizedBaseUrl
62
+ );
63
+ });
64
+ const model =
65
+ (nonEmpty(options.model) && options.model) ||
66
+ endpointSources.find((source) => nonEmpty(source.model))?.model ||
67
+ defaultModel ||
68
+ definition?.models?.[0] ||
69
+ null;
70
+ let apiKey =
71
+ (nonEmpty(options.apiKey) && options.apiKey) ||
72
+ (nonEmpty(env?.CC_API_KEY) && env.CC_API_KEY) ||
73
+ endpointSources.find((source) => nonEmpty(source.apiKey))?.apiKey ||
74
+ null;
75
+ if (!apiKey) {
76
+ const helper = endpointSources.find((source) =>
77
+ nonEmpty(source.apiKeyHelper),
78
+ )?.apiKeyHelper;
79
+ if (helper) apiKey = resolveApiKeyFromHelper(helper);
80
+ }
81
+ if (!apiKey && definition?.apiKeyEnv) {
82
+ apiKey = nonEmpty(env?.[definition.apiKeyEnv])
83
+ ? env[definition.apiKeyEnv]
84
+ : null;
85
+ }
86
+ return {
87
+ provider,
88
+ model,
89
+ baseUrl: baseUrl || null,
90
+ apiKey,
91
+ endpoint: normalizedBaseUrl,
92
+ endpointSources,
93
+ };
94
+ }
@@ -7,6 +7,11 @@
7
7
  */
8
8
  import { providerFetch } from "./formal-budget-provider-scope.js";
9
9
  import { isFormalBudgetError } from "./formal-budget-errors.js";
10
+ import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
11
+ import {
12
+ geminiGenerateContentUrl,
13
+ normalizeProviderBaseUrl,
14
+ } from "./provider-protocol.js";
10
15
  export const BUILT_IN_PROVIDERS = {
11
16
  ollama: {
12
17
  name: "ollama",
@@ -345,11 +350,18 @@ export class LLMProviderRegistry {
345
350
  const testModel = model || provider.models[0];
346
351
 
347
352
  if (name === "ollama") {
348
- const res = await _fetchWithTimeout(`${provider.baseUrl}/api/generate`, {
349
- method: "POST",
350
- headers: { "Content-Type": "application/json" },
351
- body: JSON.stringify({ model: testModel, prompt: "Hi", stream: false }),
352
- });
353
+ const res = await _fetchWithTimeout(
354
+ `${normalizeOllamaBaseUrl(provider.baseUrl)}/api/generate`,
355
+ {
356
+ method: "POST",
357
+ headers: { "Content-Type": "application/json" },
358
+ body: JSON.stringify({
359
+ model: testModel,
360
+ prompt: "Hi",
361
+ stream: false,
362
+ }),
363
+ },
364
+ );
353
365
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
354
366
  const data = await res.json();
355
367
  return {
@@ -363,23 +375,32 @@ export class LLMProviderRegistry {
363
375
  const key = this.getApiKey(name);
364
376
  if (!key) throw new Error("GEMINI_API_KEY not set");
365
377
  const res = await _fetchWithTimeout(
366
- `${provider.baseUrl}/models/${testModel}:generateContent?key=${key}`,
378
+ geminiGenerateContentUrl(provider.baseUrl, testModel),
367
379
  {
368
380
  method: "POST",
369
- headers: { "Content-Type": "application/json" },
381
+ headers: {
382
+ "Content-Type": "application/json",
383
+ "x-goog-api-key": key,
384
+ },
370
385
  body: JSON.stringify({ contents: [{ parts: [{ text: "Hi" }] }] }),
371
386
  },
372
387
  );
373
388
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
374
389
  const data = await res.json();
375
- const text = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
390
+ const text =
391
+ data.candidates?.[0]?.content?.parts
392
+ ?.filter((part) => part?.thought !== true)
393
+ ?.map((part) => part?.text || "")
394
+ ?.join("") || "";
376
395
  return { ok: true, elapsed: Date.now() - start, response: text.trim() };
377
396
  }
378
397
 
379
398
  if (name === "anthropic") {
380
399
  const key = this.getApiKey(name);
381
400
  if (!key) throw new Error("ANTHROPIC_API_KEY not set");
382
- const res = await _fetchWithTimeout(`${provider.baseUrl}/messages`, {
401
+ const res = await _fetchWithTimeout(
402
+ `${normalizeProviderBaseUrl(provider.baseUrl)}/messages`,
403
+ {
383
404
  method: "POST",
384
405
  headers: {
385
406
  "Content-Type": "application/json",
@@ -391,7 +412,8 @@ export class LLMProviderRegistry {
391
412
  max_tokens: 10,
392
413
  messages: [{ role: "user", content: "Hi" }],
393
414
  }),
394
- });
415
+ },
416
+ );
395
417
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
396
418
  const data = await res.json();
397
419
  const text = data.content?.[0]?.text || "";
@@ -402,7 +424,7 @@ export class LLMProviderRegistry {
402
424
  const key = this.getApiKey(name);
403
425
  if (!key) throw new Error(`${provider.apiKeyEnv} not set`);
404
426
  const res = await _fetchWithTimeout(
405
- `${provider.baseUrl}/chat/completions`,
427
+ `${normalizeProviderBaseUrl(provider.baseUrl)}/chat/completions`,
406
428
  {
407
429
  method: "POST",
408
430
  headers: {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Normalize an Ollama server URL for native `/api/*` requests.
3
+ *
4
+ * Ollama's OpenAI-compatible examples commonly use a `/v1` base path, while
5
+ * this CLI provider speaks Ollama's native API. Accept both forms in saved
6
+ * settings and remove trailing OpenAI/native API suffixes before appending a
7
+ * native `/api/*` path.
8
+ */
9
+ export function normalizeOllamaBaseUrl(value) {
10
+ return String(value || "http://localhost:11434")
11
+ .trim()
12
+ .replace(/\/+$/, "")
13
+ .replace(/(?:\/(?:v1|api))+$/i, "");
14
+ }
@@ -0,0 +1,104 @@
1
+ /** Shared URL and request-shape helpers for provider-specific adapters. */
2
+
3
+ export function normalizeProviderBaseUrl(baseUrl) {
4
+ return String(baseUrl || "").replace(/\/+$/u, "");
5
+ }
6
+
7
+ /**
8
+ * Google's OpenAI-compatible API lives below `/v1beta/openai`. The provider
9
+ * registry stores the native API root (`/v1beta`), so adapt only that exact
10
+ * official root when a caller explicitly uses the OpenAI-compatible protocol.
11
+ */
12
+ export function openAICompatibleBaseUrl(provider, baseUrl) {
13
+ const base = normalizeProviderBaseUrl(baseUrl);
14
+ if (provider !== "gemini") return base;
15
+ try {
16
+ const url = new URL(base);
17
+ if (
18
+ url.hostname === "generativelanguage.googleapis.com" &&
19
+ url.pathname.replace(/\/+$/u, "") === "/v1beta"
20
+ ) {
21
+ url.pathname = "/v1beta/openai";
22
+ return normalizeProviderBaseUrl(url.href);
23
+ }
24
+ } catch {
25
+ // Keep custom compatibility endpoints intact; the request layer reports
26
+ // malformed URLs with the provider's normal transport error.
27
+ }
28
+ return base;
29
+ }
30
+
31
+ export function geminiGenerateContentUrl(baseUrl, model, { stream = false } = {}) {
32
+ const action = stream ? "streamGenerateContent" : "generateContent";
33
+ const endpoint = `${normalizeProviderBaseUrl(baseUrl)}/models/${encodeURIComponent(model)}:${action}`;
34
+ return stream ? `${endpoint}?alt=sse` : endpoint;
35
+ }
36
+
37
+ function geminiTextParts(content) {
38
+ if (typeof content === "string") return [{ text: content }];
39
+ if (!Array.isArray(content)) return [];
40
+ return content.flatMap((part) => {
41
+ if (typeof part === "string") return [{ text: part }];
42
+ if (part && typeof part.text === "string") return [{ text: part.text }];
43
+ return [];
44
+ });
45
+ }
46
+
47
+ export function geminiRequestBody(
48
+ messages,
49
+ { maxOutputTokens = 4096, temperature } = {},
50
+ ) {
51
+ const contents = [];
52
+ const systemParts = [];
53
+ for (const message of messages || []) {
54
+ const parts = geminiTextParts(message?.content);
55
+ if (parts.length === 0) continue;
56
+ if (message.role === "system") {
57
+ systemParts.push(...parts);
58
+ continue;
59
+ }
60
+ contents.push({
61
+ role: message.role === "assistant" || message.role === "model" ? "model" : "user",
62
+ parts,
63
+ });
64
+ }
65
+ const generationConfig = { maxOutputTokens };
66
+ if (Number.isFinite(temperature)) generationConfig.temperature = temperature;
67
+ return {
68
+ contents,
69
+ ...(systemParts.length ? { systemInstruction: { parts: systemParts } } : {}),
70
+ generationConfig,
71
+ };
72
+ }
73
+
74
+ export function geminiResponseText(data) {
75
+ return (data?.candidates?.[0]?.content?.parts || [])
76
+ .filter((part) => part?.thought !== true)
77
+ .map((part) => (typeof part?.text === "string" ? part.text : ""))
78
+ .join("");
79
+ }
80
+
81
+ export function geminiUsage(data) {
82
+ const usage = data?.usageMetadata;
83
+ if (!usage || typeof usage !== "object") return null;
84
+ const promptTokens = Number(usage.promptTokenCount);
85
+ const outputTokens =
86
+ Number(usage.candidatesTokenCount || 0) +
87
+ Number(usage.thoughtsTokenCount || 0);
88
+ const cacheReadTokens = Number(usage.cachedContentTokenCount || 0);
89
+ if (
90
+ !Number.isSafeInteger(promptTokens) ||
91
+ promptTokens < 0 ||
92
+ !Number.isSafeInteger(outputTokens) ||
93
+ outputTokens < 0 ||
94
+ !Number.isSafeInteger(cacheReadTokens) ||
95
+ cacheReadTokens < 0 ||
96
+ cacheReadTokens > promptTokens
97
+ )
98
+ return null;
99
+ return {
100
+ inputTokens: promptTokens - cacheReadTokens,
101
+ outputTokens,
102
+ cacheReadTokens,
103
+ };
104
+ }