@tangle-network/agent-runtime 0.199.0 → 0.201.0
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/dist/{activation-c3um4SY1.d.ts → activation-BPxs-Iu2.d.ts} +2 -2
- package/dist/{activation-zQWmiiPg.js → activation-C_FN2aqK.js} +2 -2
- package/dist/{activation-zQWmiiPg.js.map → activation-C_FN2aqK.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -3
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop-DZw5QWT9.js +176 -0
- package/dist/analyst-loop-DZw5QWT9.js.map +1 -0
- package/dist/analyst-loop.d.ts +1 -11
- package/dist/analyst-loop.js +2 -2
- package/dist/candidate-execution/index.d.ts +2 -2
- package/dist/candidate-execution/index.js +3 -3
- package/dist/{coordination-driver-vWb7kViA.js → coordination-driver-qdAwriPV.js} +8 -5
- package/dist/{coordination-driver-vWb7kViA.js.map → coordination-driver-qdAwriPV.js.map} +1 -1
- package/dist/{delegate-Ch93bpH5.js → delegate-BWHG-zZW.js} +2 -2
- package/dist/{delegate-Ch93bpH5.js.map → delegate-BWHG-zZW.js.map} +1 -1
- package/dist/durable.d.ts +2 -2
- package/dist/durable.js +2 -2
- package/dist/{graph-C6pT08K-.js → graph-cCqKhLqz.js} +3 -3
- package/dist/{graph-C6pT08K-.js.map → graph-cCqKhLqz.js.map} +1 -1
- package/dist/{improvement-cycle-CxZKIbLD.js → improvement-cycle-VH_ZGcIj.js} +5 -5
- package/dist/{improvement-cycle-CxZKIbLD.js.map → improvement-cycle-VH_ZGcIj.js.map} +1 -1
- package/dist/{index-CMTUgh-T.d.ts → index-Br191WbE.d.ts} +190 -142
- package/dist/index.d.ts +6 -5
- package/dist/index.js +28 -49
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +15 -106
- package/dist/intelligence.js +9 -408
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +3 -3
- package/dist/kernel.js +8 -9
- package/dist/{loop-runner-bin-CAf1OQot.d.ts → loop-runner-bin-BNRdsDOn.d.ts} +3 -3
- package/dist/{loop-runner-bin-SNrh585k.js → loop-runner-bin-jQ8hXO9J.js} +4 -4
- package/dist/{loop-runner-bin-SNrh585k.js.map → loop-runner-bin-jQ8hXO9J.js.map} +1 -1
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +4 -4
- package/dist/{prepare-CAO1yXov.js → prepare-Dc30WRB0.js} +6 -3
- package/dist/prepare-Dc30WRB0.js.map +1 -0
- package/dist/{protected-model-port-BtXp2dUY.d.ts → protected-model-port-BlLP8ja6.d.ts} +2 -2
- package/dist/{protected-model-port-B5avcRiQ.js → protected-model-port-D5gDcnrH.js} +2 -2
- package/dist/{protected-model-port-B5avcRiQ.js.map → protected-model-port-D5gDcnrH.js.map} +1 -1
- package/dist/{provision-supervisor-D5YSCDVD.js → provision-supervisor-BA2-GPth.js} +3 -3
- package/dist/{provision-supervisor-D5YSCDVD.js.map → provision-supervisor-BA2-GPth.js.map} +1 -1
- package/dist/{redact-Dv8WcCKy.js → redact-h-oaw11Q.js} +13093 -11221
- package/dist/redact-h-oaw11Q.js.map +1 -0
- package/dist/{runtime-BN16GbTA.js → runtime-DeqdBVeC.js} +8 -9
- package/dist/{runtime-BN16GbTA.js.map → runtime-DeqdBVeC.js.map} +1 -1
- package/dist/{server-CbNb_N1x.js → server-U_k9NUbk.js} +3 -3
- package/dist/{server-CbNb_N1x.js.map → server-U_k9NUbk.js.map} +1 -1
- package/dist/{stream-agent-turn-CLOQr497.d.ts → stream-agent-turn-CJWthifS.d.ts} +42 -13
- package/dist/{structural-rollout-DANCelu6.js → structural-rollout-BcMmrpv4.js} +2 -3
- package/dist/{structural-rollout-DANCelu6.js.map → structural-rollout-BcMmrpv4.js.map} +1 -1
- package/dist/{supervise-BGNXB8No.js → supervise-DmFO50N6.js} +497 -90
- package/dist/supervise-DmFO50N6.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +13 -13
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{types-1s_29INY.d.ts → types-CuXu5zuS.d.ts} +3 -2
- package/dist/{workspace-archive-BMOnloFf.js → workspace-archive-CBX0hwL_.js} +2 -2
- package/dist/{workspace-archive-BMOnloFf.js.map → workspace-archive-CBX0hwL_.js.map} +1 -1
- package/package.json +6 -6
- package/dist/analyst-loop-BknOQUW5.js +0 -546
- package/dist/analyst-loop-BknOQUW5.js.map +0 -1
- package/dist/prepare-CAO1yXov.js.map +0 -1
- package/dist/redact-Dv8WcCKy.js.map +0 -1
- package/dist/sandbox-events-DbC2WKKS.js +0 -929
- package/dist/sandbox-events-DbC2WKKS.js.map +0 -1
- package/dist/supervise-BGNXB8No.js.map +0 -1
|
@@ -1,929 +0,0 @@
|
|
|
1
|
-
import { m as ValidationError } from "./errors-DodWX-cb.js";
|
|
2
|
-
import { CanonicalStreamEventSchema, RuntimeEventEnvelopeSchema } from "@tangle-network/agent-interface";
|
|
3
|
-
//#region src/runtime/harness-usage.ts
|
|
4
|
-
/**
|
|
5
|
-
* Read and validate one codex `turn.completed` usage record.
|
|
6
|
-
*
|
|
7
|
-
* Every counter must be a non-negative safe integer. Two cross-field invariants hold, and they are
|
|
8
|
-
* what states that the two named counters are SUBSETS of their totals rather than additions to
|
|
9
|
-
* them: `cached_input_tokens <= input_tokens` and `reasoning_output_tokens <= output_tokens`.
|
|
10
|
-
* Measured against the codex CLI: a turn reporting `output_tokens 1523` with
|
|
11
|
-
* `reasoning_output_tokens 1516` answered with about seven tokens of text.
|
|
12
|
-
*
|
|
13
|
-
* `label` names the surface the record came from, so one message serves both readers.
|
|
14
|
-
* Throws `ValidationError` on any violation.
|
|
15
|
-
*/
|
|
16
|
-
function parseCodexUsageRecord(usage, label) {
|
|
17
|
-
const record = plainRecord$1(usage);
|
|
18
|
-
if (record === void 0) throw new ValidationError(`${label}: usage must be an object, received ${describe(usage)}`);
|
|
19
|
-
const inputTokens = naturalNumber(record.input_tokens, "input_tokens", label);
|
|
20
|
-
const cachedInputTokens = naturalNumber(record.cached_input_tokens, "cached_input_tokens", label);
|
|
21
|
-
const outputTokens = naturalNumber(record.output_tokens, "output_tokens", label);
|
|
22
|
-
const reasoningOutputTokens = naturalNumber(record.reasoning_output_tokens, "reasoning_output_tokens", label);
|
|
23
|
-
const cacheWriteInputTokens = record.cache_write_input_tokens === void 0 ? void 0 : naturalNumber(record.cache_write_input_tokens, "cache_write_input_tokens", label);
|
|
24
|
-
if (cachedInputTokens > inputTokens) throw new ValidationError(`${label}: cached_input_tokens exceeds input_tokens`);
|
|
25
|
-
if (reasoningOutputTokens > outputTokens) throw new ValidationError(`${label}: reasoning_output_tokens exceeds output_tokens`);
|
|
26
|
-
return {
|
|
27
|
-
inputTokens,
|
|
28
|
-
cachedInputTokens,
|
|
29
|
-
outputTokens,
|
|
30
|
-
reasoningOutputTokens,
|
|
31
|
-
...cacheWriteInputTokens === void 0 ? {} : { cacheWriteInputTokens }
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
/** Names the wire record every codex decode error is reported against. */
|
|
35
|
-
const codexUsageContext = "codex turn.completed";
|
|
36
|
-
/**
|
|
37
|
-
* codex reports the tokens of a turn inside its own `turn.completed` event, which rides the
|
|
38
|
-
* transport as `{ type: 'raw', data: { type: 'turn.completed', usage: { … } } }`. It emits no
|
|
39
|
-
* canonical usage event, so this adapter is a codex worker's only usage source.
|
|
40
|
-
*
|
|
41
|
-
* The record is read by `parseCodexUsageRecord`, the same reader `runLocalHarness` uses on the
|
|
42
|
-
* codex CLI's own stdout, so both surfaces hold one field policy and both cross-field invariants.
|
|
43
|
-
*
|
|
44
|
-
* A `turn.completed` that carries a `usage` member the reader cannot read is a `ValidationError`
|
|
45
|
-
* naming the field. A `turn.completed` with no `usage` member reports no usage at all, which
|
|
46
|
-
* leaves the turn's tokens unknown rather than zero.
|
|
47
|
-
*/
|
|
48
|
-
const decodeCodexTurnUsage = (event) => {
|
|
49
|
-
if (String(event.type ?? "") !== "raw") return void 0;
|
|
50
|
-
const data = plainRecord$1(event.data);
|
|
51
|
-
if (data === void 0 || data.type !== "turn.completed" || data.usage === void 0) return;
|
|
52
|
-
const record = parseCodexUsageRecord(data.usage, codexUsageContext);
|
|
53
|
-
return {
|
|
54
|
-
harness: "codex",
|
|
55
|
-
input: record.inputTokens,
|
|
56
|
-
output: record.outputTokens,
|
|
57
|
-
cachedInput: record.cachedInputTokens,
|
|
58
|
-
reasoningOutput: record.reasoningOutputTokens,
|
|
59
|
-
...record.cacheWriteInputTokens === void 0 ? {} : { cacheWriteInput: record.cacheWriteInputTokens }
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* The harness → usage-decoder registry. Add a harness by adding one entry.
|
|
64
|
-
*
|
|
65
|
-
* Keyed by `HarnessType`, the vocabulary the `harness` argument is drawn from, so a key no caller
|
|
66
|
-
* can produce does not compile. A harness absent from this registry reports usage through the
|
|
67
|
-
* canonical events or not at all.
|
|
68
|
-
*/
|
|
69
|
-
const harnessUsageDecoders = { codex: decodeCodexTurnUsage };
|
|
70
|
-
/**
|
|
71
|
-
* Decode a sandbox event with one harness's adapter, or `undefined` when the event carries no
|
|
72
|
-
* harness-native usage.
|
|
73
|
-
*
|
|
74
|
-
* A NAMED harness reads with that harness's adapter only, and a named harness with no adapter
|
|
75
|
-
* reports nothing. It never falls through to another harness's adapter: a different harness's
|
|
76
|
-
* `turn.completed` decoded as codex would either drop the counters codex does not name or fail on
|
|
77
|
-
* a field codex requires, and both answers would be about the wrong harness. The composite over
|
|
78
|
-
* every registered adapter runs only when the caller cannot name the harness.
|
|
79
|
-
*
|
|
80
|
-
* Throws `ValidationError` when an adapter recognizes the event as its harness's usage carrier and
|
|
81
|
-
* cannot read the numbers.
|
|
82
|
-
*/
|
|
83
|
-
function decodeHarnessUsage(event, harness) {
|
|
84
|
-
if (!event || typeof event !== "object") return void 0;
|
|
85
|
-
if (harness !== void 0) return harnessUsageDecoders[harness]?.(event);
|
|
86
|
-
for (const decode of new Set(Object.values(harnessUsageDecoders))) {
|
|
87
|
-
const usage = decode(event);
|
|
88
|
-
if (usage !== void 0) return usage;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function plainRecord$1(value) {
|
|
92
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
93
|
-
}
|
|
94
|
-
function naturalNumber(value, field, label) {
|
|
95
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new ValidationError(`${label}: usage.${field} must be a non-negative safe integer, received ${describe(value)}`);
|
|
96
|
-
return value;
|
|
97
|
-
}
|
|
98
|
-
function describe(value) {
|
|
99
|
-
if (value === null) return "null";
|
|
100
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
101
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
102
|
-
if (Array.isArray(value)) return "an array";
|
|
103
|
-
return typeof value;
|
|
104
|
-
}
|
|
105
|
-
//#endregion
|
|
106
|
-
//#region src/runtime/timestamps.ts
|
|
107
|
-
const validTimestamp = "2026-01-01T00:00:00.000Z";
|
|
108
|
-
/** Validate a timestamp with the public runtime envelope contract. */
|
|
109
|
-
function assertRuntimeTimestamp(value, label) {
|
|
110
|
-
try {
|
|
111
|
-
RuntimeEventEnvelopeSchema.parse({
|
|
112
|
-
runId: "runtime",
|
|
113
|
-
eventId: "event",
|
|
114
|
-
sequence: 0,
|
|
115
|
-
occurredAt: value,
|
|
116
|
-
receivedAt: validTimestamp,
|
|
117
|
-
event: {
|
|
118
|
-
type: "status",
|
|
119
|
-
status: "processing"
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
} catch (error) {
|
|
123
|
-
throw new Error(`${label} must be a valid ISO timestamp`, { cause: error });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
//#endregion
|
|
127
|
-
//#region src/runtime/sandbox-transport-events.ts
|
|
128
|
-
/**
|
|
129
|
-
* Transport type whose payload is the harness's OWN event, passed through verbatim.
|
|
130
|
-
*
|
|
131
|
-
* A `raw` payload names a harness-native type from a vocabulary the canonical schema does not
|
|
132
|
-
* define (codex emits `thread.started` / `turn.started` / `turn.completed`). The transport type is
|
|
133
|
-
* therefore the only canonical statement such an event makes, and its payload must never be read
|
|
134
|
-
* as a canonical type: the mismatch guard below exists to catch a producer that MISLABELS a
|
|
135
|
-
* canonical event, which a harness-native payload cannot do. A payload that does satisfy the
|
|
136
|
-
* canonical `raw` shape (`backend` + `event`) still parses; anything else is not a canonical
|
|
137
|
-
* event, and the caller reads it off the transport event instead.
|
|
138
|
-
*/
|
|
139
|
-
const harnessNativePayloadType = "raw";
|
|
140
|
-
/** Parse canonical payload fields without treating transport identity as event data. */
|
|
141
|
-
function parseCanonicalTransportEvent(type, data, normalized, source) {
|
|
142
|
-
if (!isRecord(data)) throw new Error(`${source} emitted a canonical event without an object payload`);
|
|
143
|
-
const outerType = String(type ?? "");
|
|
144
|
-
const harnessNative = outerType === harnessNativePayloadType;
|
|
145
|
-
if (normalized !== void 0) {
|
|
146
|
-
if (!harnessNative && typeof normalized === "object" && normalized !== null && "type" in normalized && typeof normalized.type === "string" && normalized.type !== outerType) throw new Error(`${source} canonical event type "${normalized.type}" does not match transport type "${outerType}"`);
|
|
147
|
-
const candidate = CanonicalStreamEventSchema.safeParse(normalized);
|
|
148
|
-
if (!candidate.success) {
|
|
149
|
-
if (harnessNative) return void 0;
|
|
150
|
-
throw new Error(`${source} emitted an invalid normalized canonical event`, { cause: candidate.error });
|
|
151
|
-
}
|
|
152
|
-
return candidate.data;
|
|
153
|
-
}
|
|
154
|
-
const { type: embeddedType, eventId: _eventId, cursor: _cursor, sequence: _sequence, occurredAt: _occurredAt, normalized: _normalized, ...payload } = data;
|
|
155
|
-
if (!harnessNative && embeddedType !== void 0 && embeddedType !== outerType) throw new Error(`${source} canonical event type "${String(embeddedType)}" does not match transport type "${outerType}"`);
|
|
156
|
-
const candidate = CanonicalStreamEventSchema.safeParse({
|
|
157
|
-
...payload,
|
|
158
|
-
type: outerType
|
|
159
|
-
});
|
|
160
|
-
return candidate.success ? candidate.data : void 0;
|
|
161
|
-
}
|
|
162
|
-
/** Extract shared event identity without privileging one provider wire shape. */
|
|
163
|
-
function extractTransportEventIdentity(event) {
|
|
164
|
-
const record = isRecord(event) ? event : {};
|
|
165
|
-
const data = isRecord(record.data) ? record.data : {};
|
|
166
|
-
const providerEvent = isRecord(record.providerEvent) ? record.providerEvent : {};
|
|
167
|
-
const providerData = isRecord(providerEvent.data) ? providerEvent.data : {};
|
|
168
|
-
const eventId = canonicalEventId(record, data, providerEvent, providerData);
|
|
169
|
-
const cursor = stableString(record.cursor ?? data.cursor ?? providerEvent.cursor ?? providerData.cursor);
|
|
170
|
-
const sequence = optionalSequence(record.sequence ?? data.sequence ?? providerEvent.sequence ?? providerData.sequence);
|
|
171
|
-
const occurredAt = optionalTimestamp(record.occurredAt ?? data.occurredAt ?? providerEvent.occurredAt ?? providerData.occurredAt);
|
|
172
|
-
return {
|
|
173
|
-
...eventId === void 0 ? {} : { eventId },
|
|
174
|
-
...cursor === void 0 ? {} : { cursor },
|
|
175
|
-
...sequence === void 0 ? {} : { sequence },
|
|
176
|
-
...occurredAt === void 0 ? {} : { occurredAt }
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
function canonicalEventId(record, data, providerEvent, providerData) {
|
|
180
|
-
const present = [
|
|
181
|
-
["data.eventId", data.eventId],
|
|
182
|
-
["providerEvent.eventId", providerEvent.eventId],
|
|
183
|
-
["providerData.eventId", providerData.eventId],
|
|
184
|
-
["record.eventId", record.eventId]
|
|
185
|
-
].filter(([, value]) => value !== void 0);
|
|
186
|
-
if (present.length === 0) return stableString(record.id ?? providerEvent.id);
|
|
187
|
-
const normalized = present.map(([source, value]) => {
|
|
188
|
-
const eventId = stableString(value);
|
|
189
|
-
if (eventId === void 0) throw new Error(`transport event ${source} must be a stable string`);
|
|
190
|
-
return {
|
|
191
|
-
source,
|
|
192
|
-
eventId
|
|
193
|
-
};
|
|
194
|
-
});
|
|
195
|
-
const first = normalized[0];
|
|
196
|
-
if (normalized.some((candidate) => candidate.eventId !== first.eventId)) throw new Error("transport event canonical identities disagree");
|
|
197
|
-
return first.eventId;
|
|
198
|
-
}
|
|
199
|
-
function stableString(value) {
|
|
200
|
-
return typeof value === "string" && value.length > 0 && value.trim() === value ? value : void 0;
|
|
201
|
-
}
|
|
202
|
-
function finiteNonNegativeInteger(value) {
|
|
203
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
204
|
-
}
|
|
205
|
-
function optionalSequence(value) {
|
|
206
|
-
if (value === void 0) return void 0;
|
|
207
|
-
if (finiteNonNegativeInteger(value) === void 0) throw new Error("transport event sequence must be a non-negative safe integer");
|
|
208
|
-
return value;
|
|
209
|
-
}
|
|
210
|
-
function optionalTimestamp(value) {
|
|
211
|
-
if (value === void 0) return void 0;
|
|
212
|
-
assertRuntimeTimestamp(value, "occurredAt");
|
|
213
|
-
return value;
|
|
214
|
-
}
|
|
215
|
-
function isRecord(value) {
|
|
216
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
217
|
-
}
|
|
218
|
-
//#endregion
|
|
219
|
-
//#region src/runtime/sandbox-events.ts
|
|
220
|
-
const CANONICAL_STREAM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
221
|
-
"child-task",
|
|
222
|
-
"message.part.updated",
|
|
223
|
-
"tool-heartbeat",
|
|
224
|
-
"tool-slow",
|
|
225
|
-
"model-processing",
|
|
226
|
-
"status",
|
|
227
|
-
"warning",
|
|
228
|
-
"raw",
|
|
229
|
-
"session.updated",
|
|
230
|
-
"interaction",
|
|
231
|
-
"interaction.cancel",
|
|
232
|
-
"plan.submitted"
|
|
233
|
-
]);
|
|
234
|
-
/** Decode one known Agent Interface event from a Sandbox event. */
|
|
235
|
-
function canonicalStreamEventFromSandboxEvent(event) {
|
|
236
|
-
if (!event || typeof event !== "object") return void 0;
|
|
237
|
-
const type = String(event.type ?? "");
|
|
238
|
-
const data = event.data && typeof event.data === "object" ? event.data : {};
|
|
239
|
-
const normalized = data.normalized;
|
|
240
|
-
if (normalized === void 0 && !CANONICAL_STREAM_EVENT_TYPES.has(type)) return void 0;
|
|
241
|
-
return parseCanonicalTransportEvent(type, data, normalized, "sandbox");
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Forward a sandbox event to an optional observer without letting observer
|
|
245
|
-
* behavior affect the run. The observer receives a defensive copy, synchronous
|
|
246
|
-
* throws are swallowed, and returned promises are deliberately not awaited.
|
|
247
|
-
*/
|
|
248
|
-
function notifySandboxEventObserver(event, observer, meta) {
|
|
249
|
-
if (!observer) return;
|
|
250
|
-
try {
|
|
251
|
-
const result = observer(cloneEventForObserver(event), meta);
|
|
252
|
-
if (result && typeof result.then === "function") result.then(void 0, () => {});
|
|
253
|
-
} catch {}
|
|
254
|
-
}
|
|
255
|
-
function cloneEventForObserver(event) {
|
|
256
|
-
try {
|
|
257
|
-
return structuredClone(event);
|
|
258
|
-
} catch {
|
|
259
|
-
return copyPlainSpine(event, /* @__PURE__ */ new WeakMap());
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function copyPlainSpine(value, seen) {
|
|
263
|
-
if (value === null || typeof value !== "object") return value;
|
|
264
|
-
const existing = seen.get(value);
|
|
265
|
-
if (existing !== void 0) return existing;
|
|
266
|
-
if (Array.isArray(value)) {
|
|
267
|
-
const copy = [];
|
|
268
|
-
seen.set(value, copy);
|
|
269
|
-
for (const item of value) copy.push(copyPlainSpine(item, seen));
|
|
270
|
-
return copy;
|
|
271
|
-
}
|
|
272
|
-
const proto = Object.getPrototypeOf(value);
|
|
273
|
-
if (proto !== Object.prototype && proto !== null) return {};
|
|
274
|
-
const copy = {};
|
|
275
|
-
seen.set(value, copy);
|
|
276
|
-
for (const [key, child] of Object.entries(value)) copy[key] = copyPlainSpine(child, seen);
|
|
277
|
-
return copy;
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Read the served execution identity off one Sandbox event.
|
|
281
|
-
*
|
|
282
|
-
* The platform reports `effectiveBackend` on `execution.started` and again on the terminal
|
|
283
|
-
* event (`@tangle-network/sandbox`, `EffectiveBackend`). Absence returns `undefined`
|
|
284
|
-
* and must stay unknown — a request is not a receipt, so nothing here may be inferred from
|
|
285
|
-
* what was asked for.
|
|
286
|
-
*/
|
|
287
|
-
function sandboxEventServedBackend(event) {
|
|
288
|
-
if (!event || typeof event !== "object") return void 0;
|
|
289
|
-
const served = plainRecord(plainRecord(event.data)?.effectiveBackend);
|
|
290
|
-
if (!served) return void 0;
|
|
291
|
-
const provider = firstString(served.provider);
|
|
292
|
-
const model = firstString(served.model);
|
|
293
|
-
const source = firstString(served.source);
|
|
294
|
-
if (provider === void 0 && model === void 0) return void 0;
|
|
295
|
-
return {
|
|
296
|
-
...provider !== void 0 ? { provider } : {},
|
|
297
|
-
...model !== void 0 ? { model } : {},
|
|
298
|
-
...source !== void 0 ? { source } : {}
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Fail the execution when the platform reports serving a model other than the exact one asked for.
|
|
303
|
-
*
|
|
304
|
-
* Measured motive (agent-runtime#892, live infrastructure 2026-08-17): 6 of 6 boxes whose profile
|
|
305
|
-
* declared `zai-coding-plan/glm-5.2` reported
|
|
306
|
-
* `{"provider":"openai-compat","model":"deepseek/deepseek-v4-flash","source":"environment"}`,
|
|
307
|
-
* while the materialization receipt recorded the declared model as `status: "known"`. Sending
|
|
308
|
-
* `backend.model` makes that substitution unlikely; only reading the report back makes it
|
|
309
|
-
* detectable. A run that cannot say which model produced its evidence must not settle as one
|
|
310
|
-
* that can.
|
|
311
|
-
*
|
|
312
|
-
* Silent when the platform reports no served model: unobserved stays unobserved.
|
|
313
|
-
*/
|
|
314
|
-
function assertSandboxServedModel(event, expected) {
|
|
315
|
-
const wanted = expected?.model?.trim();
|
|
316
|
-
if (!wanted) return;
|
|
317
|
-
const served = sandboxEventServedBackend(event);
|
|
318
|
-
if (served?.model === void 0) return;
|
|
319
|
-
if (sameModelId(served.model, wanted, [served.provider, expected?.provider])) return;
|
|
320
|
-
const attribution = [served.provider !== void 0 ? `provider ${JSON.stringify(served.provider)}` : void 0, served.source !== void 0 ? `source ${JSON.stringify(served.source)}` : void 0].filter((part) => part !== void 0);
|
|
321
|
-
const detail = attribution.length > 0 ? ` (${attribution.join(", ")})` : "";
|
|
322
|
-
throw new Error(`sandbox served model ${JSON.stringify(served.model)}${detail} instead of the exact profile model ${JSON.stringify(wanted)}`);
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Do two model ids name the same model, allowing for routing prefixes?
|
|
326
|
-
*
|
|
327
|
-
* Either side may spell the model bare (`glm-5.2`), provider-qualified
|
|
328
|
-
* (`zai-coding-plan/glm-5.2`), or route-qualified, and the platform reports the provider in its
|
|
329
|
-
* own field rather than always in the id. So the comparison drops any known provider prefix and
|
|
330
|
-
* then accepts a `/`-boundary suffix match: a longer route to the SAME leaf model is a routing
|
|
331
|
-
* difference, not a substitution.
|
|
332
|
-
*
|
|
333
|
-
* What it deliberately does NOT absorb is a different leaf: `glm-5.2` against `glm-5.3` stays a
|
|
334
|
-
* mismatch, which is the substitution measured directly against the provider API (a request for
|
|
335
|
-
* `glm-5.2` was served `glm-5.3`). A version suffix is the whole difference between two
|
|
336
|
-
* instruments, so nothing here may treat it as noise.
|
|
337
|
-
*/
|
|
338
|
-
function sameModelId(served, wanted, providers) {
|
|
339
|
-
const a = stripProviderPrefix(served, providers);
|
|
340
|
-
const b = stripProviderPrefix(wanted, providers);
|
|
341
|
-
return a === b || a.endsWith(`/${b}`) || b.endsWith(`/${a}`);
|
|
342
|
-
}
|
|
343
|
-
function stripProviderPrefix(id, providers) {
|
|
344
|
-
let out = id.trim().toLowerCase();
|
|
345
|
-
for (const provider of providers) {
|
|
346
|
-
const prefix = provider?.trim().toLowerCase();
|
|
347
|
-
if (prefix && out.startsWith(`${prefix}/`)) out = out.slice(prefix.length + 1);
|
|
348
|
-
}
|
|
349
|
-
return out;
|
|
350
|
-
}
|
|
351
|
-
function firstString(...values) {
|
|
352
|
-
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
353
|
-
}
|
|
354
|
-
const terminalTypes = /* @__PURE__ */ new Set([
|
|
355
|
-
"message.completed",
|
|
356
|
-
"result",
|
|
357
|
-
"final",
|
|
358
|
-
"done"
|
|
359
|
-
]);
|
|
360
|
-
/** True for an event type that ends a sandbox turn. */
|
|
361
|
-
function isSandboxTerminalEvent(type) {
|
|
362
|
-
return terminalTypes.has(type);
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Which member of a terminal event's `data` carries its usage receipt.
|
|
366
|
-
*
|
|
367
|
-
* The one place the terminal types legitimately differ, stated rather than implied by two lists:
|
|
368
|
-
* sandbox 0.4.0's `done` reports under `tokenUsage` with the cost at the top level, every other
|
|
369
|
-
* terminal type reports under `usage`.
|
|
370
|
-
*/
|
|
371
|
-
function sandboxTerminalUsageField(type) {
|
|
372
|
-
return type === "done" ? "tokenUsage" : "usage";
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Extract a `RuntimeStreamEvent`-shaped `llm_call` from a sandbox event when
|
|
376
|
-
* the event carries usage/cost data. Returns `undefined` for non-cost events
|
|
377
|
-
* so the kernel can iterate the full stream without branching.
|
|
378
|
-
*
|
|
379
|
-
* Pure by contract: it never throws on a failed run. The terminal truth
|
|
380
|
-
* boundary is the public Sandbox outcome tracker, applied after the complete
|
|
381
|
-
* stream. Post-hoc readers — {@link sumSandboxUsage}, the
|
|
382
|
-
* analyst trace store, the chat projection — must stay able to read a failed
|
|
383
|
-
* turn's events, which is when reading them matters most.
|
|
384
|
-
*
|
|
385
|
-
* Canonical cost-carrying types observed in the wild:
|
|
386
|
-
* - `llm_call` — `data: { model, tokensIn, tokensOut, costUsd, ... }`
|
|
387
|
-
* - `message.completed` / `result` — `data: { usage: { inputTokens,
|
|
388
|
-
* outputTokens, totalCostUsd? } }`
|
|
389
|
-
* - `cost.usage` / `usage` — same shape under a dedicated type
|
|
390
|
-
*
|
|
391
|
-
* Numeric coercion is strict: `Number.isFinite` gates every accumulator write
|
|
392
|
-
* so a sentinel `NaN` from a misbehaving backend cannot poison the ledger.
|
|
393
|
-
*/
|
|
394
|
-
function extractLlmCallEvent(event, agentRunName) {
|
|
395
|
-
if (!event || typeof event !== "object") return void 0;
|
|
396
|
-
const type = String(event.type ?? "");
|
|
397
|
-
const data = event.data && typeof event.data === "object" ? event.data : {};
|
|
398
|
-
if (type === "llm_call" || type === "cost.usage" || type === "usage") return buildLlmCall(data, agentRunName);
|
|
399
|
-
if (isSandboxTerminalEvent(type) && sandboxTerminalUsageField(type) === "usage") {
|
|
400
|
-
const usage = data.usage;
|
|
401
|
-
if (!usage || typeof usage !== "object") return void 0;
|
|
402
|
-
return buildLlmCall({
|
|
403
|
-
...usage,
|
|
404
|
-
model: data.model ?? usage.model,
|
|
405
|
-
...usage.costUsd === void 0 && data.costUsd !== void 0 ? { costUsd: data.costUsd } : {}
|
|
406
|
-
}, agentRunName);
|
|
407
|
-
}
|
|
408
|
-
if (type === "done") {
|
|
409
|
-
const usage = data.tokenUsage;
|
|
410
|
-
if (!usage || typeof usage !== "object") return void 0;
|
|
411
|
-
const out = pickFiniteNumber(usage, [
|
|
412
|
-
"outputTokens",
|
|
413
|
-
"completion_tokens",
|
|
414
|
-
"tokensOut"
|
|
415
|
-
]);
|
|
416
|
-
const reasoning = pickFiniteNumber(usage, ["reasoningTokens"]);
|
|
417
|
-
const mergedOut = out !== void 0 || reasoning !== void 0 ? (out ?? 0) + (reasoning ?? 0) : void 0;
|
|
418
|
-
const cache = readPromptCacheUsage(usage);
|
|
419
|
-
return buildLlmCall({
|
|
420
|
-
inputTokens: usage.inputTokens,
|
|
421
|
-
outputTokens: mergedOut,
|
|
422
|
-
totalCostUsd: data.totalCostUsd,
|
|
423
|
-
...data.costUsd === void 0 ? {} : { costUsd: data.costUsd },
|
|
424
|
-
model: data.model ?? usage.model,
|
|
425
|
-
...cache !== void 0 ? { promptCache: cache } : {}
|
|
426
|
-
}, agentRunName);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
/** A {@link SandboxUsageLedger} for one worker. Pass the worker's harness to decode with that
|
|
430
|
-
* harness's adapter; omit it to try every registered adapter. */
|
|
431
|
-
function createSandboxUsageLedger(harness) {
|
|
432
|
-
let held = [];
|
|
433
|
-
let sawCanonical = false;
|
|
434
|
-
return {
|
|
435
|
-
observe(event, agentRunName) {
|
|
436
|
-
const call = extractLlmCallEvent(event, agentRunName);
|
|
437
|
-
if (call) {
|
|
438
|
-
sawCanonical = true;
|
|
439
|
-
return call;
|
|
440
|
-
}
|
|
441
|
-
let usage;
|
|
442
|
-
try {
|
|
443
|
-
usage = decodeHarnessUsage(event, harness);
|
|
444
|
-
} catch (err) {
|
|
445
|
-
return {
|
|
446
|
-
type: "llm_call",
|
|
447
|
-
model: agentRunName,
|
|
448
|
-
tokensKnown: false,
|
|
449
|
-
usdKnown: false,
|
|
450
|
-
tokensUnknownReason: err instanceof Error ? err.message : String(err)
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
if (usage) held.push(usage);
|
|
454
|
-
},
|
|
455
|
-
settleTurn(agentRunName) {
|
|
456
|
-
const reports = held;
|
|
457
|
-
const canonical = sawCanonical;
|
|
458
|
-
held = [];
|
|
459
|
-
sawCanonical = false;
|
|
460
|
-
if (canonical || reports.length === 0) return void 0;
|
|
461
|
-
return harnessUsageLlmCall(reports, agentRunName);
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Fold harness-native usage reports into the canonical `llm_call` the accounting paths read.
|
|
467
|
-
*
|
|
468
|
-
* Every counter a `HarnessUsage` carries beside `input` and `output` CLASSIFIES one of those two
|
|
469
|
-
* totals, so none of them is added to the total it describes:
|
|
470
|
-
*
|
|
471
|
-
* - the prompt-cache counters classify `input` and ride `promptCache`, the convention the
|
|
472
|
-
* terminal `tokenUsage` branch above already follows and `promptCacheTokenClasses` folds,
|
|
473
|
-
* where `freshInput = input - cacheRead - cacheWrite`;
|
|
474
|
-
* - `reasoningOutput` classifies `output`. Codex counts its reasoning tokens INSIDE
|
|
475
|
-
* `output_tokens` — a turn reporting `output_tokens 1523` with `reasoning_output_tokens 1516`
|
|
476
|
-
* answered with about seven tokens of text — and `parseCodexUsageRecord` holds that as an
|
|
477
|
-
* invariant. The canonical `llm_call` carries no reasoning class, so the count is not
|
|
478
|
-
* forwarded to {@link buildLlmCall}, whose `reasoningTokens` input is for the sandbox
|
|
479
|
-
* `tokenUsage` record that reports reasoning BESIDE its output count.
|
|
480
|
-
*
|
|
481
|
-
* A counter no report carried stays absent, because a zero would claim the provider measured none.
|
|
482
|
-
*/
|
|
483
|
-
function harnessUsageLlmCall(reports, agentRunName) {
|
|
484
|
-
let inputTokens = 0;
|
|
485
|
-
let outputTokens = 0;
|
|
486
|
-
let readTokens;
|
|
487
|
-
let writeTokens;
|
|
488
|
-
for (const report of reports) {
|
|
489
|
-
inputTokens += report.input;
|
|
490
|
-
outputTokens += report.output;
|
|
491
|
-
if (report.cachedInput !== void 0) readTokens = (readTokens ?? 0) + report.cachedInput;
|
|
492
|
-
if (report.cacheWriteInput !== void 0) writeTokens = (writeTokens ?? 0) + report.cacheWriteInput;
|
|
493
|
-
}
|
|
494
|
-
const promptCache = {};
|
|
495
|
-
if (readTokens !== void 0) promptCache.readTokens = readTokens;
|
|
496
|
-
if (writeTokens !== void 0) promptCache.writeTokens = writeTokens;
|
|
497
|
-
return buildLlmCall({
|
|
498
|
-
inputTokens,
|
|
499
|
-
outputTokens,
|
|
500
|
-
...Object.keys(promptCache).length > 0 ? { promptCache } : {}
|
|
501
|
-
}, agentRunName);
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Sum the token usage + USD cost of a sandbox turn's events — the one honest way to meter an
|
|
505
|
-
* `openSandboxRun` cell. Folds a {@link SandboxUsageLedger} over the stream, so it reads usage off
|
|
506
|
-
* EVERY backend event shape — the canonical events plus a harness that reports usage only in its
|
|
507
|
-
* own event — and a `runProfileMatrix` dispatch can report it to `ctx.cost`:
|
|
508
|
-
*
|
|
509
|
-
* receipt: (turn) => {
|
|
510
|
-
* const u = sumSandboxUsage(turn.events)
|
|
511
|
-
* return { model, inputTokens: u.input, outputTokens: u.output,
|
|
512
|
-
* ...(u.tokensKnown === false ? { usageUnknown: true } : {}),
|
|
513
|
-
* ...(u.usdKnown !== false && u.costUsd > 0 ? { actualCostUsd: u.costUsd } : {}),
|
|
514
|
-
* ...(u.usdKnown === false ? { costUnknown: true } : {}),
|
|
515
|
-
* ...(u.estimatedCostUsd !== undefined ? { estimatedCostUsd: u.estimatedCostUsd } : {}) }
|
|
516
|
-
* }
|
|
517
|
-
*
|
|
518
|
-
* Without this a cell reads `{tokens:0, cost:0}` and the backend-integrity guard correctly aborts the
|
|
519
|
-
* matrix as a stub. `agentRunName` is the fallback model label for cost-only events (default `'agent'`).
|
|
520
|
-
*
|
|
521
|
-
* Pure by contract, like the ledger it folds: it never throws. A harness receipt the ledger cannot
|
|
522
|
-
* read leaves the result at `tokensKnown: false` with `tokensUnknownReason` carrying the decode
|
|
523
|
-
* message — an unreadable receipt is a different fact from a turn that reported no usage, and a
|
|
524
|
-
* post-hoc reader that threw would lose the whole failed turn it exists to report.
|
|
525
|
-
*/
|
|
526
|
-
function sumSandboxUsage(events, agentRunName = "agent") {
|
|
527
|
-
let input = 0;
|
|
528
|
-
let output = 0;
|
|
529
|
-
let costUsd = 0;
|
|
530
|
-
let estimatedCostUsd = 0;
|
|
531
|
-
let sawEstimate = false;
|
|
532
|
-
let sawCall = false;
|
|
533
|
-
let tokensKnown = true;
|
|
534
|
-
let usdKnown = true;
|
|
535
|
-
let unreadable;
|
|
536
|
-
const ledger = createSandboxUsageLedger();
|
|
537
|
-
const credit = (call) => {
|
|
538
|
-
sawCall = true;
|
|
539
|
-
input += call.tokensIn ?? 0;
|
|
540
|
-
output += call.tokensOut ?? 0;
|
|
541
|
-
costUsd += call.costUsd ?? 0;
|
|
542
|
-
if (call.tokensKnown === false) tokensKnown = false;
|
|
543
|
-
if (call.usdKnown === false) usdKnown = false;
|
|
544
|
-
unreadable ??= call.tokensUnknownReason;
|
|
545
|
-
if (call.estimatedCostUsd !== void 0) {
|
|
546
|
-
estimatedCostUsd += call.estimatedCostUsd;
|
|
547
|
-
sawEstimate = true;
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
for (const ev of events) {
|
|
551
|
-
const call = ledger.observe(ev, agentRunName);
|
|
552
|
-
if (call) credit(call);
|
|
553
|
-
}
|
|
554
|
-
const harnessCall = ledger.settleTurn(agentRunName);
|
|
555
|
-
if (harnessCall) credit(harnessCall);
|
|
556
|
-
return {
|
|
557
|
-
input,
|
|
558
|
-
output,
|
|
559
|
-
costUsd,
|
|
560
|
-
...sawCall && tokensKnown ? {} : { tokensKnown: false },
|
|
561
|
-
...sawCall && usdKnown ? {} : { usdKnown: false },
|
|
562
|
-
...sawEstimate ? { estimatedCostUsd } : {},
|
|
563
|
-
...unreadable === void 0 ? {} : { tokensUnknownReason: unreadable }
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
function buildLlmCall(data, agentRunName) {
|
|
567
|
-
const tokensIn = pickFiniteNumber(data, [
|
|
568
|
-
"tokensIn",
|
|
569
|
-
"inputTokens",
|
|
570
|
-
"prompt_tokens"
|
|
571
|
-
]);
|
|
572
|
-
const outputTokens = pickFiniteNumber(data, [
|
|
573
|
-
"tokensOut",
|
|
574
|
-
"outputTokens",
|
|
575
|
-
"completion_tokens"
|
|
576
|
-
]);
|
|
577
|
-
const reasoningTokens = pickFiniteNumber(data, ["reasoningTokens"]);
|
|
578
|
-
const tokensOut = outputTokens !== void 0 || reasoningTokens !== void 0 ? (outputTokens ?? 0) + (reasoningTokens ?? 0) : void 0;
|
|
579
|
-
const reportedCostUsd = pickFiniteNumber(data, [
|
|
580
|
-
"costUsd",
|
|
581
|
-
"totalCostUsd",
|
|
582
|
-
"cost_usd",
|
|
583
|
-
"cost"
|
|
584
|
-
]);
|
|
585
|
-
const explicitTokensKnown = data.tokensKnown ?? data.tokens_known;
|
|
586
|
-
const explicitCostKnown = data.costKnown ?? data.cost_known ?? data.usdKnown ?? data.usd_known;
|
|
587
|
-
const costProvenance = data.costProvenance ?? data.cost_provenance;
|
|
588
|
-
const explicitEstimate = pickFiniteNumber(data, ["estimatedCostUsd", "estimated_cost_usd"]);
|
|
589
|
-
const catalogEstimate = costProvenance === "catalog-estimate" ? explicitEstimate ?? reportedCostUsd : explicitEstimate;
|
|
590
|
-
const costUsd = costProvenance === "catalog-estimate" ? void 0 : reportedCostUsd;
|
|
591
|
-
const promptCache = readPromptCacheUsage(data);
|
|
592
|
-
const tokensKnown = explicitTokensKnown !== false && tokensIn !== void 0 && tokensOut !== void 0;
|
|
593
|
-
const usdKnown = explicitCostKnown !== false && costUsd !== void 0 && (costProvenance === void 0 || costProvenance === "provider-receipt" || costProvenance === "billing-receipt");
|
|
594
|
-
if (tokensIn === void 0 && tokensOut === void 0 && costUsd === void 0 && catalogEstimate === void 0 && promptCache === void 0 && explicitTokensKnown !== false && explicitCostKnown !== false) return;
|
|
595
|
-
const event = {
|
|
596
|
-
type: "llm_call",
|
|
597
|
-
model: typeof data.model === "string" && data.model.length > 0 ? data.model : agentRunName
|
|
598
|
-
};
|
|
599
|
-
if (tokensIn !== void 0) event.tokensIn = tokensIn;
|
|
600
|
-
if (tokensOut !== void 0) event.tokensOut = tokensOut;
|
|
601
|
-
if (!tokensKnown) event.tokensKnown = false;
|
|
602
|
-
if (costUsd !== void 0) event.costUsd = costUsd;
|
|
603
|
-
if (!usdKnown) event.usdKnown = false;
|
|
604
|
-
if (catalogEstimate !== void 0) event.estimatedCostUsd = catalogEstimate;
|
|
605
|
-
if (promptCache !== void 0) event.promptCache = promptCache;
|
|
606
|
-
return event;
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Every wire spelling of a prompt-cache READ counter, in precedence order: the canonical name
|
|
610
|
-
* first, then the spellings observed on the paths that reach this extractor — the sandbox terminal
|
|
611
|
-
* `tokenUsage` record, the cli-bridge OpenAI-compatible usage object (which normalizes every
|
|
612
|
-
* backend to Anthropic's `cache_read_input_tokens`), Anthropic verbatim, OpenAI's
|
|
613
|
-
* `prompt_tokens_details.cached_tokens`, and DeepSeek's `prompt_cache_hit_tokens`.
|
|
614
|
-
*/
|
|
615
|
-
const PROMPT_CACHE_READ_KEYS = [
|
|
616
|
-
"readTokens",
|
|
617
|
-
"read_tokens",
|
|
618
|
-
"cacheReadInputTokens",
|
|
619
|
-
"cache_read_input_tokens",
|
|
620
|
-
"cacheRead",
|
|
621
|
-
"cache_read",
|
|
622
|
-
"cachedInputTokens",
|
|
623
|
-
"cachedTokens",
|
|
624
|
-
"cached_tokens",
|
|
625
|
-
"prompt_cache_hit_tokens"
|
|
626
|
-
];
|
|
627
|
-
/**
|
|
628
|
-
* Every wire spelling of a prompt-cache WRITE counter. OpenAI reports no write counter at all, so
|
|
629
|
-
* an OpenAI-shaped usage record matches nothing here and the write stays absent — which is the
|
|
630
|
-
* point: absent is not zero.
|
|
631
|
-
*/
|
|
632
|
-
const PROMPT_CACHE_WRITE_KEYS = [
|
|
633
|
-
"writeTokens",
|
|
634
|
-
"write_tokens",
|
|
635
|
-
"cacheWriteInputTokens",
|
|
636
|
-
"cache_write_input_tokens",
|
|
637
|
-
"cacheCreationInputTokens",
|
|
638
|
-
"cache_creation_input_tokens",
|
|
639
|
-
"cacheWrite",
|
|
640
|
-
"cache_write"
|
|
641
|
-
];
|
|
642
|
-
const PROMPT_CACHE_MISS_KEYS = [
|
|
643
|
-
"missTokens",
|
|
644
|
-
"miss_tokens",
|
|
645
|
-
"prompt_cache_miss_tokens"
|
|
646
|
-
];
|
|
647
|
-
const PROMPT_CACHE_SAVINGS_KEYS = ["readSavingsUsd", "read_savings_usd"];
|
|
648
|
-
/**
|
|
649
|
-
* Read the provider's own prompt-cache accounting off one usage-bearing event payload, in the
|
|
650
|
-
* `PromptCacheUsage` vocabulary the router, driver, and chat-client paths already speak.
|
|
651
|
-
*
|
|
652
|
-
* Two sources, one record. A payload that already carries a `promptCache` / `prompt_cache` object
|
|
653
|
-
* is forwarded VERBATIM — those fields are the provider's own report, and an unrecognized one must
|
|
654
|
-
* stay visible rather than be bucketed away. On top of that, flat provider counters are translated
|
|
655
|
-
* to the canonical names, filling only the names the verbatim object did not already define.
|
|
656
|
-
*
|
|
657
|
-
* A counter the payload does not carry is left out. Defaulting it to zero would assert the provider
|
|
658
|
-
* measured no cache, which is a different fact from a provider that reported nothing — and it is
|
|
659
|
-
* the fact the budget reads to decide whether its charge is a measurement or a bound.
|
|
660
|
-
*/
|
|
661
|
-
function readPromptCacheUsage(data) {
|
|
662
|
-
const declared = finiteMetadata(data.promptCache ?? data.prompt_cache);
|
|
663
|
-
const nested = plainRecord(data.promptCache) ?? plainRecord(data.prompt_cache);
|
|
664
|
-
const sources = [
|
|
665
|
-
nested,
|
|
666
|
-
data,
|
|
667
|
-
plainRecord(data.prompt_tokens_details)
|
|
668
|
-
].filter((s) => s !== void 0);
|
|
669
|
-
const canonical = {};
|
|
670
|
-
const readTokens = pickTokenCount(sources, PROMPT_CACHE_READ_KEYS);
|
|
671
|
-
const writeTokens = pickTokenCount(sources, PROMPT_CACHE_WRITE_KEYS);
|
|
672
|
-
const missTokens = pickTokenCount(sources, PROMPT_CACHE_MISS_KEYS);
|
|
673
|
-
const readSavingsUsd = pickNonNegativeNumber(sources, PROMPT_CACHE_SAVINGS_KEYS);
|
|
674
|
-
const status = nested !== void 0 && typeof nested.status === "string" ? nested.status : void 0;
|
|
675
|
-
if (readTokens !== void 0) canonical.readTokens = readTokens;
|
|
676
|
-
if (writeTokens !== void 0) canonical.writeTokens = writeTokens;
|
|
677
|
-
if (missTokens !== void 0) canonical.missTokens = missTokens;
|
|
678
|
-
if (readSavingsUsd !== void 0) canonical.readSavingsUsd = readSavingsUsd;
|
|
679
|
-
if (status !== void 0) canonical.status = status;
|
|
680
|
-
const merged = {
|
|
681
|
-
...canonical,
|
|
682
|
-
...declared
|
|
683
|
-
};
|
|
684
|
-
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
685
|
-
}
|
|
686
|
-
function plainRecord(value) {
|
|
687
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
688
|
-
}
|
|
689
|
-
function pickTokenCount(sources, keys) {
|
|
690
|
-
for (const key of keys) for (const source of sources) {
|
|
691
|
-
const value = source[key];
|
|
692
|
-
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
function pickNonNegativeNumber(sources, keys) {
|
|
696
|
-
for (const key of keys) for (const source of sources) {
|
|
697
|
-
const value = source[key];
|
|
698
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
function finiteMetadata(value) {
|
|
702
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
703
|
-
const result = {};
|
|
704
|
-
for (const [key, entry] of Object.entries(value)) if (typeof entry === "number" && Number.isFinite(entry) || typeof entry === "string") result[key] = entry;
|
|
705
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
706
|
-
}
|
|
707
|
-
function pickFiniteNumber(data, keys) {
|
|
708
|
-
for (const key of keys) {
|
|
709
|
-
const value = data[key];
|
|
710
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* Fresh per-turn {@link SandboxToolPartState} for {@link mapSandboxToolEvent} — an
|
|
715
|
-
* empty call-status map so each turn projects tool frames independently.
|
|
716
|
-
*
|
|
717
|
-
* @experimental
|
|
718
|
-
*/
|
|
719
|
-
function createSandboxToolPartState() {
|
|
720
|
-
return {
|
|
721
|
-
statusByCall: /* @__PURE__ */ new Map(),
|
|
722
|
-
seq: 0
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
/** Statuses that settle one tool call as a failure. A failed tool call stays a tool result:
|
|
726
|
-
* the run's terminal state comes from the public Sandbox outcome tracker, never from here. */
|
|
727
|
-
const TERMINAL_FAILURE = /^(error|errored|failed|failure|cancelled|canceled|timeout|timed_out)$/i;
|
|
728
|
-
/**
|
|
729
|
-
* Project one `SandboxEvent` onto the `tool_call` / `tool_result` variants of
|
|
730
|
-
* `RuntimeStreamEvent` — the tool-part projection `mapSandboxEvent`
|
|
731
|
-
* deliberately does NOT perform. Opt-in and additive: `mapSandboxEvent`'s
|
|
732
|
-
* default vocabulary (text/reasoning deltas + `llm_call`) is unchanged;
|
|
733
|
-
* consumers that need the tool surface (chat UIs rendering tool activity)
|
|
734
|
-
* compose this projector alongside it — `streamAgentTurn` does exactly that
|
|
735
|
-
* under its `preserveToolParts` option.
|
|
736
|
-
*
|
|
737
|
-
* Handled shapes (observed on the opencode / claude-code sandbox backends):
|
|
738
|
-
* - `message.part.updated` with `part.type === 'tool'` — stateful: a
|
|
739
|
-
* `tool_call` on the call id's first frame (args from `state.input` or
|
|
740
|
-
* `state.metadata.input`), a `tool_result` when the status transitions to
|
|
741
|
-
* `completed` (result from `state.output` / `metadata.output`) or to a
|
|
742
|
-
* terminal failure (result is `{ error, status, output? }` — the error
|
|
743
|
-
* surfaced in-band, never dropped).
|
|
744
|
-
* - bare `tool*` event types (`tool.call`, `tool_result`, …) — stateless:
|
|
745
|
-
* `*result*` types project to `tool_result`, the rest to `tool_call`.
|
|
746
|
-
*
|
|
747
|
-
* Returns `[]` for every non-tool event.
|
|
748
|
-
*
|
|
749
|
-
* @experimental
|
|
750
|
-
*/
|
|
751
|
-
function mapSandboxToolEvent(event, state) {
|
|
752
|
-
if (!event || typeof event !== "object") return [];
|
|
753
|
-
const type = String(event.type ?? "");
|
|
754
|
-
const data = event.data && typeof event.data === "object" ? event.data : {};
|
|
755
|
-
if (type === "message.part.updated") {
|
|
756
|
-
const part = data.part && typeof data.part === "object" ? data.part : {};
|
|
757
|
-
if (String(part.type ?? "") !== "tool") return [];
|
|
758
|
-
return projectToolPart(part, state, typeof event.id === "string" ? event.id : void 0);
|
|
759
|
-
}
|
|
760
|
-
if (type.includes("tool")) {
|
|
761
|
-
const callId = pickString(data, [
|
|
762
|
-
"toolCallId",
|
|
763
|
-
"tool_use_id",
|
|
764
|
-
"id"
|
|
765
|
-
]) ?? (typeof event.id === "string" ? event.id : void 0) ?? `sandbox-tool-${++state.seq}`;
|
|
766
|
-
const toolName = pickString(data, [
|
|
767
|
-
"name",
|
|
768
|
-
"toolName",
|
|
769
|
-
"tool"
|
|
770
|
-
]) ?? "sandbox_tool";
|
|
771
|
-
if (type.includes("result")) return [{
|
|
772
|
-
type: "tool_result",
|
|
773
|
-
toolName,
|
|
774
|
-
toolCallId: callId,
|
|
775
|
-
result: data.output ?? data.result ?? data.content ?? data
|
|
776
|
-
}];
|
|
777
|
-
return [{
|
|
778
|
-
type: "tool_call",
|
|
779
|
-
toolName,
|
|
780
|
-
toolCallId: callId,
|
|
781
|
-
args: data.input ?? data.args ?? {}
|
|
782
|
-
}];
|
|
783
|
-
}
|
|
784
|
-
return [];
|
|
785
|
-
}
|
|
786
|
-
function projectToolPart(part, state, eventId) {
|
|
787
|
-
const callId = pickString(part, [
|
|
788
|
-
"callID",
|
|
789
|
-
"callId",
|
|
790
|
-
"toolCallId",
|
|
791
|
-
"id"
|
|
792
|
-
]) ?? eventId ?? `sandbox-tool-${++state.seq}`;
|
|
793
|
-
const toolName = pickString(part, [
|
|
794
|
-
"tool",
|
|
795
|
-
"toolName",
|
|
796
|
-
"name"
|
|
797
|
-
]) ?? "sandbox_tool";
|
|
798
|
-
const toolState = part.state && typeof part.state === "object" ? part.state : {};
|
|
799
|
-
const metadata = toolState.metadata && typeof toolState.metadata === "object" ? toolState.metadata : {};
|
|
800
|
-
const status = pickString(toolState, ["status"]) ?? "updated";
|
|
801
|
-
const previous = state.statusByCall.get(callId);
|
|
802
|
-
if (previous === "completed" || previous !== void 0 && TERMINAL_FAILURE.test(previous)) return [];
|
|
803
|
-
const out = [];
|
|
804
|
-
if (previous === void 0) out.push({
|
|
805
|
-
type: "tool_call",
|
|
806
|
-
toolName,
|
|
807
|
-
toolCallId: callId,
|
|
808
|
-
args: toolState.input ?? metadata.input ?? {}
|
|
809
|
-
});
|
|
810
|
-
state.statusByCall.set(callId, status);
|
|
811
|
-
if (status === "completed") out.push({
|
|
812
|
-
type: "tool_result",
|
|
813
|
-
toolName,
|
|
814
|
-
toolCallId: callId,
|
|
815
|
-
result: toolState.output ?? metadata.output ?? ""
|
|
816
|
-
});
|
|
817
|
-
else if (TERMINAL_FAILURE.test(status)) {
|
|
818
|
-
const message = pickString(toolState, ["error", "message"]) ?? pickString(metadata, ["error", "message"]) ?? `sandbox tool ended with status ${status}`;
|
|
819
|
-
const output = toolState.output ?? metadata.output;
|
|
820
|
-
out.push({
|
|
821
|
-
type: "tool_result",
|
|
822
|
-
toolName,
|
|
823
|
-
toolCallId: callId,
|
|
824
|
-
result: {
|
|
825
|
-
error: message,
|
|
826
|
-
status,
|
|
827
|
-
...output !== void 0 ? { output } : {}
|
|
828
|
-
}
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
return out;
|
|
832
|
-
}
|
|
833
|
-
function pickString(data, keys) {
|
|
834
|
-
for (const key of keys) {
|
|
835
|
-
const value = data[key];
|
|
836
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* Project one `SandboxEvent` onto the `RuntimeStreamEvent` chat-UX vocabulary,
|
|
841
|
-
* for runtimes that bridge a sandbox `streamPrompt` into the
|
|
842
|
-
* `AgentRuntime.act` streaming contract. Returns `undefined` for events that
|
|
843
|
-
* have no faithful projection — the raw stream is preserved separately for the
|
|
844
|
-
* `OutputAdapter`, so an unmapped event never loses data.
|
|
845
|
-
*
|
|
846
|
-
* Mapped (the task-optional incremental variants — no synthesized task
|
|
847
|
-
* lifecycle, no guessed tool-part shapes):
|
|
848
|
-
* - `message.part.updated` text part → `text_delta`
|
|
849
|
-
* - `message.part.updated` reasoning/thinking part → `reasoning_delta`
|
|
850
|
-
* - cost-bearing events → `llm_call` (shared with the ledger extractor)
|
|
851
|
-
*
|
|
852
|
-
* Tool parts are deliberately NOT mapped here (unchanged default) — compose
|
|
853
|
-
* {@link mapSandboxToolEvent} alongside when a consumer needs them.
|
|
854
|
-
*
|
|
855
|
-
* The opencode backend emits incremental text as
|
|
856
|
-
* `{ type: 'message.part.updated', data: { part: { type, text }, delta } }`;
|
|
857
|
-
* `delta` is the increment, `part.text` the running accumulation.
|
|
858
|
-
*/
|
|
859
|
-
function mapSandboxEvent(event, opts = {}) {
|
|
860
|
-
if (!event || typeof event !== "object") return void 0;
|
|
861
|
-
const type = String(event.type ?? "");
|
|
862
|
-
const data = event.data && typeof event.data === "object" ? event.data : {};
|
|
863
|
-
if (type === "message.part.updated") {
|
|
864
|
-
const part = data.part && typeof data.part === "object" ? data.part : {};
|
|
865
|
-
const partType = String(part.type ?? "");
|
|
866
|
-
const text = (typeof data.delta === "string" ? data.delta : void 0) ?? (typeof part.text === "string" ? part.text : void 0);
|
|
867
|
-
if (text === void 0) return void 0;
|
|
868
|
-
if (partType === "text") return {
|
|
869
|
-
type: "text_delta",
|
|
870
|
-
text
|
|
871
|
-
};
|
|
872
|
-
if (partType === "reasoning" || partType === "thinking") return {
|
|
873
|
-
type: "reasoning_delta",
|
|
874
|
-
text
|
|
875
|
-
};
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
return extractLlmCallEvent(event, opts.agentRunName ?? "agent");
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Project one `SandboxEvent` onto Runtime's executor progress vocabulary: incremental text and
|
|
882
|
-
* reasoning, tool calls and results, and an interaction request. It composes the existing
|
|
883
|
-
* projections ({@link mapSandboxEvent}, {@link mapSandboxToolEvent}, and the canonical Agent
|
|
884
|
-
* Interface decode) so every sandbox-shaped executor publishes live output through one reader.
|
|
885
|
-
* Usage-bearing events project to nothing here — accounting stays on the `tokens`/`cost`
|
|
886
|
-
* channels.
|
|
887
|
-
*
|
|
888
|
-
* Pass one {@link SandboxToolPartState} per turn so a multi-frame tool call yields one call and
|
|
889
|
-
* at most one result.
|
|
890
|
-
*
|
|
891
|
-
* @experimental
|
|
892
|
-
*/
|
|
893
|
-
function sandboxProgressEvents(event, state) {
|
|
894
|
-
const canonical = canonicalStreamEventFromSandboxEvent(event);
|
|
895
|
-
if (canonical?.type === "interaction") return [{
|
|
896
|
-
kind: "interaction",
|
|
897
|
-
request: canonical.request
|
|
898
|
-
}];
|
|
899
|
-
if (canonical?.type === "child-task") return [{
|
|
900
|
-
kind: "child_task",
|
|
901
|
-
event: canonical
|
|
902
|
-
}];
|
|
903
|
-
const tools = mapSandboxToolEvent(event, state).map((projected) => projected.type === "tool_call" ? {
|
|
904
|
-
kind: "tool_call",
|
|
905
|
-
toolName: projected.toolName,
|
|
906
|
-
...projected.toolCallId === void 0 ? {} : { toolCallId: projected.toolCallId },
|
|
907
|
-
...projected.args === void 0 ? {} : { args: projected.args }
|
|
908
|
-
} : {
|
|
909
|
-
kind: "tool_result",
|
|
910
|
-
toolName: projected.toolName,
|
|
911
|
-
...projected.toolCallId === void 0 ? {} : { toolCallId: projected.toolCallId },
|
|
912
|
-
...projected.result === void 0 ? {} : { result: projected.result }
|
|
913
|
-
});
|
|
914
|
-
if (tools.length > 0) return tools;
|
|
915
|
-
const mapped = mapSandboxEvent(event);
|
|
916
|
-
if (mapped?.type === "text_delta") return [{
|
|
917
|
-
kind: "text_delta",
|
|
918
|
-
text: mapped.text
|
|
919
|
-
}];
|
|
920
|
-
if (mapped?.type === "reasoning_delta") return [{
|
|
921
|
-
kind: "reasoning_delta",
|
|
922
|
-
text: mapped.text
|
|
923
|
-
}];
|
|
924
|
-
return [];
|
|
925
|
-
}
|
|
926
|
-
//#endregion
|
|
927
|
-
export { parseCodexUsageRecord as _, extractLlmCallEvent as a, mapSandboxToolEvent as c, sandboxProgressEvents as d, sandboxTerminalUsageField as f, decodeHarnessUsage as g, parseCanonicalTransportEvent as h, createSandboxUsageLedger as i, notifySandboxEventObserver as l, extractTransportEventIdentity as m, canonicalStreamEventFromSandboxEvent as n, isSandboxTerminalEvent as o, sumSandboxUsage as p, createSandboxToolPartState as r, mapSandboxEvent as s, assertSandboxServedModel as t, sandboxEventServedBackend as u };
|
|
928
|
-
|
|
929
|
-
//# sourceMappingURL=sandbox-events-DbC2WKKS.js.map
|