chainlesschain 0.165.2 → 0.165.4
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 +151 -29
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-C__nnraW.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-CsKWHEZ-.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-BhIjfjWO.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-BjuXFzWZ.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-k6whDkZJ.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-C4FBMYRh.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-C_vRrSXk.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-CngFZeVp.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-B_H9EBRm.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-DLEC5kt7.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-m3ClR4xh.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-CmLSFrgp.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-DFGQteHN.js} +1 -1
- package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-EUUVORHE.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-2PSg0Yr7.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-CcKur7gB.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-DK_Fezfa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-Dbw89xl7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CH66h_Ec.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-D1igRb17.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-DVAsrobk.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-Dl7W8AQs.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-D5tpaEQC.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-O_VQf8gq.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BTVcgS9n.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BRnkJAIz.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-8OWSCkDj.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-WXLYTQj2.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-BezaUhtc.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-CuRay3Gu.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-DfWLDW1m.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-C55y2pPY.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-Dk08Uycg.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-C11fSB5V.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-mr0eZc97.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C0iCSRbb.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BxVavObl.js → MobileProjects-CY-xMG8Z.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-Bplqc_Wc.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BlzwsoSM.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-BBLmTty6.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-DtT1uf1W.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-DcqQAxTK.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-cb0kWoZe.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CnwojoSI.js → OrderTableRenderer-kw9HZQNN.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-DlTlbkUN.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-pp1qs-oY.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-BESLnyYz.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-B-J1mGuf.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-B36PeT1_.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CTK2ad-3.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-zJNGXsac.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-b9fz0Qf0.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-DjZbZgiB.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-C-hHJXlD.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CXoOa8eX.js → Providers-Dz-CAYYU.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-A0yoQ3-3.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DrFgFJNC.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-KC8hF1g4.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-DGTPIk6u.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-QmbwM7Yk.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-BxjJylia.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B7Wda1J6.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-_plQ_YSA.js} +1 -1
- package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-CD2YJZfx.js} +2 -2
- package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-qQE7NmwE.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-DgQqkFf7.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-C4C0oFFt.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-Px2N2B5c.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DN6C3lQd.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-jm5aH_m7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-DlkJcv67.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-DyrTHUBs.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-mC434PtL.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-o0_s3NgK.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-DAGlAXOw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BnulS_bs.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-suQbVXYM.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-DbIJAPo5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-CQNpbIHi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-D8aWCERk.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-CuLpYfZc.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-CsfG8ZMc.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-BYsEAZbd.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-FT4Um3-I.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CKbS_Wlt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-CFJtzuJM.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-B9kSRW6K.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-DR-x0Xpi.js} +1 -1
- package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-7jHr_A_k.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-B3ZX81mK.js} +1 -1
- package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-B7osrXqy.js} +3 -3
- package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-BGbPnC5x.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-BI5hP_qy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-BK3TiKVq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BVPU43b1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-BW-t9n-q.js} +1 -1
- package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BfFCQHL3.js} +1 -1
- package/src/assets/web-panel/assets/index-Bizb4hkw.js +1 -0
- package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-Bt4fS2eL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-BwR7yc2h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-Bzz0C1BZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-C2JvmYRc.js} +1 -1
- package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-CGaXUsIs.js} +1 -1
- package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-CJmIQ9XR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C_enk9I_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Cr2FLUsD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CvrCCqJt.js} +1 -1
- package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-D4LfPvWA.js} +1 -1
- package/src/assets/web-panel/assets/{index-By5n8up9.js → index-DMGmg497.js} +1 -1
- package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DShSkww-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-DTxNy0ZU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-DW4BWjTk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-DWdqx7zI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-Dap9c3eD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-DcfxhKXR.js} +1 -1
- package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-DdkIhfi-.js} +1 -1
- package/src/assets/web-panel/assets/{index-brCInrzq.js → index-DeSgw-WV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-DiNUEpSQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-DjEP7WkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-DtkFqPVA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-KqDaCApw.js} +1 -1
- package/src/assets/web-panel/assets/index-OHSl-xCh.js +1 -0
- package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-OSzjTzGM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-QBSeNVO3.js} +1 -1
- package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-WE08gMJP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-r2VI1Ikk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-tqSEHPRT.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-Dd7n7VgI.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BwxouRw1.js} +1 -1
- package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-DObN0PG9.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-B3nOUQc7.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-o9WU2Nbl.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-CL_vvfQ_.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-CYrM2tyK.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-k_06cQ96.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-CPKG0pxS.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-BDDEeLTw.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-Cmb7RTh7.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-e2IedC4N.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-CE_gxVnm.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-BkMIIKMz.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-DW886XV1.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Dwo_adaf.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +105 -35
- package/src/commands/artifacts.js +363 -33
- package/src/commands/cowork.js +202 -36
- package/src/commands/session-budget.js +155 -0
- package/src/commands/session-location.js +948 -0
- package/src/commands/session.js +72 -46
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/artifact-protocol.js +77 -13
- package/src/gateways/ws/session-protocol.js +391 -24
- package/src/gateways/ws/ws-agent-handler.js +191 -14
- package/src/gateways/ws/ws-server.js +14 -3
- package/src/gateways/ws/ws-session-gateway.js +101 -5
- package/src/harness/jsonl-session-store.js +1210 -0
- package/src/lib/agent-team/team-runner.js +43 -13
- package/src/lib/artifact-access-ledger.js +461 -0
- package/src/lib/artifact-cleanup-ledger.js +861 -0
- package/src/lib/artifact-deletion-ledger.js +677 -0
- package/src/lib/artifact-store.js +256 -51
- package/src/lib/background-agent-keeper-protocol.js +75 -4
- package/src/lib/background-agent-launch-barrier.js +74 -0
- package/src/lib/background-agent-supervisor.js +149 -5
- package/src/lib/background-turn-bootstrap-protocol.js +4 -0
- package/src/lib/command-lifecycle-report.js +8 -4
- package/src/lib/cowork-adapter.js +9 -5
- package/src/lib/cowork-task-runner.js +20 -0
- package/src/lib/cowork-workflow.js +178 -20
- package/src/lib/direct-model-usage.js +62 -22
- package/src/lib/dynamic-workflow-runtime.js +3184 -111
- package/src/lib/execution-location-result-apply.js +339 -0
- package/src/lib/execution-location-result-artifact.js +271 -0
- package/src/lib/execution-location-result-review.js +253 -0
- package/src/lib/execution-location-result-store.js +331 -0
- package/src/lib/execution-location-result.js +617 -0
- package/src/lib/execution-location-target.js +236 -1
- package/src/lib/managed-tool-checkpoint.js +47 -4
- package/src/lib/process-execution-broker/workspace-transaction.js +3 -0
- package/src/lib/session-budget-production-root.js +376 -0
- package/src/lib/session-budget-runtime.js +109 -18
- package/src/lib/session-budget-usage.js +63 -0
- package/src/lib/session-host-snapshot.js +19 -2
- package/src/lib/session-resource-budget.js +766 -105
- package/src/lib/sub-agent-context.js +69 -22
- package/src/lib/web-ui-server.js +12 -1
- package/src/lib/with-file-lock.js +175 -26
- package/src/repl/agent-repl.js +237 -16
- package/src/runtime/agent-core.js +123 -0
- package/src/runtime/headless-runner.js +135 -46
- package/src/runtime/headless-stream.js +133 -25
- package/src/runtime/policies/agent-policy.js +10 -0
- package/src/workers/background-agent-keeper.js +112 -19
- package/src/workers/background-agent-worker.js +383 -134
- package/src/workers/background-turn-bootstrap.js +13 -8
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CY5m8ZHg.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
|
@@ -15,10 +15,14 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
|
-
import { randomUUID } from "node:crypto";
|
|
18
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
19
19
|
import { CostBudget } from "./cost-budget.js";
|
|
20
20
|
|
|
21
21
|
export const SESSION_RESOURCE_BUDGET_VERSION = 1;
|
|
22
|
+
export const SESSION_BUDGET_RECOVERY_ADJUDICATION_SCHEMA =
|
|
23
|
+
"chainlesschain.session-budget-recovery-adjudication/v1";
|
|
24
|
+
export const SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA =
|
|
25
|
+
"chainlesschain.session-budget-recovery-receipt-history/v1";
|
|
22
26
|
|
|
23
27
|
const INTEGER_LIMITS = new Set([
|
|
24
28
|
"maxConcurrent",
|
|
@@ -56,6 +60,8 @@ const USAGE_TOKEN_FIELDS = Object.freeze([
|
|
|
56
60
|
|
|
57
61
|
const MAX_TIMER_DELAY = 2_147_483_647;
|
|
58
62
|
const RESOURCE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,159}$/;
|
|
63
|
+
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
64
|
+
const MAX_RECOVERY_ADJUDICATION_RECEIPTS = 2048;
|
|
59
65
|
const OPAQUE_AUTHORITY_ID_PATTERNS = Object.freeze({
|
|
60
66
|
work: /^work-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
|
|
61
67
|
tool: /^tool-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
|
|
@@ -95,6 +101,35 @@ function isRecord(value) {
|
|
|
95
101
|
return proto === Object.prototype || proto === null;
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
function canonicalJson(value) {
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
107
|
+
}
|
|
108
|
+
if (isRecord(value)) {
|
|
109
|
+
return `{${Object.keys(value)
|
|
110
|
+
.sort()
|
|
111
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
112
|
+
.join(",")}}`;
|
|
113
|
+
}
|
|
114
|
+
return JSON.stringify(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function sha256(value) {
|
|
118
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeRecoveryIdentity(field, raw) {
|
|
122
|
+
if (
|
|
123
|
+
typeof raw !== "string" ||
|
|
124
|
+
raw.length < 1 ||
|
|
125
|
+
raw.length > 256 ||
|
|
126
|
+
/[\u0000-\u001f\u007f]/u.test(raw)
|
|
127
|
+
) {
|
|
128
|
+
throw new TypeError(`invalid session budget recovery ${field}`);
|
|
129
|
+
}
|
|
130
|
+
return raw;
|
|
131
|
+
}
|
|
132
|
+
|
|
98
133
|
function normalizeLimit(field, raw) {
|
|
99
134
|
if (raw === undefined || raw === null || raw === "") return null;
|
|
100
135
|
const value = Number(raw);
|
|
@@ -150,6 +185,405 @@ function normalizeUsageTokens(usage) {
|
|
|
150
185
|
return { fields, total };
|
|
151
186
|
}
|
|
152
187
|
|
|
188
|
+
function normalizeUsageAccounting({
|
|
189
|
+
usage = null,
|
|
190
|
+
usageRecords = null,
|
|
191
|
+
provider,
|
|
192
|
+
model,
|
|
193
|
+
} = {}) {
|
|
194
|
+
if (usageRecords != null && !Array.isArray(usageRecords)) {
|
|
195
|
+
throw new TypeError("invalid session budget usage records");
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(usageRecords) && usageRecords.length > 0) {
|
|
198
|
+
const detailedFields = Object.fromEntries(
|
|
199
|
+
USAGE_TOKEN_FIELDS.map((field) => [field, 0]),
|
|
200
|
+
);
|
|
201
|
+
let detailedTokenCount = 0;
|
|
202
|
+
const pricedRecords = usageRecords.map((record, index) => {
|
|
203
|
+
if (!isRecord(record) || !isRecord(record.usage)) {
|
|
204
|
+
throw new TypeError(
|
|
205
|
+
`invalid session budget usage record at index ${index}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const normalized = normalizeUsageTokens(record.usage);
|
|
209
|
+
for (const field of USAGE_TOKEN_FIELDS) {
|
|
210
|
+
detailedFields[field] += normalized.fields[field];
|
|
211
|
+
if (!Number.isSafeInteger(detailedFields[field])) {
|
|
212
|
+
throw new TypeError(
|
|
213
|
+
`session budget usage record aggregate exceeds safe integer: ${field}`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
detailedTokenCount += normalized.total;
|
|
218
|
+
if (!Number.isSafeInteger(detailedTokenCount)) {
|
|
219
|
+
throw new TypeError("session budget token total exceeds safe integer");
|
|
220
|
+
}
|
|
221
|
+
return { record, tokens: normalized.total };
|
|
222
|
+
});
|
|
223
|
+
if (usage != null) {
|
|
224
|
+
const aggregate = normalizeUsageTokens(usage);
|
|
225
|
+
for (const field of USAGE_TOKEN_FIELDS) {
|
|
226
|
+
if (aggregate.fields[field] !== detailedFields[field]) {
|
|
227
|
+
throw new TypeError(
|
|
228
|
+
`session budget usage records do not match aggregate usage: ${field}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { tokenCount: aggregate.total, pricedRecords };
|
|
233
|
+
}
|
|
234
|
+
return { tokenCount: detailedTokenCount, pricedRecords };
|
|
235
|
+
}
|
|
236
|
+
const normalized = normalizeUsageTokens(usage);
|
|
237
|
+
return {
|
|
238
|
+
tokenCount: normalized.total,
|
|
239
|
+
pricedRecords:
|
|
240
|
+
usage == null
|
|
241
|
+
? []
|
|
242
|
+
: [{ record: { provider, model, usage }, tokens: normalized.total }],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function cloneCostBudget(cost) {
|
|
247
|
+
const clone = new CostBudget({
|
|
248
|
+
limitUsd: cost.limitUsd,
|
|
249
|
+
table: cost.table,
|
|
250
|
+
});
|
|
251
|
+
clone.spentUsd = cost.spentUsd;
|
|
252
|
+
clone.priced = cost.priced;
|
|
253
|
+
clone.sawUnpriced = cost.sawUnpriced;
|
|
254
|
+
clone.sawFree = cost.sawFree;
|
|
255
|
+
clone._warned = cost._warned;
|
|
256
|
+
return clone;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function calculateUsageAccounting(
|
|
260
|
+
{ tokens, cost, unpricedUsage },
|
|
261
|
+
usageInput,
|
|
262
|
+
) {
|
|
263
|
+
const { tokenCount, pricedRecords } = normalizeUsageAccounting(usageInput);
|
|
264
|
+
const nextTokens = tokens + tokenCount;
|
|
265
|
+
if (!Number.isSafeInteger(nextTokens)) {
|
|
266
|
+
throw new TypeError("session budget token total exceeds safe integer");
|
|
267
|
+
}
|
|
268
|
+
const nextCost = cloneCostBudget(cost);
|
|
269
|
+
let nextUnpricedUsage = unpricedUsage;
|
|
270
|
+
for (const { record, tokens: recordTokens } of pricedRecords) {
|
|
271
|
+
const estimate = nextCost.add(record);
|
|
272
|
+
const price = Number(estimate?.totalCost);
|
|
273
|
+
if (
|
|
274
|
+
nextCost.enabled() &&
|
|
275
|
+
recordTokens > 0 &&
|
|
276
|
+
estimate?.free !== true &&
|
|
277
|
+
(estimate?.matched !== true || !Number.isFinite(price) || price < 0)
|
|
278
|
+
) {
|
|
279
|
+
nextUnpricedUsage = true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
tokens: nextTokens,
|
|
284
|
+
cost: nextCost,
|
|
285
|
+
unpricedUsage: nextUnpricedUsage,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function canonicalRecoveryUsageRecord(record, field) {
|
|
290
|
+
if (!isRecord(record) || !isRecord(record.usage)) {
|
|
291
|
+
throw new TypeError(`invalid session budget recovery ${field}`);
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
provider: normalizeRecoveryIdentity(`${field} provider`, record.provider),
|
|
295
|
+
model: normalizeRecoveryIdentity(`${field} model`, record.model),
|
|
296
|
+
usage: normalizeUsageTokens(record.usage).fields,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function canonicalRecoverySettlement(record, index) {
|
|
301
|
+
const base = { authorityId: record.authorityId };
|
|
302
|
+
if (Array.isArray(record.usageRecords) && record.usageRecords.length > 0) {
|
|
303
|
+
const usageRecords = record.usageRecords
|
|
304
|
+
.map((entry, recordIndex) =>
|
|
305
|
+
canonicalRecoveryUsageRecord(
|
|
306
|
+
entry,
|
|
307
|
+
`settlement ${index} usage record ${recordIndex}`,
|
|
308
|
+
),
|
|
309
|
+
)
|
|
310
|
+
.sort((left, right) =>
|
|
311
|
+
canonicalJson(left).localeCompare(canonicalJson(right)),
|
|
312
|
+
);
|
|
313
|
+
return {
|
|
314
|
+
...base,
|
|
315
|
+
...(record.usage != null
|
|
316
|
+
? { usage: normalizeUsageTokens(record.usage).fields }
|
|
317
|
+
: {}),
|
|
318
|
+
usageRecords,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
...base,
|
|
323
|
+
...canonicalRecoveryUsageRecord(record, `settlement ${index}`),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function cloneCanonicalRecord(value) {
|
|
328
|
+
return JSON.parse(canonicalJson(value));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function isOpaqueAuthorityId(value) {
|
|
332
|
+
return Object.values(OPAQUE_AUTHORITY_ID_PATTERNS).some((pattern) =>
|
|
333
|
+
pattern.test(value),
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function normalizeRecoveryReceipt(value, index) {
|
|
338
|
+
if (
|
|
339
|
+
!isRecord(value) ||
|
|
340
|
+
value.schema !== SESSION_BUDGET_RECOVERY_ADJUDICATION_SCHEMA ||
|
|
341
|
+
!Number.isSafeInteger(value.sequence) ||
|
|
342
|
+
value.sequence < 1 ||
|
|
343
|
+
(value.previousDigest !== null &&
|
|
344
|
+
!SHA256_PATTERN.test(value.previousDigest)) ||
|
|
345
|
+
!Array.isArray(value.abandoned) ||
|
|
346
|
+
!Array.isArray(value.settled) ||
|
|
347
|
+
!isRecord(value.totalsBefore) ||
|
|
348
|
+
!isRecord(value.totalsAfter)
|
|
349
|
+
) {
|
|
350
|
+
throw new TypeError(
|
|
351
|
+
`invalid session budget recovery receipt at index ${index}`,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
const abandoned = value.abandoned.map((raw) => String(raw)).sort();
|
|
355
|
+
const settled = value.settled
|
|
356
|
+
.map((record, settlementIndex) => {
|
|
357
|
+
if (!isRecord(record)) {
|
|
358
|
+
throw new TypeError(
|
|
359
|
+
`invalid session budget recovery receipt settlement at index ${index}:${settlementIndex}`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
const authorityId = String(record.authorityId || "");
|
|
363
|
+
if (!OPAQUE_AUTHORITY_ID_PATTERNS.usage.test(authorityId)) {
|
|
364
|
+
throw new TypeError(
|
|
365
|
+
`invalid session budget recovery receipt authority at index ${index}:${settlementIndex}`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return canonicalRecoverySettlement(
|
|
369
|
+
{ ...record, authorityId },
|
|
370
|
+
settlementIndex,
|
|
371
|
+
);
|
|
372
|
+
})
|
|
373
|
+
.sort((left, right) => left.authorityId.localeCompare(right.authorityId));
|
|
374
|
+
const allAuthorityIds = [
|
|
375
|
+
...abandoned,
|
|
376
|
+
...settled.map((record) => record.authorityId),
|
|
377
|
+
];
|
|
378
|
+
if (
|
|
379
|
+
abandoned.some((authorityId) => !isOpaqueAuthorityId(authorityId)) ||
|
|
380
|
+
new Set(allAuthorityIds).size !== allAuthorityIds.length ||
|
|
381
|
+
canonicalJson(abandoned) !== canonicalJson(value.abandoned) ||
|
|
382
|
+
canonicalJson(settled) !== canonicalJson(value.settled)
|
|
383
|
+
) {
|
|
384
|
+
throw new TypeError(
|
|
385
|
+
`invalid session budget recovery receipt authorities at index ${index}`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const totalsBefore = {
|
|
389
|
+
tokens: normalizeNonNegativeInteger(
|
|
390
|
+
"recovery receipt tokens before",
|
|
391
|
+
value.totalsBefore.tokens,
|
|
392
|
+
),
|
|
393
|
+
spentUsd: normalizeNonNegativeNumber(
|
|
394
|
+
"recovery receipt USD before",
|
|
395
|
+
value.totalsBefore.spentUsd,
|
|
396
|
+
),
|
|
397
|
+
};
|
|
398
|
+
const totalsAfter = {
|
|
399
|
+
tokens: normalizeNonNegativeInteger(
|
|
400
|
+
"recovery receipt tokens after",
|
|
401
|
+
value.totalsAfter.tokens,
|
|
402
|
+
),
|
|
403
|
+
spentUsd: normalizeNonNegativeNumber(
|
|
404
|
+
"recovery receipt USD after",
|
|
405
|
+
value.totalsAfter.spentUsd,
|
|
406
|
+
),
|
|
407
|
+
};
|
|
408
|
+
if (
|
|
409
|
+
totalsAfter.tokens < totalsBefore.tokens ||
|
|
410
|
+
totalsAfter.spentUsd < totalsBefore.spentUsd
|
|
411
|
+
) {
|
|
412
|
+
throw new TypeError(
|
|
413
|
+
`invalid session budget recovery receipt totals at index ${index}`,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
const core = {
|
|
417
|
+
schema: SESSION_BUDGET_RECOVERY_ADJUDICATION_SCHEMA,
|
|
418
|
+
sequence: value.sequence,
|
|
419
|
+
previousDigest: value.previousDigest,
|
|
420
|
+
abandoned,
|
|
421
|
+
settled,
|
|
422
|
+
totalsBefore,
|
|
423
|
+
totalsAfter,
|
|
424
|
+
};
|
|
425
|
+
const digest = sha256(canonicalJson(core));
|
|
426
|
+
if (value.digest !== digest) {
|
|
427
|
+
throw new TypeError(
|
|
428
|
+
`invalid session budget recovery receipt digest at index ${index}`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return { ...core, digest };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function normalizeRecoveryReceiptHistory(value, adjudication, totals) {
|
|
435
|
+
if (
|
|
436
|
+
!isRecord(value) ||
|
|
437
|
+
value.schema !== SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA ||
|
|
438
|
+
!Number.isSafeInteger(value.baseSequence) ||
|
|
439
|
+
value.baseSequence < 0 ||
|
|
440
|
+
(value.baseDigest !== null && !SHA256_PATTERN.test(value.baseDigest)) ||
|
|
441
|
+
(value.baseSequence === 0) !== (value.baseDigest === null) ||
|
|
442
|
+
!Array.isArray(value.entries) ||
|
|
443
|
+
value.entries.length < 1 ||
|
|
444
|
+
value.entries.length > MAX_RECOVERY_ADJUDICATION_RECEIPTS ||
|
|
445
|
+
value.baseSequence + value.entries.length !== adjudication.count
|
|
446
|
+
) {
|
|
447
|
+
throw new TypeError(
|
|
448
|
+
"invalid session budget recovery adjudication receipt history",
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const entries = value.entries.map(normalizeRecoveryReceipt);
|
|
452
|
+
let previousDigest = value.baseDigest;
|
|
453
|
+
let previousTotals = null;
|
|
454
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
455
|
+
const entry = entries[index];
|
|
456
|
+
if (
|
|
457
|
+
entry.sequence !== value.baseSequence + index + 1 ||
|
|
458
|
+
entry.previousDigest !== previousDigest ||
|
|
459
|
+
(previousTotals !== null &&
|
|
460
|
+
(entry.totalsBefore.tokens !== previousTotals.tokens ||
|
|
461
|
+
entry.totalsBefore.spentUsd !== previousTotals.spentUsd))
|
|
462
|
+
) {
|
|
463
|
+
throw new TypeError(
|
|
464
|
+
"invalid session budget recovery adjudication receipt chain",
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
previousDigest = entry.digest;
|
|
468
|
+
previousTotals = entry.totalsAfter;
|
|
469
|
+
}
|
|
470
|
+
const last = entries.at(-1);
|
|
471
|
+
if (
|
|
472
|
+
last.sequence !== adjudication.count ||
|
|
473
|
+
last.digest !== adjudication.headDigest ||
|
|
474
|
+
last.digest !== adjudication.last.digest ||
|
|
475
|
+
last.previousDigest !== adjudication.last.previousDigest ||
|
|
476
|
+
last.settled.length !== adjudication.last.settledCount ||
|
|
477
|
+
last.abandoned.length !== adjudication.last.abandonedCount ||
|
|
478
|
+
last.totalsAfter.tokens - last.totalsBefore.tokens !==
|
|
479
|
+
adjudication.last.tokenDelta ||
|
|
480
|
+
last.totalsAfter.spentUsd - last.totalsBefore.spentUsd !==
|
|
481
|
+
adjudication.last.spentUsdDelta ||
|
|
482
|
+
last.totalsAfter.tokens !== totals.tokens ||
|
|
483
|
+
last.totalsAfter.spentUsd !== totals.spentUsd
|
|
484
|
+
) {
|
|
485
|
+
throw new TypeError(
|
|
486
|
+
"invalid session budget recovery adjudication receipt head",
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
schema: SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA,
|
|
491
|
+
baseSequence: value.baseSequence,
|
|
492
|
+
baseDigest: value.baseDigest,
|
|
493
|
+
entries,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function normalizeRecoveryAdjudicationState(value, totals) {
|
|
498
|
+
if (value === undefined || value === null) return null;
|
|
499
|
+
if (
|
|
500
|
+
!isRecord(value) ||
|
|
501
|
+
value.schema !== SESSION_BUDGET_RECOVERY_ADJUDICATION_SCHEMA ||
|
|
502
|
+
!Number.isSafeInteger(value.count) ||
|
|
503
|
+
value.count < 1 ||
|
|
504
|
+
!SHA256_PATTERN.test(value.headDigest) ||
|
|
505
|
+
!isRecord(value.last) ||
|
|
506
|
+
value.last.sequence !== value.count ||
|
|
507
|
+
value.last.digest !== value.headDigest ||
|
|
508
|
+
!SHA256_PATTERN.test(value.last.digest) ||
|
|
509
|
+
(value.last.previousDigest !== null &&
|
|
510
|
+
!SHA256_PATTERN.test(value.last.previousDigest)) ||
|
|
511
|
+
!Number.isSafeInteger(value.last.settledCount) ||
|
|
512
|
+
value.last.settledCount < 0 ||
|
|
513
|
+
!Number.isSafeInteger(value.last.abandonedCount) ||
|
|
514
|
+
value.last.abandonedCount < 0 ||
|
|
515
|
+
!Number.isSafeInteger(value.last.tokenDelta) ||
|
|
516
|
+
value.last.tokenDelta < 0 ||
|
|
517
|
+
!Number.isFinite(value.last.spentUsdDelta) ||
|
|
518
|
+
value.last.spentUsdDelta < 0
|
|
519
|
+
) {
|
|
520
|
+
throw new TypeError("invalid session budget recovery adjudication state");
|
|
521
|
+
}
|
|
522
|
+
if (
|
|
523
|
+
(value.count === 1 && value.last.previousDigest !== null) ||
|
|
524
|
+
(value.count > 1 && value.last.previousDigest === null)
|
|
525
|
+
) {
|
|
526
|
+
throw new TypeError("invalid session budget recovery adjudication chain");
|
|
527
|
+
}
|
|
528
|
+
const adjudication = {
|
|
529
|
+
schema: SESSION_BUDGET_RECOVERY_ADJUDICATION_SCHEMA,
|
|
530
|
+
count: value.count,
|
|
531
|
+
headDigest: value.headDigest,
|
|
532
|
+
last: {
|
|
533
|
+
sequence: value.last.sequence,
|
|
534
|
+
digest: value.last.digest,
|
|
535
|
+
previousDigest: value.last.previousDigest,
|
|
536
|
+
settledCount: value.last.settledCount,
|
|
537
|
+
abandonedCount: value.last.abandonedCount,
|
|
538
|
+
tokenDelta: value.last.tokenDelta,
|
|
539
|
+
spentUsdDelta: value.last.spentUsdDelta,
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
if (value.history !== undefined) {
|
|
543
|
+
adjudication.history = normalizeRecoveryReceiptHistory(
|
|
544
|
+
value.history,
|
|
545
|
+
adjudication,
|
|
546
|
+
totals,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
return adjudication;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function cloneRecoveryAdjudication(value, { includeReceipts = true } = {}) {
|
|
553
|
+
if (!value) return null;
|
|
554
|
+
const cloned = {
|
|
555
|
+
schema: value.schema,
|
|
556
|
+
count: value.count,
|
|
557
|
+
headDigest: value.headDigest,
|
|
558
|
+
last: { ...value.last },
|
|
559
|
+
};
|
|
560
|
+
if (value.history) {
|
|
561
|
+
cloned.history = includeReceipts
|
|
562
|
+
? {
|
|
563
|
+
schema: value.history.schema,
|
|
564
|
+
baseSequence: value.history.baseSequence,
|
|
565
|
+
baseDigest: value.history.baseDigest,
|
|
566
|
+
entries: value.history.entries.map(cloneCanonicalRecord),
|
|
567
|
+
}
|
|
568
|
+
: {
|
|
569
|
+
schema: value.history.schema,
|
|
570
|
+
baseSequence: value.history.baseSequence,
|
|
571
|
+
baseDigest: value.history.baseDigest,
|
|
572
|
+
retainedCount: value.history.entries.length,
|
|
573
|
+
complete: value.history.baseSequence === 0,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
return cloned;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function normalizeUsageSettlementLabel(raw) {
|
|
580
|
+
const value = String(raw || "");
|
|
581
|
+
if (!RESOURCE_ID_PATTERN.test(value)) {
|
|
582
|
+
throw new TypeError("invalid session budget usage settlement id");
|
|
583
|
+
}
|
|
584
|
+
return value;
|
|
585
|
+
}
|
|
586
|
+
|
|
153
587
|
function safeReason(reason, fallback = "session-aborted") {
|
|
154
588
|
if (reason instanceof Error) return reason;
|
|
155
589
|
const text = String(reason || fallback).slice(0, 512);
|
|
@@ -258,6 +692,10 @@ function validateSnapshot(snapshot) {
|
|
|
258
692
|
}
|
|
259
693
|
const work = validateResourceList(snapshot.inFlight.work, "work");
|
|
260
694
|
const tools = validateResourceList(snapshot.inFlight.tools, "tools");
|
|
695
|
+
const recoveryAdjudication = normalizeRecoveryAdjudicationState(
|
|
696
|
+
snapshot.state.recoveryAdjudication,
|
|
697
|
+
totals,
|
|
698
|
+
);
|
|
261
699
|
const workIds = new Set(work.map((entry) => entry.id));
|
|
262
700
|
if (tools.some((entry) => workIds.has(entry.id))) {
|
|
263
701
|
throw new TypeError(
|
|
@@ -270,6 +708,7 @@ function validateSnapshot(snapshot) {
|
|
|
270
708
|
state: {
|
|
271
709
|
started: snapshot.state.started,
|
|
272
710
|
abort: snapshot.state.abort,
|
|
711
|
+
recoveryAdjudication,
|
|
273
712
|
},
|
|
274
713
|
inFlight: {
|
|
275
714
|
work,
|
|
@@ -340,6 +779,13 @@ export function normalizeSessionResourceBudgetSnapshot(snapshot) {
|
|
|
340
779
|
message: `Session resource budget stopped: ${abortReason}`,
|
|
341
780
|
}
|
|
342
781
|
: null,
|
|
782
|
+
...(normalized.state.recoveryAdjudication
|
|
783
|
+
? {
|
|
784
|
+
recoveryAdjudication: cloneRecoveryAdjudication(
|
|
785
|
+
normalized.state.recoveryAdjudication,
|
|
786
|
+
),
|
|
787
|
+
}
|
|
788
|
+
: {}),
|
|
343
789
|
},
|
|
344
790
|
};
|
|
345
791
|
}
|
|
@@ -423,9 +869,11 @@ export class SessionResourceBudget {
|
|
|
423
869
|
this._activeWorkLabels = new Map();
|
|
424
870
|
this._activeToolLabels = new Map();
|
|
425
871
|
this._pendingUsage = new Map();
|
|
872
|
+
this._pendingUsageLabels = new Map();
|
|
426
873
|
this._issuedAuthorityIds = new Set();
|
|
427
874
|
this._abortables = new Map();
|
|
428
875
|
this._recoveryUnknown = new Map();
|
|
876
|
+
this._recoveryAdjudication = null;
|
|
429
877
|
this._deadlineTimer = null;
|
|
430
878
|
this._abortController = new AbortController();
|
|
431
879
|
this._abortState = null;
|
|
@@ -701,7 +1149,6 @@ export class SessionResourceBudget {
|
|
|
701
1149
|
|
|
702
1150
|
reason(now = this._now()) {
|
|
703
1151
|
if (this._abortState) return this._abortState.reason;
|
|
704
|
-
if (this._pendingUsage.size > 0) return "usage-settlement-pending";
|
|
705
1152
|
if (this._recoveryUnknown.size > 0) return "recovery-required";
|
|
706
1153
|
const continuous = this._continuousReason(now);
|
|
707
1154
|
if (continuous) return continuous;
|
|
@@ -733,9 +1180,6 @@ export class SessionResourceBudget {
|
|
|
733
1180
|
if (this.signal.aborted) {
|
|
734
1181
|
return this._admissionFailure(this.reason() || "session-aborted", { id });
|
|
735
1182
|
}
|
|
736
|
-
if (this._pendingUsage.size > 0) {
|
|
737
|
-
return this._admissionFailure("usage-settlement-pending", { id });
|
|
738
|
-
}
|
|
739
1183
|
if (this._recoveryUnknown.size > 0) {
|
|
740
1184
|
return this._admissionFailure("recovery-required", { id });
|
|
741
1185
|
}
|
|
@@ -794,9 +1238,6 @@ export class SessionResourceBudget {
|
|
|
794
1238
|
id: key,
|
|
795
1239
|
});
|
|
796
1240
|
}
|
|
797
|
-
if (this._pendingUsage.size > 0) {
|
|
798
|
-
return this._admissionFailure("usage-settlement-pending", { id: key });
|
|
799
|
-
}
|
|
800
1241
|
if (this._recoveryUnknown.size > 0) {
|
|
801
1242
|
return this._admissionFailure("recovery-required", { id: key });
|
|
802
1243
|
}
|
|
@@ -917,9 +1358,6 @@ export class SessionResourceBudget {
|
|
|
917
1358
|
id: key,
|
|
918
1359
|
});
|
|
919
1360
|
}
|
|
920
|
-
if (this._pendingUsage.size > 0) {
|
|
921
|
-
return this._admissionFailure("usage-settlement-pending", { id: key });
|
|
922
|
-
}
|
|
923
1361
|
if (this._recoveryUnknown.size > 0) {
|
|
924
1362
|
return this._admissionFailure("recovery-required", { id: key });
|
|
925
1363
|
}
|
|
@@ -997,112 +1435,143 @@ export class SessionResourceBudget {
|
|
|
997
1435
|
return true;
|
|
998
1436
|
}
|
|
999
1437
|
|
|
1000
|
-
|
|
1438
|
+
beginUsageSettlement({ id, settleAfterAbort = false } = {}) {
|
|
1001
1439
|
this._assertAuthorityMutationAllowed();
|
|
1002
|
-
|
|
1003
|
-
if (this._pendingUsage.size > 0) {
|
|
1004
|
-
throw new SessionBudgetError("usage-settlement-pending");
|
|
1005
|
-
}
|
|
1006
|
-
let tokenCount;
|
|
1007
|
-
let pricedRecords;
|
|
1440
|
+
const label = normalizeUsageSettlementLabel(id);
|
|
1008
1441
|
try {
|
|
1009
|
-
|
|
1010
|
-
throw new TypeError("invalid session budget usage records");
|
|
1011
|
-
}
|
|
1012
|
-
if (Array.isArray(usageRecords) && usageRecords.length > 0) {
|
|
1013
|
-
const detailedFields = Object.fromEntries(
|
|
1014
|
-
USAGE_TOKEN_FIELDS.map((field) => [field, 0]),
|
|
1015
|
-
);
|
|
1016
|
-
let detailedTokenCount = 0;
|
|
1017
|
-
pricedRecords = usageRecords.map((record, index) => {
|
|
1018
|
-
if (!isRecord(record) || !isRecord(record.usage)) {
|
|
1019
|
-
throw new TypeError(
|
|
1020
|
-
`invalid session budget usage record at index ${index}`,
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
const normalized = normalizeUsageTokens(record.usage);
|
|
1024
|
-
for (const field of USAGE_TOKEN_FIELDS) {
|
|
1025
|
-
detailedFields[field] += normalized.fields[field];
|
|
1026
|
-
if (!Number.isSafeInteger(detailedFields[field])) {
|
|
1027
|
-
throw new TypeError(
|
|
1028
|
-
`session budget usage record aggregate exceeds safe integer: ${field}`,
|
|
1029
|
-
);
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
detailedTokenCount += normalized.total;
|
|
1033
|
-
if (!Number.isSafeInteger(detailedTokenCount)) {
|
|
1034
|
-
throw new TypeError(
|
|
1035
|
-
"session budget token total exceeds safe integer",
|
|
1036
|
-
);
|
|
1037
|
-
}
|
|
1038
|
-
return { record, tokens: normalized.total };
|
|
1039
|
-
});
|
|
1040
|
-
if (usage != null) {
|
|
1041
|
-
const aggregate = normalizeUsageTokens(usage);
|
|
1042
|
-
for (const field of USAGE_TOKEN_FIELDS) {
|
|
1043
|
-
if (aggregate.fields[field] !== detailedFields[field]) {
|
|
1044
|
-
throw new TypeError(
|
|
1045
|
-
`session budget usage records do not match aggregate usage: ${field}`,
|
|
1046
|
-
);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
tokenCount = aggregate.total;
|
|
1050
|
-
} else {
|
|
1051
|
-
tokenCount = detailedTokenCount;
|
|
1052
|
-
}
|
|
1053
|
-
} else {
|
|
1054
|
-
tokenCount = normalizeUsageTokens(usage).total;
|
|
1055
|
-
pricedRecords =
|
|
1056
|
-
usage == null
|
|
1057
|
-
? []
|
|
1058
|
-
: [{ record: { provider, model, usage }, tokens: tokenCount }];
|
|
1059
|
-
}
|
|
1060
|
-
if (!Number.isSafeInteger(tokenCount)) {
|
|
1061
|
-
throw new TypeError("session budget token total exceeds safe integer");
|
|
1062
|
-
}
|
|
1442
|
+
this.start();
|
|
1063
1443
|
} catch (error) {
|
|
1064
|
-
this.
|
|
1065
|
-
throw error;
|
|
1444
|
+
return this._persistenceAdmissionFailure(error, { id: label });
|
|
1066
1445
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1446
|
+
if (this.signal.aborted && settleAfterAbort !== true) {
|
|
1447
|
+
return this._admissionFailure(this.reason() || "session-aborted", {
|
|
1448
|
+
id: label,
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
if (this._recoveryUnknown.size > 0) {
|
|
1452
|
+
return this._admissionFailure("recovery-required", { id: label });
|
|
1074
1453
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1454
|
+
if (this._pendingUsageLabels.has(label)) {
|
|
1455
|
+
return this._admissionFailure("duplicate-usage-settlement-id", {
|
|
1456
|
+
id: label,
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
const authorityId = this._nextUsageSettlementId();
|
|
1460
|
+
const entry = {
|
|
1461
|
+
id: authorityId,
|
|
1462
|
+
label,
|
|
1078
1463
|
kind: "usage-settlement",
|
|
1079
1464
|
depth: 0,
|
|
1080
1465
|
startedAt: this._now(),
|
|
1466
|
+
};
|
|
1467
|
+
this._pendingUsage.set(authorityId, entry);
|
|
1468
|
+
this._pendingUsageLabels.set(label, authorityId);
|
|
1469
|
+
try {
|
|
1470
|
+
this._authorityChanged(
|
|
1471
|
+
"budget:usage-settlement-started",
|
|
1472
|
+
{
|
|
1473
|
+
pendingUsage: this._pendingUsage.size,
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
rollback: () => {
|
|
1477
|
+
this._pendingUsage.delete(authorityId);
|
|
1478
|
+
this._pendingUsageLabels.delete(label);
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
);
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
return this._persistenceAdmissionFailure(error, { id: label });
|
|
1484
|
+
}
|
|
1485
|
+
return { ok: true, id: label, authorityId };
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
_pendingUsageSettlement(callId) {
|
|
1489
|
+
if (callId !== undefined && callId !== null) {
|
|
1490
|
+
const label = normalizeUsageSettlementLabel(callId);
|
|
1491
|
+
const authorityId = this._pendingUsageLabels.get(label);
|
|
1492
|
+
if (!authorityId) {
|
|
1493
|
+
throw new SessionBudgetError("usage-settlement-not-started");
|
|
1494
|
+
}
|
|
1495
|
+
return { label, authorityId };
|
|
1496
|
+
}
|
|
1497
|
+
if (this._pendingUsage.size > 0) {
|
|
1498
|
+
throw new SessionBudgetError("usage-settlement-pending");
|
|
1499
|
+
}
|
|
1500
|
+
const label = `inline:${randomUUID()}`;
|
|
1501
|
+
const admission = this.beginUsageSettlement({
|
|
1502
|
+
id: label,
|
|
1503
|
+
settleAfterAbort: true,
|
|
1081
1504
|
});
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1505
|
+
if (!admission?.ok) {
|
|
1506
|
+
if (admission?.reason === "persistence-failed") {
|
|
1507
|
+
this._assertAuthorityMutationAllowed();
|
|
1508
|
+
}
|
|
1509
|
+
throw new SessionBudgetError(
|
|
1510
|
+
admission?.reason || "usage-settlement-not-started",
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
return { label, authorityId: admission.authorityId };
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
markUsageUnknown({ callId } = {}) {
|
|
1517
|
+
this._assertAuthorityMutationAllowed();
|
|
1518
|
+
const { label, authorityId } = this._pendingUsageSettlement(callId);
|
|
1519
|
+
const pending = this._pendingUsage.get(authorityId);
|
|
1520
|
+
this._pendingUsage.delete(authorityId);
|
|
1521
|
+
this._pendingUsageLabels.delete(label);
|
|
1522
|
+
const recovery = { ...pending, resourceType: "work" };
|
|
1523
|
+
this._recoveryUnknown.set(authorityId, recovery);
|
|
1524
|
+
this._authorityChanged(
|
|
1525
|
+
"budget:usage-unknown",
|
|
1526
|
+
{
|
|
1527
|
+
pendingUsage: this._pendingUsage.size,
|
|
1528
|
+
pendingRecovery: this._recoveryUnknown.size,
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
rollback: () => {
|
|
1532
|
+
this._recoveryUnknown.delete(authorityId);
|
|
1533
|
+
this._pendingUsage.set(authorityId, pending);
|
|
1534
|
+
this._pendingUsageLabels.set(label, authorityId);
|
|
1535
|
+
},
|
|
1536
|
+
},
|
|
1537
|
+
);
|
|
1538
|
+
return this.status();
|
|
1539
|
+
}
|
|
1086
1540
|
|
|
1541
|
+
recordUsage({
|
|
1542
|
+
callId = null,
|
|
1543
|
+
usage = null,
|
|
1544
|
+
usageRecords = null,
|
|
1545
|
+
provider,
|
|
1546
|
+
model,
|
|
1547
|
+
} = {}) {
|
|
1548
|
+
this._assertAuthorityMutationAllowed();
|
|
1549
|
+
this.start();
|
|
1550
|
+
let accounting;
|
|
1087
1551
|
try {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
this.
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
) {
|
|
1097
|
-
this.unpricedUsage = true;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1552
|
+
accounting = calculateUsageAccounting(
|
|
1553
|
+
{
|
|
1554
|
+
tokens: this.tokens,
|
|
1555
|
+
cost: this.cost,
|
|
1556
|
+
unpricedUsage: this.unpricedUsage,
|
|
1557
|
+
},
|
|
1558
|
+
{ usage, usageRecords, provider, model },
|
|
1559
|
+
);
|
|
1100
1560
|
} catch (error) {
|
|
1101
1561
|
this.abort(error, { reason: "invalid-usage" });
|
|
1102
1562
|
throw error;
|
|
1103
1563
|
}
|
|
1564
|
+
const { label, authorityId: settlementId } =
|
|
1565
|
+
this._pendingUsageSettlement(callId);
|
|
1104
1566
|
const pendingSettlement = this._pendingUsage.get(settlementId);
|
|
1567
|
+
const previousTokens = this.tokens;
|
|
1568
|
+
const previousCost = this.cost;
|
|
1569
|
+
const previousUnpricedUsage = this.unpricedUsage;
|
|
1570
|
+
this.tokens = accounting.tokens;
|
|
1571
|
+
this.cost = accounting.cost;
|
|
1572
|
+
this.unpricedUsage = accounting.unpricedUsage;
|
|
1105
1573
|
this._pendingUsage.delete(settlementId);
|
|
1574
|
+
this._pendingUsageLabels.delete(label);
|
|
1106
1575
|
this._authorityChanged(
|
|
1107
1576
|
"budget:usage-recorded",
|
|
1108
1577
|
{
|
|
@@ -1111,7 +1580,11 @@ export class SessionResourceBudget {
|
|
|
1111
1580
|
},
|
|
1112
1581
|
{
|
|
1113
1582
|
rollback: () => {
|
|
1583
|
+
this.tokens = previousTokens;
|
|
1584
|
+
this.cost = previousCost;
|
|
1585
|
+
this.unpricedUsage = previousUnpricedUsage;
|
|
1114
1586
|
this._pendingUsage.set(settlementId, pendingSettlement);
|
|
1587
|
+
this._pendingUsageLabels.set(label, settlementId);
|
|
1115
1588
|
},
|
|
1116
1589
|
},
|
|
1117
1590
|
);
|
|
@@ -1223,6 +1696,7 @@ export class SessionResourceBudget {
|
|
|
1223
1696
|
cleanupErrors,
|
|
1224
1697
|
activeWork: this._activeWork.size,
|
|
1225
1698
|
activeTools: this._activeTools.size,
|
|
1699
|
+
pendingUsage: this._pendingUsage.size,
|
|
1226
1700
|
};
|
|
1227
1701
|
if (skipAuthorityChange) this._emit("budget:aborted", detail);
|
|
1228
1702
|
else this._authorityChanged("budget:aborted", detail);
|
|
@@ -1276,15 +1750,50 @@ export class SessionResourceBudget {
|
|
|
1276
1750
|
});
|
|
1277
1751
|
}
|
|
1278
1752
|
|
|
1279
|
-
adjudicateRecovery({ abandoned = [] } = {}) {
|
|
1753
|
+
adjudicateRecovery({ abandoned = [], settled = [] } = {}) {
|
|
1280
1754
|
this._assertAuthorityMutationAllowed();
|
|
1281
1755
|
if (!Array.isArray(abandoned)) {
|
|
1282
1756
|
throw new TypeError(
|
|
1283
1757
|
"session budget abandoned recovery ids must be an array",
|
|
1284
1758
|
);
|
|
1285
1759
|
}
|
|
1286
|
-
|
|
1760
|
+
if (!Array.isArray(settled)) {
|
|
1761
|
+
throw new TypeError(
|
|
1762
|
+
"session budget settled recovery records must be an array",
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
const abandonedIds = abandoned.map((id) => String(id));
|
|
1766
|
+
const normalizedSettlements = settled.map((record, index) => {
|
|
1767
|
+
if (!isRecord(record)) {
|
|
1768
|
+
throw new TypeError(
|
|
1769
|
+
`invalid session budget recovery settlement at index ${index}`,
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
const authorityId = String(record.authorityId || "");
|
|
1773
|
+
if (!OPAQUE_AUTHORITY_ID_PATTERNS.usage.test(authorityId)) {
|
|
1774
|
+
throw new TypeError(
|
|
1775
|
+
`invalid session budget recovery settlement authority at index ${index}`,
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
const normalized = { ...record, authorityId };
|
|
1779
|
+
return {
|
|
1780
|
+
...normalized,
|
|
1781
|
+
canonical: canonicalRecoverySettlement(normalized, index),
|
|
1782
|
+
};
|
|
1783
|
+
});
|
|
1784
|
+
const settledIds = normalizedSettlements.map(
|
|
1785
|
+
(record) => record.authorityId,
|
|
1786
|
+
);
|
|
1787
|
+
const suppliedIds = [...abandonedIds, ...settledIds];
|
|
1788
|
+
const supplied = new Set(suppliedIds);
|
|
1287
1789
|
const expected = new Set(this._recoveryUnknown.keys());
|
|
1790
|
+
if (supplied.size !== suppliedIds.length) {
|
|
1791
|
+
return {
|
|
1792
|
+
ok: false,
|
|
1793
|
+
reason: "recovery-adjudication-duplicate",
|
|
1794
|
+
pending: this.pendingRecovery(),
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1288
1797
|
if (
|
|
1289
1798
|
supplied.size !== expected.size ||
|
|
1290
1799
|
[...expected].some((id) => !supplied.has(id))
|
|
@@ -1295,23 +1804,160 @@ export class SessionResourceBudget {
|
|
|
1295
1804
|
pending: this.pendingRecovery(),
|
|
1296
1805
|
};
|
|
1297
1806
|
}
|
|
1807
|
+
for (const record of normalizedSettlements) {
|
|
1808
|
+
const pending = this._recoveryUnknown.get(record.authorityId);
|
|
1809
|
+
if (
|
|
1810
|
+
pending?.resourceType !== "work" ||
|
|
1811
|
+
pending?.kind !== "usage-settlement"
|
|
1812
|
+
) {
|
|
1813
|
+
return {
|
|
1814
|
+
ok: false,
|
|
1815
|
+
reason: "recovery-settlement-not-usage",
|
|
1816
|
+
authorityId: record.authorityId,
|
|
1817
|
+
pending: this.pendingRecovery(),
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
let accounting = {
|
|
1822
|
+
tokens: this.tokens,
|
|
1823
|
+
cost: this.cost,
|
|
1824
|
+
unpricedUsage: this.unpricedUsage,
|
|
1825
|
+
};
|
|
1826
|
+
for (const record of normalizedSettlements) {
|
|
1827
|
+
accounting = calculateUsageAccounting(accounting, record);
|
|
1828
|
+
}
|
|
1829
|
+
const previousAdjudication = this._recoveryAdjudication;
|
|
1830
|
+
const sequence = (previousAdjudication?.count || 0) + 1;
|
|
1831
|
+
const previousDigest = previousAdjudication?.headDigest || null;
|
|
1832
|
+
const sortedAbandoned = [...abandonedIds].sort();
|
|
1833
|
+
const sortedSettlements = normalizedSettlements
|
|
1834
|
+
.map((record) => record.canonical)
|
|
1835
|
+
.sort((left, right) =>
|
|
1836
|
+
left.authorityId.localeCompare(right.authorityId),
|
|
1837
|
+
);
|
|
1838
|
+
const tokenDelta = accounting.tokens - this.tokens;
|
|
1839
|
+
const spentUsdDelta = accounting.cost.spentUsd - this.cost.spentUsd;
|
|
1840
|
+
const adjudicationCore = {
|
|
1841
|
+
schema: SESSION_BUDGET_RECOVERY_ADJUDICATION_SCHEMA,
|
|
1842
|
+
sequence,
|
|
1843
|
+
previousDigest,
|
|
1844
|
+
abandoned: sortedAbandoned,
|
|
1845
|
+
settled: sortedSettlements,
|
|
1846
|
+
totalsBefore: {
|
|
1847
|
+
tokens: this.tokens,
|
|
1848
|
+
spentUsd: this.cost.spentUsd,
|
|
1849
|
+
},
|
|
1850
|
+
totalsAfter: {
|
|
1851
|
+
tokens: accounting.tokens,
|
|
1852
|
+
spentUsd: accounting.cost.spentUsd,
|
|
1853
|
+
},
|
|
1854
|
+
};
|
|
1855
|
+
const adjudicationDigest = sha256(canonicalJson(adjudicationCore));
|
|
1856
|
+
const receipt = {
|
|
1857
|
+
...adjudicationCore,
|
|
1858
|
+
digest: adjudicationDigest,
|
|
1859
|
+
};
|
|
1860
|
+
const adjudicationState = {
|
|
1861
|
+
sequence,
|
|
1862
|
+
digest: adjudicationDigest,
|
|
1863
|
+
previousDigest,
|
|
1864
|
+
settledCount: settledIds.length,
|
|
1865
|
+
abandonedCount: abandonedIds.length,
|
|
1866
|
+
tokenDelta,
|
|
1867
|
+
spentUsdDelta,
|
|
1868
|
+
};
|
|
1869
|
+
const adjudication = {
|
|
1870
|
+
schema: SESSION_BUDGET_RECOVERY_ADJUDICATION_SCHEMA,
|
|
1871
|
+
...adjudicationState,
|
|
1872
|
+
};
|
|
1298
1873
|
const previousRecovery = [...this._recoveryUnknown.entries()];
|
|
1874
|
+
const previousTokens = this.tokens;
|
|
1875
|
+
const previousCost = this.cost;
|
|
1876
|
+
const previousUnpricedUsage = this.unpricedUsage;
|
|
1877
|
+
this.tokens = accounting.tokens;
|
|
1878
|
+
this.cost = accounting.cost;
|
|
1879
|
+
this.unpricedUsage = accounting.unpricedUsage;
|
|
1880
|
+
this._recoveryAdjudication = {
|
|
1881
|
+
schema: SESSION_BUDGET_RECOVERY_ADJUDICATION_SCHEMA,
|
|
1882
|
+
count: sequence,
|
|
1883
|
+
headDigest: adjudicationDigest,
|
|
1884
|
+
last: adjudicationState,
|
|
1885
|
+
history: {
|
|
1886
|
+
schema: SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA,
|
|
1887
|
+
baseSequence:
|
|
1888
|
+
previousAdjudication?.history?.baseSequence ??
|
|
1889
|
+
previousAdjudication?.count ??
|
|
1890
|
+
0,
|
|
1891
|
+
baseDigest:
|
|
1892
|
+
previousAdjudication?.history !== undefined
|
|
1893
|
+
? previousAdjudication.history.baseDigest
|
|
1894
|
+
: previousAdjudication?.headDigest || null,
|
|
1895
|
+
entries: [
|
|
1896
|
+
...(previousAdjudication?.history?.entries || []).map(
|
|
1897
|
+
cloneCanonicalRecord,
|
|
1898
|
+
),
|
|
1899
|
+
receipt,
|
|
1900
|
+
],
|
|
1901
|
+
},
|
|
1902
|
+
};
|
|
1299
1903
|
this._recoveryUnknown.clear();
|
|
1300
1904
|
this._authorityChanged(
|
|
1301
1905
|
"budget:recovery-adjudicated",
|
|
1302
1906
|
{
|
|
1303
|
-
abandonedCount:
|
|
1907
|
+
abandonedCount: abandonedIds.length,
|
|
1908
|
+
settledCount: settledIds.length,
|
|
1909
|
+
tokens: this.tokens,
|
|
1910
|
+
spentUsd: this.cost.spentUsd,
|
|
1911
|
+
adjudicationDigest,
|
|
1304
1912
|
},
|
|
1305
1913
|
{
|
|
1306
1914
|
rollback: () => {
|
|
1915
|
+
this.tokens = previousTokens;
|
|
1916
|
+
this.cost = previousCost;
|
|
1917
|
+
this.unpricedUsage = previousUnpricedUsage;
|
|
1918
|
+
this._recoveryAdjudication = previousAdjudication;
|
|
1307
1919
|
for (const [id, entry] of previousRecovery) {
|
|
1308
1920
|
this._recoveryUnknown.set(id, entry);
|
|
1309
1921
|
}
|
|
1310
1922
|
},
|
|
1311
1923
|
},
|
|
1312
1924
|
);
|
|
1313
|
-
this.
|
|
1314
|
-
|
|
1925
|
+
const reason = this._continuousReason();
|
|
1926
|
+
if (reason) this._abortFor(reason);
|
|
1927
|
+
else this._armDeadline();
|
|
1928
|
+
return {
|
|
1929
|
+
ok: true,
|
|
1930
|
+
abandoned: [...abandonedIds].sort(),
|
|
1931
|
+
settled: [...settledIds].sort(),
|
|
1932
|
+
adjudication: { ...adjudication },
|
|
1933
|
+
receipt: cloneCanonicalRecord(receipt),
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
recoveryAdjudicationReceipts() {
|
|
1938
|
+
if (!this._recoveryAdjudication) {
|
|
1939
|
+
return {
|
|
1940
|
+
schema: SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA,
|
|
1941
|
+
count: 0,
|
|
1942
|
+
headDigest: null,
|
|
1943
|
+
baseSequence: 0,
|
|
1944
|
+
baseDigest: null,
|
|
1945
|
+
complete: true,
|
|
1946
|
+
entries: [],
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
const history = this._recoveryAdjudication.history;
|
|
1950
|
+
return {
|
|
1951
|
+
schema: SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA,
|
|
1952
|
+
count: this._recoveryAdjudication.count,
|
|
1953
|
+
headDigest: this._recoveryAdjudication.headDigest,
|
|
1954
|
+
baseSequence: history?.baseSequence ?? this._recoveryAdjudication.count,
|
|
1955
|
+
baseDigest: history
|
|
1956
|
+
? history.baseDigest
|
|
1957
|
+
: this._recoveryAdjudication.headDigest,
|
|
1958
|
+
complete: history ? history.baseSequence === 0 : false,
|
|
1959
|
+
entries: history?.entries.map(cloneCanonicalRecord) || [],
|
|
1960
|
+
};
|
|
1315
1961
|
}
|
|
1316
1962
|
|
|
1317
1963
|
status(now = this._now()) {
|
|
@@ -1332,9 +1978,14 @@ export class SessionResourceBudget {
|
|
|
1332
1978
|
toolMs: this._toolMsAt(now),
|
|
1333
1979
|
maxToolMs: this.maxToolMs,
|
|
1334
1980
|
activeTools: this._activeTools.size,
|
|
1981
|
+
pendingUsage: this._pendingUsage.size,
|
|
1335
1982
|
resources: this._abortables.size,
|
|
1336
1983
|
recoveryRequired: this._recoveryUnknown.size > 0,
|
|
1337
1984
|
pendingRecovery: this._recoveryUnknown.size,
|
|
1985
|
+
recoveryAdjudication: cloneRecoveryAdjudication(
|
|
1986
|
+
this._recoveryAdjudication,
|
|
1987
|
+
{ includeReceipts: false },
|
|
1988
|
+
),
|
|
1338
1989
|
unpricedUsage: this.unpricedUsage,
|
|
1339
1990
|
aborted: this.signal.aborted,
|
|
1340
1991
|
reason: this.reason(now),
|
|
@@ -1386,6 +2037,13 @@ export class SessionResourceBudget {
|
|
|
1386
2037
|
state: {
|
|
1387
2038
|
started: this._startedAt !== null,
|
|
1388
2039
|
abort: this._abortState ? { ...this._abortState } : null,
|
|
2040
|
+
...(this._recoveryAdjudication
|
|
2041
|
+
? {
|
|
2042
|
+
recoveryAdjudication: cloneRecoveryAdjudication(
|
|
2043
|
+
this._recoveryAdjudication,
|
|
2044
|
+
),
|
|
2045
|
+
}
|
|
2046
|
+
: {}),
|
|
1389
2047
|
},
|
|
1390
2048
|
};
|
|
1391
2049
|
}
|
|
@@ -1442,6 +2100,9 @@ export class SessionResourceBudget {
|
|
|
1442
2100
|
budget._toolMs = normalized.totals.toolMs;
|
|
1443
2101
|
const restoredAt = budget._now();
|
|
1444
2102
|
budget._startedAt = normalized.state.started ? restoredAt : null;
|
|
2103
|
+
budget._recoveryAdjudication = cloneRecoveryAdjudication(
|
|
2104
|
+
normalized.state.recoveryAdjudication,
|
|
2105
|
+
);
|
|
1445
2106
|
|
|
1446
2107
|
if (recoverUnsettled === "require-adjudication") {
|
|
1447
2108
|
for (const entry of normalized.inFlight.work) {
|