chainlesschain 0.166.29 → 0.166.32
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 +4 -2
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-D4Dhiipq.js → AIOps-DKUOkYex.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-P6fPjlUB.js → ActionButton-B3luFrPA.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CzeKpXdh.js → Analytics-jbZ8R2S3.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Cj8CqntA.js → AppLayout-CFS2vzPS.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BOe7lviy.js → Artifacts-sS0Qpy5q.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-D6kxPJyo.js → Audit-CZ7kfjCG.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-OVuYY65D.js → BackgroundAgents-BpWc5SgP.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-UAlkEXOa.js → Backup-DpEYnvoy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DikQWFIL.js → BaseInput-kFzUGLnl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CEpqrZcV.js → Chat-BP_8aAfJ.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-yFeZ15D4.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-D2mdaw7o.js → Checkbox-HR1HQ06d.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CJ2LEcdr.js → Codegen-DxAqDPjT.js} +1 -1
- package/src/assets/web-panel/assets/{Col-ej56LWvr.js → Col-BaCLih7J.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D9neNmTp.js → Community-ZX64uW8D.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BzWfgovR.js → Compact-CLtMoXTt.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-dStoa8Mw.js → Compliance-BlyxBDrs.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BxYPXbQl.js → Cowork-CtQZIYPh.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DxOQIcrL.js → Cron-BebN-ajI.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BYyOjqVA.js → Crosschain-3gRU0-58.js} +1 -1
- package/src/assets/web-panel/assets/{DID-c81gsUZq.js → DID-DSVTtLnb.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CHIjYxb2.js → Dashboard-CvnhOWqc.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-C7hRZzkL.js → Dropdown-BhdofmCJ.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer--h_ShKAT.js → EmailListRenderer-Dhpua9Lo.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bq68asD0.js → FamilyGuardDashboard-DA1mLqQ4.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DGKsWpUO.js → Federation-D6SUyKGb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-1HbCXJAf.js → FormItemContext-BcMiwguB.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BlKJ93Fy.js → GenericCardRenderer-CGEB1WcH.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DJA-kxuw.js → Git-DGCNNqR_.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BWZPtzHj.js → Governance-D4HVyFdL.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-D3QXUTYP.js → Inference-BOoEmvKl.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BM37hWOP.js → KnowledgeGraph-ECLI0C0_.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Bpu4Of_0.js → Logs-Cf9GjHtX.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-C3J59f8v.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BZe0jmAU.js → Marketplace-DeL4rOAJ.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-_q_nfUnU.js → McpTools-Dvn3KpLW.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CIzcovHt.js → Memory-CSlLy-aw.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DZzo5UjF.js → MobileBridge-yNIe8-74.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DVZr9bVO.js → MobileProjects-CsS8Lozw.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-pULSsEOx.js → Mtc-Q7iMzSds.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BLz9fiz0.js → MtcAudit-CW71IQrK.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-B1qxfPkT.js → Multisig-pursAeeT.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bkb9YB7P.js → NLProgramming-DtNo1qqO.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-N0G87PD1.js → Notes-B1xUFwUT.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-D0jB-hRv.js → NotificationSettings-BgmQs_J8.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-soFvgov0.js → OrderTableRenderer-DI9t8YZl.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BNBL1-7S.js → Organization-BbrrT6gR.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-yqaJxwEo.js → Overflow-BcPi4YUc.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-1JHDOnl3.js → P2P-DfRteW76.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-De81lu5Q.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DOQh1Wzv.js → Permissions-DLCRaOe3.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-SVa21bUw.js → PersonalDataHub-fuTL6z8u.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Du3gCATi.js → Pipeline-D3ztsQDw.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CPW8EDaC.js → Privacy-4f-SQ4ie.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CAu5N0A_.js → ProjectInit-CC_ZFjrV.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DVTow6ty.js → ProjectSettings-C9FWHcne.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CBUF9hPa.js → Projects-CR3ETMQQ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B7pNEoaY.js → Providers-BWcbdgXx.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BHs1Kx7e.js → QrScannerModal-BavxpdIK.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-IBrdC5B5.js → QuickAsk-CZ7gupY4.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BtkLuO-e.js → Recommend-BQNpBYLL.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Du4_izg3.js → RemoteSession-2LU0Vr44.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-IEbMZN6v.js → Reputation-oYWo7qyp.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D5UM_ivw.js → Row-B8Yj-Csx.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DTTNnkdJ.js → RssFeed-CbxKbHJr.js} +3 -3
- package/src/assets/web-panel/assets/{Search-B5f7ggJE.js → Search-DGe-u9s0.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CsZp2uGu.js → Security-BwjfMAFI.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BJhguqIh.js → Services-BB5NQHwQ.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CAwO2JUK.js → Skeleton-BPSVUive.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-hKJcP390.js → Skills-DU2D-IYQ.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BCFiB8oS.js → Sla-DLX0vcyj.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Bi1Y-Tgf.js → SpeechSettings-CsPt1rpa.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-ZHYDv46g.js → SyncSettings-QuDslViv.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BMCSrUy-.js → Tasks-C-jF6Kwk.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DF_FOEH8.js → Templates-B6tY1zm-.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C7Q2LriV.js → Tenant-BXx5FAY1.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C59ckWXW.js → Terminal-CNk8KI4J.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Biu1m6Uf.js → TimelineRenderer-Bo2YVc2l.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CoHYWg3-.js → Tokens-Bs1Oq8t9.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DglXXzEp.js → Trigger-CtDhiQxG.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CBBLh73r.js → Trust-riAZiPHp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DuNdKeiD.js → UkeySign-DZRzYaNL.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BizHWVrh.js → VideoEditing-DTwOdg2x.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ClUQGSKi.js → Wallet-ByuDECxb.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-zyD7K5f5.js → WebAuthn-CkSKfId0.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-qbTI37Fi.js → WorkflowEditor-BmyJJndu.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CHv66tBY.js → chat-s1LiqLff.js} +1 -1
- package/src/assets/web-panel/assets/{colors-vBsvcJjY.js → colors-CMKVIFUx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CdBQZWFX.js → compact-item-DQGdG4Y5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CLE2XsPX.js → createContext-Csln9jj1.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-C8YOOF96.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-Du5rnUSg.js → hasIn-D1RYDhMF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHhNie07.js → index--G_4msyB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DADajBnD.js → index-8mlgjcck.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bdfg7CY3.js → index-B0vls3Cv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ca0WeKz1.js → index-B39ap5Fq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BY0Jg8JC.js → index-BFHhLE91.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRiOcWZ1.js → index-BJsa5jwk.js} +1 -1
- package/src/assets/web-panel/assets/index-BUXHmygu.js +1 -0
- package/src/assets/web-panel/assets/{index-sLcRd9pi.js → index-BiT8auXA.js} +1 -1
- package/src/assets/web-panel/assets/{index-x_0JRAO3.js → index-Bl5BNEwF.js} +1 -1
- package/src/assets/web-panel/assets/{index-dEhWAtoe.js → index-Bqhj13Jf.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByEfQiLz.js → index-BsmEoBNu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrFwv-2v.js → index-BvrkxTu0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CigkcWWh.js → index-BxVtT9q8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsyYG63S.js → index-C1y8O1ZP.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dv9b_iFq.js → index-C8sTim5H.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuSZyAD0.js → index-CGBHLqy3.js} +1 -1
- package/src/assets/web-panel/assets/{index-9nDAQkO-.js → index-CHmRaTdN.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-KoBLmE.js → index-CJwH7xWo.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-jjL85C.js → index-CTg3OrkW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxmlGwpv.js → index-CUVbDS_3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BojUN0eY.js → index-CUlRsoXi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnB7oqAR.js → index-Cew8V1UQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cc53mZoS.js → index-CiB2ZOQm.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz48QuqC.js → index-CmAjtQdv.js} +1 -1
- package/src/assets/web-panel/assets/index-CsNpDb_W.js +1 -0
- package/src/assets/web-panel/assets/{index-Bu3v3PRU.js → index-D1kWd1be.js} +1 -1
- package/src/assets/web-panel/assets/{index-aYAtijnz.js → index-D2v6xn0e.js} +1 -1
- package/src/assets/web-panel/assets/{index-sz6sGerd.js → index-D3TPgm-r.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDaW0C3l.js → index-D6eoC11O.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvHjeRw2.js → index-DHic4uvV.js} +1 -1
- package/src/assets/web-panel/assets/{index-q3sGyZnD.js → index-DIFiRztW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHdrZp_q.js → index-DQ86qdvJ.js} +19 -19
- package/src/assets/web-panel/assets/{index-DzYEc47A.js → index-DU23ertt.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-3GrN7D.js → index-DauplA4h.js} +1 -1
- package/src/assets/web-panel/assets/{index-uHX8ZrJ9.js → index-DmIeFdLq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKoOU-ne.js → index-FLnF1aVs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cuvyt0vv.js → index-jPM2-LMz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bwqa-FcB.js → index-rHRlGEnX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnZkmomS.js → index-ugL-2h6d.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CThtsKhS.js → initDefaultProps-DWJU65d6.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BoqT16ZT.js → motion-B0cCdvX2.js} +1 -1
- package/src/assets/web-panel/assets/{move-1Jf6ZXg5.js → move-CSWGW4YZ.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DjfCYvnq.js → mtc-parser-_P9unVrC.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BaJPeP5Y.js → omit-eUFaswv5.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B7nqrfVR.js → pickAttrs-Hmk4wpqj.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Dmb7i435.js → placementArrow-COIVVq2l.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B0TqfHJR.js → responsiveObserve-CkXZS7fs.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C9PINvAM.js → slide-BGdyZ1Ue.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DAug5z5S.js → statusUtils-rbVNSZAN.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-o6QvGnrq.js → styleChecker-CcdFI5tK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CtZwQFCt.js → useFlexGapSupport-C9yiJQMh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-B0HVNsmA.js → useFs-B73ulQmg.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CbLIg5eO.js → usePersonalDataHub-DNN-hD_j.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-uOK2NMHj.js → vnode-BN1KizIr.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BhyA2sIv.js → zoom-0KE4Sg53.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/llm.js +32 -60
- package/src/data/changelog.json +39 -0
- package/src/lib/evolution/evolution-file-witness.js +59 -13
- package/src/lib/evolution/evolution-ledger-file-backend.js +16 -1
- package/src/lib/llm-connection-config.js +131 -0
- package/src/lib/llm-connection-probe.js +85 -0
- package/src/lib/remote-read-loop-guard.js +231 -0
- package/src/lib/task-progress-tracker.js +5 -1
- package/src/lib/web-fetch.js +129 -12
- package/src/runtime/agent-core.js +41 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BLE9L1yw.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-DAZcw_ZS.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-D1FgaOLV.js +0 -7
- package/src/assets/web-panel/assets/devWarning-DRQGMdYB.js +0 -1
- package/src/assets/web-panel/assets/index-CPJgV0Ho.js +0 -1
- package/src/assets/web-panel/assets/index-OtXVaszG.js +0 -1
package/src/commands/llm.js
CHANGED
|
@@ -43,7 +43,11 @@ import {
|
|
|
43
43
|
autoFailStuckRequestsV2,
|
|
44
44
|
getLlmProvidersStatsV2,
|
|
45
45
|
} from "../lib/llm-providers.js";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
readLlmConnectionInput,
|
|
48
|
+
saveLlmConnection,
|
|
49
|
+
} from "../lib/llm-connection-config.js";
|
|
50
|
+
import { probeLlmConnection } from "../lib/llm-connection-probe.js";
|
|
47
51
|
|
|
48
52
|
/**
|
|
49
53
|
* Resolve the effective `cc llm test` target from CLI flags + persisted config.
|
|
@@ -109,6 +113,28 @@ export function resolveLlmTestTarget(
|
|
|
109
113
|
export function registerLlmCommand(program) {
|
|
110
114
|
const llm = program.command("llm").description("LLM provider management");
|
|
111
115
|
|
|
116
|
+
llm
|
|
117
|
+
.command("configure")
|
|
118
|
+
.description(
|
|
119
|
+
"Atomically save an LLM connection from stdin JSON (secrets never enter argv)",
|
|
120
|
+
)
|
|
121
|
+
.option(
|
|
122
|
+
"--storage <mode>",
|
|
123
|
+
"Secret storage: auto, keychain, or file",
|
|
124
|
+
"auto",
|
|
125
|
+
)
|
|
126
|
+
.action(async (options) => {
|
|
127
|
+
try {
|
|
128
|
+
const saved = saveLlmConnection(await readLlmConnectionInput(), {
|
|
129
|
+
storage: options.storage,
|
|
130
|
+
});
|
|
131
|
+
console.log(JSON.stringify({ ok: true, ...saved }));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.error(error.message);
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
112
138
|
// llm models - list available models
|
|
113
139
|
llm
|
|
114
140
|
.command("models")
|
|
@@ -195,65 +221,11 @@ export function registerLlmCommand(program) {
|
|
|
195
221
|
try {
|
|
196
222
|
const start = Date.now();
|
|
197
223
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
model: target.model,
|
|
204
|
-
prompt: "Say hi in one word.",
|
|
205
|
-
stream: false,
|
|
206
|
-
}),
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
if (!response.ok) {
|
|
210
|
-
throw new Error(
|
|
211
|
-
await formatProviderResponseError("ollama", response),
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const data = await response.json();
|
|
216
|
-
const elapsed = Date.now() - start;
|
|
217
|
-
|
|
218
|
-
spinner.succeed(
|
|
219
|
-
`${chalk.green("Connected")} to Ollama (${target.model}) in ${elapsed}ms`,
|
|
220
|
-
);
|
|
221
|
-
logger.log(
|
|
222
|
-
` Response: ${chalk.gray((data.response || "").trim().substring(0, 100))}`,
|
|
223
|
-
);
|
|
224
|
-
} else {
|
|
225
|
-
// OpenAI-compatible path (openai, volcengine, deepseek, dashscope,
|
|
226
|
-
// kimi, minimax, mistral, …).
|
|
227
|
-
if (!target.apiKey) throw new Error("API key required");
|
|
228
|
-
|
|
229
|
-
const response = await fetch(`${target.baseUrl}/chat/completions`, {
|
|
230
|
-
method: "POST",
|
|
231
|
-
headers: {
|
|
232
|
-
"Content-Type": "application/json",
|
|
233
|
-
Authorization: `Bearer ${target.apiKey}`,
|
|
234
|
-
},
|
|
235
|
-
body: JSON.stringify({
|
|
236
|
-
model: target.model,
|
|
237
|
-
messages: [{ role: "user", content: "Say hi in one word." }],
|
|
238
|
-
max_tokens: 10,
|
|
239
|
-
}),
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
if (!response.ok) {
|
|
243
|
-
throw new Error(
|
|
244
|
-
await formatProviderResponseError(target.provider, response),
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const data = await response.json();
|
|
249
|
-
const elapsed = Date.now() - start;
|
|
250
|
-
const reply = data.choices?.[0]?.message?.content?.trim() || "";
|
|
251
|
-
|
|
252
|
-
spinner.succeed(
|
|
253
|
-
`${chalk.green("Connected")} to ${target.label} (${target.model}) in ${elapsed}ms`,
|
|
254
|
-
);
|
|
255
|
-
logger.log(` Response: ${chalk.gray(reply)}`);
|
|
256
|
-
}
|
|
224
|
+
const reply = await probeLlmConnection(target);
|
|
225
|
+
spinner.succeed(
|
|
226
|
+
`${chalk.green("Connected")} to ${target.label} (${target.model}) in ${Date.now() - start}ms`,
|
|
227
|
+
);
|
|
228
|
+
logger.log(` Response: ${chalk.gray(reply)}`);
|
|
257
229
|
} catch (err) {
|
|
258
230
|
spinner.fail(`Test failed: ${err.message}`);
|
|
259
231
|
process.exit(1);
|
package/src/data/changelog.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "CHANGELOG.md",
|
|
3
3
|
"releases": [
|
|
4
|
+
{
|
|
5
|
+
"productVersion": null,
|
|
6
|
+
"cliVersion": "0.166.32",
|
|
7
|
+
"cliVersions": ["0.166.30", "0.166.32"],
|
|
8
|
+
"date": "2026-09-07",
|
|
9
|
+
"title": "",
|
|
10
|
+
"sections": [
|
|
11
|
+
{
|
|
12
|
+
"heading": "Fixed - cc CLI 0.166.32: release-compatible web fetch and CI log recovery",
|
|
13
|
+
"body": "> `chainlesschain` **0.166.30 -> 0.166.32** (2026-09-07). The `0.166.31`\n> candidate was not published because two existing cache tests required the\n> flag-gated `node:sqlite` API on Node 22.12. Its immutable tag is retained.\n\n- Include the hostname-fetch repair, bounded request duration, structured errors\n and repeated GitHub Actions log recovery prepared in the 0.166.31 candidate.\n- Exercise Desktop cache isolation and encrypted replay with the shipped SQLite\n driver on the minimum supported Node 22.12, without skipping assertions.\n- Give Windows crash-recovery fixtures enough time to reach their intended\n checkpoints before lease expiry, while preserving real expiry, stale-fence,\n single-effect and rollback recovery checks.\n- Verify fresh Wiki-recovery worker instances with child-generated identities\n bound to the spawned child, since Windows can reuse exited process IDs.\n- Require the complete CLI CI and CLI Strict Sandbox matrices for the new exact\n release commit before npm publication."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"productVersion": null,
|
|
19
|
+
"cliVersion": "0.166.31",
|
|
20
|
+
"cliVersions": ["0.166.30", "0.166.31"],
|
|
21
|
+
"date": "2026-09-07",
|
|
22
|
+
"title": "",
|
|
23
|
+
"sections": [
|
|
24
|
+
{
|
|
25
|
+
"heading": "Fixed - cc CLI 0.166.31: reliable web fetch and bounded CI log recovery",
|
|
26
|
+
"body": "> `chainlesschain` **0.166.30 -> 0.166.31** (2026-09-07). Publication requires\n> successful CLI CI and CLI Strict Sandbox matrices for the exact release commit.\n\n- Fix Node 22 multi-address DNS callbacks that caused hostname fetches to fail\n with `Invalid IP address: undefined`, while preserving private-address checks.\n- Bound the full fetch duration, including redirects and streaming responses;\n return structured HTTP, timeout and response-size errors with retry guidance.\n- Recognize repeated failed fetches and unchanged GitHub Actions logs across\n web pages, `gh run view` and `gh api`; retain bounded evidence through context\n compaction and offer a recovery turn before stopping persistent retry loops.\n- Preserve status monitoring, new job/log evidence and real local edits as\n recovery paths, and never skip shell execution based on cached stdout.\n- Validate with 158 focused unit/integration tests covering network failures,\n resource budgets, compaction, recovery and existing agent-loop behavior."
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"productVersion": null,
|
|
32
|
+
"cliVersion": "0.166.30",
|
|
33
|
+
"cliVersions": ["0.166.29", "0.166.30"],
|
|
34
|
+
"date": "2026-09-07",
|
|
35
|
+
"title": "",
|
|
36
|
+
"sections": [
|
|
37
|
+
{
|
|
38
|
+
"heading": "Added - cc CLI 0.166.30: custom LLM connections and page-first IDE workspaces",
|
|
39
|
+
"body": "> `chainlesschain` **0.166.29 -> 0.166.30** (2026-09-07), paired with\n> VS Code / VSCodium extension **0.37.87**. Publication requires the exact\n> release commit's complete CLI CI, CLI Strict Sandbox and IDE release gates.\n\n- Save custom relay URLs, protocol selections, model aliases and credentials\n atomically with `llm configure`; accept secrets only through bounded JSON\n stdin, never argv. Changing destinations requires a destination-specific key.\n- Test OpenAI-compatible, Anthropic, Gemini and Ollama endpoints using their\n native protocols, bounded request timeouts and no credential-bearing redirects.\n- Open Evolution Workbench on a status overview with a version list, optional\n filtering, evidence inspection and confirmed, capability-gated decisions.\n- Open the IDE Skill Library on a paginated catalog; task retrieval is optional\n and preserves canonical digest/conflict/authority validation without execution.\n- Add an isolated source-checkout local Workbench test profile. Test identities\n remain explicitly marked; this does not provision real identity or approval\n services or enable mutation in an unconfigured production deployment."
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
4
43
|
{
|
|
5
44
|
"productVersion": null,
|
|
6
45
|
"cliVersion": "0.166.29",
|
|
@@ -21,6 +21,8 @@ const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$/u;
|
|
|
21
21
|
const DEFAULT_MAXIMUM_BYTES = 64 * 1024 * 1024;
|
|
22
22
|
const MAXIMUM_CACHED_RECORDS = 1024;
|
|
23
23
|
const MAXIMUM_CACHED_TEXT_BYTES = 16 * 1024 * 1024;
|
|
24
|
+
const MAXIMUM_TRUST_EPOCH_CHARS = 256;
|
|
25
|
+
const MAXIMUM_TRUST_EPOCH_RETRIES = 2;
|
|
24
26
|
const STORE_KEYS = new Set([
|
|
25
27
|
"current",
|
|
26
28
|
"discardedAnchors",
|
|
@@ -140,6 +142,24 @@ function capture(owner, name) {
|
|
|
140
142
|
return (...args) => Reflect.apply(owner[name], owner, args);
|
|
141
143
|
}
|
|
142
144
|
|
|
145
|
+
function captureOptional(owner, name) {
|
|
146
|
+
if (owner?.[name] === undefined) return null;
|
|
147
|
+
if (typeof owner[name] !== "function")
|
|
148
|
+
throw new TypeError(`${name} port must be a function`);
|
|
149
|
+
return (...args) => Reflect.apply(owner[name], owner, args);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function normalizeTrustEpoch(value) {
|
|
153
|
+
if (
|
|
154
|
+
typeof value !== "string" ||
|
|
155
|
+
value.trim() === "" ||
|
|
156
|
+
value.length > MAXIMUM_TRUST_EPOCH_CHARS
|
|
157
|
+
) {
|
|
158
|
+
throw new Error("witness verifier trust epoch is invalid");
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
|
|
143
163
|
function sameTrust(value, trust) {
|
|
144
164
|
return (
|
|
145
165
|
value?.algorithm === trust.algorithm &&
|
|
@@ -192,6 +212,7 @@ export function createEvolutionFileWitness({
|
|
|
192
212
|
const trust = normalizeTrust(trustInput);
|
|
193
213
|
const sign = capture(signer, "sign");
|
|
194
214
|
const verify = capture(verifier, "verify");
|
|
215
|
+
const readTrustEpoch = captureOptional(verifier, "getTrustEpoch");
|
|
195
216
|
if (typeof lock !== "function" || typeof random !== "function") {
|
|
196
217
|
throw new TypeError("witness lock and random ports are required");
|
|
197
218
|
}
|
|
@@ -213,7 +234,7 @@ export function createEvolutionFileWitness({
|
|
|
213
234
|
recordEncodings.size >= MAXIMUM_CACHED_RECORDS ||
|
|
214
235
|
recordEncodings.has(record.witnessDigest)
|
|
215
236
|
)
|
|
216
|
-
return;
|
|
237
|
+
return null;
|
|
217
238
|
// Only flat scalar records may share field values with the cache. Caller
|
|
218
239
|
// objects, signature objects and verifier buffers must never be retained.
|
|
219
240
|
for (const key of RECORD_KEYS) {
|
|
@@ -223,7 +244,7 @@ export function createEvolutionFileWitness({
|
|
|
223
244
|
value !== null &&
|
|
224
245
|
!["string", "number", "boolean"].includes(typeof value)
|
|
225
246
|
)
|
|
226
|
-
return;
|
|
247
|
+
return null;
|
|
227
248
|
}
|
|
228
249
|
for (const key of SIGNATURE_KEYS) {
|
|
229
250
|
if (typeof record.signature[key] !== "string") return;
|
|
@@ -233,16 +254,19 @@ export function createEvolutionFileWitness({
|
|
|
233
254
|
// strings (bounded by encoded.length). Entry count bounds object overhead;
|
|
234
255
|
// this is not a claim about the entire process's RSS.
|
|
235
256
|
const bytes = 2 * (message.length + 2 * encoded.length);
|
|
236
|
-
if (cachedTextBytes + bytes > MAXIMUM_CACHED_TEXT_BYTES) return;
|
|
257
|
+
if (cachedTextBytes + bytes > MAXIMUM_CACHED_TEXT_BYTES) return null;
|
|
237
258
|
// Decode from the small owned encoding so parser substring backing stores
|
|
238
259
|
// cannot retain the entire source history through one cached scalar/key.
|
|
239
260
|
const ownedRecord = JSON.parse(encoded);
|
|
240
|
-
|
|
261
|
+
const cached = {
|
|
241
262
|
record: ownedRecord,
|
|
242
263
|
message,
|
|
243
264
|
encoded,
|
|
244
|
-
|
|
265
|
+
verifiedTrustEpoch: null,
|
|
266
|
+
};
|
|
267
|
+
recordEncodings.set(ownedRecord.witnessDigest, cached);
|
|
245
268
|
cachedTextBytes += bytes;
|
|
269
|
+
return cached;
|
|
246
270
|
};
|
|
247
271
|
const encodeRecord = (record) =>
|
|
248
272
|
cachedEncoding(record)?.encoded ?? canonical(record);
|
|
@@ -266,8 +290,15 @@ export function createEvolutionFileWitness({
|
|
|
266
290
|
});
|
|
267
291
|
};
|
|
268
292
|
|
|
269
|
-
const verifySigned = (record) => {
|
|
293
|
+
const verifySigned = (record, trustEpoch = null) => {
|
|
270
294
|
const cached = cachedEncoding(record);
|
|
295
|
+
if (
|
|
296
|
+
cached &&
|
|
297
|
+
trustEpoch !== null &&
|
|
298
|
+
cached.verifiedTrustEpoch === trustEpoch
|
|
299
|
+
) {
|
|
300
|
+
return record;
|
|
301
|
+
}
|
|
271
302
|
if (cached) {
|
|
272
303
|
// Reconsult the current verifier even for byte-identical historical
|
|
273
304
|
// records. Give it fresh mutable inputs, not the cached representation.
|
|
@@ -281,6 +312,7 @@ export function createEvolutionFileWitness({
|
|
|
281
312
|
) {
|
|
282
313
|
throw new Error("witness record authentication failed");
|
|
283
314
|
}
|
|
315
|
+
cached.verifiedTrustEpoch = trustEpoch;
|
|
284
316
|
return record;
|
|
285
317
|
}
|
|
286
318
|
assertExactKeys(record, RECORD_KEYS, "witness record");
|
|
@@ -372,10 +404,14 @@ export function createEvolutionFileWitness({
|
|
|
372
404
|
) {
|
|
373
405
|
throw new Error("witness record state is inconsistent");
|
|
374
406
|
}
|
|
375
|
-
rememberEncoding(record, encodedMessage);
|
|
407
|
+
const remembered = rememberEncoding(record, encodedMessage);
|
|
408
|
+
if (remembered) remembered.verifiedTrustEpoch = trustEpoch;
|
|
376
409
|
return record;
|
|
377
410
|
};
|
|
378
411
|
|
|
412
|
+
const currentTrustEpoch = () =>
|
|
413
|
+
readTrustEpoch === null ? null : normalizeTrustEpoch(readTrustEpoch());
|
|
414
|
+
|
|
379
415
|
const emptyDiscardDigest = hash(
|
|
380
416
|
Buffer.from(`${DISCARD_DOMAIN}${canonical([])}`, "utf8"),
|
|
381
417
|
);
|
|
@@ -421,7 +457,7 @@ export function createEvolutionFileWitness({
|
|
|
421
457
|
"witnessDigest",
|
|
422
458
|
"evolution-ledger-witness",
|
|
423
459
|
);
|
|
424
|
-
return verifySigned(record);
|
|
460
|
+
return verifySigned(record, currentTrustEpoch());
|
|
425
461
|
};
|
|
426
462
|
|
|
427
463
|
const emptyStore = () => {
|
|
@@ -434,7 +470,7 @@ export function createEvolutionFileWitness({
|
|
|
434
470
|
};
|
|
435
471
|
};
|
|
436
472
|
|
|
437
|
-
const validateStore = (store) => {
|
|
473
|
+
const validateStore = (store, trustEpoch = null) => {
|
|
438
474
|
assertExactKeys(store, STORE_KEYS, "witness store");
|
|
439
475
|
if (
|
|
440
476
|
store.schema !== EVOLUTION_FILE_WITNESS_STORE_SCHEMA ||
|
|
@@ -479,7 +515,7 @@ export function createEvolutionFileWitness({
|
|
|
479
515
|
}
|
|
480
516
|
let discardIndex = 0;
|
|
481
517
|
for (const [index, record] of store.history.entries()) {
|
|
482
|
-
verifySigned(record);
|
|
518
|
+
verifySigned(record, trustEpoch);
|
|
483
519
|
if (index === 0) continue;
|
|
484
520
|
const previous = store.history[index - 1];
|
|
485
521
|
if (
|
|
@@ -512,7 +548,7 @@ export function createEvolutionFileWitness({
|
|
|
512
548
|
if (
|
|
513
549
|
discardIndex !== store.discardedAnchors.length ||
|
|
514
550
|
store.current?.witnessDigest !== store.history.at(-1).witnessDigest ||
|
|
515
|
-
encodeRecord(verifySigned(store.current)) !==
|
|
551
|
+
encodeRecord(verifySigned(store.current, trustEpoch)) !==
|
|
516
552
|
encodeRecord(store.history.at(-1))
|
|
517
553
|
) {
|
|
518
554
|
throw new Error("witness current state is not its durable history");
|
|
@@ -564,9 +600,19 @@ export function createEvolutionFileWitness({
|
|
|
564
600
|
) {
|
|
565
601
|
throw new Error("witness store changed while reading");
|
|
566
602
|
}
|
|
567
|
-
|
|
568
|
-
|
|
603
|
+
const parsed = JSON.parse(
|
|
604
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
569
605
|
);
|
|
606
|
+
for (
|
|
607
|
+
let attempt = 0;
|
|
608
|
+
attempt < MAXIMUM_TRUST_EPOCH_RETRIES;
|
|
609
|
+
attempt += 1
|
|
610
|
+
) {
|
|
611
|
+
const trustEpoch = currentTrustEpoch();
|
|
612
|
+
const store = validateStore(parsed, trustEpoch);
|
|
613
|
+
if (trustEpoch === currentTrustEpoch()) return store;
|
|
614
|
+
}
|
|
615
|
+
throw new Error("witness verifier trust epoch changed while reading");
|
|
570
616
|
} finally {
|
|
571
617
|
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
572
618
|
}
|
|
@@ -23,6 +23,11 @@ function capture(owner, name, label) {
|
|
|
23
23
|
return Object.freeze((...args) => operation(...args));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function captureOptional(owner, name, label) {
|
|
27
|
+
if (owner?.[name] === undefined) return null;
|
|
28
|
+
return capture(owner, name, label);
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
function normalizeAuthority(value, label) {
|
|
27
32
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
28
33
|
throw new TypeError(`${label} authority is required`);
|
|
@@ -34,6 +39,11 @@ function normalizeAuthority(value, label) {
|
|
|
34
39
|
verifierIdentity: value.verifier,
|
|
35
40
|
sign: capture(value.signer, "sign", `${label}.signer`),
|
|
36
41
|
verify: capture(value.verifier, "verify", `${label}.verifier`),
|
|
42
|
+
getTrustEpoch: captureOptional(
|
|
43
|
+
value.verifier,
|
|
44
|
+
"getTrustEpoch",
|
|
45
|
+
`${label}.verifier`,
|
|
46
|
+
),
|
|
37
47
|
});
|
|
38
48
|
}
|
|
39
49
|
|
|
@@ -123,7 +133,12 @@ export function createEvolutionLedgerFileBackend({
|
|
|
123
133
|
filePath: paths.witnessPath,
|
|
124
134
|
trust: witnessAuthority.trust,
|
|
125
135
|
signer: { sign: witnessAuthority.sign },
|
|
126
|
-
verifier: {
|
|
136
|
+
verifier: {
|
|
137
|
+
verify: witnessAuthority.verify,
|
|
138
|
+
...(witnessAuthority.getTrustEpoch === null
|
|
139
|
+
? {}
|
|
140
|
+
: { getTrustEpoch: witnessAuthority.getTrustEpoch }),
|
|
141
|
+
},
|
|
127
142
|
fsImpl,
|
|
128
143
|
...(lock === undefined ? {} : { lock }),
|
|
129
144
|
...(random === undefined ? {} : { random }),
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
|
|
2
|
+
import {
|
|
3
|
+
setSecretConfigValue,
|
|
4
|
+
updateConfigAtomically,
|
|
5
|
+
} from "./config-manager.js";
|
|
6
|
+
|
|
7
|
+
export function normalizeLlmConnection(input) {
|
|
8
|
+
if (
|
|
9
|
+
!input ||
|
|
10
|
+
typeof input !== "object" ||
|
|
11
|
+
Array.isArray(input) ||
|
|
12
|
+
Object.keys(input).some(
|
|
13
|
+
(k) =>
|
|
14
|
+
![
|
|
15
|
+
"provider",
|
|
16
|
+
"model",
|
|
17
|
+
"baseUrl",
|
|
18
|
+
"visionModel",
|
|
19
|
+
"apiKey",
|
|
20
|
+
"allowHttp",
|
|
21
|
+
].includes(k),
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
throw new Error("Invalid LLM connection fields");
|
|
25
|
+
const value = {};
|
|
26
|
+
for (const key of ["provider", "model", "baseUrl", "visionModel", "apiKey"]) {
|
|
27
|
+
if (input[key] != null && typeof input[key] !== "string")
|
|
28
|
+
throw new Error(`Invalid ${key}`);
|
|
29
|
+
value[key] = (input[key] || "").trim();
|
|
30
|
+
if (value[key].length > (key === "apiKey" ? 8192 : 2048))
|
|
31
|
+
throw new Error(`${key} is too long`);
|
|
32
|
+
}
|
|
33
|
+
if (!Object.hasOwn(BUILT_IN_PROVIDERS, value.provider))
|
|
34
|
+
throw new Error("Choose a supported provider/protocol");
|
|
35
|
+
if (!value.model || /[\r\n\0]/.test(value.model + value.visionModel))
|
|
36
|
+
throw new Error("A valid model name is required");
|
|
37
|
+
let url;
|
|
38
|
+
try {
|
|
39
|
+
url = new URL(value.baseUrl);
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error("Invalid LLM Base URL");
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
!["http:", "https:"].includes(url.protocol) ||
|
|
45
|
+
url.username ||
|
|
46
|
+
url.password ||
|
|
47
|
+
url.search ||
|
|
48
|
+
url.hash
|
|
49
|
+
)
|
|
50
|
+
throw new Error(
|
|
51
|
+
"Base URL must be HTTP(S), without credentials, query or fragment",
|
|
52
|
+
);
|
|
53
|
+
if (
|
|
54
|
+
url.protocol === "http:" &&
|
|
55
|
+
!["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) &&
|
|
56
|
+
input.allowHttp !== true
|
|
57
|
+
)
|
|
58
|
+
throw new Error("Confirm unencrypted HTTP before using a remote endpoint");
|
|
59
|
+
if (
|
|
60
|
+
/\/(chat\/completions|messages|responses|api\/generate)\/?$/.test(
|
|
61
|
+
url.pathname,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
throw new Error(
|
|
65
|
+
"Use the API base path, not a completion/messages endpoint",
|
|
66
|
+
);
|
|
67
|
+
value.baseUrl = url.href.replace(/\/+$/, "");
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function saveLlmConnection(input, { storage = "auto" } = {}) {
|
|
72
|
+
if (!["auto", "keychain", "file"].includes(storage))
|
|
73
|
+
throw new Error("Secret storage must be auto, keychain, or file");
|
|
74
|
+
const value = normalizeLlmConnection(input);
|
|
75
|
+
const mutate = (config) => {
|
|
76
|
+
const current = config.llm || {};
|
|
77
|
+
const sameEndpoint =
|
|
78
|
+
current.provider === value.provider &&
|
|
79
|
+
String(
|
|
80
|
+
current.baseUrl || BUILT_IN_PROVIDERS[current.provider]?.baseUrl || "",
|
|
81
|
+
).replace(/\/+$/, "") === value.baseUrl;
|
|
82
|
+
if (
|
|
83
|
+
value.provider !== "ollama" &&
|
|
84
|
+
!value.apiKey &&
|
|
85
|
+
(!sameEndpoint || !current.apiKey)
|
|
86
|
+
)
|
|
87
|
+
throw new Error(
|
|
88
|
+
"A new endpoint requires its own API key; the existing key was not reused",
|
|
89
|
+
);
|
|
90
|
+
config.llm = {
|
|
91
|
+
...current,
|
|
92
|
+
provider: value.provider,
|
|
93
|
+
model: value.model,
|
|
94
|
+
baseUrl: value.baseUrl,
|
|
95
|
+
visionModel: value.visionModel || null,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
// A single lock and atomic rename bind endpoint and credential together.
|
|
99
|
+
// An interrupted save cannot route the old credential to the new endpoint.
|
|
100
|
+
if (value.apiKey)
|
|
101
|
+
setSecretConfigValue("llm.apiKey", value.apiKey, {
|
|
102
|
+
storage,
|
|
103
|
+
configMutator: mutate,
|
|
104
|
+
});
|
|
105
|
+
else updateConfigAtomically(mutate);
|
|
106
|
+
return {
|
|
107
|
+
provider: value.provider,
|
|
108
|
+
model: value.model,
|
|
109
|
+
baseUrl: value.baseUrl,
|
|
110
|
+
visionModel: value.visionModel || null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function readLlmConnectionInput(stream = process.stdin) {
|
|
115
|
+
if (stream.isTTY)
|
|
116
|
+
throw new Error("Send the LLM configuration JSON through stdin");
|
|
117
|
+
const chunks = [];
|
|
118
|
+
let size = 0;
|
|
119
|
+
for await (const chunk of stream) {
|
|
120
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
121
|
+
size += buffer.length;
|
|
122
|
+
if (size > 32768) throw new Error("LLM configuration input is too large");
|
|
123
|
+
chunks.push(buffer);
|
|
124
|
+
}
|
|
125
|
+
const input = Buffer.concat(chunks).toString("utf8");
|
|
126
|
+
try {
|
|
127
|
+
return JSON.parse(input);
|
|
128
|
+
} catch {
|
|
129
|
+
throw new Error("LLM configuration input must be valid JSON");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { formatProviderResponseError } from "./provider-http-error.js";
|
|
2
|
+
|
|
3
|
+
export async function probeLlmConnection(
|
|
4
|
+
target,
|
|
5
|
+
{ fetchImpl = fetch, timeoutMs = 20_000 } = {},
|
|
6
|
+
) {
|
|
7
|
+
const base = target.baseUrl.replace(/\/+$/, "");
|
|
8
|
+
const headers = { "Content-Type": "application/json" };
|
|
9
|
+
let url, body;
|
|
10
|
+
if (target.provider === "ollama") {
|
|
11
|
+
url = `${base}/api/generate`;
|
|
12
|
+
body = {
|
|
13
|
+
model: target.model,
|
|
14
|
+
prompt: "Say hi in one word.",
|
|
15
|
+
stream: false,
|
|
16
|
+
};
|
|
17
|
+
} else {
|
|
18
|
+
if (!target.apiKey) throw new Error("API key required");
|
|
19
|
+
if (target.provider === "anthropic") {
|
|
20
|
+
url = `${base}/messages`;
|
|
21
|
+
headers["x-api-key"] = target.apiKey;
|
|
22
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
23
|
+
body = {
|
|
24
|
+
model: target.model,
|
|
25
|
+
max_tokens: 16,
|
|
26
|
+
messages: [{ role: "user", content: "Say hi in one word." }],
|
|
27
|
+
};
|
|
28
|
+
} else if (target.provider === "gemini") {
|
|
29
|
+
url = `${base}/models/${encodeURIComponent(target.model)}:generateContent`;
|
|
30
|
+
headers["x-goog-api-key"] = target.apiKey;
|
|
31
|
+
body = {
|
|
32
|
+
contents: [{ parts: [{ text: "Say hi in one word." }] }],
|
|
33
|
+
generationConfig: { maxOutputTokens: 16 },
|
|
34
|
+
};
|
|
35
|
+
} else {
|
|
36
|
+
url = `${base}/chat/completions`;
|
|
37
|
+
headers.Authorization = `Bearer ${target.apiKey}`;
|
|
38
|
+
body = {
|
|
39
|
+
model: target.model,
|
|
40
|
+
messages: [{ role: "user", content: "Say hi in one word." }],
|
|
41
|
+
max_tokens: 16,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetchImpl(url, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers,
|
|
51
|
+
body: JSON.stringify(body),
|
|
52
|
+
signal: controller.signal,
|
|
53
|
+
redirect: "error",
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok)
|
|
56
|
+
throw new Error(
|
|
57
|
+
await formatProviderResponseError(target.provider, response),
|
|
58
|
+
);
|
|
59
|
+
const data = await response.json();
|
|
60
|
+
const text =
|
|
61
|
+
target.provider === "ollama"
|
|
62
|
+
? data.response
|
|
63
|
+
: target.provider === "anthropic"
|
|
64
|
+
? data.content
|
|
65
|
+
?.filter((p) => p.type === "text")
|
|
66
|
+
.map((p) => p.text)
|
|
67
|
+
.join("")
|
|
68
|
+
: target.provider === "gemini"
|
|
69
|
+
? data.candidates?.[0]?.content?.parts
|
|
70
|
+
?.map((p) => p.text || "")
|
|
71
|
+
.join("")
|
|
72
|
+
: data.choices?.[0]?.message?.content;
|
|
73
|
+
if (typeof text !== "string" || !text.trim())
|
|
74
|
+
throw new Error(
|
|
75
|
+
"The endpoint returned no model text; check the protocol and model",
|
|
76
|
+
);
|
|
77
|
+
return text.trim().slice(0, 100);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (controller.signal.aborted)
|
|
80
|
+
throw new Error(`Connection test timed out after ${timeoutMs / 1000}s`);
|
|
81
|
+
throw error;
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
}
|