@tangle-network/agent-eval 0.120.2 → 0.120.3
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/CHANGELOG.md +6 -0
- package/dist/analyst/index.d.ts +3111 -0
- package/dist/analyst/index.js +403 -0
- package/dist/analyst/index.js.map +1 -0
- package/dist/authenticity/index.d.ts +161 -0
- package/dist/authenticity/index.js +215 -0
- package/dist/authenticity/index.js.map +1 -0
- package/dist/belief-state/index.d.ts +1301 -0
- package/dist/belief-state/index.js +2152 -0
- package/dist/belief-state/index.js.map +1 -0
- package/dist/benchmarks/index.d.ts +974 -0
- package/dist/benchmarks/index.js +60 -0
- package/dist/benchmarks/index.js.map +1 -0
- package/dist/builder-eval/index.d.ts +695 -0
- package/dist/builder-eval/index.js +366 -0
- package/dist/builder-eval/index.js.map +1 -0
- package/dist/campaign/index.d.ts +7454 -0
- package/dist/campaign/index.js +272 -0
- package/dist/campaign/index.js.map +1 -0
- package/dist/chunk-3CDFMEMO.js +3878 -0
- package/dist/chunk-3CDFMEMO.js.map +1 -0
- package/dist/chunk-3RF76KTD.js +84 -0
- package/dist/chunk-3RF76KTD.js.map +1 -0
- package/dist/chunk-3XH4Y2SS.js +750 -0
- package/dist/chunk-3XH4Y2SS.js.map +1 -0
- package/dist/chunk-3YYRZDON.js +45 -0
- package/dist/chunk-3YYRZDON.js.map +1 -0
- package/dist/chunk-5BYTIDZ7.js +550 -0
- package/dist/chunk-5BYTIDZ7.js.map +1 -0
- package/dist/chunk-5CVUPHJ4.js +2668 -0
- package/dist/chunk-5CVUPHJ4.js.map +1 -0
- package/dist/chunk-ARU2PZFM.js +312 -0
- package/dist/chunk-ARU2PZFM.js.map +1 -0
- package/dist/chunk-BOD4O7OF.js +40 -0
- package/dist/chunk-BOD4O7OF.js.map +1 -0
- package/dist/chunk-CVJP5TMD.js +766 -0
- package/dist/chunk-CVJP5TMD.js.map +1 -0
- package/dist/chunk-DPZAEKA6.js +880 -0
- package/dist/chunk-DPZAEKA6.js.map +1 -0
- package/dist/chunk-DTJ6QUQB.js +131 -0
- package/dist/chunk-DTJ6QUQB.js.map +1 -0
- package/dist/chunk-GGE4NNQT.js +65 -0
- package/dist/chunk-GGE4NNQT.js.map +1 -0
- package/dist/chunk-H5UD2323.js +286 -0
- package/dist/chunk-H5UD2323.js.map +1 -0
- package/dist/chunk-HHWE3POT.js +94 -0
- package/dist/chunk-HHWE3POT.js.map +1 -0
- package/dist/chunk-HKUCJ437.js +787 -0
- package/dist/chunk-HKUCJ437.js.map +1 -0
- package/dist/chunk-JHCHEVET.js +274 -0
- package/dist/chunk-JHCHEVET.js.map +1 -0
- package/dist/chunk-K4DBDHLK.js +158 -0
- package/dist/chunk-K4DBDHLK.js.map +1 -0
- package/dist/chunk-K6N6XJJX.js +306 -0
- package/dist/chunk-K6N6XJJX.js.map +1 -0
- package/dist/chunk-MA6HLL3S.js +65 -0
- package/dist/chunk-MA6HLL3S.js.map +1 -0
- package/dist/chunk-MAZ26DC7.js +99 -0
- package/dist/chunk-MAZ26DC7.js.map +1 -0
- package/dist/chunk-MOXWMGPC.js +577 -0
- package/dist/chunk-MOXWMGPC.js.map +1 -0
- package/dist/chunk-NJC7U437.js +626 -0
- package/dist/chunk-NJC7U437.js.map +1 -0
- package/dist/chunk-NMN4WGSJ.js +1030 -0
- package/dist/chunk-NMN4WGSJ.js.map +1 -0
- package/dist/chunk-NPCTHQIO.js +91 -0
- package/dist/chunk-NPCTHQIO.js.map +1 -0
- package/dist/chunk-ONWEPEDO.js +57 -0
- package/dist/chunk-ONWEPEDO.js.map +1 -0
- package/dist/chunk-OYZAPX5G.js +1526 -0
- package/dist/chunk-OYZAPX5G.js.map +1 -0
- package/dist/chunk-P5MGQ2FY.js +7958 -0
- package/dist/chunk-P5MGQ2FY.js.map +1 -0
- package/dist/chunk-PC4UYEBM.js +166 -0
- package/dist/chunk-PC4UYEBM.js.map +1 -0
- package/dist/chunk-PJQFMIOX.js +1182 -0
- package/dist/chunk-PJQFMIOX.js.map +1 -0
- package/dist/chunk-PXD6ZFNY.js +1107 -0
- package/dist/chunk-PXD6ZFNY.js.map +1 -0
- package/dist/chunk-PXE2VKMX.js +140 -0
- package/dist/chunk-PXE2VKMX.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-QBRSJK47.js +622 -0
- package/dist/chunk-QBRSJK47.js.map +1 -0
- package/dist/chunk-S3UZOQ5Y.js +328 -0
- package/dist/chunk-S3UZOQ5Y.js.map +1 -0
- package/dist/chunk-SQQED7ZH.js +998 -0
- package/dist/chunk-SQQED7ZH.js.map +1 -0
- package/dist/chunk-SYV364BL.js +1266 -0
- package/dist/chunk-SYV364BL.js.map +1 -0
- package/dist/chunk-T4SQEITX.js +95 -0
- package/dist/chunk-T4SQEITX.js.map +1 -0
- package/dist/chunk-TT4KNT67.js +124 -0
- package/dist/chunk-TT4KNT67.js.map +1 -0
- package/dist/chunk-U5CHZ5M3.js +357 -0
- package/dist/chunk-U5CHZ5M3.js.map +1 -0
- package/dist/chunk-ULOKLHIQ.js +1937 -0
- package/dist/chunk-ULOKLHIQ.js.map +1 -0
- package/dist/chunk-VI2UW6B6.js +162 -0
- package/dist/chunk-VI2UW6B6.js.map +1 -0
- package/dist/chunk-VQMK5FMP.js +247 -0
- package/dist/chunk-VQMK5FMP.js.map +1 -0
- package/dist/chunk-VSMTAMNK.js +53 -0
- package/dist/chunk-VSMTAMNK.js.map +1 -0
- package/dist/chunk-VZSRQ272.js +149 -0
- package/dist/chunk-VZSRQ272.js.map +1 -0
- package/dist/chunk-WW2A73HW.js +159 -0
- package/dist/chunk-WW2A73HW.js.map +1 -0
- package/dist/chunk-X4UCIOTZ.js +136 -0
- package/dist/chunk-X4UCIOTZ.js.map +1 -0
- package/dist/chunk-XJYR7XFV.js +317 -0
- package/dist/chunk-XJYR7XFV.js.map +1 -0
- package/dist/chunk-ZET2UAYW.js +89 -0
- package/dist/chunk-ZET2UAYW.js.map +1 -0
- package/dist/chunk-ZMXDQ4K7.js +870 -0
- package/dist/chunk-ZMXDQ4K7.js.map +1 -0
- package/dist/chunk-ZZUXHH3R.js +99 -0
- package/dist/chunk-ZZUXHH3R.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +112 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract/index.d.ts +4972 -0
- package/dist/contract/index.js +1654 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/control.d.ts +1013 -0
- package/dist/control.js +34 -0
- package/dist/control.js.map +1 -0
- package/dist/fuzz.d.ts +759 -0
- package/dist/fuzz.js +714 -0
- package/dist/fuzz.js.map +1 -0
- package/dist/hosted/index.d.ts +730 -0
- package/dist/hosted/index.js +14 -0
- package/dist/hosted/index.js.map +1 -0
- package/dist/index.d.ts +16780 -0
- package/dist/index.js +12168 -0
- package/dist/index.js.map +1 -0
- package/dist/matrix/index.d.ts +155 -0
- package/dist/matrix/index.js +8 -0
- package/dist/matrix/index.js.map +1 -0
- package/dist/meta-eval/index.d.ts +1030 -0
- package/dist/meta-eval/index.js +417 -0
- package/dist/meta-eval/index.js.map +1 -0
- package/dist/multishot/index.d.ts +579 -0
- package/dist/multishot/index.js +589 -0
- package/dist/multishot/index.js.map +1 -0
- package/dist/openapi.json +992 -0
- package/dist/pipelines/index.d.ts +567 -0
- package/dist/pipelines/index.js +515 -0
- package/dist/pipelines/index.js.map +1 -0
- package/dist/reporting.d.ts +1277 -0
- package/dist/reporting.js +48 -0
- package/dist/reporting.js.map +1 -0
- package/dist/rl.d.ts +4092 -0
- package/dist/rl.js +1724 -0
- package/dist/rl.js.map +1 -0
- package/dist/run-campaign-75RTPVV5.js +14 -0
- package/dist/run-campaign-75RTPVV5.js.map +1 -0
- package/dist/storyboard/index.d.ts +279 -0
- package/dist/storyboard/index.js +767 -0
- package/dist/storyboard/index.js.map +1 -0
- package/dist/trace-attributes.d.ts +52 -0
- package/dist/trace-attributes.js +62 -0
- package/dist/trace-attributes.js.map +1 -0
- package/dist/traces.d.ts +2343 -0
- package/dist/traces.js +249 -0
- package/dist/traces.js.map +1 -0
- package/dist/wire/index.d.ts +1252 -0
- package/dist/wire/index.js +81 -0
- package/dist/wire/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
import {
|
|
2
|
+
estimateCost,
|
|
3
|
+
isModelPriced,
|
|
4
|
+
resolveModelPricing
|
|
5
|
+
} from "./chunk-VI2UW6B6.js";
|
|
6
|
+
import {
|
|
7
|
+
ValidationError
|
|
8
|
+
} from "./chunk-ONWEPEDO.js";
|
|
9
|
+
|
|
10
|
+
// src/cost-ledger.ts
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
var CostCeilingReachedError = class extends ValidationError {
|
|
13
|
+
constructor(ceilingUsd, committedAndReservedUsd, requestedUsd, phase, actor) {
|
|
14
|
+
super(
|
|
15
|
+
`CostLedger: reserving ${requestedUsd} for '${actor}' during '${phase}' would exceed ceiling ${ceilingUsd} with ${committedAndReservedUsd} already committed or reserved`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var CostAccountingIncompleteError = class extends ValidationError {
|
|
20
|
+
};
|
|
21
|
+
var CostReservationExceededError = class extends ValidationError {
|
|
22
|
+
constructor(actor, actualUsd, maximumUsd) {
|
|
23
|
+
super(
|
|
24
|
+
`CostLedger: '${actor}' charged ${actualUsd}, exceeding its enforced maximum ${maximumUsd}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var CostCallConflictError = class extends ValidationError {
|
|
29
|
+
callId;
|
|
30
|
+
receipt;
|
|
31
|
+
constructor(message, options = {}) {
|
|
32
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
33
|
+
this.callId = options.callId;
|
|
34
|
+
this.receipt = options.receipt ? cloneReceipt(options.receipt) : void 0;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var CostLedgerPersistenceError = class extends ValidationError {
|
|
38
|
+
callId;
|
|
39
|
+
receipt;
|
|
40
|
+
constructor(cause, callId, receipt) {
|
|
41
|
+
super(
|
|
42
|
+
`CostLedger: failed to persist${callId ? ` call '${callId}'` : ""}: ${toError(cause).message}`,
|
|
43
|
+
{ cause }
|
|
44
|
+
);
|
|
45
|
+
this.callId = callId;
|
|
46
|
+
this.receipt = receipt ? cloneReceipt(receipt) : void 0;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var CostReceiptCaptureError = class extends ValidationError {
|
|
50
|
+
callId;
|
|
51
|
+
receipt;
|
|
52
|
+
receiptError;
|
|
53
|
+
constructor(callId, cause, receiptError, receipt) {
|
|
54
|
+
super(`CostLedger: could not capture the provider receipt for call '${callId}'`, { cause });
|
|
55
|
+
this.callId = callId;
|
|
56
|
+
this.receipt = receipt ? cloneReceipt(receipt) : void 0;
|
|
57
|
+
this.receiptError = toError(receiptError);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var CostLedger = class {
|
|
61
|
+
records = /* @__PURE__ */ new Map();
|
|
62
|
+
activeCallIds = /* @__PURE__ */ new Set();
|
|
63
|
+
lateCallIds = /* @__PURE__ */ new Set();
|
|
64
|
+
idleWaiters = /* @__PURE__ */ new Set();
|
|
65
|
+
completedTasks = 0;
|
|
66
|
+
revision = "memory";
|
|
67
|
+
costLimitPersisted = false;
|
|
68
|
+
costCeilingUsd;
|
|
69
|
+
persistence;
|
|
70
|
+
constructor(input) {
|
|
71
|
+
const options = typeof input === "number" ? { costCeilingUsd: input } : input ?? {};
|
|
72
|
+
this.persistence = options.persistence;
|
|
73
|
+
if (options.costCeilingUsd !== void 0) {
|
|
74
|
+
assertNonNegative(options.costCeilingUsd, "costCeilingUsd");
|
|
75
|
+
}
|
|
76
|
+
let persistedCostCeilingUsd;
|
|
77
|
+
if (this.persistence) {
|
|
78
|
+
let stored;
|
|
79
|
+
try {
|
|
80
|
+
stored = this.persistence.read();
|
|
81
|
+
} catch (cause) {
|
|
82
|
+
throw new CostLedgerPersistenceError(cause);
|
|
83
|
+
}
|
|
84
|
+
assertString(stored.revision, "persistence revision");
|
|
85
|
+
this.revision = stored.revision;
|
|
86
|
+
const restored = parseEvents(stored.events);
|
|
87
|
+
for (const record of restored.records) this.records.set(record.callId, record);
|
|
88
|
+
this.completedTasks = restored.completedTasks;
|
|
89
|
+
persistedCostCeilingUsd = restored.costCeilingUsd;
|
|
90
|
+
}
|
|
91
|
+
if (options.costCeilingUsd !== void 0 && persistedCostCeilingUsd !== void 0 && options.costCeilingUsd !== persistedCostCeilingUsd) {
|
|
92
|
+
throw new ValidationError(
|
|
93
|
+
`CostLedger: requested cost ceiling ${options.costCeilingUsd} does not match persisted ceiling ${persistedCostCeilingUsd}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
this.costCeilingUsd = persistedCostCeilingUsd ?? options.costCeilingUsd;
|
|
97
|
+
this.costLimitPersisted = !this.persistence || this.costCeilingUsd === void 0 || persistedCostCeilingUsd !== void 0;
|
|
98
|
+
if (this.costCeilingUsd !== void 0 && [...this.records.values()].some(
|
|
99
|
+
(record) => record.status === "pending" && record.maximumCostUsd === void 0
|
|
100
|
+
)) {
|
|
101
|
+
throw new ValidationError("CostLedger: capped event log contains an unbounded pending call");
|
|
102
|
+
}
|
|
103
|
+
if (options.receipts?.length) {
|
|
104
|
+
const imported = options.receipts.map(
|
|
105
|
+
(receipt, index) => parseImportedReceipt(receipt, `imported receipt ${index + 1}`)
|
|
106
|
+
);
|
|
107
|
+
this.ensureCostLimitPersisted();
|
|
108
|
+
for (const receipt of imported) this.appendRecord(receipt);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async runPaidCall(input) {
|
|
112
|
+
let callId;
|
|
113
|
+
let pending;
|
|
114
|
+
try {
|
|
115
|
+
callId = resolveCallId(input.callId);
|
|
116
|
+
validateAttribution(input);
|
|
117
|
+
if (input.signal?.aborted) {
|
|
118
|
+
return { succeeded: false, callId, error: abortError(input.signal) };
|
|
119
|
+
}
|
|
120
|
+
if (this.records.has(callId)) {
|
|
121
|
+
return {
|
|
122
|
+
succeeded: false,
|
|
123
|
+
callId,
|
|
124
|
+
error: new CostCallConflictError(`CostLedger: callId '${callId}' already exists`)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
this.ensureCostLimitPersisted(callId);
|
|
128
|
+
const summary = this.summary();
|
|
129
|
+
if (summary.unresolvedCalls > 0) {
|
|
130
|
+
return {
|
|
131
|
+
succeeded: false,
|
|
132
|
+
callId,
|
|
133
|
+
error: new CostAccountingIncompleteError(
|
|
134
|
+
`CostLedger: ${summary.unresolvedCalls} unresolved call(s) must be reconciled before new paid work`
|
|
135
|
+
)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const maximumCostUsd = this.resolveMaximum(input.maximumCharge);
|
|
139
|
+
if (this.costCeilingUsd !== void 0 && this.hasIncompleteSettledCall()) {
|
|
140
|
+
return {
|
|
141
|
+
succeeded: false,
|
|
142
|
+
callId,
|
|
143
|
+
error: new CostAccountingIncompleteError(
|
|
144
|
+
`CostLedger: accounting is incomplete; refusing paid call '${input.actor}' during '${input.phase}'`
|
|
145
|
+
)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (this.costCeilingUsd !== void 0) {
|
|
149
|
+
const committedAndReserved = summary.totalCostUsd + summary.reservedCostUsd;
|
|
150
|
+
if (committedAndReserved + maximumCostUsd > this.costCeilingUsd) {
|
|
151
|
+
return {
|
|
152
|
+
succeeded: false,
|
|
153
|
+
callId,
|
|
154
|
+
error: new CostCeilingReachedError(
|
|
155
|
+
this.costCeilingUsd,
|
|
156
|
+
committedAndReserved,
|
|
157
|
+
maximumCostUsd,
|
|
158
|
+
input.phase,
|
|
159
|
+
input.actor
|
|
160
|
+
)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
pending = {
|
|
165
|
+
status: "pending",
|
|
166
|
+
callId,
|
|
167
|
+
channel: input.channel,
|
|
168
|
+
phase: input.phase,
|
|
169
|
+
actor: input.actor,
|
|
170
|
+
model: pendingModel(input),
|
|
171
|
+
...maximumCostUsd === void 0 ? {} : { maximumCostUsd },
|
|
172
|
+
...input.tags ? { tags: { ...input.tags } } : {},
|
|
173
|
+
timestamp: Date.now()
|
|
174
|
+
};
|
|
175
|
+
this.appendRecord(pending);
|
|
176
|
+
this.activeCallIds.add(callId);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return { succeeded: false, ...callId ? { callId } : {}, error: toError(error) };
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
return await this.execute(input, pending);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return { succeeded: false, callId: pending.callId, error: toError(error) };
|
|
184
|
+
} finally {
|
|
185
|
+
if (!this.lateCallIds.has(pending.callId)) this.releaseActiveCall(pending.callId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Wait until every call started by this ledger has produced a durable outcome. */
|
|
189
|
+
async waitForIdle(options = {}) {
|
|
190
|
+
if (this.activeCallIds.size === 0) return true;
|
|
191
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
192
|
+
assertTimeout(timeoutMs, "waitForIdle.timeoutMs");
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
let timer;
|
|
195
|
+
const finish = (settled) => {
|
|
196
|
+
this.idleWaiters.delete(onIdle);
|
|
197
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
198
|
+
resolve(settled);
|
|
199
|
+
};
|
|
200
|
+
const onIdle = () => finish(true);
|
|
201
|
+
this.idleWaiters.add(onIdle);
|
|
202
|
+
timer = setTimeout(() => finish(false), timeoutMs);
|
|
203
|
+
if (this.activeCallIds.size === 0) onIdle();
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/** Settle a call left pending by a crashed process after reconciling with the provider. */
|
|
207
|
+
reconcile(callId, observed, options = {}) {
|
|
208
|
+
const pending = [...this.records.values()].find(
|
|
209
|
+
(record) => record.callId === callId && record.status === "pending"
|
|
210
|
+
);
|
|
211
|
+
if (!pending) throw new CostCallConflictError(`CostLedger: no pending call '${callId}'`);
|
|
212
|
+
if (this.activeCallIds.has(callId)) {
|
|
213
|
+
throw new CostCallConflictError(`CostLedger: call '${callId}' is still active`);
|
|
214
|
+
}
|
|
215
|
+
this.ensureCostLimitPersisted(callId);
|
|
216
|
+
return this.commitReceipt(pending, observed, options.error);
|
|
217
|
+
}
|
|
218
|
+
list(filter) {
|
|
219
|
+
return [...this.records.values()].filter((record) => record.status === "settled").filter((receipt) => matches(receipt, filter)).map(cloneReceipt);
|
|
220
|
+
}
|
|
221
|
+
summary(filter) {
|
|
222
|
+
const records = [...this.records.values()].filter((record) => matches(record, filter));
|
|
223
|
+
const pending = records.filter(
|
|
224
|
+
(record) => record.status === "pending"
|
|
225
|
+
);
|
|
226
|
+
const receipts = records.filter((record) => record.status === "settled");
|
|
227
|
+
const byChannel = /* @__PURE__ */ new Map();
|
|
228
|
+
const unpriced = /* @__PURE__ */ new Set();
|
|
229
|
+
const incompleteReasons = pending.map(
|
|
230
|
+
(record) => `call '${record.callId}' for '${record.actor}' is pending`
|
|
231
|
+
);
|
|
232
|
+
let inputTokens = 0;
|
|
233
|
+
let outputTokens = 0;
|
|
234
|
+
let reasoningTokens = 0;
|
|
235
|
+
let cachedTokens = 0;
|
|
236
|
+
let cacheWriteTokens = 0;
|
|
237
|
+
let totalCostUsd = 0;
|
|
238
|
+
for (const receipt of receipts) {
|
|
239
|
+
inputTokens += receipt.inputTokens;
|
|
240
|
+
outputTokens += receipt.outputTokens;
|
|
241
|
+
reasoningTokens += receipt.reasoningTokens ?? 0;
|
|
242
|
+
cachedTokens += receipt.cachedTokens ?? 0;
|
|
243
|
+
cacheWriteTokens += receipt.cacheWriteTokens ?? 0;
|
|
244
|
+
totalCostUsd += receipt.costUsd;
|
|
245
|
+
if (receipt.costUnknown) {
|
|
246
|
+
unpriced.add(receipt.model);
|
|
247
|
+
incompleteReasons.push(
|
|
248
|
+
receipt.error ?? `cost unknown for '${receipt.actor}' using '${receipt.model}'`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (receipt.usageUnknown) {
|
|
252
|
+
incompleteReasons.push(
|
|
253
|
+
`token usage unknown for '${receipt.actor}' using '${receipt.model}'`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (receipt.maximumCostUsd !== void 0 && receipt.costUsd > receipt.maximumCostUsd) {
|
|
257
|
+
incompleteReasons.push(
|
|
258
|
+
`'${receipt.actor}' charged ${receipt.costUsd}, exceeding its enforced maximum ${receipt.maximumCostUsd}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
const rollup = byChannel.get(receipt.channel) ?? emptyRollup(receipt.channel);
|
|
262
|
+
rollup.calls += 1;
|
|
263
|
+
rollup.inputTokens += receipt.inputTokens;
|
|
264
|
+
rollup.outputTokens += receipt.outputTokens;
|
|
265
|
+
rollup.reasoningTokens = (rollup.reasoningTokens ?? 0) + (receipt.reasoningTokens ?? 0);
|
|
266
|
+
rollup.cachedTokens += receipt.cachedTokens ?? 0;
|
|
267
|
+
rollup.cacheWriteTokens = (rollup.cacheWriteTokens ?? 0) + (receipt.cacheWriteTokens ?? 0);
|
|
268
|
+
rollup.costUsd += receipt.costUsd;
|
|
269
|
+
if (receipt.costUnknown) rollup.unpricedCalls += 1;
|
|
270
|
+
if (receipt.usageUnknown) rollup.unknownUsageCalls += 1;
|
|
271
|
+
byChannel.set(receipt.channel, rollup);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
totalCalls: receipts.length,
|
|
275
|
+
pendingCalls: pending.length,
|
|
276
|
+
unresolvedCalls: pending.filter(
|
|
277
|
+
(record) => !this.activeCallIds.has(record.callId) || this.lateCallIds.has(record.callId)
|
|
278
|
+
).length,
|
|
279
|
+
reservedCostUsd: pending.reduce((sum, record) => sum + (record.maximumCostUsd ?? 0), 0),
|
|
280
|
+
inputTokens,
|
|
281
|
+
outputTokens,
|
|
282
|
+
reasoningTokens,
|
|
283
|
+
cachedTokens,
|
|
284
|
+
cacheWriteTokens,
|
|
285
|
+
totalCostUsd,
|
|
286
|
+
byChannel: [...byChannel.values()].sort((a, b) => a.channel.localeCompare(b.channel)),
|
|
287
|
+
unpricedModels: [...unpriced].sort(),
|
|
288
|
+
fullyPriced: unpriced.size === 0,
|
|
289
|
+
usageComplete: pending.length === 0 && receipts.every((receipt) => !receipt.usageUnknown),
|
|
290
|
+
accountingComplete: incompleteReasons.length === 0,
|
|
291
|
+
incompleteReasons: [...new Set(incompleteReasons)]
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
markCompleted(count = 1) {
|
|
295
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
296
|
+
throw new ValidationError(
|
|
297
|
+
`CostLedger.markCompleted: count must be a non-negative integer, got ${count}`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (count === 0) return;
|
|
301
|
+
this.ensureCostLimitPersisted();
|
|
302
|
+
this.appendEvent({ version: 1, completedTasks: count });
|
|
303
|
+
this.completedTasks += count;
|
|
304
|
+
}
|
|
305
|
+
costPerCompletedTask() {
|
|
306
|
+
return this.completedTasks === 0 ? null : this.summary().totalCostUsd / this.completedTasks;
|
|
307
|
+
}
|
|
308
|
+
async execute(input, pending) {
|
|
309
|
+
const signal = input.signal ?? new AbortController().signal;
|
|
310
|
+
if (signal.aborted) {
|
|
311
|
+
return this.commitOutcome(pending, abortError(signal), {
|
|
312
|
+
model: pending.model,
|
|
313
|
+
inputTokens: 0,
|
|
314
|
+
outputTokens: 0,
|
|
315
|
+
actualCostUsd: 0
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const operation = Promise.resolve().then(() => input.execute(signal, pending.callId));
|
|
319
|
+
const settled = await settle(operation, signal);
|
|
320
|
+
if (settled.kind === "aborted") {
|
|
321
|
+
this.lateCallIds.add(pending.callId);
|
|
322
|
+
this.captureLateOutcome(input, pending, operation);
|
|
323
|
+
return paidFailure(pending.callId, abortError(signal));
|
|
324
|
+
}
|
|
325
|
+
if (settled.kind === "error") {
|
|
326
|
+
let observed;
|
|
327
|
+
try {
|
|
328
|
+
observed = input.receiptFromError?.(settled.error);
|
|
329
|
+
} catch (receiptError) {
|
|
330
|
+
return this.captureFailure(pending, settled.error, receiptError);
|
|
331
|
+
}
|
|
332
|
+
return this.commitOutcome(pending, settled.error, observed ?? unknownReceipt(pending.model));
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const receipt = this.commitReceipt(pending, input.receipt(settled.value));
|
|
336
|
+
if (receipt.maximumCostUsd !== void 0 && receipt.costUsd > receipt.maximumCostUsd) {
|
|
337
|
+
return paidFailure(
|
|
338
|
+
pending.callId,
|
|
339
|
+
new CostReservationExceededError(pending.actor, receipt.costUsd, receipt.maximumCostUsd),
|
|
340
|
+
receipt
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return { succeeded: true, callId: pending.callId, value: settled.value, receipt };
|
|
344
|
+
} catch (receiptError) {
|
|
345
|
+
if (receiptError instanceof CostLedgerPersistenceError || receiptError instanceof CostCallConflictError) {
|
|
346
|
+
return paidFailure(pending.callId, receiptError);
|
|
347
|
+
}
|
|
348
|
+
return this.captureFailure(pending, receiptError, receiptError);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
captureLateOutcome(input, pending, operation) {
|
|
352
|
+
void operation.then(
|
|
353
|
+
(value) => {
|
|
354
|
+
if (this.records.get(pending.callId)?.status !== "pending") return;
|
|
355
|
+
try {
|
|
356
|
+
this.commitReceipt(pending, input.receipt(value));
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
(cause) => {
|
|
361
|
+
if (this.records.get(pending.callId)?.status !== "pending") return;
|
|
362
|
+
const error = toError(cause);
|
|
363
|
+
try {
|
|
364
|
+
const observed = input.receiptFromError?.(error);
|
|
365
|
+
this.commitReceipt(pending, observed ?? unknownReceipt(pending.model), error.message);
|
|
366
|
+
} catch (receiptError) {
|
|
367
|
+
if (receiptError instanceof CostLedgerPersistenceError || receiptError instanceof CostCallConflictError) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
this.captureFailure(pending, error, receiptError);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
).finally(() => {
|
|
374
|
+
this.lateCallIds.delete(pending.callId);
|
|
375
|
+
this.releaseActiveCall(pending.callId);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
releaseActiveCall(callId) {
|
|
379
|
+
this.activeCallIds.delete(callId);
|
|
380
|
+
if (this.activeCallIds.size > 0) return;
|
|
381
|
+
for (const resolve of this.idleWaiters) resolve();
|
|
382
|
+
this.idleWaiters.clear();
|
|
383
|
+
}
|
|
384
|
+
commitOutcome(pending, error, observed) {
|
|
385
|
+
try {
|
|
386
|
+
const receipt = this.commitReceipt(pending, observed, error.message);
|
|
387
|
+
return paidFailure(pending.callId, error, receipt);
|
|
388
|
+
} catch (receiptError) {
|
|
389
|
+
if (receiptError instanceof CostLedgerPersistenceError || receiptError instanceof CostCallConflictError) {
|
|
390
|
+
return paidFailure(pending.callId, receiptError);
|
|
391
|
+
}
|
|
392
|
+
return this.captureFailure(pending, error, receiptError);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
captureFailure(pending, cause, receiptError) {
|
|
396
|
+
try {
|
|
397
|
+
const receipt = this.commitReceipt(
|
|
398
|
+
pending,
|
|
399
|
+
unknownReceipt(pending.model),
|
|
400
|
+
toError(receiptError).message
|
|
401
|
+
);
|
|
402
|
+
return paidFailure(
|
|
403
|
+
pending.callId,
|
|
404
|
+
new CostReceiptCaptureError(pending.callId, cause, receiptError, receipt),
|
|
405
|
+
receipt
|
|
406
|
+
);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
const typed = error instanceof Error ? error : toError(error);
|
|
409
|
+
return paidFailure(pending.callId, typed);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
commitReceipt(pending, observed, error) {
|
|
413
|
+
const receipt = buildReceipt(pending, observed, error);
|
|
414
|
+
if (this.records.get(pending.callId)?.status !== "pending") {
|
|
415
|
+
throw new CostCallConflictError(`CostLedger: call '${pending.callId}' is not pending`);
|
|
416
|
+
}
|
|
417
|
+
this.appendRecord(receipt);
|
|
418
|
+
return cloneReceipt(receipt);
|
|
419
|
+
}
|
|
420
|
+
resolveMaximum(maximum) {
|
|
421
|
+
if (!maximum) {
|
|
422
|
+
if (this.costCeilingUsd !== void 0) {
|
|
423
|
+
throw new CostAccountingIncompleteError(
|
|
424
|
+
"CostLedger: capped paid calls require a hard maximumCharge before execution"
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
return void 0;
|
|
428
|
+
}
|
|
429
|
+
if ("externallyEnforcedMaximumUsd" in maximum) {
|
|
430
|
+
assertNonNegative(
|
|
431
|
+
maximum.externallyEnforcedMaximumUsd,
|
|
432
|
+
"maximumCharge.externallyEnforcedMaximumUsd"
|
|
433
|
+
);
|
|
434
|
+
return maximum.externallyEnforcedMaximumUsd;
|
|
435
|
+
}
|
|
436
|
+
const priced = costForUsage(maximum.model, maximum);
|
|
437
|
+
if (priced.costUnknown) {
|
|
438
|
+
if (this.costCeilingUsd !== void 0) {
|
|
439
|
+
throw new CostAccountingIncompleteError(
|
|
440
|
+
`CostLedger: cannot reserve unpriced model '${maximum.model}' in a capped run`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
445
|
+
return priced.costUsd;
|
|
446
|
+
}
|
|
447
|
+
hasIncompleteSettledCall() {
|
|
448
|
+
return [...this.records.values()].some(
|
|
449
|
+
(record) => record.status === "settled" && (record.costUnknown || record.usageUnknown || record.maximumCostUsd !== void 0 && record.costUsd > record.maximumCostUsd)
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
appendRecord(record) {
|
|
453
|
+
const callId = record.callId;
|
|
454
|
+
const receipt = record.status === "settled" ? record : void 0;
|
|
455
|
+
validateTransition(this.records, record);
|
|
456
|
+
const event = record.status === "settled" && (record.reasoningTokens !== void 0 || record.cacheWriteTokens !== void 0) ? { version: 2, record: cloneReceipt(record) } : { version: 1, record: cloneRecord(record) };
|
|
457
|
+
this.appendEvent(event, callId, receipt);
|
|
458
|
+
this.records.set(callId, cloneRecord(record));
|
|
459
|
+
}
|
|
460
|
+
ensureCostLimitPersisted(callId) {
|
|
461
|
+
if (this.costLimitPersisted || this.costCeilingUsd === void 0) return;
|
|
462
|
+
this.appendEvent({ version: 1, costCeilingUsd: this.costCeilingUsd }, callId);
|
|
463
|
+
this.costLimitPersisted = true;
|
|
464
|
+
}
|
|
465
|
+
appendEvent(event, callId, receipt) {
|
|
466
|
+
try {
|
|
467
|
+
if (this.persistence) {
|
|
468
|
+
const nextRevision = this.persistence.append(this.revision, `${JSON.stringify(event)}
|
|
469
|
+
`);
|
|
470
|
+
if (nextRevision === void 0) {
|
|
471
|
+
throw new CostCallConflictError(
|
|
472
|
+
`CostLedger: persisted revision changed while writing call '${callId}'`,
|
|
473
|
+
{ callId, receipt }
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
assertString(nextRevision, "persistence revision");
|
|
477
|
+
this.revision = nextRevision;
|
|
478
|
+
}
|
|
479
|
+
} catch (cause) {
|
|
480
|
+
if (cause instanceof CostCallConflictError) throw cause;
|
|
481
|
+
throw new CostLedgerPersistenceError(cause, callId, receipt);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
function modelPriceKey(model) {
|
|
486
|
+
return isModelPriced(model) ? model : null;
|
|
487
|
+
}
|
|
488
|
+
function costForUsage(model, usage) {
|
|
489
|
+
assertUsage(usage);
|
|
490
|
+
if (!resolveModelPricing(model)) return { costUsd: 0, costUnknown: true };
|
|
491
|
+
return {
|
|
492
|
+
costUsd: estimateCost(
|
|
493
|
+
usage.inputTokens + (usage.cachedTokens ?? 0) + (usage.cacheWriteTokens ?? 0),
|
|
494
|
+
usage.outputTokens,
|
|
495
|
+
model
|
|
496
|
+
),
|
|
497
|
+
costUnknown: false
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
async function settle(promise, signal) {
|
|
501
|
+
return await new Promise((resolve) => {
|
|
502
|
+
let done = false;
|
|
503
|
+
const finish = (value) => {
|
|
504
|
+
if (done) return;
|
|
505
|
+
done = true;
|
|
506
|
+
signal.removeEventListener("abort", onAbort);
|
|
507
|
+
resolve(value);
|
|
508
|
+
};
|
|
509
|
+
const onAbort = () => finish({ kind: "aborted" });
|
|
510
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
511
|
+
if (signal.aborted) onAbort();
|
|
512
|
+
promise.then(
|
|
513
|
+
(value) => finish({ kind: "value", value }),
|
|
514
|
+
(error) => finish({ kind: "error", error: toError(error) })
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
function buildReceipt(pending, observed, error) {
|
|
519
|
+
assertUsage(observed);
|
|
520
|
+
assertString(observed.model, "receipt.model");
|
|
521
|
+
const estimated = costForUsage(observed.model, observed);
|
|
522
|
+
const hasActual = observed.actualCostUsd !== void 0;
|
|
523
|
+
if (hasActual && observed.costUnknown === true) {
|
|
524
|
+
throw new ValidationError(
|
|
525
|
+
"CostLedger: a receipt cannot have both actualCostUsd and costUnknown=true"
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (hasActual) assertNonNegative(observed.actualCostUsd, "actualCostUsd");
|
|
529
|
+
const usageUnknown = observed.usageUnknown === true;
|
|
530
|
+
const costUnknown = observed.costUnknown === true || !hasActual && (usageUnknown || estimated.costUnknown);
|
|
531
|
+
const resolvedPricing = !hasActual && !costUnknown ? resolveModelPricing(observed.model) : null;
|
|
532
|
+
return parseReceipt(
|
|
533
|
+
{
|
|
534
|
+
status: "settled",
|
|
535
|
+
callId: pending.callId,
|
|
536
|
+
channel: pending.channel,
|
|
537
|
+
phase: pending.phase,
|
|
538
|
+
actor: pending.actor,
|
|
539
|
+
model: observed.model,
|
|
540
|
+
inputTokens: observed.inputTokens,
|
|
541
|
+
outputTokens: observed.outputTokens,
|
|
542
|
+
...observed.reasoningTokens === void 0 ? {} : { reasoningTokens: observed.reasoningTokens },
|
|
543
|
+
...observed.cachedTokens === void 0 ? {} : { cachedTokens: observed.cachedTokens },
|
|
544
|
+
...observed.cacheWriteTokens === void 0 ? {} : { cacheWriteTokens: observed.cacheWriteTokens },
|
|
545
|
+
costUsd: costUnknown ? 0 : hasActual ? observed.actualCostUsd : estimated.costUsd,
|
|
546
|
+
costUnknown,
|
|
547
|
+
usageUnknown,
|
|
548
|
+
...resolvedPricing ? {
|
|
549
|
+
pricing: {
|
|
550
|
+
inputUsdPerThousand: resolvedPricing.input,
|
|
551
|
+
outputUsdPerThousand: resolvedPricing.output
|
|
552
|
+
}
|
|
553
|
+
} : {},
|
|
554
|
+
...hasActual ? { actualCostUsd: observed.actualCostUsd } : {},
|
|
555
|
+
...pending.maximumCostUsd === void 0 ? {} : { maximumCostUsd: pending.maximumCostUsd },
|
|
556
|
+
...error ? { error } : {},
|
|
557
|
+
...pending.tags ? { tags: { ...pending.tags } } : {},
|
|
558
|
+
timestamp: pending.timestamp
|
|
559
|
+
},
|
|
560
|
+
"provider receipt"
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
var NonEmptyString = z.string().refine((value) => value.trim().length > 0, "must be non-empty");
|
|
564
|
+
var TokenCount = z.number().int().nonnegative().finite();
|
|
565
|
+
var NonNegative = z.number().nonnegative().finite();
|
|
566
|
+
var Positive = z.number().positive().finite();
|
|
567
|
+
var Tags = z.record(NonEmptyString, z.string());
|
|
568
|
+
var CostPricingSchema = z.strictObject({
|
|
569
|
+
inputUsdPerThousand: Positive,
|
|
570
|
+
outputUsdPerThousand: Positive
|
|
571
|
+
});
|
|
572
|
+
var CostCallBaseShape = {
|
|
573
|
+
callId: NonEmptyString,
|
|
574
|
+
channel: NonEmptyString,
|
|
575
|
+
phase: NonEmptyString,
|
|
576
|
+
actor: NonEmptyString,
|
|
577
|
+
model: NonEmptyString,
|
|
578
|
+
maximumCostUsd: NonNegative.optional(),
|
|
579
|
+
tags: Tags.optional(),
|
|
580
|
+
timestamp: NonNegative
|
|
581
|
+
};
|
|
582
|
+
var PendingCostCallSchema = z.strictObject({
|
|
583
|
+
status: z.literal("pending"),
|
|
584
|
+
...CostCallBaseShape
|
|
585
|
+
});
|
|
586
|
+
var CostReceiptBaseShape = {
|
|
587
|
+
status: z.literal("settled"),
|
|
588
|
+
...CostCallBaseShape,
|
|
589
|
+
inputTokens: TokenCount,
|
|
590
|
+
outputTokens: TokenCount,
|
|
591
|
+
cachedTokens: TokenCount.optional(),
|
|
592
|
+
costUsd: NonNegative,
|
|
593
|
+
costUnknown: z.boolean(),
|
|
594
|
+
usageUnknown: z.boolean().default(false),
|
|
595
|
+
pricing: CostPricingSchema.optional(),
|
|
596
|
+
actualCostUsd: NonNegative.optional(),
|
|
597
|
+
error: z.string().optional()
|
|
598
|
+
};
|
|
599
|
+
var LegacyCostReceiptSchema = z.strictObject(CostReceiptBaseShape).superRefine((receipt, ctx) => validateCostReceipt(receipt, ctx));
|
|
600
|
+
var CostReceiptSchema = z.strictObject({
|
|
601
|
+
...CostReceiptBaseShape,
|
|
602
|
+
reasoningTokens: TokenCount.optional(),
|
|
603
|
+
cacheWriteTokens: TokenCount.optional()
|
|
604
|
+
}).superRefine((receipt, ctx) => validateCostReceipt(receipt, ctx));
|
|
605
|
+
function validateCostReceipt(receipt, ctx) {
|
|
606
|
+
if (receipt.reasoningTokens !== void 0 && receipt.reasoningTokens > receipt.outputTokens) {
|
|
607
|
+
ctx.addIssue({ code: "custom", message: "reasoningTokens must not exceed outputTokens" });
|
|
608
|
+
}
|
|
609
|
+
if (receipt.actualCostUsd !== void 0) {
|
|
610
|
+
if (receipt.costUnknown || receipt.costUsd !== receipt.actualCostUsd) {
|
|
611
|
+
ctx.addIssue({ code: "custom", message: "actual cost must be known and equal costUsd" });
|
|
612
|
+
}
|
|
613
|
+
if (receipt.pricing !== void 0) {
|
|
614
|
+
ctx.addIssue({ code: "custom", message: "actual cost must not include estimated pricing" });
|
|
615
|
+
}
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (receipt.costUnknown) {
|
|
619
|
+
if (receipt.costUsd !== 0) {
|
|
620
|
+
ctx.addIssue({ code: "custom", message: "unknown cost must have costUsd 0" });
|
|
621
|
+
}
|
|
622
|
+
if (receipt.pricing !== void 0) {
|
|
623
|
+
ctx.addIssue({ code: "custom", message: "unknown cost must not include estimated pricing" });
|
|
624
|
+
}
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (receipt.usageUnknown) {
|
|
628
|
+
ctx.addIssue({ code: "custom", message: "known estimated cost requires known usage" });
|
|
629
|
+
}
|
|
630
|
+
if (!receipt.pricing) {
|
|
631
|
+
ctx.addIssue({ code: "custom", message: "known estimated cost requires a pricing snapshot" });
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const expected = costFromPricing(receipt, receipt.pricing);
|
|
635
|
+
if (receipt.costUsd !== expected) {
|
|
636
|
+
ctx.addIssue({
|
|
637
|
+
code: "custom",
|
|
638
|
+
message: `estimated cost ${receipt.costUsd} does not match pricing snapshot ${expected}`
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
var CostLedgerEventSchema = z.union([
|
|
643
|
+
z.strictObject({
|
|
644
|
+
version: z.literal(1),
|
|
645
|
+
record: z.union([PendingCostCallSchema, LegacyCostReceiptSchema])
|
|
646
|
+
}),
|
|
647
|
+
z.strictObject({
|
|
648
|
+
version: z.literal(2),
|
|
649
|
+
record: CostReceiptSchema
|
|
650
|
+
}),
|
|
651
|
+
z.strictObject({
|
|
652
|
+
version: z.literal(1),
|
|
653
|
+
completedTasks: TokenCount
|
|
654
|
+
}),
|
|
655
|
+
z.strictObject({
|
|
656
|
+
version: z.literal(1),
|
|
657
|
+
costCeilingUsd: NonNegative
|
|
658
|
+
})
|
|
659
|
+
]);
|
|
660
|
+
function parseEvents(serialized) {
|
|
661
|
+
const records = /* @__PURE__ */ new Map();
|
|
662
|
+
let completedTasks = 0;
|
|
663
|
+
let costCeilingUsd;
|
|
664
|
+
let lineNumber = 0;
|
|
665
|
+
try {
|
|
666
|
+
for (const line of serialized.split("\n")) {
|
|
667
|
+
if (!line.trim()) continue;
|
|
668
|
+
lineNumber += 1;
|
|
669
|
+
const event = CostLedgerEventSchema.parse(JSON.parse(line));
|
|
670
|
+
if ("record" in event) {
|
|
671
|
+
const record = event.record;
|
|
672
|
+
validateTransition(records, record);
|
|
673
|
+
records.set(record.callId, cloneRecord(record));
|
|
674
|
+
} else if ("completedTasks" in event) {
|
|
675
|
+
completedTasks += event.completedTasks;
|
|
676
|
+
if (!Number.isSafeInteger(completedTasks)) {
|
|
677
|
+
throw new ValidationError("CostLedger: completed task count exceeds safe integer range");
|
|
678
|
+
}
|
|
679
|
+
} else {
|
|
680
|
+
if (costCeilingUsd !== void 0) {
|
|
681
|
+
throw new ValidationError("CostLedger: duplicate persisted cost ceiling");
|
|
682
|
+
}
|
|
683
|
+
costCeilingUsd = event.costCeilingUsd;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
records: [...records.values()],
|
|
688
|
+
completedTasks,
|
|
689
|
+
...costCeilingUsd === void 0 ? {} : { costCeilingUsd }
|
|
690
|
+
};
|
|
691
|
+
} catch (cause) {
|
|
692
|
+
throw new ValidationError(
|
|
693
|
+
`CostLedger: invalid persisted event ${lineNumber || 1}: ${validationMessage(cause)}`,
|
|
694
|
+
{ cause }
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function validateTransition(records, record) {
|
|
699
|
+
const current = records.get(record.callId);
|
|
700
|
+
if (!current) return;
|
|
701
|
+
if (record.status === "pending" || current.status === "settled") {
|
|
702
|
+
throw new CostCallConflictError(`CostLedger: duplicate callId '${record.callId}'`);
|
|
703
|
+
}
|
|
704
|
+
if (!sameAttribution(current, record)) {
|
|
705
|
+
throw new ValidationError(`CostLedger: receipt attribution changed for call '${record.callId}'`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function sameAttribution(before, after) {
|
|
709
|
+
return before.channel === after.channel && before.phase === after.phase && before.actor === after.actor && before.maximumCostUsd === after.maximumCostUsd && before.timestamp === after.timestamp && JSON.stringify(before.tags ?? {}) === JSON.stringify(after.tags ?? {});
|
|
710
|
+
}
|
|
711
|
+
function parseReceipt(value, path) {
|
|
712
|
+
try {
|
|
713
|
+
return CostReceiptSchema.parse(value);
|
|
714
|
+
} catch (cause) {
|
|
715
|
+
throw new ValidationError(`CostLedger: invalid ${path}: ${validationMessage(cause)}`, { cause });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function parseImportedReceipt(value, path) {
|
|
719
|
+
if (typeof value !== "object" || value === null) return parseReceipt(value, path);
|
|
720
|
+
const candidate = { ...value };
|
|
721
|
+
if (candidate.status === "settled" && candidate.actualCostUsd === void 0 && candidate.costUnknown === false && candidate.pricing === void 0 && typeof candidate.model === "string") {
|
|
722
|
+
const pricing = resolveModelPricing(candidate.model);
|
|
723
|
+
if (pricing) {
|
|
724
|
+
candidate.pricing = {
|
|
725
|
+
inputUsdPerThousand: pricing.input,
|
|
726
|
+
outputUsdPerThousand: pricing.output
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return parseReceipt(candidate, path);
|
|
731
|
+
}
|
|
732
|
+
function validateAttribution(input) {
|
|
733
|
+
assertString(input.channel, "channel");
|
|
734
|
+
assertString(input.phase, "phase");
|
|
735
|
+
assertString(input.actor, "actor");
|
|
736
|
+
if (input.model !== void 0) assertString(input.model, "model");
|
|
737
|
+
if (input.tags !== void 0) {
|
|
738
|
+
const parsed = Tags.safeParse(input.tags);
|
|
739
|
+
if (!parsed.success)
|
|
740
|
+
throw new ValidationError(`CostLedger: invalid tags: ${parsed.error.message}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function matches(record, filter) {
|
|
744
|
+
if (!filter) return true;
|
|
745
|
+
if (filter.channel !== void 0 && record.channel !== filter.channel) return false;
|
|
746
|
+
if (filter.phase !== void 0 && record.phase !== filter.phase) return false;
|
|
747
|
+
return Object.entries(filter.tags ?? {}).every(([key, value]) => record.tags?.[key] === value);
|
|
748
|
+
}
|
|
749
|
+
function cloneRecord(record) {
|
|
750
|
+
if (record.status === "settled") return cloneReceipt(record);
|
|
751
|
+
const { tags, ...rest } = record;
|
|
752
|
+
return { ...rest, ...tags ? { tags: { ...tags } } : {} };
|
|
753
|
+
}
|
|
754
|
+
function cloneReceipt(receipt) {
|
|
755
|
+
const { pricing, tags, ...rest } = receipt;
|
|
756
|
+
return {
|
|
757
|
+
...rest,
|
|
758
|
+
...tags ? { tags: { ...tags } } : {},
|
|
759
|
+
...pricing ? { pricing: { ...pricing } } : {}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function costFromPricing(usage, pricing) {
|
|
763
|
+
return (usage.inputTokens + (usage.cachedTokens ?? 0) + (usage.cacheWriteTokens ?? 0)) / 1e3 * pricing.inputUsdPerThousand + usage.outputTokens / 1e3 * pricing.outputUsdPerThousand;
|
|
764
|
+
}
|
|
765
|
+
function emptyRollup(channel) {
|
|
766
|
+
return {
|
|
767
|
+
channel,
|
|
768
|
+
calls: 0,
|
|
769
|
+
inputTokens: 0,
|
|
770
|
+
outputTokens: 0,
|
|
771
|
+
reasoningTokens: 0,
|
|
772
|
+
cachedTokens: 0,
|
|
773
|
+
cacheWriteTokens: 0,
|
|
774
|
+
costUsd: 0,
|
|
775
|
+
unpricedCalls: 0,
|
|
776
|
+
unknownUsageCalls: 0
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function pendingModel(input) {
|
|
780
|
+
if (input.model) return input.model;
|
|
781
|
+
if (input.maximumCharge && "model" in input.maximumCharge) return input.maximumCharge.model;
|
|
782
|
+
return "unknown";
|
|
783
|
+
}
|
|
784
|
+
function unknownReceipt(model) {
|
|
785
|
+
return { model, inputTokens: 0, outputTokens: 0, costUnknown: true, usageUnknown: true };
|
|
786
|
+
}
|
|
787
|
+
function resolveCallId(input) {
|
|
788
|
+
if (input !== void 0) {
|
|
789
|
+
assertString(input, "callId");
|
|
790
|
+
return input;
|
|
791
|
+
}
|
|
792
|
+
if (typeof globalThis.crypto?.randomUUID !== "function") {
|
|
793
|
+
throw new ValidationError("CostLedger: crypto.randomUUID is required when callId is omitted");
|
|
794
|
+
}
|
|
795
|
+
return globalThis.crypto.randomUUID();
|
|
796
|
+
}
|
|
797
|
+
function abortError(signal) {
|
|
798
|
+
const reason = signal.reason;
|
|
799
|
+
if (reason instanceof Error) return reason;
|
|
800
|
+
const error = new Error("CostLedger: paid call aborted");
|
|
801
|
+
error.name = "AbortError";
|
|
802
|
+
return error;
|
|
803
|
+
}
|
|
804
|
+
function toError(value) {
|
|
805
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
806
|
+
}
|
|
807
|
+
function paidFailure(callId, error, receipt) {
|
|
808
|
+
return {
|
|
809
|
+
succeeded: false,
|
|
810
|
+
callId,
|
|
811
|
+
error,
|
|
812
|
+
...receipt ? { receipt: cloneReceipt(receipt) } : {}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function validationMessage(cause) {
|
|
816
|
+
return cause instanceof z.ZodError ? z.prettifyError(cause) : toError(cause).message;
|
|
817
|
+
}
|
|
818
|
+
function assertUsage(usage) {
|
|
819
|
+
assertTokenCount(usage.inputTokens, "inputTokens");
|
|
820
|
+
assertTokenCount(usage.outputTokens, "outputTokens");
|
|
821
|
+
if (usage.reasoningTokens !== void 0) {
|
|
822
|
+
assertTokenCount(usage.reasoningTokens, "reasoningTokens");
|
|
823
|
+
if (usage.reasoningTokens > usage.outputTokens) {
|
|
824
|
+
throw new ValidationError("CostLedger: reasoningTokens must not exceed outputTokens");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (usage.cachedTokens !== void 0) assertTokenCount(usage.cachedTokens, "cachedTokens");
|
|
828
|
+
if (usage.cacheWriteTokens !== void 0) {
|
|
829
|
+
assertTokenCount(usage.cacheWriteTokens, "cacheWriteTokens");
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function assertTokenCount(value, name) {
|
|
833
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
834
|
+
throw new ValidationError(
|
|
835
|
+
`CostLedger: ${name} must be a non-negative integer, got ${String(value)}`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function assertNonNegative(value, name) {
|
|
840
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
841
|
+
throw new ValidationError(
|
|
842
|
+
`CostLedger: ${name} must be a non-negative finite number, got ${String(value)}`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function assertTimeout(value, name) {
|
|
847
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > 2147483647) {
|
|
848
|
+
throw new ValidationError(
|
|
849
|
+
`CostLedger: ${name} must be a non-negative safe integer no greater than 2147483647, got ${String(value)}`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function assertString(value, name) {
|
|
854
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
855
|
+
throw new ValidationError(`CostLedger: ${name} must be a non-empty string`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export {
|
|
860
|
+
CostCeilingReachedError,
|
|
861
|
+
CostAccountingIncompleteError,
|
|
862
|
+
CostReservationExceededError,
|
|
863
|
+
CostCallConflictError,
|
|
864
|
+
CostLedgerPersistenceError,
|
|
865
|
+
CostReceiptCaptureError,
|
|
866
|
+
CostLedger,
|
|
867
|
+
modelPriceKey,
|
|
868
|
+
costForUsage
|
|
869
|
+
};
|
|
870
|
+
//# sourceMappingURL=chunk-ZMXDQ4K7.js.map
|