chainlesschain 0.166.99 → 0.166.101
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 +7 -8
- package/package.json +2 -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/assets/{AIOps-BUgaCgVU.js → AIOps-BjBS_29I.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-b0MpUSSZ.js → ActionButton-CUnnQUaj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-hbXTvHqO.js → Analytics-BqYZxgnY.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CLQoAVu4.js → AppLayout-m8B886ka.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-ytHzl2L2.js → Artifacts-JEOxU3Ug.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BuhkLm0C.js → Audit-DAXS5c9J.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CS81hxZV.js → BackgroundAgents-e3a9XHw3.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DzR-_Qvi.js → Backup-D3Qmp68h.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-r8Ku_lYw.js → BaseInput-C7CXyiUl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C3Uxtoaa.js → Chat-2AHlAGi8.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D3KsyCUV.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BnG2P5yc.js → Checkbox-YfBuQlBH.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BdgU6HZy.js → Codegen-DCCQDfTL.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DT8t9wmf.js → Col-03i2WfrV.js} +1 -1
- package/src/assets/web-panel/assets/{Community-ChkxR5oe.js → Community-CJQIpng6.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DIwLeTk3.js → Compact-CCtcQ2pq.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CAqzGLvW.js → Compliance-Vxl7ZszJ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bb3jejMG.js → Cowork-DN-BwA8I.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BYRkZLta.js → Cron-Dc2Q46X7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BD6z227t.js → Crosschain-CQCblsl-.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DA3G5Wgo.js → DID-YYvqUR8C.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-yaZePHOu.js → Dashboard-Cafy3DZT.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BqY5MBx2.js → Dropdown-BOyYpIpv.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-cIWwOgHm.js → EmailListRenderer-B7NYjc1j.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-zvV6ymca.js → EvolutionSettings-C5wfxztf.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CAmh1U2b.js → FamilyGuardDashboard-aA7KnDGK.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-5qd2LCg8.js → Federation-DlgXUu1L.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Bdkj1sRJ.js → FormItemContext-B5YsrLDg.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B-gWN2P3.js → GenericCardRenderer-DaWojPFw.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BEMh_NGd.js → Git-dkhh6Fx2.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-qLGvA2Hw.js → Governance-Cz6vmkjc.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DblBwYuW.js → Inference-DWt0hbh8.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BISegVD_.js → KnowledgeGraph-BZJ0Z-nL.js} +1 -1
- package/src/assets/web-panel/assets/Logs-BaBBmL1A.js +2 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-C1yByobo.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CHKXMOkH.js → Marketplace-ct8sgQ0s.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B5G1elqd.js → McpTools-CXKCRGYQ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-O5Ib0uTR.js → Memory-O7ZwlS6a.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BaGYL7rT.js → MobileBridge-By12C-eC.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Ba-EVdex.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CNjJ97jg.js → Mtc-g-932zV2.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-DjBN-Wqz.js → MtcAudit-BW8hB0ly.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-B850R9lO.js → Multisig-zspLaMbT.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bn8sbFfV.js → NLProgramming-dJeUPDoq.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BpLmMp3Q.js → Notes-C4msiRQL.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Dbk97rjb.js → NotificationSettings-MOCHM-qg.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BOBX_I72.js → OrderTableRenderer-CX0T66NM.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B45DpOuE.js → Organization-DOZurBfk.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DmKH7eer.js → Overflow-qOT9BhUp.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CC4FaXTl.js → P2P-BMoy7Eo4.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C2SgP4PI.js → PdhVaultBrowser-ByyV1q-D.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BQB4QFj_.js → Permissions-6Qyro4ye.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dy6GGpn4.js → PersonalDataHub-DR3DRNSX.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-B9HB5pRc.js → Pipeline-Cr6TTdud.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D7pOmg-p.js → Privacy-B70otG0l.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DglBqvec.js → ProjectInit-BHCqxoSk.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Bho22znM.js → ProjectSettings-B80GZpZk.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-C0bSuM8U.js → Projects-cq-b9-fQ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BgAgo1Yr.js → Providers-dsVTimHa.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C2fBjCC8.js → QrScannerModal-DRJ8YepZ.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C8MiF-rk.js → QuickAsk-BSXdEjIB.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-D_TzHdQf.js → Recommend-CQLJGyKC.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BHO7b6xE.js → RemoteSession-BF9WUFVZ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BPxNmNhD.js → Reputation-Cy9o5tvW.js} +1 -1
- package/src/assets/web-panel/assets/{Row-fwhot5Bk.js → Row-BP346_3b.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CZbD1B-5.js → RssFeed-BKulxXf3.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BRs6YpAE.js → Search-EemQj2Dv.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CB344tKC.js → Security-DcWPAwwk.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Beo-7fEl.js → Services-Bltqciay.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CDXx96vD.js → Skeleton-BwRyOGY8.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-b-mQTJK4.js → Skills-D98qlvpJ.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BvjP-IS9.js → Sla-BUTtBMY0.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C6wsV-cO.js → SpeechSettings-C7kcY_98.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BQZoko-e.js → SyncSettings-Bsk6lKvT.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-6Z0rJJVO.js → Tasks-DvSrtz0P.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Cc9IXtEQ.js → Templates-BUM6LwPs.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BLc7AjOI.js → Tenant-D9TkOm7z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CUN49v85.js → Terminal-CP8ZVErN.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CX-SksHN.js → TimelineRenderer-V3SoKZ1T.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Btrm0-rK.js → Tokens-CCRlUTOA.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CIboUvKe.js → Trigger-CznUcPme.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Rv9jEs5v.js → Trust-D1GIk_NF.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B_xomdD5.js → UkeySign-DHtvtyC-.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CrvWeMow.js → VideoEditing-CVxdaJIq.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BjvWgk_7.js → Wallet-D221OjyE.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CwfpFw92.js → WebAuthn-Cw0-ESsq.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Dkk2QsOe.js → WorkflowEditor-ZYScrBx3.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Dah1DVes.js → chat-BU0e1obR.js} +1 -1
- package/src/assets/web-panel/assets/{colors-JEpgQ10k.js → colors-gzYweuha.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item--DuG_cxf.js → compact-item-ChAXal_k.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BJ_GZvon.js → createContext-of3FolD7.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Pa13TVrO.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-yZRLDmWK.js → hasIn-4PsK_Kx8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQ4Fch3e.js → index-1dzYRBbg.js} +1 -1
- package/src/assets/web-panel/assets/{index-L9CFyRIi.js → index-B5L7zddz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxnkldGo.js → index-BOeX7BzS.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6Le2m3d.js → index-BdxRVNPY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaDLWIWo.js → index-BfI4dXbi.js} +1 -1
- package/src/assets/web-panel/assets/{index-o2qO_dYf.js → index-Bftq5rJR.js} +1 -1
- package/src/assets/web-panel/assets/index-BhQg-v6V.js +1 -0
- package/src/assets/web-panel/assets/{index-DhX48E81.js → index-By-pper5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bdg_nWD1.js → index-CCLsbul3.js} +1 -1
- package/src/assets/web-panel/assets/{index-cHx5KzUt.js → index-CGFL59q4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DemMuw7n.js → index-CHv_63Cp.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZBesaCN.js → index-CO2owKe0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxB49Mow.js → index-CeEE_SGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKl40wrO.js → index-Cn1clrxx.js} +1 -1
- package/src/assets/web-panel/assets/{index-VIgVth92.js → index-Cx4Z6nUl.js} +1 -1
- package/src/assets/web-panel/assets/{index-kZE-K3ZO.js → index-CxzBcXOa.js} +1 -1
- package/src/assets/web-panel/assets/{index-rkvSOceQ.js → index-CyDQNprx.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cmr0AzcK.js → index-D-Zss5KL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DddwZCT9.js → index-D1PGrfHE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUjyj7qR.js → index-DDYj14Q4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjuD8smQ.js → index-DEB5fQqG.js} +1 -1
- package/src/assets/web-panel/assets/{index-asia_JPc.js → index-DEXBckNR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFJbJ8rs.js → index-DEzjUgkl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFj7ylAJ.js → index-DJVrzXtk.js} +1 -1
- package/src/assets/web-panel/assets/index-DUD5o-vW.js +1 -0
- package/src/assets/web-panel/assets/{index-DroaQypF.js → index-DZ0oKtBg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbMGGcpZ.js → index-D_5LTPMH.js} +1 -1
- package/src/assets/web-panel/assets/{index-IjOa5lYn.js → index-D_9k680X.js} +1 -1
- package/src/assets/web-panel/assets/{index-vdTrEOj7.js → index-D_ialOHx.js} +1 -1
- package/src/assets/web-panel/assets/{index-9L3Pg2tc.js → index-DjRAIRX7.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dq-8PFhL.js → index-Djfgsjfy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJLz3SpM.js → index-DsoV9rzn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWkKE8Oj.js → index-Dy0ojH6G.js} +3 -3
- package/src/assets/web-panel/assets/{index-C8nXkCMf.js → index-E8oDMSAo.js} +1 -1
- package/src/assets/web-panel/assets/{index-C56b8e9s.js → index-Ef5YkVAn.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6ohEAEV.js → index-Wr91cnWr.js} +1 -1
- package/src/assets/web-panel/assets/{index-1QZASeW1.js → index-cFkMIx1x.js} +1 -1
- package/src/assets/web-panel/assets/{index-BM7xJKGD.js → index-wo0De0iG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQedFuDw.js → index-yMf7zmWv.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-anKyyjGc.js → initDefaultProps-D0Bx6pgr.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Cqp0nx5A.js → motion-BnLuJqJ5.js} +1 -1
- package/src/assets/web-panel/assets/{move-Ca3mgkM2.js → move-Bxpq9xgR.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BmwuhJ3z.js → mtc-parser-BdmrNhOs.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C9Mf05WJ.js → omit-tjJD34BZ.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-2PbxB9Ds.js → pickAttrs-CMFzLt7b.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CTVbOFHt.js → placementArrow-BZf6O3cd.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Nrm8Vbmr.js → responsiveObserve-DFDLazsV.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Dh4r1zqR.js → slide-CMbTvp4Y.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-vzNoQL-r.js → statusUtils-FOan8MI7.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BXpGfNkI.js → styleChecker-DTABspd4.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CVWBRdhs.js → useFlexGapSupport-CNHXWBpY.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BJWMLffI.js → useFs-5K-c_RoL.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CWCe5VHG.js → usePersonalDataHub-Di0hL4G-.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D-NBEb8m.js → vnode-DTyDv4ja.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C0mnhfGf.js → zoom-ontRt8ft.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/eval.js +11 -8
- package/src/data/changelog.json +26 -0
- package/src/harness/provider-backed-compaction.js +13 -5
- package/src/harness/worktree-isolator.js +20 -8
- package/src/lib/chat-core.js +57 -25
- package/src/lib/context-memory-kernel/live-compaction.js +142 -61
- package/src/lib/eval/verify01-execution-window.js +70 -0
- package/src/lib/eval/verify01-execution.js +9 -1
- package/src/lib/eval/verify01-host-capture.js +6 -2
- package/src/lib/formal-budget-authority.js +533 -0
- package/src/lib/formal-budget-contract.js +609 -0
- package/src/lib/formal-budget-transport.js +345 -0
- package/src/lib/provider-http-error.js +16 -0
- package/src/lib/stream-retry.js +195 -1
- package/src/runtime/agent-core.js +272 -32
- package/src/runtime/headless-stream.js +136 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-RvblDu77.js +0 -1
- package/src/assets/web-panel/assets/Logs-BD2UR7kE.js +0 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-B-TlDCmr.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-Dsctbob8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-B6kQEceX.js +0 -1
- package/src/assets/web-panel/assets/index-CSeiH6HA.js +0 -1
- package/src/assets/web-panel/assets/index-CayGTz6d.js +0 -1
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import {
|
|
2
|
+
budgetError,
|
|
3
|
+
canonical,
|
|
4
|
+
requireBudget,
|
|
5
|
+
} from "./formal-budget-contract.js";
|
|
6
|
+
|
|
7
|
+
export const FORMAL_BUDGET_RUNTIME_READINESS = Object.freeze({
|
|
8
|
+
paidReady: false,
|
|
9
|
+
reason:
|
|
10
|
+
"Agent, Chat, compaction, advisor, probes, retries and child-process egress are not yet all routed through this authority",
|
|
11
|
+
});
|
|
12
|
+
export function requireFormalBudgetRuntime() {
|
|
13
|
+
throw budgetError(
|
|
14
|
+
"CC_BUDGET_RUNTIME_NOT_COVERED",
|
|
15
|
+
FORMAL_BUDGET_RUNTIME_READINESS.reason,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// JSON.parse alone accepts duplicate keys (including escaped aliases) and
|
|
20
|
+
// overflows numeric literals to Infinity. Neither is safe accounting evidence.
|
|
21
|
+
export function parseBudgetJson(text) {
|
|
22
|
+
requireBudget(typeof text === "string", "JSON text required");
|
|
23
|
+
let at = 0;
|
|
24
|
+
const whitespace = () => {
|
|
25
|
+
while (at < text.length && /[\x20\t\r\n]/u.test(text[at])) at++;
|
|
26
|
+
};
|
|
27
|
+
function string() {
|
|
28
|
+
requireBudget(text[at] === '"', "JSON string expected");
|
|
29
|
+
const start = at++;
|
|
30
|
+
while (at < text.length) {
|
|
31
|
+
const char = text[at++];
|
|
32
|
+
if (char === '"') return JSON.parse(text.slice(start, at));
|
|
33
|
+
if (char === "\\") at++;
|
|
34
|
+
}
|
|
35
|
+
requireBudget(false, "Unterminated JSON string");
|
|
36
|
+
}
|
|
37
|
+
function value(depth) {
|
|
38
|
+
requireBudget(depth <= 64, "JSON nesting exceeds accounting parser bound");
|
|
39
|
+
whitespace();
|
|
40
|
+
if (text[at] === '"') {
|
|
41
|
+
string();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (text[at] === "{") {
|
|
45
|
+
at++;
|
|
46
|
+
whitespace();
|
|
47
|
+
const keys = new Set();
|
|
48
|
+
if (text[at] === "}") {
|
|
49
|
+
at++;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
for (;;) {
|
|
53
|
+
whitespace();
|
|
54
|
+
const key = string();
|
|
55
|
+
requireBudget(
|
|
56
|
+
!keys.has(key),
|
|
57
|
+
"Duplicate JSON member in provider evidence",
|
|
58
|
+
);
|
|
59
|
+
keys.add(key);
|
|
60
|
+
whitespace();
|
|
61
|
+
requireBudget(text[at++] === ":", "JSON member separator expected");
|
|
62
|
+
value(depth + 1);
|
|
63
|
+
whitespace();
|
|
64
|
+
const separator = text[at++];
|
|
65
|
+
if (separator === "}") return;
|
|
66
|
+
requireBudget(separator === ",", "JSON object separator expected");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (text[at] === "[") {
|
|
70
|
+
at++;
|
|
71
|
+
whitespace();
|
|
72
|
+
if (text[at] === "]") {
|
|
73
|
+
at++;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
for (;;) {
|
|
77
|
+
value(depth + 1);
|
|
78
|
+
whitespace();
|
|
79
|
+
const separator = text[at++];
|
|
80
|
+
if (separator === "]") return;
|
|
81
|
+
requireBudget(separator === ",", "JSON array separator expected");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const literal of ["true", "false", "null"])
|
|
85
|
+
if (text.startsWith(literal, at)) {
|
|
86
|
+
at += literal.length;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const number = text
|
|
90
|
+
.slice(at)
|
|
91
|
+
.match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/u)?.[0];
|
|
92
|
+
requireBudget(
|
|
93
|
+
number && Number.isFinite(Number(number)),
|
|
94
|
+
"Invalid or non-finite JSON number",
|
|
95
|
+
);
|
|
96
|
+
at += number.length;
|
|
97
|
+
}
|
|
98
|
+
value(0);
|
|
99
|
+
whitespace();
|
|
100
|
+
requireBudget(at === text.length, "Trailing JSON input");
|
|
101
|
+
return JSON.parse(text);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Allowlist only; the caller must still supply the existing OS sandbox. */
|
|
105
|
+
export function formalBudgetTaskEnvironment(environment = process.env) {
|
|
106
|
+
return Object.fromEntries(
|
|
107
|
+
[
|
|
108
|
+
"PATH",
|
|
109
|
+
"Path",
|
|
110
|
+
"SystemRoot",
|
|
111
|
+
"SYSTEMROOT",
|
|
112
|
+
"WINDIR",
|
|
113
|
+
"ComSpec",
|
|
114
|
+
"PATHEXT",
|
|
115
|
+
"TEMP",
|
|
116
|
+
"TMP",
|
|
117
|
+
"TMPDIR",
|
|
118
|
+
"LANG",
|
|
119
|
+
"LC_ALL",
|
|
120
|
+
"LC_CTYPE",
|
|
121
|
+
]
|
|
122
|
+
.filter((key) => typeof environment[key] === "string")
|
|
123
|
+
.map((key) => [key, environment[key]]),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function boundedText(response, maximumBytes) {
|
|
128
|
+
requireBudget(response.body?.getReader, "Response body unavailable");
|
|
129
|
+
const reader = response.body.getReader(),
|
|
130
|
+
chunks = [];
|
|
131
|
+
let size = 0;
|
|
132
|
+
try {
|
|
133
|
+
for (;;) {
|
|
134
|
+
const { done, value } = await reader.read();
|
|
135
|
+
if (done) break;
|
|
136
|
+
size += value.byteLength;
|
|
137
|
+
requireBudget(
|
|
138
|
+
size <= maximumBytes,
|
|
139
|
+
"Provider response exceeded bounded parser",
|
|
140
|
+
);
|
|
141
|
+
chunks.push(Buffer.from(value));
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
try {
|
|
145
|
+
await reader.cancel();
|
|
146
|
+
} catch {
|
|
147
|
+
/* keep original unknown outcome */
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
} finally {
|
|
151
|
+
reader.releaseLock();
|
|
152
|
+
}
|
|
153
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
154
|
+
Buffer.concat(chunks),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
function streamReceipt(text) {
|
|
158
|
+
let terminal = false,
|
|
159
|
+
receipt = null,
|
|
160
|
+
usage = null,
|
|
161
|
+
finishReason = null;
|
|
162
|
+
for (const frame of text.replaceAll("\r\n", "\n").split("\n\n")) {
|
|
163
|
+
if (!frame.trim()) continue;
|
|
164
|
+
const data = frame
|
|
165
|
+
.split("\n")
|
|
166
|
+
.filter((line) => line.startsWith("data:"))
|
|
167
|
+
.map((line) => line.slice(5).trimStart())
|
|
168
|
+
.join("\n");
|
|
169
|
+
if (!data) continue;
|
|
170
|
+
requireBudget(!terminal, "Unexpected stream data after terminal marker");
|
|
171
|
+
if (data === "[DONE]") {
|
|
172
|
+
requireBudget(
|
|
173
|
+
finishReason !== null,
|
|
174
|
+
"Stream ended without a completed choice",
|
|
175
|
+
);
|
|
176
|
+
terminal = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const row = parseBudgetJson(data);
|
|
180
|
+
requireBudget(row && !Object.hasOwn(row, "error"), "Provider stream error");
|
|
181
|
+
const identity = {
|
|
182
|
+
id: row.id,
|
|
183
|
+
model: row.model,
|
|
184
|
+
service_tier: row.service_tier,
|
|
185
|
+
};
|
|
186
|
+
if (!receipt) receipt = identity;
|
|
187
|
+
else {
|
|
188
|
+
// Some providers only send service_tier on the final usage frame.
|
|
189
|
+
requireBudget(
|
|
190
|
+
row.id === receipt.id && row.model === receipt.model,
|
|
191
|
+
"Stream identity changed",
|
|
192
|
+
);
|
|
193
|
+
if (row.service_tier !== undefined) {
|
|
194
|
+
requireBudget(
|
|
195
|
+
receipt.service_tier === undefined ||
|
|
196
|
+
row.service_tier === receipt.service_tier,
|
|
197
|
+
"Stream tier changed",
|
|
198
|
+
);
|
|
199
|
+
receipt.service_tier = row.service_tier;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
requireBudget(
|
|
203
|
+
Array.isArray(row.choices) &&
|
|
204
|
+
row.choices.length <= 1 &&
|
|
205
|
+
row.choices.every((choice) => choice.index === 0),
|
|
206
|
+
"Multiple/unknown streamed completions",
|
|
207
|
+
);
|
|
208
|
+
for (const choice of row.choices) {
|
|
209
|
+
requireBudget(
|
|
210
|
+
finishReason === null,
|
|
211
|
+
"Choice data received after completed stream choice",
|
|
212
|
+
);
|
|
213
|
+
if (choice.finish_reason != null) {
|
|
214
|
+
requireBudget(
|
|
215
|
+
["stop", "length", "tool_calls"].includes(choice.finish_reason),
|
|
216
|
+
"Unsupported stream finish reason",
|
|
217
|
+
);
|
|
218
|
+
finishReason = choice.finish_reason;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (row.usage != null) {
|
|
222
|
+
requireBudget(
|
|
223
|
+
finishReason !== null,
|
|
224
|
+
"Usage received before complete stream choice",
|
|
225
|
+
);
|
|
226
|
+
requireBudget(
|
|
227
|
+
usage === null || canonical(usage) === canonical(row.usage),
|
|
228
|
+
"Conflicting streamed usage",
|
|
229
|
+
);
|
|
230
|
+
usage = row.usage;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
requireBudget(
|
|
234
|
+
terminal && finishReason !== null && receipt && usage,
|
|
235
|
+
"Incomplete stream or missing usage",
|
|
236
|
+
);
|
|
237
|
+
return { ...receipt, usage };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Trusted-host JSON/SSE transport: credential stays in this closure, and every
|
|
242
|
+
* physical request consumes a durable dispatch exactly once. No auto retries.
|
|
243
|
+
* This is not the unrestricted Agent/Chat transport and is not auto-enabled.
|
|
244
|
+
*/
|
|
245
|
+
export function createFormalBudgetTransport({
|
|
246
|
+
authority,
|
|
247
|
+
credentialProvider,
|
|
248
|
+
fetchImpl = globalThis.fetch,
|
|
249
|
+
maximumResponseBytes = 16 * 1024 * 1024,
|
|
250
|
+
timeoutMs = 60_000,
|
|
251
|
+
}) {
|
|
252
|
+
requireBudget(
|
|
253
|
+
authority &&
|
|
254
|
+
typeof credentialProvider === "function" &&
|
|
255
|
+
typeof fetchImpl === "function",
|
|
256
|
+
"Authority and trusted credential provider required",
|
|
257
|
+
);
|
|
258
|
+
requireBudget(
|
|
259
|
+
Number.isSafeInteger(maximumResponseBytes) &&
|
|
260
|
+
maximumResponseBytes > 0 &&
|
|
261
|
+
maximumResponseBytes <= 16 * 1024 * 1024,
|
|
262
|
+
"Invalid response byte bound",
|
|
263
|
+
);
|
|
264
|
+
requireBudget(
|
|
265
|
+
Number.isSafeInteger(timeoutMs) && timeoutMs > 0 && timeoutMs <= 300_000,
|
|
266
|
+
"Invalid timeout",
|
|
267
|
+
);
|
|
268
|
+
return Object.freeze({
|
|
269
|
+
async dispatch({ runId, callId, body, signal }) {
|
|
270
|
+
// Snapshot before await so caller mutation cannot alter the reserved body.
|
|
271
|
+
const request = JSON.parse(canonical(body));
|
|
272
|
+
authority.reserve({ runId, callId, body: request });
|
|
273
|
+
let settled = false;
|
|
274
|
+
try {
|
|
275
|
+
const reserved = authority.reservedContract(callId);
|
|
276
|
+
const credential = await credentialProvider({
|
|
277
|
+
contractId: reserved.id,
|
|
278
|
+
provider: reserved.provider,
|
|
279
|
+
endpoint: reserved.endpoint,
|
|
280
|
+
});
|
|
281
|
+
requireBudget(
|
|
282
|
+
typeof credential === "string" &&
|
|
283
|
+
credential.length > 0 &&
|
|
284
|
+
!/[\r\n]/u.test(credential),
|
|
285
|
+
"Credential unavailable",
|
|
286
|
+
);
|
|
287
|
+
const deadline = AbortSignal.timeout(timeoutMs);
|
|
288
|
+
const options = {
|
|
289
|
+
method: "POST",
|
|
290
|
+
redirect: "error",
|
|
291
|
+
headers: {
|
|
292
|
+
"Content-Type": "application/json",
|
|
293
|
+
Authorization: `Bearer ${credential}`,
|
|
294
|
+
},
|
|
295
|
+
body: JSON.stringify(request),
|
|
296
|
+
signal: signal ? AbortSignal.any([signal, deadline]) : deadline,
|
|
297
|
+
};
|
|
298
|
+
// Final expiry/request check is AFTER every awaited preparation. There
|
|
299
|
+
// must be no await between this durable admission and physical fetch.
|
|
300
|
+
const contract = authority.markDispatched(callId, request);
|
|
301
|
+
const response = await fetchImpl(contract.endpoint, options);
|
|
302
|
+
requireBudget(response.ok, "Provider HTTP response not successful");
|
|
303
|
+
const text = await boundedText(response, maximumResponseBytes);
|
|
304
|
+
let receipt;
|
|
305
|
+
if (request.stream === true) {
|
|
306
|
+
requireBudget(
|
|
307
|
+
response.headers.get("content-type")?.includes("text/event-stream"),
|
|
308
|
+
"Unexpected stream content type",
|
|
309
|
+
);
|
|
310
|
+
receipt = streamReceipt(text);
|
|
311
|
+
} else {
|
|
312
|
+
receipt = parseBudgetJson(text);
|
|
313
|
+
requireBudget(
|
|
314
|
+
receipt && !Object.hasOwn(receipt, "error"),
|
|
315
|
+
"Provider error envelope cannot settle",
|
|
316
|
+
);
|
|
317
|
+
requireBudget(
|
|
318
|
+
Array.isArray(receipt.choices) &&
|
|
319
|
+
receipt.choices.length === 1 &&
|
|
320
|
+
receipt.choices[0].index === 0 &&
|
|
321
|
+
["stop", "length", "tool_calls"].includes(
|
|
322
|
+
receipt.choices[0].finish_reason,
|
|
323
|
+
),
|
|
324
|
+
"Incomplete or multiple completion response",
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
const cost = authority.settle(callId, receipt);
|
|
328
|
+
settled = true;
|
|
329
|
+
return { body: text, streaming: request.stream === true, cost };
|
|
330
|
+
} catch {
|
|
331
|
+
if (!settled) {
|
|
332
|
+
// Do not persist provider errors which may contain credentials/content.
|
|
333
|
+
authority.markUnknown(
|
|
334
|
+
callId,
|
|
335
|
+
"transport or settlement outcome unknown",
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
throw budgetError(
|
|
339
|
+
"CC_BUDGET_TRANSPORT_UNKNOWN",
|
|
340
|
+
"Provider outcome unknown; reservation retained and further dispatch blocked",
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
}
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
* Keep structured provider diagnostics while redacting credential-shaped text.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import {
|
|
7
|
+
attachStreamRetryMetadata,
|
|
8
|
+
captureStreamRetryMetadata,
|
|
9
|
+
} from "./stream-retry.js";
|
|
10
|
+
|
|
6
11
|
function safePart(value, secrets = []) {
|
|
7
12
|
if (value == null) return "";
|
|
8
13
|
let text = String(value);
|
|
@@ -111,3 +116,14 @@ export async function formatProviderResponseError(
|
|
|
111
116
|
secrets,
|
|
112
117
|
});
|
|
113
118
|
}
|
|
119
|
+
|
|
120
|
+
/** Keep response status authoritative even if reading its error body fails. */
|
|
121
|
+
export async function createProviderResponseError(provider, response, options) {
|
|
122
|
+
const metadata = captureStreamRetryMetadata(response);
|
|
123
|
+
let message = await formatProviderResponseError(provider, response, options);
|
|
124
|
+
const remaining = metadata.retryAfterAt - Date.now();
|
|
125
|
+
if (Number.isFinite(remaining) && remaining > 0) {
|
|
126
|
+
message += ` Retry-After: wait at least ${Math.ceil(remaining / 1000)}s before an explicit retry.`;
|
|
127
|
+
}
|
|
128
|
+
return attachStreamRetryMetadata(new Error(message), metadata);
|
|
129
|
+
}
|
package/src/lib/stream-retry.js
CHANGED
|
@@ -12,11 +12,196 @@
|
|
|
12
12
|
* agent runtime.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
createAbortError,
|
|
17
|
+
isAbortError,
|
|
18
|
+
raceWithAbort,
|
|
19
|
+
throwIfAborted,
|
|
20
|
+
} from "./abort-utils.js";
|
|
16
21
|
|
|
17
22
|
/** Bounded auto-retry budget for streaming connection drops (Claude-Code 2.1.181). */
|
|
18
23
|
export const STREAM_RETRY_MAX = 2;
|
|
19
24
|
export const STREAM_RETRY_BASE_MS = 400;
|
|
25
|
+
export const STREAM_RETRY_MAX_DELAY_MS = 60_000;
|
|
26
|
+
|
|
27
|
+
function isFailureStatus(status) {
|
|
28
|
+
return Number.isInteger(status) && status >= 400 && status <= 599;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function retryAfterHeader(headers) {
|
|
32
|
+
if (!headers) return undefined;
|
|
33
|
+
if (typeof headers.get === "function") return headers.get("retry-after");
|
|
34
|
+
const entries = Object.entries(headers).filter(
|
|
35
|
+
([name]) => name.toLowerCase() === "retry-after",
|
|
36
|
+
);
|
|
37
|
+
// Multiple spellings or a list of values are ambiguous, not a delay.
|
|
38
|
+
return entries.length === 1 ? entries[0][1] : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function httpDate(value, now) {
|
|
42
|
+
// Accept the three HTTP-date wire formats, not Date.parse's permissive
|
|
43
|
+
// numeric/local-date syntax (e.g. "-1", "1.5", or "2026-10-12").
|
|
44
|
+
const weekday = "(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)";
|
|
45
|
+
const month = "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
|
|
46
|
+
let match = new RegExp(
|
|
47
|
+
`^(${weekday}), (\\d{2}) ${month} (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$`,
|
|
48
|
+
).exec(value);
|
|
49
|
+
let day, monthName, year, hour, minute, second, dayName;
|
|
50
|
+
if (match) {
|
|
51
|
+
[, dayName, day, monthName, year, hour, minute, second] = match;
|
|
52
|
+
} else {
|
|
53
|
+
match = new RegExp(
|
|
54
|
+
`^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\\d{2})-${month}-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$`,
|
|
55
|
+
).exec(value);
|
|
56
|
+
if (match) {
|
|
57
|
+
[, dayName, day, monthName, year, hour, minute, second] = match;
|
|
58
|
+
const currentYear = new Date(now).getUTCFullYear();
|
|
59
|
+
year = Math.floor(currentYear / 100) * 100 + Number(year);
|
|
60
|
+
if (year > currentYear + 50) year -= 100;
|
|
61
|
+
} else {
|
|
62
|
+
match = new RegExp(
|
|
63
|
+
`^(${weekday}) ${month} ( [1-9]|[12]\\d|3[01]) (\\d{2}):(\\d{2}):(\\d{2}) (\\d{4})$`,
|
|
64
|
+
).exec(value);
|
|
65
|
+
if (!match) return undefined;
|
|
66
|
+
[, dayName, monthName, day, hour, minute, second, year] = match;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const months = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" ");
|
|
70
|
+
const monthIndex = months.indexOf(monthName);
|
|
71
|
+
const date = new Date(0);
|
|
72
|
+
date.setUTCFullYear(Number(year), monthIndex, Number(day));
|
|
73
|
+
date.setUTCHours(Number(hour), Number(minute), Number(second), 0);
|
|
74
|
+
// Reject normalized invalid calendar dates/times and mismatched weekdays.
|
|
75
|
+
if (
|
|
76
|
+
date.getUTCFullYear() !== Number(year) ||
|
|
77
|
+
date.getUTCMonth() !== monthIndex ||
|
|
78
|
+
date.getUTCDate() !== Number(day) ||
|
|
79
|
+
date.getUTCHours() !== Number(hour) ||
|
|
80
|
+
date.getUTCMinutes() !== Number(minute) ||
|
|
81
|
+
date.getUTCSeconds() !== Number(second) ||
|
|
82
|
+
date.toUTCString().slice(0, 3) !== dayName.slice(0, 3)
|
|
83
|
+
) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return date.getTime();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Capture a deadline when headers arrive, before reading a slow response body.
|
|
90
|
+
* No metadata here grants permission to retry an HTTP/provider verdict.
|
|
91
|
+
*/
|
|
92
|
+
export function captureStreamRetryMetadata(response, now = Date.now()) {
|
|
93
|
+
const metadata = {};
|
|
94
|
+
if (Number.isInteger(response?.status)) metadata.status = response.status;
|
|
95
|
+
const raw = retryAfterHeader(response?.headers);
|
|
96
|
+
if (typeof raw !== "string" || !Number.isFinite(now)) return metadata;
|
|
97
|
+
const value = raw.trim();
|
|
98
|
+
if (/^\d+$/.test(value)) {
|
|
99
|
+
const seconds = Number(value);
|
|
100
|
+
const deadline = now + seconds * 1000;
|
|
101
|
+
// A valid but unrepresentably large delay is a refusal to retry within our
|
|
102
|
+
// budget, NOT a missing header. Infinity is an internal fail-closed sentinel.
|
|
103
|
+
metadata.retryAfterAt = Number.isSafeInteger(deadline)
|
|
104
|
+
? deadline
|
|
105
|
+
: Infinity;
|
|
106
|
+
} else {
|
|
107
|
+
const deadline = httpDate(value, now);
|
|
108
|
+
if (deadline !== undefined) metadata.retryAfterAt = deadline;
|
|
109
|
+
}
|
|
110
|
+
return metadata;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Preserve the original Error identity/code and the strongest known verdict. */
|
|
114
|
+
export function attachStreamRetryMetadata(error, metadata = {}) {
|
|
115
|
+
if (!error || (typeof error !== "object" && typeof error !== "function"))
|
|
116
|
+
return error;
|
|
117
|
+
const hasFailureStatus = [
|
|
118
|
+
error.status,
|
|
119
|
+
error.statusCode,
|
|
120
|
+
error.cause?.status,
|
|
121
|
+
error.cause?.statusCode,
|
|
122
|
+
].some(isFailureStatus);
|
|
123
|
+
if (!hasFailureStatus && Number.isInteger(metadata.status)) {
|
|
124
|
+
error.status = metadata.status;
|
|
125
|
+
}
|
|
126
|
+
if (
|
|
127
|
+
typeof metadata.retryAfterAt === "number" &&
|
|
128
|
+
!Number.isNaN(metadata.retryAfterAt) &&
|
|
129
|
+
metadata.retryAfterAt !== -Infinity
|
|
130
|
+
) {
|
|
131
|
+
const previous = [error.retryAfterAt, error.cause?.retryAfterAt].filter(
|
|
132
|
+
(value) => typeof value === "number" && !Number.isNaN(value),
|
|
133
|
+
);
|
|
134
|
+
error.retryAfterAt = Math.max(metadata.retryAfterAt, ...previous);
|
|
135
|
+
}
|
|
136
|
+
return error;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Resolve the wait AFTER retry eligibility is checked; null means give up.
|
|
140
|
+
* Never clamp a server deadline to a shorter wait or authorize an HTTP replay.
|
|
141
|
+
* Attempt 1 is the first retry, matching the existing retry observer contract.
|
|
142
|
+
*/
|
|
143
|
+
export function resolveStreamRetryDelay(error, attempt, options = {}) {
|
|
144
|
+
const base = options.baseDelayMs ?? STREAM_RETRY_BASE_MS;
|
|
145
|
+
const now = options.now ?? Date.now();
|
|
146
|
+
const max = options.maxDelayMs ?? STREAM_RETRY_MAX_DELAY_MS;
|
|
147
|
+
if (
|
|
148
|
+
!Number.isInteger(attempt) ||
|
|
149
|
+
attempt < 1 ||
|
|
150
|
+
!Number.isFinite(base) ||
|
|
151
|
+
base < 0 ||
|
|
152
|
+
!Number.isFinite(now) ||
|
|
153
|
+
!Number.isFinite(max) ||
|
|
154
|
+
max < 0
|
|
155
|
+
)
|
|
156
|
+
return null;
|
|
157
|
+
const deadlines = [error?.retryAfterAt, error?.cause?.retryAfterAt].filter(
|
|
158
|
+
(value) => typeof value === "number" && !Number.isNaN(value),
|
|
159
|
+
);
|
|
160
|
+
const delay = Math.ceil(
|
|
161
|
+
Math.max(base * 2 ** (attempt - 1), 0, ...deadlines.map((at) => at - now)),
|
|
162
|
+
);
|
|
163
|
+
// Node clamps overflowing setTimeout delays to 1ms. Reject rather than
|
|
164
|
+
// accidentally turning a requested long delay into an immediate replay.
|
|
165
|
+
return Number.isFinite(delay) && delay <= max && delay <= 2_147_483_647
|
|
166
|
+
? delay
|
|
167
|
+
: null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Wait without keeping an obsolete backoff alive after user cancellation. */
|
|
171
|
+
export async function waitForStreamRetry(ms, signal, { sleep } = {}) {
|
|
172
|
+
throwIfAborted(signal);
|
|
173
|
+
if (!Number.isFinite(ms) || ms < 0 || ms > 2_147_483_647)
|
|
174
|
+
throw new RangeError("Invalid streaming retry delay");
|
|
175
|
+
if (sleep) {
|
|
176
|
+
await raceWithAbort(
|
|
177
|
+
Promise.resolve().then(() => {
|
|
178
|
+
throwIfAborted(signal);
|
|
179
|
+
return sleep(ms);
|
|
180
|
+
}),
|
|
181
|
+
signal,
|
|
182
|
+
);
|
|
183
|
+
throwIfAborted(signal);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
await new Promise((resolve, reject) => {
|
|
187
|
+
let timer;
|
|
188
|
+
const cleanup = () => {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
signal?.removeEventListener?.("abort", onAbort);
|
|
191
|
+
};
|
|
192
|
+
const onAbort = () => {
|
|
193
|
+
cleanup();
|
|
194
|
+
reject(signal.reason || createAbortError());
|
|
195
|
+
};
|
|
196
|
+
timer = setTimeout(() => {
|
|
197
|
+
cleanup();
|
|
198
|
+
resolve();
|
|
199
|
+
}, ms);
|
|
200
|
+
signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
201
|
+
if (signal?.aborted) onAbort();
|
|
202
|
+
});
|
|
203
|
+
throwIfAborted(signal);
|
|
204
|
+
}
|
|
20
205
|
|
|
21
206
|
/**
|
|
22
207
|
* Hard ceiling on the *configurable* retry budget (Claude-Code 2.1.186:
|
|
@@ -61,6 +246,15 @@ export function isRetryableStreamError(err, signal) {
|
|
|
61
246
|
if (isAbortError(err)) return false;
|
|
62
247
|
if (signal && signal.aborted) return false;
|
|
63
248
|
if (!err) return false;
|
|
249
|
+
if (
|
|
250
|
+
[err, err.cause].some(
|
|
251
|
+
(error) =>
|
|
252
|
+
error?.noReplay === true ||
|
|
253
|
+
error?.streamOutputVisible === true ||
|
|
254
|
+
error?.isOutputBackpressureFailure === true,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
return false;
|
|
64
258
|
const msg = String(err.message || err).toLowerCase();
|
|
65
259
|
// A provider verdict stays terminal even when its detail mentions a timeout,
|
|
66
260
|
// reset or network failure. Inspect it before transport codes and wording so
|