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.
- package/README.md +14 -6
- package/package.json +3 -2
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DkrNbDzS.js → AIOps-CVac3rIN.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DJjFXDhv.js → ActionButton-ChM9GxMl.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DyUbagRn.js → Analytics-CV5399Js.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-p7kJWSvn.js → AppLayout-CuonVXhg.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-C3rVh4Ak.js → Artifacts-D9qKfgb6.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bch_L_YO.js → Audit-DUvLysQc.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BNHy8zP6.js → BackgroundAgents-DfKohQeT.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BUaye_Ys.js → Backup-CohoEI8R.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D49mzAVz.js → BaseInput-BwdoA3xv.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-2MDzM_us.js → Chat-DRkuz1d2.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-v3pzeHNC.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-5p1T_KEq.js → Checkbox-B-GNGpKv.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-TKtiLaoZ.js → Codegen-Bs2lAsoZ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BVCMG9mS.js → Col-Bk1mJKDZ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D09-QczH.js → Community-B_FDSCOo.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-QuRMCnj7.js → Compact-No1qSLLH.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CN0PV7fk.js → Compliance-CcrG43-d.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-5N2zhAty.js → Cowork-DLkj2ywK.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BxT8UZ0S.js → Cron-sQHELVEz.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Dp-noQl4.js → Crosschain-CRTZ3GZg.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B7MZFkjG.js → DID-Ds2bjvxE.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DulJ3rKV.js → Dashboard-DOONG2NX.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DV4ov4zF.js → Dropdown-Br3PNA2L.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DB2-ooz6.js → EmailListRenderer-DT6UUqjZ.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-B1Rb6p7q.js → EvolutionSettings-BSLuEjlT.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-U8aHayx-.js → FamilyGuardDashboard-2aEM9avE.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BkQULBHY.js → Federation-D3B0tlQU.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CtviG260.js → FormItemContext-DTVkVVQP.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DnPirB24.js → GenericCardRenderer-BgFEgP4y.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CK57Oro6.js → Git-3t71m6Xd.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DbI-LB8-.js → Governance-DS7WvlaX.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-sEG4T1Jg.js → Inference-CeT-xWrI.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-9SLJR3h9.js → KnowledgeGraph-BQzMncp5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BIZYcISQ.js → Logs-CC5DOKhV.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-9nfWupWP.js → MarkdownRenderer-5jYM4kYG.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CRmLLwAE.js → Marketplace-CpYDyy3o.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DkOFkSO1.js → McpTools-DRdAZPMr.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-M29Kd9TU.js → Memory-CBLRr-l8.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Tl6JvPw_.js → MobileBridge-Bnj9veNN.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Bq-FPDzS.js → MobileProjects-lYFPD24p.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DkUjX1F5.js → Mtc-BMU_C9vD.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BCqr1I4q.js → MtcAudit-DQV-zumU.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-DmoqYGQ5.js → Multisig-LpqpqV1d.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DNRQeRke.js → NLProgramming-C4OMklSO.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CQSBeiY6.js → Notes-Cg0QCinP.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-gNY9bbH6.js → NotificationSettings-amoZoMna.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DW0CNjh8.js → OrderTableRenderer-CxNvhf5n.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CthnbKag.js → Organization-BetJmyX5.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-_GQ3sbXu.js → Overflow-DbeoPtVD.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BTr7ea6W.js → P2P-B5sitQWT.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-AP9SG2ic.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DMny2Cjs.js → Permissions-Bc7fgNKx.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CyYlMSoI.js → PersonalDataHub-BwdGMcWZ.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-FASu2Oc4.js → Pipeline-DVuJA9BA.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-9bC5FnsJ.js → Privacy-BGzwOYRG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BOYZwB5G.js → ProjectInit-BpHcC5br.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-45DViNG-.js → ProjectSettings-BI5G5gsc.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-C2fZzMZc.js → Projects-Ce92-l1V.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BgjvZx9F.js → Providers-CRHCcLFS.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Brw-3csB.js → QrScannerModal-qSeg5xag.js} +1 -1
- package/src/assets/web-panel/assets/QuickAsk-CA8oSceR.js +1 -0
- package/src/assets/web-panel/assets/{Recommend-DuQGxjIR.js → Recommend-DMvPodNo.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-T1cdSSRL.js → RemoteSession-BWsapEyN.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-9FT2dI4G.js → Reputation-BZJETVmC.js} +1 -1
- package/src/assets/web-panel/assets/{Row-yNIbNftX.js → Row-CUE1OSMp.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BIchNoPd.js → RssFeed-DvMazcUD.js} +2 -2
- package/src/assets/web-panel/assets/{Search-msDJ8UTZ.js → Search-e1c-NXWv.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DE46oBYT.js → Security-Dc9GZsx2.js} +3 -3
- package/src/assets/web-panel/assets/{Services-Dori7vc3.js → Services-AFz6ke-4.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-D3O3sUFz.js → Skeleton-gow6MYcY.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BV0ct8SO.js → Skills-h6AxYjUP.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DflIVOrh.js → Sla-Cr4CkJ0O.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cpbv2Zz6.js → SpeechSettings-Bd5yuBtH.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Dgg6-wbe.js → SyncSettings-1t4QZMLW.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cb6e4Klx.js → Tasks-u-DrjLRM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-GfdAATGz.js → Templates-C0yLUC9b.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-D0gecLXl.js → Tenant-ds1YT5iS.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BFRPJt-0.js → Terminal-BoIskW2m.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BobtChxX.js → TimelineRenderer-BkdVK24X.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CifeZtHV.js → Tokens-djyDgmfk.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BHBvbBCf.js → Trigger-CYefDS3H.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-6lvqjKYp.js → Trust-CE_dbep_.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CqzMc-G5.js → UkeySign-DwVs_fey.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-OIiRG582.js → VideoEditing-BTUqMHAV.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-4MeKq0q_.js → Wallet-Bu_E7Rrc.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-eHBRjGQG.js → WebAuthn-DTbre5AB.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DlzYdEqW.js → WorkflowEditor-BsBzd3gt.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CBcjOzkX.js → chat-tdifQQvu.js} +1 -1
- package/src/assets/web-panel/assets/{colors-zgwsYQSa.js → colors-D3SNW2Vx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Dcd9H5Bn.js → compact-item-UBlCjrAW.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DR1tO-8r.js → createContext-DKsnEnFt.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BBnVfJHh.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CtfbUH_p.js → hasIn-BkeySXWe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXfGHUz_.js → index-6Nvzgytx.js} +1 -1
- package/src/assets/web-panel/assets/index-B2w_3mtS.js +1 -0
- package/src/assets/web-panel/assets/{index-DYPMJ5Mb.js → index-BEiHKah0.js} +25 -25
- package/src/assets/web-panel/assets/{index-LN7Hkl1C.js → index-BIeFLaMV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DA2-Bhy8.js → index-BV2zQteq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwHgaYVH.js → index-BVvz3ntc.js} +1 -1
- package/src/assets/web-panel/assets/index-BoHurydC.js +1 -0
- package/src/assets/web-panel/assets/{index-DVpp9YSQ.js → index-Bw0k3VgD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaGNqkY0.js → index-C-KTHSUE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWuCGhHa.js → index-C9ImNw5O.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTx4FyLU.js → index-CG_b1xyX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cur8LNNn.js → index-CUjyL0ZS.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfzOYmOj.js → index-CXkn77ZN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cdhy5ckh.js → index-CZ6JUooI.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1GxtY-h.js → index-Cuz2QCFT.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0kP88Pb.js → index-D5Umd_Z-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHEiuvhA.js → index-D6es7MSG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPLLgPxA.js → index-D93iSDUw.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3QUK1ks.js → index-D9m1HQzl.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWG98orp.js → index-D9um0YVl.js} +1 -1
- package/src/assets/web-panel/assets/{index-hTcre7U9.js → index-DD6dD3LV.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5XGvJb0.js → index-DFMrIEIt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvPOpvvH.js → index-DSCiZJXb.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8WPohye.js → index-DSe_7MSy.js} +1 -1
- package/src/assets/web-panel/assets/{index-JSfldpw4.js → index-DYFjOyTH.js} +1 -1
- package/src/assets/web-panel/assets/{index-5XaB_Tjg.js → index-DdyTv6yc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoxowMYS.js → index-DfrW-iVs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvcz0RO.js → index-Dg8-Duze.js} +1 -1
- package/src/assets/web-panel/assets/{index-J-ORW1i6.js → index-DiDgwRty.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT7zIkqP.js → index-DlEIp1gJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7ZOiIKb.js → index-DlO6HdD9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeAi4CNt.js → index-DtxUSZSS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmVIM-m2.js → index-DugZRCiI.js} +1 -1
- package/src/assets/web-panel/assets/{index-oWOlF7CM.js → index-P-ailFQY.js} +1 -1
- package/src/assets/web-panel/assets/{index-dvF6C39V.js → index-PWScrdlX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSQCHpDo.js → index-SpQ26gD6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmzQzTY5.js → index-TLBpgN30.js} +1 -1
- package/src/assets/web-panel/assets/{index-z8lrkcb-.js → index-bOIcWW8F.js} +1 -1
- package/src/assets/web-panel/assets/{index-BndPtS9D.js → index-pgGKNvmm.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CVMtDrWZ.js → initDefaultProps-CVCXG2Hv.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Dye3IU8k.js → motion-Dv00jw87.js} +1 -1
- package/src/assets/web-panel/assets/{move-C251Zo34.js → move-BFz51iHf.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BDiOORoQ.js → mtc-parser-BK2woIDE.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DTDA_RHv.js → omit-D2X_Gwoo.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CqEUTPcT.js → pickAttrs-CLmFyJ1q.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BOB8QWH3.js → placementArrow-CCzPddQm.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CZDXP1d9.js → responsiveObserve-hcm_Zlxd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DPaRw3E9.js → slide-BxfZEFVp.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Ba9d7XZ7.js → statusUtils-kzB9pT-t.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-mZKU3lvW.js → styleChecker-CDFd7YCI.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-VcauLq0M.js → useFlexGapSupport-Czl7NNhS.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Cal9KRcx.js → useFs-y0SAzirG.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DISoRyQB.js → usePersonalDataHub-BvdPznrA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B58C6w6B.js → vnode-D8mNbyZ7.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BPlBAMKB.js → zoom-_hun6w9W.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agent.js +12 -5
- package/src/commands/ask.js +96 -21
- package/src/commands/complete.js +14 -10
- package/src/commands/llm.js +23 -41
- package/src/commands/session.js +2 -2
- package/src/commands/stream.js +9 -5
- package/src/commands/ui.js +30 -2
- package/src/data/changelog.json +65 -0
- package/src/gateways/ws/llm-chat-protocol.js +131 -40
- package/src/gateways/ws/llm-creds.js +29 -79
- package/src/gateways/ws/message-dispatcher.js +2 -0
- package/src/gateways/ws/ws-agent-handler.js +1 -1
- package/src/gateways/ws/ws-session-gateway.js +26 -12
- package/src/lib/__tests__/provider-stream.test.js +70 -4
- package/src/lib/chat-core.js +186 -24
- package/src/lib/cowork-adapter.js +29 -15
- package/src/lib/evolution/agent-evolution-runtime-composition.js +10 -7
- package/src/lib/evolution/evolution-deployment-config.js +36 -4
- package/src/lib/evolution/evolution-deployment-loader.js +117 -4
- package/src/lib/evolution/evolution-deployment-profile.js +2 -2
- package/src/lib/evolution/evolution-evidence-projector.js +18 -3
- package/src/lib/evolution/evolution-file-witness.js +10 -0
- package/src/lib/evolution/evolution-ledger.js +237 -28
- package/src/lib/evolution/evolution-windows-directory-durability.js +322 -0
- package/src/lib/evolution/governed-hub-llm.js +2 -1
- package/src/lib/evolution/governed-model-turn.js +4 -1
- package/src/lib/evolution/local-ui-evolution-deployment.js +768 -0
- package/src/lib/evolution/local-ui-evolution-host.mjs +44 -0
- package/src/lib/evolution/local-ui-evolution-runtime.js +624 -0
- package/src/lib/evolution/opaque-hex-id.js +28 -0
- package/src/lib/hub-llm-client.js +22 -14
- package/src/lib/image-input.js +19 -3
- package/src/lib/llm-config-defaults.js +19 -30
- package/src/lib/llm-connection-config.js +4 -0
- package/src/lib/llm-connection-probe.js +5 -1
- package/src/lib/llm-connection-resolution.js +94 -0
- package/src/lib/llm-providers.js +33 -11
- package/src/lib/ollama-base-url.js +14 -0
- package/src/lib/provider-protocol.js +104 -0
- package/src/lib/provider-stream.js +163 -4
- package/src/lib/runnable-provider.js +1 -0
- package/src/repl/chat-repl.js +58 -11
- package/src/runtime/agent-core.js +26 -43
- package/src/runtime/headless-runner.js +11 -4
- package/src/runtime/headless-stream.js +10 -4
- package/src/runtime/policies/agent-policy.js +17 -11
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DOX3Y5EJ.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +0 -7
- package/src/assets/web-panel/assets/QuickAsk-DWG-A5zB.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DYhpRtve.js +0 -1
- package/src/assets/web-panel/assets/index-DPJXFIY7.js +0 -1
- package/src/assets/web-panel/assets/index-DjBhR5M_.js +0 -1
|
@@ -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
|
-
|
|
103
|
-
model = o.model ||
|
|
104
|
-
baseUrl = o.baseUrl ||
|
|
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 =
|
|
111
|
-
baseUrl =
|
|
112
|
-
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 =
|
|
118
|
-
baseUrl =
|
|
119
|
-
apiKey =
|
|
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 =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
package/src/lib/image-input.js
CHANGED
|
@@ -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
|
-
|
|
344
|
+
sameProvider
|
|
332
345
|
? llm.model
|
|
333
346
|
: undefined;
|
|
334
347
|
const visionModel = supportsVisionModel(mainModel)
|
|
335
348
|
? mainModel
|
|
336
|
-
: llm.visionModel ||
|
|
349
|
+
: (sameProvider ? llm.visionModel : null) ||
|
|
350
|
+
DEFAULT_VISION_MODELS[provider] ||
|
|
351
|
+
mainModel ||
|
|
352
|
+
DEFAULT_VISION_MODEL;
|
|
337
353
|
return {
|
|
338
|
-
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.
|
|
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
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
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)
|
|
111
|
-
|
|
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 =
|
|
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
|
+
}
|
package/src/lib/llm-providers.js
CHANGED
|
@@ -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(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
378
|
+
geminiGenerateContentUrl(provider.baseUrl, testModel),
|
|
367
379
|
{
|
|
368
380
|
method: "POST",
|
|
369
|
-
headers: {
|
|
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 =
|
|
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(
|
|
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
|
+
}
|