chainlesschain 0.162.95 → 0.162.96
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 +1 -1
- package/bin/chainlesschain.js +8 -18
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DLvvkb9x.js → AIOps-Bo9Ux24z.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BxhQTwqd.js → ActionButton-CExgXSQT.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C4oUXzhG.js → Analytics-BNnZhRTZ.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CjzKJdv3.js → AppLayout-D9xfqFCT.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-B-MrugUs.js → Audit-Cpgu8uCw.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ecIfEpIZ.js → Backup-CHdEJnNE.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BUcSfWnx.js → BaseInput-Cbw-PxBq.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DIc1n_Lk.js → Chat-ChHTZK_6.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BGzg7oVA.js → ChatBubbleRenderer-Dl6z6ZH7.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BQ3Ueff-.js → Checkbox-Cj8d9bru.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CvSGLnqE.js → Codegen-C2YobW4j.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Br9IXZcE.js → Col-CxfV5FGT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-tPuLOf0r.js → Community-CiwOASMj.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-vMWH73BE.js → Compact-CTg_2iKW.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Ct9z699x.js → Compliance-HMbsTmtw.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bxs1kL1y.js → Cowork-DyNDqGR2.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-QEpr2k4U.js → Cron-9-x5M7qy.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-u4xbUw6O.js → Crosschain-4msgDaYJ.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DXTEvyrW.js → DID--2Beb5Po.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C4pZgzkg.js → Dashboard-T6nq0Lzs.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-LRuqREGe.js → Dropdown-D_Q6T10v.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D7XI1yBM.js → EmailListRenderer-TnN21bJx.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-3y_SuAMQ.js → FamilyGuardDashboard-kky_uIij.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DbjbJJCW.js → Federation-C1GkNY4B.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-WPpMZcUw.js → FormItemContext-5-_Fc5YV.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-nNT9Tn4E.js → GenericCardRenderer-CmHiB7sr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DqDaVzwV.js → Git-Cuci_BPI.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BquBZpcg.js → Governance-BU0cO2LD.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-RcPPYijs.js → Inference-BDFNgEcx.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-ByXfZV13.js → KnowledgeGraph-DITXUzHz.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CHT3DUcq.js → Logs-Ea-07CRd.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-Bi0HJ9pN.js → Marketplace-D6OBbRVG.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-QCBxPRRc.js → McpTools-eiKpIJMi.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D4RlxGXn.js → Memory-M-X-T8UF.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CU_PV7e-.js → MobileBridge-xcZb9Ekv.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-c1CMFD8z.js → MobileProjects-DCfoTPRC.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BezDB1-s.js → Mtc-BWfGDzD8.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CgLpiHpE.js → MtcAudit-imBuld-U.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-vx6bz-bA.js → Multisig-C2kcsq7I.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CGOagg3-.js → NLProgramming-DObvXSXP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BcuDGYRS.js → Notes-C-6OGJMe.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-ZAGCDUpc.js → NotificationSettings-ub7Ozxci.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-B5onE0eX.js → OrderTableRenderer-CR2W89Vk.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-y_y20-5Y.js → Organization-DNA_Wty7.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BEICS31D.js → Overflow-rT4Wb4rQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D_WRMzh4.js → P2P-CK-hARKv.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-C2pbtNbT.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-3aqCc_4f.js → Permissions-B8EIEqGJ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DT3R9g9Y.js → PersonalDataHub-BxNVNU3s.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-MiMWIhw0.js → Pipeline-CT9pbkyY.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-C3JsLuDa.js → Privacy-DhCNd2CS.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-EhHVi1ff.js → ProjectInit-Qngnx-vQ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-D_dNEA37.js → ProjectSettings-Dfn-nQc1.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BKdiSaPR.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CPEhi2iZ.js → Providers-4o9B_n-X.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BFQpMleX.js → QuickAsk-bs1qvF30.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DzbovRC8.js → Recommend-CuEnKA2K.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DoBeV3TD.js → Reputation-DbfvmRb2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DthhwX_B.js → Row-DvMJGAI3.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Dsf5U0lP.js → RssFeed-oJ0BZvmA.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CJPB8kKl.js → Search-DnIVrY9O.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DI9MfpdD.js → Security-CIWhI_Tc.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BdG0TFGf.js → Services-DLrnpjwm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DSYazY_B.js → Skeleton-CYmqgtVb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BD309VTT.js → Skills-BUzCkoeu.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CaUlw4_6.js → Sla-DYRE4bnS.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CZAPkR7H.js → SpeechSettings-_6MurdOg.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CFRSowN3.js → SyncSettings-UIdnF5Li.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Di8EOluc.js → Tasks-COx2CjT8.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-B5FTziwi.js → Templates-BcHAmkZo.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Dj3XYfBg.js → Tenant-CjYQS8yZ.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CznH_rJT.js → Terminal-BmfOPcWt.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DTHzaXPt.js → TimelineRenderer-D5HuarNQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bo56nuML.js → Tokens-C_fbProA.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-pyVUVBwb.js → Trigger-JPulpRx8.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cp7jMkKN.js → Trust-CnO-062O.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-aPcZCdpz.js → UkeySign-HpNovvau.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DAv5j--w.js → VideoEditing-yMncxH7y.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DRhK7IDR.js → Wallet-Cug-t7Np.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B2yQJuq8.js → WebAuthn--U1-NR-3.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D98DhR54.js → WorkflowEditor-R-XvSJ3S.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BXrRNi8C.js → chat-CCI1i6Rp.js} +1 -1
- package/src/assets/web-panel/assets/{colors-jQ94T_qn.js → colors-C-mqoaLt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-KqUJLlF2.js → compact-item-De_N2iRR.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DxM3zuQW.js → createContext-DbTM6sjZ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-OcLPHSl5.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BGm946KV.js → hasIn-D0Adylyc.js} +1 -1
- package/src/assets/web-panel/assets/index-7E_mHkDU.js +1 -0
- package/src/assets/web-panel/assets/{index-Dib0FsTT.js → index-AY937BxS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5NQai7O.js → index-B6hgy5VC.js} +1 -1
- package/src/assets/web-panel/assets/{index-LWTZAud0.js → index-B7XnpLHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXMZLfd8.js → index-B7gw-tDm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTUs9t5F.js → index-BBgDgkwW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ce-YoL4x.js → index-BMmeiw88.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaU_fSK7.js → index-BU6XtnSx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMp41Q1Y.js → index-Bi7VUG0R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbQvgx2e.js → index-BtE3cw9m.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWcd5zDG.js → index-C9ej-MF0.js} +3 -3
- package/src/assets/web-panel/assets/{index-BbS0cBU9.js → index-CIEiQl1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1VWL6Lc.js → index-CKgaW9E5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzsaWQy.js → index-CY0KKR5f.js} +1 -1
- package/src/assets/web-panel/assets/index-CbV3-WlB.js +1 -0
- package/src/assets/web-panel/assets/{index-DSzb9VOG.js → index-Co1UkcFX.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-2HpfWo.js → index-CzP5d5LW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DboUBf6R.js → index-D0NJ5lRi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZMzj4ay.js → index-D0gBsqTX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CghRL1dh.js → index-D5F6shtn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz_SFSix.js → index-DVtamtlm.js} +1 -1
- package/src/assets/web-panel/assets/{index-T4UJiTi3.js → index-DWT18hDG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CreXAr5Y.js → index-DaXb4B9P.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Z2m28C.js → index-DjQA0Vez.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crw76vIF.js → index-Ds8NJVEL.js} +1 -1
- package/src/assets/web-panel/assets/{index-mZNLT2SP.js → index-DuP-8HWu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnEZfi5D.js → index-DwLTiJ31.js} +1 -1
- package/src/assets/web-panel/assets/{index-1P0sXNhG.js → index-T3VhH8cO.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4b7KjRq.js → index-TDY1o0w3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOYcemym.js → index-TRy2Gc3V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAPk6ntc.js → index-W1OMxCmI.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dgzw6eKr.js → index-ftFRZbuc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7sBLech.js → index-iGPGaORA.js} +1 -1
- package/src/assets/web-panel/assets/{index-FSYz5aww.js → index-jhy5KQvg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpVYD4j.js → index-qUSZJcgw.js} +1 -1
- package/src/assets/web-panel/assets/{index-yavsyHif.js → index-w3f6lqkD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfE3jxrT.js → index-xdMc71Kh.js} +1 -1
- package/src/assets/web-panel/assets/{index-aOLXuHpG.js → index-xhX5eg2Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGjCjsf1.js → index-zkTiW6d6.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BDoZJI7g.js → initDefaultProps-CIHqouWk.js} +1 -1
- package/src/assets/web-panel/assets/{motion-sceLjgp-.js → motion-D1iUKftk.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTWQpFa7.js → move-YOpWjmjL.js} +1 -1
- package/src/assets/web-panel/assets/{omit-dr-NCrHZ.js → omit-C_1357UY.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DK-GX_Z2.js → pickAttrs-CDb4dq6o.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIPf--18.js → placementArrow-D-OGYyhd.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BzrWT5tV.js → responsiveObserve-hS-j69A0.js} +1 -1
- package/src/assets/web-panel/assets/{slide-D5XhxlET.js → slide-tnjuvphU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-6DI-k-9E.js → statusUtils-BERfMMcU.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DMw-eyn9.js → styleChecker-DKpl2K9E.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D4oVdliM.js → useFlexGapSupport-C_sX4D3N.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D3PPZnvy.js → useFs-DBcxC1vK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Gh5ba4KP.js → usePersonalDataHub-CeoDf2XI.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CVEM1u4x.js → vnode-DC9Pmj_u.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-ClT_-9RZ.js → zoom-CZA58J7M.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/activitypub.js +19 -3
- package/src/commands/agent.js +25 -0
- package/src/commands/ask.js +13 -0
- package/src/commands/config.js +17 -1
- package/src/commands/cost.js +6 -1
- package/src/commands/dao.js +16 -3
- package/src/commands/dlp.js +7 -1
- package/src/commands/hub.js +13 -0
- package/src/commands/mtc.js +37 -1
- package/src/gateways/discord/discord-formatter.js +18 -1
- package/src/gateways/telegram/telegram-formatter.js +6 -3
- package/src/gateways/ws/ws-server.js +19 -1
- package/src/harness/plugin-manager.js +20 -0
- package/src/harness/worktree-isolator.js +17 -1
- package/src/lib/__tests__/model-deprecation.test.js +194 -0
- package/src/lib/a2a-protocol.js +23 -9
- package/src/lib/agent-core.js +2 -5
- package/src/lib/audit-mtc.js +40 -2
- package/src/lib/chat-core.js +141 -18
- package/src/lib/cli-anything-bridge.js +5 -0
- package/src/lib/cli-numeric.js +38 -0
- package/src/lib/config-manager.js +37 -6
- package/src/lib/cowork/ab-comparator-cli.js +6 -2
- package/src/lib/cowork-cron.js +45 -34
- package/src/lib/cowork-learning.js +31 -1
- package/src/lib/cowork-share.js +39 -3
- package/src/lib/cowork-template-marketplace.js +54 -2
- package/src/lib/cowork-workflow.js +33 -6
- package/src/lib/cross-chain-mtc.js +41 -12
- package/src/lib/crypto-manager.js +12 -2
- package/src/lib/dbevo.js +19 -8
- package/src/lib/downloader.js +24 -9
- package/src/lib/evomap-governance.js +4 -4
- package/src/lib/evomap-manager.js +54 -7
- package/src/lib/fatal-handler.js +48 -0
- package/src/lib/file-checkpoint.js +26 -0
- package/src/lib/git-integration.js +28 -0
- package/src/lib/goal-store.js +36 -10
- package/src/lib/hierarchical-memory.js +6 -0
- package/src/lib/llm-pricing.js +52 -2
- package/src/lib/mcp-oauth.js +37 -7
- package/src/lib/memory-manager.js +23 -2
- package/src/lib/model-deprecation.js +249 -0
- package/src/lib/pdh-feedback-ledger.js +131 -0
- package/src/lib/permanent-memory.js +30 -2
- package/src/lib/permission-rules.cjs +11 -3
- package/src/lib/personal-data-hub-aichat-wizard.js +33 -3
- package/src/lib/personal-data-hub-wiring.js +36 -13
- package/src/lib/project-instructions.js +10 -4
- package/src/lib/runnable-provider.js +62 -31
- package/src/lib/session-usage.js +34 -1
- package/src/lib/skill-loader.js +4 -1
- package/src/lib/skill-packs/generator.js +25 -2
- package/src/lib/stream-retry.js +56 -0
- package/src/lib/sync-credentials.js +53 -17
- package/src/lib/token-tracker.js +12 -2
- package/src/lib/with-file-lock.js +87 -0
- package/src/repl/agent-repl.js +39 -7
- package/src/repl/chat-repl.js +10 -0
- package/src/repl/session-cost.js +31 -3
- package/src/runtime/agent-core.js +474 -53
- package/src/runtime/coding-agent-contract-shared.cjs +58 -1
- package/src/runtime/coding-agent-policy.cjs +10 -0
- package/src/runtime/file-ref-expander.js +29 -1
- package/src/runtime/headless-runner.js +12 -1
- package/src/runtime/headless-stream.js +131 -5
- package/src/skills/video-editing/tools/commit.js +15 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-C8f5NbbI.js +0 -7
- package/src/assets/web-panel/assets/Projects-CXydUXp8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Bo1r5_zX.js +0 -1
- package/src/assets/web-panel/assets/index-BCU6dVQ-.js +0 -1
- package/src/assets/web-panel/assets/index-N_oOFphx.js +0 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stream-retry — shared classification of transient streaming-chat failures.
|
|
3
|
+
*
|
|
4
|
+
* A streaming LLM call can drop mid-flight (ECONNRESET / "terminated" / socket
|
|
5
|
+
* hangup / DNS). When that happens BEFORE any output reached the user it is safe
|
|
6
|
+
* to re-issue the request. This module is the single source of truth for "is
|
|
7
|
+
* this error a retryable connection drop?" so the agent path (agent-core) and
|
|
8
|
+
* the chat path (chat-core) classify identically.
|
|
9
|
+
*
|
|
10
|
+
* Pure + dependency-light (only the shared abort-utils) so it can be unit-tested
|
|
11
|
+
* and imported by either streaming implementation without pulling in the heavy
|
|
12
|
+
* agent runtime.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { isAbortError } from "./abort-utils.js";
|
|
16
|
+
|
|
17
|
+
/** Bounded auto-retry budget for streaming connection drops (Claude-Code 2.1.181). */
|
|
18
|
+
export const STREAM_RETRY_MAX = 2;
|
|
19
|
+
export const STREAM_RETRY_BASE_MS = 400;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Is this error from a streaming chat request a transient API CONNECTION drop
|
|
23
|
+
* that is safe to retry? True only for genuine network failures (reset /
|
|
24
|
+
* timeout / DNS / refused / socket hangup / undici "terminated" / "fetch
|
|
25
|
+
* failed"). False for user aborts and for HTTP/status errors (a 4xx/auth/5xx is
|
|
26
|
+
* the server's verdict carried in the message, not a dropped pipe — retrying a
|
|
27
|
+
* connection that never dropped won't help and could double-bill).
|
|
28
|
+
*
|
|
29
|
+
* @param {unknown} err
|
|
30
|
+
* @param {AbortSignal} [signal] the caller's signal — a user abort is never retryable
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
export function isRetryableStreamError(err, signal) {
|
|
34
|
+
if (isAbortError(err)) return false;
|
|
35
|
+
if (signal && signal.aborted) return false;
|
|
36
|
+
if (!err) return false;
|
|
37
|
+
const code = String(err.code || err.cause?.code || "").toUpperCase();
|
|
38
|
+
if (
|
|
39
|
+
[
|
|
40
|
+
"ECONNRESET",
|
|
41
|
+
"ETIMEDOUT",
|
|
42
|
+
"ECONNREFUSED",
|
|
43
|
+
"EAI_AGAIN",
|
|
44
|
+
"EPIPE",
|
|
45
|
+
"ENETUNREACH",
|
|
46
|
+
"ENOTFOUND",
|
|
47
|
+
"UND_ERR_SOCKET",
|
|
48
|
+
].includes(code)
|
|
49
|
+
) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
const msg = String(err.message || err).toLowerCase();
|
|
53
|
+
return /econnreset|etimedout|econnrefused|eai_again|socket hang ?up|terminated|fetch failed|network error|timed?\s*out|premature close/.test(
|
|
54
|
+
msg,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -23,6 +23,7 @@ import fs from "node:fs";
|
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import os from "node:os";
|
|
25
25
|
import crypto from "node:crypto";
|
|
26
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
26
27
|
|
|
27
28
|
const SENSITIVE_FIELDS = ["sync.webdav.password", "sync.oss.secretAccessKey"];
|
|
28
29
|
const ALLOWED_PROVIDER_IDS = ["webdav", "oss"];
|
|
@@ -55,6 +56,37 @@ function _ensureDir() {
|
|
|
55
56
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Write a secret file atomically. A crash (or concurrent writer) mid-write must
|
|
61
|
+
* never corrupt the master key or the encrypted vault — a truncated key throws
|
|
62
|
+
* on every load, a truncated vault fails AEAD decrypt and forces a reconfigure.
|
|
63
|
+
* Temp sibling + rename (atomic within a filesystem); when `mode` is given the
|
|
64
|
+
* temp is created with it (0600) so the secret is never world-readable in the
|
|
65
|
+
* window before rename. chmod after rename is belt-and-suspenders (best-effort
|
|
66
|
+
* on NTFS / older fs).
|
|
67
|
+
*/
|
|
68
|
+
function _atomicWriteFileSync(filePath, data, mode) {
|
|
69
|
+
const tmp = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
70
|
+
try {
|
|
71
|
+
fs.writeFileSync(tmp, data, mode != null ? { mode } : undefined);
|
|
72
|
+
fs.renameSync(tmp, filePath);
|
|
73
|
+
if (mode != null) {
|
|
74
|
+
try {
|
|
75
|
+
fs.chmodSync(filePath, mode);
|
|
76
|
+
} catch (_e) {
|
|
77
|
+
/* non-fatal: NTFS / older fs */
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
try {
|
|
82
|
+
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
|
|
83
|
+
} catch {
|
|
84
|
+
/* best-effort temp cleanup */
|
|
85
|
+
}
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
58
90
|
function _loadOrCreateMasterKey() {
|
|
59
91
|
_ensureDir();
|
|
60
92
|
const kp = _keyPath();
|
|
@@ -69,12 +101,10 @@ function _loadOrCreateMasterKey() {
|
|
|
69
101
|
return buf;
|
|
70
102
|
}
|
|
71
103
|
const key = crypto.randomBytes(KEY_LEN);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
/* non-fatal: NTFS / older fs */
|
|
77
|
-
}
|
|
104
|
+
// Atomic + 0600 from creation: a crash mid-write must not leave a truncated
|
|
105
|
+
// master key (a wrong-length key file then throws on every load), and the
|
|
106
|
+
// secret must never be briefly world-readable in the gap before chmod.
|
|
107
|
+
_atomicWriteFileSync(kp, key, 0o600);
|
|
78
108
|
return key;
|
|
79
109
|
}
|
|
80
110
|
|
|
@@ -123,7 +153,7 @@ function loadAll() {
|
|
|
123
153
|
|
|
124
154
|
function saveAll(all) {
|
|
125
155
|
_ensureDir();
|
|
126
|
-
|
|
156
|
+
_atomicWriteFileSync(_vaultPath(), _encryptBlob(JSON.stringify(all)));
|
|
127
157
|
return true;
|
|
128
158
|
}
|
|
129
159
|
|
|
@@ -184,20 +214,26 @@ function setCredentials(providerId, creds) {
|
|
|
184
214
|
if (!creds || typeof creds !== "object") {
|
|
185
215
|
throw new Error("sync-credentials: creds must be an object");
|
|
186
216
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
217
|
+
// Serialize the decrypt-modify-encrypt across processes so a concurrent write
|
|
218
|
+
// to a different provider doesn't drop this one (best-effort, never hangs).
|
|
219
|
+
return withFileLock(_vaultPath(), () => {
|
|
220
|
+
const all = loadAll();
|
|
221
|
+
if (!all.sync || typeof all.sync !== "object") all.sync = {};
|
|
222
|
+
all.sync[providerId] = { ...creds };
|
|
223
|
+
return saveAll(all);
|
|
224
|
+
});
|
|
191
225
|
}
|
|
192
226
|
|
|
193
227
|
function clearCredentials(providerId) {
|
|
194
228
|
assertProviderId(providerId);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
229
|
+
return withFileLock(_vaultPath(), () => {
|
|
230
|
+
const all = loadAll();
|
|
231
|
+
if (all?.sync?.[providerId]) {
|
|
232
|
+
delete all.sync[providerId];
|
|
233
|
+
return saveAll(all);
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
});
|
|
201
237
|
}
|
|
202
238
|
|
|
203
239
|
/** Test seam: override the resolved chainlesschain dir without env leak. */
|
package/src/lib/token-tracker.js
CHANGED
|
@@ -21,9 +21,19 @@ const PRICING = {
|
|
|
21
21
|
_default: { input: 2.5, output: 10 },
|
|
22
22
|
},
|
|
23
23
|
anthropic: {
|
|
24
|
+
// Current Anthropic list prices (USD/1M), verified 2026-06-21 vs the
|
|
25
|
+
// claude-api reference. Lookup is EXACT-key (calculateCost), so keep current
|
|
26
|
+
// model ids listed or they fall back to the Sonnet _default and mis-price —
|
|
27
|
+
// e.g. claude-opus-4-8 would otherwise be billed at $3/$15 not $5/$25. The
|
|
28
|
+
// Opus tier dropped to $5/$25 with Opus 4.5; Haiku 4.5 is $1/$5.
|
|
29
|
+
"claude-opus-4-8": { input: 5, output: 25 },
|
|
30
|
+
"claude-opus-4-7": { input: 5, output: 25 },
|
|
31
|
+
"claude-opus-4-6": { input: 5, output: 25 },
|
|
32
|
+
"claude-opus-4-5": { input: 5, output: 25 },
|
|
24
33
|
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
25
|
-
"claude-
|
|
26
|
-
"claude-haiku-4-5-20251001": { input:
|
|
34
|
+
"claude-haiku-4-5": { input: 1, output: 5 },
|
|
35
|
+
"claude-haiku-4-5-20251001": { input: 1, output: 5 },
|
|
36
|
+
"claude-fable-5": { input: 10, output: 50 },
|
|
27
37
|
_default: { input: 3, output: 15 },
|
|
28
38
|
},
|
|
29
39
|
deepseek: {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort cross-process advisory lock around a state file.
|
|
3
|
+
*
|
|
4
|
+
* Multiple `cc` processes (and, for some dirs, the desktop app) can do
|
|
5
|
+
* read-modify-write on the same JSON state file. An atomic temp+rename makes
|
|
6
|
+
* each write crash-safe but does NOT prevent a lost update: A reads, B reads, A
|
|
7
|
+
* writes, B writes → A's change is gone. This serializes the whole
|
|
8
|
+
* read-mutate-write across processes using a lock DIRECTORY — `mkdir` is
|
|
9
|
+
* atomic-exclusive on POSIX and Windows, so exactly one process holds it.
|
|
10
|
+
*
|
|
11
|
+
* SAFETY — this is intentionally best-effort. If the lock can't be acquired
|
|
12
|
+
* within `timeoutMs`, it PROCEEDS WITHOUT THE LOCK rather than hanging or
|
|
13
|
+
* throwing: worst case degrades to the pre-lock behavior (a rare lost update),
|
|
14
|
+
* never a frozen CLI. A lock left by a crashed holder is reclaimed once its
|
|
15
|
+
* mtime is older than `staleMs`. The lock is always released in `finally`.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} targetPath the file being guarded (lock is `${targetPath}.lock`)
|
|
18
|
+
* @param {(ctx:{locked:boolean})=>T} fn critical section; receives whether the lock was held
|
|
19
|
+
* @returns {T} whatever `fn` returns
|
|
20
|
+
*/
|
|
21
|
+
export function withFileLock(targetPath, fn, opts = {}) {
|
|
22
|
+
const {
|
|
23
|
+
timeoutMs = 2000,
|
|
24
|
+
staleMs = 30000,
|
|
25
|
+
retryMs = 25,
|
|
26
|
+
_fs = defaultFs,
|
|
27
|
+
_now = () => Date.now(),
|
|
28
|
+
_sleep = sleepSync,
|
|
29
|
+
} = opts;
|
|
30
|
+
|
|
31
|
+
const lockDir = `${targetPath}.lock`;
|
|
32
|
+
let held = false;
|
|
33
|
+
const deadline = _now() + timeoutMs;
|
|
34
|
+
|
|
35
|
+
for (;;) {
|
|
36
|
+
try {
|
|
37
|
+
_fs.mkdirSync(lockDir);
|
|
38
|
+
held = true;
|
|
39
|
+
break;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (!err || err.code !== "EEXIST") break; // unexpected fs error → run unlocked
|
|
42
|
+
// Reclaim a stale lock left by a crashed holder.
|
|
43
|
+
try {
|
|
44
|
+
const age = _now() - _fs.statSync(lockDir).mtimeMs;
|
|
45
|
+
if (age > staleMs) {
|
|
46
|
+
_fs.rmSync(lockDir, { recursive: true, force: true });
|
|
47
|
+
continue; // retry acquisition immediately
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
/* stat lost a race with the holder releasing — fall through to wait */
|
|
51
|
+
}
|
|
52
|
+
if (_now() >= deadline) break; // timed out → run unlocked (best-effort)
|
|
53
|
+
_sleep(retryMs);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
return fn({ locked: held });
|
|
59
|
+
} finally {
|
|
60
|
+
if (held) {
|
|
61
|
+
try {
|
|
62
|
+
_fs.rmSync(lockDir, { recursive: true, force: true });
|
|
63
|
+
} catch {
|
|
64
|
+
/* best-effort release */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
import fs from "node:fs";
|
|
71
|
+
const defaultFs = {
|
|
72
|
+
mkdirSync: (p) => fs.mkdirSync(p),
|
|
73
|
+
statSync: (p) => fs.statSync(p),
|
|
74
|
+
rmSync: (p, o) => fs.rmSync(p, o),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Synchronous sleep that doesn't busy-spin the CPU when possible. */
|
|
78
|
+
function sleepSync(ms) {
|
|
79
|
+
try {
|
|
80
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
81
|
+
} catch {
|
|
82
|
+
const end = Date.now() + ms; // SharedArrayBuffer unavailable — bounded spin
|
|
83
|
+
while (Date.now() < end) {
|
|
84
|
+
/* spin */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -286,9 +286,19 @@ export async function startAgentRepl(options = {}) {
|
|
|
286
286
|
// an auto-detected image path so the REPL switches to a vision-capable model
|
|
287
287
|
// for that turn only (resolveVisionLlm falls back to the default when unset).
|
|
288
288
|
let _visionModel;
|
|
289
|
+
// Hard stream inactivity timeout override (config.llm.streamStallTimeoutMs,
|
|
290
|
+
// ms): a stream silent that long is aborted + retried instead of hanging.
|
|
291
|
+
// Unset → agent-core's 180s default (matches cc chat/ask). Set to 0 to
|
|
292
|
+
// disable. Left undefined here means "use the default".
|
|
293
|
+
let _streamStallTimeoutMs;
|
|
289
294
|
try {
|
|
290
295
|
const { loadConfig } = await import("../lib/config-manager.js");
|
|
291
|
-
|
|
296
|
+
const _cfg = loadConfig();
|
|
297
|
+
_visionModel = _cfg?.llm?.visionModel || undefined;
|
|
298
|
+
const raw = _cfg?.llm?.streamStallTimeoutMs;
|
|
299
|
+
const t = Number(raw);
|
|
300
|
+
// Accept 0 (explicit disable) — only ignore absent/invalid values.
|
|
301
|
+
if (raw != null && Number.isFinite(t) && t >= 0) _streamStallTimeoutMs = t;
|
|
292
302
|
} catch {
|
|
293
303
|
/* optional — resolveVisionLlm falls back to DEFAULT_VISION_MODEL */
|
|
294
304
|
}
|
|
@@ -721,6 +731,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
721
731
|
// an IDE integrated terminal. --ide forces it, --no-ide disables it
|
|
722
732
|
// (parity with headless; auto-detect already works via process.env).
|
|
723
733
|
ide: options.ide,
|
|
734
|
+
pdh: options.pdh,
|
|
724
735
|
cwd: process.cwd(),
|
|
725
736
|
// advertise the session id to spawned stdio MCP servers
|
|
726
737
|
sessionId,
|
|
@@ -2463,12 +2474,19 @@ export async function startAgentRepl(options = {}) {
|
|
|
2463
2474
|
process.stdout.write(chalk.gray(` [step] ${evt.step}\n`));
|
|
2464
2475
|
};
|
|
2465
2476
|
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2477
|
+
// Attach the live-output listeners ONCE per REPL session — the agent is
|
|
2478
|
+
// a session-scoped singleton (created once above), so re-attaching on
|
|
2479
|
+
// every `/auto` would accumulate listeners (duplicate output + leak +
|
|
2480
|
+
// MaxListenersExceededWarning). The handlers are stateless.
|
|
2481
|
+
if (!autonomousAgent._replListenersAttached) {
|
|
2482
|
+
autonomousAgent._replListenersAttached = true;
|
|
2483
|
+
autonomousAgent.on("goal:completed", goalListener);
|
|
2484
|
+
autonomousAgent.on("goal:failed", goalListener);
|
|
2485
|
+
autonomousAgent.on("step:started", stepListener);
|
|
2486
|
+
autonomousAgent.on("step:completed", (evt) => {
|
|
2487
|
+
process.stdout.write(chalk.green(` [done] ${evt.step}\n`));
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2472
2490
|
|
|
2473
2491
|
logger.info(`Submitting goal: ${chalk.cyan(autoArg)}`);
|
|
2474
2492
|
try {
|
|
@@ -3249,6 +3267,20 @@ export async function startAgentRepl(options = {}) {
|
|
|
3249
3267
|
` ⟳ connection dropped — retrying (attempt ${attempt})…\n`,
|
|
3250
3268
|
),
|
|
3251
3269
|
),
|
|
3270
|
+
// Stream-stall hint (Claude-Code 2.1.185): the connection is alive but
|
|
3271
|
+
// the API has gone silent mid-response — tell the user we're still
|
|
3272
|
+
// waiting instead of leaving a frozen spinner. stderr so it never
|
|
3273
|
+
// corrupts the streamed answer on stdout.
|
|
3274
|
+
onStall: (ms) =>
|
|
3275
|
+
process.stderr.write(
|
|
3276
|
+
chalk.dim(
|
|
3277
|
+
` ⏳ waiting for API response (silent ${Math.round(ms / 1000)}s)…\n`,
|
|
3278
|
+
),
|
|
3279
|
+
),
|
|
3280
|
+
// Hard inactivity timeout: abort + retry a dead-but-open stream instead
|
|
3281
|
+
// of hanging forever. undefined → agent-core's 180s default (matches cc
|
|
3282
|
+
// chat/ask); config.llm.streamStallTimeoutMs tunes or disables (0).
|
|
3283
|
+
streamStallTimeoutMs: _streamStallTimeoutMs,
|
|
3252
3284
|
signal: _turnAbort.signal,
|
|
3253
3285
|
// On an auto-detected image turn, switch to the vision LLM for this
|
|
3254
3286
|
// turn only (provider/baseUrl/apiKey unchanged, model → vision model).
|
package/src/repl/chat-repl.js
CHANGED
|
@@ -183,6 +183,13 @@ export async function startChatRepl(options = {}) {
|
|
|
183
183
|
const onUsage = (u) => {
|
|
184
184
|
capturedUsage = u;
|
|
185
185
|
};
|
|
186
|
+
// Stall hint (cc agent parity): the connection is alive but the API has
|
|
187
|
+
// gone silent — reassure instead of a frozen cursor. stderr so it never
|
|
188
|
+
// corrupts the streamed answer on stdout.
|
|
189
|
+
const onStall = (ms) =>
|
|
190
|
+
process.stderr.write(
|
|
191
|
+
`\x1b[2m ⏳ waiting for API response (silent ${Math.round(ms / 1000)}s)…\x1b[0m\n`,
|
|
192
|
+
);
|
|
186
193
|
|
|
187
194
|
if (sessionId)
|
|
188
195
|
appendEvent(sessionId, "user_message", { content: trimmed });
|
|
@@ -194,6 +201,7 @@ export async function startChatRepl(options = {}) {
|
|
|
194
201
|
baseUrl,
|
|
195
202
|
onToken,
|
|
196
203
|
onUsage,
|
|
204
|
+
onStall,
|
|
197
205
|
);
|
|
198
206
|
} else if (provider === "anthropic") {
|
|
199
207
|
const providerDef = BUILT_IN_PROVIDERS.anthropic;
|
|
@@ -215,6 +223,7 @@ export async function startChatRepl(options = {}) {
|
|
|
215
223
|
key,
|
|
216
224
|
onToken,
|
|
217
225
|
onUsage,
|
|
226
|
+
onStall,
|
|
218
227
|
);
|
|
219
228
|
} else {
|
|
220
229
|
// OpenAI-compatible providers (openai, volcengine, deepseek, dashscope, mistral, gemini, anthropic-proxy)
|
|
@@ -237,6 +246,7 @@ export async function startChatRepl(options = {}) {
|
|
|
237
246
|
key,
|
|
238
247
|
onToken,
|
|
239
248
|
onUsage,
|
|
249
|
+
onStall,
|
|
240
250
|
);
|
|
241
251
|
}
|
|
242
252
|
|
package/src/repl/session-cost.js
CHANGED
|
@@ -24,7 +24,14 @@ function fmtUsd(n) {
|
|
|
24
24
|
/** A fresh accumulator: running totals + per provider/model rows. */
|
|
25
25
|
export function newCostStore() {
|
|
26
26
|
return {
|
|
27
|
-
total: {
|
|
27
|
+
total: {
|
|
28
|
+
inputTokens: 0,
|
|
29
|
+
outputTokens: 0,
|
|
30
|
+
totalTokens: 0,
|
|
31
|
+
cacheReadTokens: 0,
|
|
32
|
+
cacheCreationTokens: 0,
|
|
33
|
+
calls: 0,
|
|
34
|
+
},
|
|
28
35
|
byKey: new Map(),
|
|
29
36
|
};
|
|
30
37
|
}
|
|
@@ -43,10 +50,16 @@ export function addUsage(store, events) {
|
|
|
43
50
|
const inT = num(u.input_tokens ?? u.prompt_tokens ?? u.inputTokens);
|
|
44
51
|
const outT = num(u.output_tokens ?? u.completion_tokens ?? u.outputTokens);
|
|
45
52
|
const totT = num(u.total_tokens ?? u.totalTokens ?? inT + outT);
|
|
46
|
-
|
|
53
|
+
// Prompt-cache token counts (Anthropic caching) so /cost prices cached
|
|
54
|
+
// reads/writes correctly; absent on non-caching providers → 0.
|
|
55
|
+
const cR = num(u.cache_read_input_tokens ?? u.cacheReadTokens);
|
|
56
|
+
const cC = num(u.cache_creation_input_tokens ?? u.cacheCreationTokens);
|
|
57
|
+
if (inT === 0 && outT === 0 && totT === 0 && cR === 0 && cC === 0) continue;
|
|
47
58
|
s.total.inputTokens += inT;
|
|
48
59
|
s.total.outputTokens += outT;
|
|
49
60
|
s.total.totalTokens += totT;
|
|
61
|
+
s.total.cacheReadTokens += cR;
|
|
62
|
+
s.total.cacheCreationTokens += cC;
|
|
50
63
|
s.total.calls += 1;
|
|
51
64
|
const key = `${ev.provider || "?"}/${ev.model || "?"}`;
|
|
52
65
|
let row = s.byKey.get(key);
|
|
@@ -57,6 +70,8 @@ export function addUsage(store, events) {
|
|
|
57
70
|
inputTokens: 0,
|
|
58
71
|
outputTokens: 0,
|
|
59
72
|
totalTokens: 0,
|
|
73
|
+
cacheReadTokens: 0,
|
|
74
|
+
cacheCreationTokens: 0,
|
|
60
75
|
calls: 0,
|
|
61
76
|
};
|
|
62
77
|
s.byKey.set(key, row);
|
|
@@ -64,6 +79,8 @@ export function addUsage(store, events) {
|
|
|
64
79
|
row.inputTokens += inT;
|
|
65
80
|
row.outputTokens += outT;
|
|
66
81
|
row.totalTokens += totT;
|
|
82
|
+
row.cacheReadTokens += cR;
|
|
83
|
+
row.cacheCreationTokens += cC;
|
|
67
84
|
row.calls += 1;
|
|
68
85
|
}
|
|
69
86
|
return s;
|
|
@@ -172,10 +189,21 @@ export function renderSessionCost(store, { pricingOverrides, roles } = {}) {
|
|
|
172
189
|
` total: ${fmtUsd(result.cost.totalCost)} USD ` +
|
|
173
190
|
`(${result.total.totalTokens.toLocaleString()} tokens, ${result.total.calls} calls)`,
|
|
174
191
|
);
|
|
192
|
+
// Cache line only when caching actually happened this session.
|
|
193
|
+
const cacheRead = num(result.total.cacheReadTokens);
|
|
194
|
+
const cacheWrite = num(result.total.cacheCreationTokens);
|
|
195
|
+
if (cacheRead || cacheWrite) {
|
|
196
|
+
lines.push(
|
|
197
|
+
` cache: ${cacheRead.toLocaleString()} read + ${cacheWrite.toLocaleString()} write tokens`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
175
200
|
for (const row of result.byModel) {
|
|
176
201
|
const provider = (row.provider || "?").padEnd(10);
|
|
177
202
|
const model = (row.model || "?").padEnd(24);
|
|
178
|
-
const
|
|
203
|
+
const cacheSuffix = num(row.cacheReadTokens)
|
|
204
|
+
? ` cache_read=${row.cacheReadTokens}`
|
|
205
|
+
: "";
|
|
206
|
+
const tokens = `in=${row.inputTokens} out=${row.outputTokens}${cacheSuffix}`;
|
|
179
207
|
const price = row.free
|
|
180
208
|
? "free (local)"
|
|
181
209
|
: row.matched
|