@tangle-network/agent-runtime 0.197.1 → 0.198.2

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.
Files changed (103) hide show
  1. package/README.md +1 -2
  2. package/dist/{improve-DZs0KXhK.d.ts → activation-DFRTurvU.d.ts} +99 -5
  3. package/dist/{activation-BxMZybuo.js → activation-IBEVN3VI.js} +2 -2
  4. package/dist/{activation-BxMZybuo.js.map → activation-IBEVN3VI.js.map} +1 -1
  5. package/dist/agent.d.ts +1 -1
  6. package/dist/agent.js +2 -2
  7. package/dist/candidate-execution/index.d.ts +370 -2
  8. package/dist/candidate-execution/index.js +828 -4
  9. package/dist/candidate-execution/index.js.map +1 -0
  10. package/dist/{coordination-driver-zkCrfEbS.js → coordination-driver-NFvZ5ofi.js} +57 -26
  11. package/dist/coordination-driver-NFvZ5ofi.js.map +1 -0
  12. package/dist/{delegate-DHeUYU5E.js → delegate-dN5yooEj.js} +2 -2
  13. package/dist/{delegate-DHeUYU5E.js.map → delegate-dN5yooEj.js.map} +1 -1
  14. package/dist/durable.d.ts +2 -2
  15. package/dist/durable.js +4 -5
  16. package/dist/durable.js.map +1 -1
  17. package/dist/{graph-51SJOEcw.js → graph-CgCVtMuz.js} +3 -3
  18. package/dist/{graph-51SJOEcw.js.map → graph-CgCVtMuz.js.map} +1 -1
  19. package/dist/{improvement-cycle-Tw5nYT5R.js → improvement-cycle-Cfu6kDOs.js} +8 -10
  20. package/dist/{improvement-cycle-Tw5nYT5R.js.map → improvement-cycle-Cfu6kDOs.js.map} +1 -1
  21. package/dist/{index-CYOJsxSg.d.ts → index-CMTUgh-T.d.ts} +9 -11
  22. package/dist/index.d.ts +694 -16
  23. package/dist/index.js +1780 -17
  24. package/dist/index.js.map +1 -1
  25. package/dist/intelligence.d.ts +3 -7
  26. package/dist/intelligence.js +5 -6
  27. package/dist/intelligence.js.map +1 -1
  28. package/dist/kernel.d.ts +2 -5
  29. package/dist/kernel.js +8 -10
  30. package/dist/{loop-runner-bin-DwrSB04m.d.ts → loop-runner-bin-CAf1OQot.d.ts} +3 -3
  31. package/dist/{loop-runner-bin-2LQSjRTB.js → loop-runner-bin-WQniYJ8C.js} +3 -3
  32. package/dist/{loop-runner-bin-2LQSjRTB.js.map → loop-runner-bin-WQniYJ8C.js.map} +1 -1
  33. package/dist/loop-runner-bin.d.ts +1 -1
  34. package/dist/loop-runner-bin.js +1 -1
  35. package/dist/mcp/bin.js +3 -3
  36. package/dist/mcp/index.d.ts +2 -3
  37. package/dist/mcp/index.js +4 -5
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/{prepare-DDGp0-rW.js → prepare-CAO1yXov.js} +2407 -2407
  40. package/dist/prepare-CAO1yXov.js.map +1 -0
  41. package/dist/{protected-model-port-DxFN8DLS.js → protected-model-port-B5avcRiQ.js} +2 -2
  42. package/dist/{protected-model-port-DxFN8DLS.js.map → protected-model-port-B5avcRiQ.js.map} +1 -1
  43. package/dist/{provision-supervisor-CpMMShE_.js → provision-supervisor-BxsIaJ35.js} +3 -6
  44. package/dist/{provision-supervisor-CpMMShE_.js.map → provision-supervisor-BxsIaJ35.js.map} +1 -1
  45. package/dist/{redact-Cbl2O-4N.js → redact-DqfB7oB4.js} +5233 -2526
  46. package/dist/redact-DqfB7oB4.js.map +1 -0
  47. package/dist/{runtime-BSFz2z7h.js → runtime-CHEtvaTY.js} +427 -23
  48. package/dist/runtime-CHEtvaTY.js.map +1 -0
  49. package/dist/{server-COXa19sp.js → server-ccGua5tH.js} +4 -4
  50. package/dist/{server-COXa19sp.js.map → server-ccGua5tH.js.map} +1 -1
  51. package/dist/{types-DFLZMaeh.d.ts → stream-agent-turn-CLOQr497.d.ts} +1986 -6
  52. package/dist/{structural-rollout-DPbZWgEm.js → structural-rollout-BmDuXyR9.js} +1101 -8
  53. package/dist/structural-rollout-BmDuXyR9.js.map +1 -0
  54. package/dist/{supervise-b93YUaka.js → supervise-DVt8-TI-.js} +9 -5
  55. package/dist/supervise-DVt8-TI-.js.map +1 -0
  56. package/dist/testing.d.ts +2 -2
  57. package/dist/testing.js +13 -13
  58. package/dist/tui/index.d.ts +1 -1
  59. package/dist/tui/index.js +1 -1
  60. package/dist/{workspace-archive-Ybomp7AN.js → workspace-archive-BMOnloFf.js} +3 -3
  61. package/dist/{workspace-archive-Ybomp7AN.js.map → workspace-archive-BMOnloFf.js.map} +1 -1
  62. package/package.json +1 -28
  63. package/dist/activation-DyWB0K6E.d.ts +0 -98
  64. package/dist/authored-code-URmkdgjv.js +0 -37
  65. package/dist/authored-code-URmkdgjv.js.map +0 -1
  66. package/dist/candidate-execution-nvqVIMyS.js +0 -829
  67. package/dist/candidate-execution-nvqVIMyS.js.map +0 -1
  68. package/dist/conversation-BxJ0SIBM.js +0 -1363
  69. package/dist/conversation-BxJ0SIBM.js.map +0 -1
  70. package/dist/conversation.d.ts +0 -2
  71. package/dist/conversation.js +0 -2
  72. package/dist/coordination-driver-zkCrfEbS.js.map +0 -1
  73. package/dist/environment-provider-1fKZh2zl.js +0 -2281
  74. package/dist/environment-provider-1fKZh2zl.js.map +0 -1
  75. package/dist/environment-provider-B-I2jlQy.d.ts +0 -143
  76. package/dist/environment-provider.d.ts +0 -2
  77. package/dist/environment-provider.js +0 -2
  78. package/dist/graph.d.ts +0 -753
  79. package/dist/graph.js +0 -2111
  80. package/dist/graph.js.map +0 -1
  81. package/dist/index-CUosKU4N.d.ts +0 -372
  82. package/dist/index-D9mb6fn2.d.ts +0 -691
  83. package/dist/index-ZnxSe6iK.d.ts +0 -138
  84. package/dist/jsonl-file-BEpaEYjT.js +0 -141
  85. package/dist/jsonl-file-BEpaEYjT.js.map +0 -1
  86. package/dist/knowledge-B_MsOtDG.js +0 -428
  87. package/dist/knowledge-B_MsOtDG.js.map +0 -1
  88. package/dist/knowledge.d.ts +0 -2
  89. package/dist/knowledge.js +0 -2
  90. package/dist/materialization-Cy0oM8tb.js +0 -672
  91. package/dist/materialization-Cy0oM8tb.js.map +0 -1
  92. package/dist/prepare-DDGp0-rW.js.map +0 -1
  93. package/dist/primeintellect/index.d.ts +0 -218
  94. package/dist/primeintellect/index.js +0 -739
  95. package/dist/primeintellect/index.js.map +0 -1
  96. package/dist/redact-Cbl2O-4N.js.map +0 -1
  97. package/dist/runtime-0xNaV6TJ.d.ts +0 -1699
  98. package/dist/runtime-BSFz2z7h.js.map +0 -1
  99. package/dist/stream-agent-turn-Dt5mZpc3.js +0 -1103
  100. package/dist/stream-agent-turn-Dt5mZpc3.js.map +0 -1
  101. package/dist/stream-agent-turn-urHpmO_Z.d.ts +0 -160
  102. package/dist/structural-rollout-DPbZWgEm.js.map +0 -1
  103. package/dist/supervise-b93YUaka.js.map +0 -1
@@ -1,2281 +0,0 @@
1
- import { $ as promptOptionsFromAgentTurnInput, D as detachedSnapshot, Q as promptFromAgentTurnInput, R as awaitAbortable, X as sameControlCoordinates, o as finalizeRuntimeOwnedPendingExecutor, r as attestRuntimeOwnedPendingExecutor, u as newExecutionAttemptId } from "./materialization-Cy0oM8tb.js";
2
- import { d as sandboxProgressEvents, f as sandboxTerminalUsageField, n as canonicalStreamEventFromSandboxEvent, o as isSandboxTerminalEvent, r as createSandboxToolPartState } from "./sandbox-events-DbC2WKKS.js";
3
- import { a as concreteProfileModel, c as profileModelExecutionSettings, o as enforceTokenLimits } from "./model-policy-DKDyr-fc.js";
4
- import { AgentExactRunControlRefSchema, AgentInteractiveSessionRefSchema, AgentInteractiveSessionStartSchema, AgentRunCancellationRequestSchema, WorkspaceRequestSchema, agentInteractiveSessionRefMatchesStart, canonicalCandidateDigest, canonicalWorkspaceCwd, harnessSystemPromptIntents, workspaceCwdPathForBase } from "@tangle-network/agent-interface";
5
- import { randomUUID } from "node:crypto";
6
- import { isDeepStrictEqual } from "node:util";
7
- import { createAgentRunOutcomeTracker } from "@tangle-network/sandbox/runtime";
8
- //#region src/runtime/supervise/abortable.ts
9
- /**
10
- * One abort race, and one abort cascade, for the supervision tree.
11
- *
12
- * Every layer that can be cancelled needs the same three things: the reason a signal
13
- * carries, an `AbortError` built from it, and a settle-once race between the work and
14
- * the signal. Written per layer, each drifts — a listener stops being removed on one
15
- * path, a late resolution overwrites a cancellation that already won, or a cascade
16
- * forwards no reason and renames every downstream death to the layer's own fallback.
17
- *
18
- * The fallback message is the only thing a layer supplies, so the error still names
19
- * which layer cancelled.
20
- */
21
- /**
22
- * The reason a signal carries, or `fallback` when it states none.
23
- *
24
- * A cascade that drops the upstream reason turns every downstream death into its own generic
25
- * message: the worker's `down` record then says nothing about WHY, which makes a whole class of
26
- * child mortality undiagnosable from the journal alone.
27
- */
28
- function abortReason(signal, fallback) {
29
- const reason = signal.reason;
30
- if (typeof reason === "string" && reason.length > 0) return reason;
31
- if (reason instanceof Error && reason.name !== "AbortError" && reason.message.length > 0) return reason.message;
32
- return fallback;
33
- }
34
- /** An `AbortError` carrying the signal's own string reason, or `fallback` when it states none. */
35
- function abortError$1(signal, fallback) {
36
- const reason = abortReason(signal, fallback);
37
- const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : fallback);
38
- error.name = "AbortError";
39
- return error;
40
- }
41
- /**
42
- * Cascade N abort signals into one downstream abort that fires when ANY of them does, carrying
43
- * that signal's own reason ({@link abortReason}). Node-portable: `AbortSignal.any` needs >=20.3
44
- * and the package floor is >=20, and it would drop the reason anyway.
45
- *
46
- * The one cascade every executor uses, so a reason forwarded here reaches every layer below.
47
- */
48
- function linkAbort(...sources) {
49
- const controller = new AbortController();
50
- const listeners = [];
51
- const release = () => {
52
- for (const { source, onAbort } of listeners) source.removeEventListener("abort", onAbort);
53
- listeners.length = 0;
54
- };
55
- const abort = (reason) => {
56
- release();
57
- controller.abort(reason);
58
- };
59
- const alreadyAborted = sources.find((source) => source.aborted);
60
- if (alreadyAborted !== void 0) {
61
- controller.abort(abortReason(alreadyAborted, "aborted by parent scope"));
62
- return {
63
- signal: controller.signal,
64
- abort,
65
- release
66
- };
67
- }
68
- for (const source of sources) {
69
- const onAbort = () => abort(abortReason(source, "aborted by parent scope"));
70
- listeners.push({
71
- source,
72
- onAbort
73
- });
74
- source.addEventListener("abort", onAbort, { once: true });
75
- }
76
- return {
77
- signal: controller.signal,
78
- abort,
79
- release
80
- };
81
- }
82
- /**
83
- * Settle `act()` against `signal`. The first outcome wins and the listener is always
84
- * removed, so neither a late resolution nor a late abort can settle the promise twice.
85
- * The losing promise stays observed, so a late rejection cannot surface as an unhandled
86
- * process error.
87
- *
88
- * The abort rejection is deferred one microtask so a result the action had ALREADY
89
- * produced still wins. It does not hand the race to a listener the action registers
90
- * from inside `act()`: this function subscribes before it calls `act()`, so its own
91
- * abort handler runs first and settles the promise. A caller that must let the action
92
- * observe the abort itself has to hold a live promise and await that instead.
93
- *
94
- * A synchronous throw from `act()` propagates as the rejection, not as an abort.
95
- */
96
- async function runAbortable(act, signal, fallback) {
97
- if (signal.aborted) throw abortError$1(signal, fallback);
98
- return await new Promise((resolve, reject) => {
99
- let settled = false;
100
- const cleanup = () => signal.removeEventListener("abort", onAbort);
101
- const onAbort = () => {
102
- queueMicrotask(() => {
103
- if (settled) return;
104
- settled = true;
105
- cleanup();
106
- reject(abortError$1(signal, fallback));
107
- });
108
- };
109
- signal.addEventListener("abort", onAbort, { once: true });
110
- let work;
111
- try {
112
- work = Promise.resolve(act());
113
- } catch (error) {
114
- cleanup();
115
- reject(error);
116
- return;
117
- }
118
- work.then((value) => {
119
- if (settled) return;
120
- settled = true;
121
- cleanup();
122
- resolve(value);
123
- }, (error) => {
124
- if (settled) return;
125
- settled = true;
126
- cleanup();
127
- reject(error);
128
- });
129
- });
130
- }
131
- //#endregion
132
- //#region src/runtime/util.ts
133
- /**
134
- * Best-effort sandbox delete. Skips instances without a `delete` (test fakes);
135
- * swallows errors (the platform reaps on expiry). Returns `false` when delete
136
- * threw, `true` otherwise, so callers can surface a leak if they choose.
137
- */
138
- async function deleteBoxSafe(box) {
139
- if (!box || typeof box.delete !== "function") return true;
140
- try {
141
- await box.delete();
142
- return true;
143
- } catch {
144
- return false;
145
- }
146
- }
147
- /** Short base36 id for trace correlation. Not cryptographic, not collision-free. */
148
- function randomSuffix(len = 8) {
149
- return Math.random().toString(36).slice(2, 2 + len);
150
- }
151
- /** Collision-resistant id for sandbox naming (find-by-name recovery must be unique). */
152
- function randomUuid() {
153
- return crypto.randomUUID();
154
- }
155
- /** Construct an AbortError. Downstream code pattern-matches on `err.name`. */
156
- function abortError() {
157
- const err = /* @__PURE__ */ new Error("aborted");
158
- err.name = "AbortError";
159
- return err;
160
- }
161
- /** Throw an AbortError. */
162
- function throwAbort() {
163
- throw abortError();
164
- }
165
- /** Throw if the signal is already aborted; otherwise no-op. */
166
- function throwIfAborted$1(signal) {
167
- if (signal?.aborted) throw abortError();
168
- }
169
- /** True for any error whose `name` is `AbortError` (the cross-kernel abort contract). */
170
- function isAbortError(err) {
171
- return err instanceof Error && err.name === "AbortError";
172
- }
173
- /**
174
- * Sleep that resolves early on abort and always clears its timer so it never
175
- * keeps the event loop alive. Resolves (does not reject) on abort — callers
176
- * re-check the signal explicitly after the sleep.
177
- */
178
- function sleep(ms, signal) {
179
- return new Promise((resolve) => {
180
- if (signal?.aborted) {
181
- resolve();
182
- return;
183
- }
184
- let onAbort;
185
- const timer = setTimeout(() => {
186
- if (onAbort && signal) signal.removeEventListener("abort", onAbort);
187
- resolve();
188
- }, ms);
189
- if (signal) {
190
- onAbort = () => {
191
- clearTimeout(timer);
192
- resolve();
193
- };
194
- signal.addEventListener("abort", onAbort, { once: true });
195
- }
196
- });
197
- }
198
- /**
199
- * Race a promise against a timeout. Resolves with the value if it settles in
200
- * time, otherwise resolves with `undefined`. Always clears the timer.
201
- */
202
- function withTimeout(promise, ms) {
203
- return new Promise((resolve) => {
204
- const timer = setTimeout(() => resolve(void 0), ms);
205
- promise.then((value) => {
206
- clearTimeout(timer);
207
- resolve(value);
208
- }, () => {
209
- clearTimeout(timer);
210
- resolve(void 0);
211
- });
212
- });
213
- }
214
- /**
215
- * `JSON.stringify` with a `String()` fallback on throw (cyclic / non-JSON).
216
- * Strings pass through unstringified so a preview of a string output is the
217
- * string itself, not a quoted re-encoding.
218
- */
219
- function stringifySafe(value, opts = {}) {
220
- let s;
221
- try {
222
- if (typeof value === "string") s = value;
223
- else s = (opts.pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value)) ?? String(value);
224
- } catch {
225
- s = String(value);
226
- }
227
- if (opts.max !== void 0 && s.length > opts.max) return `${s.slice(0, opts.max)}…`;
228
- return s;
229
- }
230
- /** A fresh zero token-usage accumulator. */
231
- function zeroTokenUsage() {
232
- return {
233
- input: 0,
234
- output: 0
235
- };
236
- }
237
- /**
238
- * Sum the catalog-priced part of a dollar total across spends, as a field to spread.
239
- *
240
- * Returns nothing when no input carried one. A fold of pure provider receipts must not gain a
241
- * `usdEstimated: 0`, which would read as "this runtime checked and priced none" on a path that
242
- * never prices at all.
243
- */
244
- function usdEstimatedOf(...spends) {
245
- let total = 0;
246
- let priced = false;
247
- for (const spend of spends) {
248
- if (spend.usdEstimated === void 0) continue;
249
- priced = true;
250
- total += spend.usdEstimated;
251
- }
252
- return priced ? { usdEstimated: total } : {};
253
- }
254
- /** A conserved spend with every channel at zero and no completeness claim on it. */
255
- function zeroSpend() {
256
- return {
257
- iterations: 0,
258
- tokens: zeroTokenUsage(),
259
- usd: 0,
260
- ms: 0
261
- };
262
- }
263
- /**
264
- * The spend of work that HAPPENED and reported no usage receipt.
265
- *
266
- * Not the same value as {@link zeroSpend} even though both carry `{0,0}` tokens and `$0`. A bare
267
- * zero asserts a MEASUREMENT — "this ran and cost nothing" — and every consumer downstream reads it
268
- * that way: the pool keeps reporting `readout().tokensKnown === true`, the journal totals stay
269
- * clean, the OTEL span records a priced zero, and a caller's token-denominated ceiling can never
270
- * fire no matter how much the work really burned. A ceiling that cannot fire is worse than no
271
- * ceiling, because it reads as protection.
272
- *
273
- * `tokensKnown` is the marker the substrate already threads end to end for exactly this case
274
- * (`budget.ts`, `otel-spans.ts`, `spawn-journal.ts`, `supervisor.ts`): the work is recorded, the
275
- * zero is labelled a floor rather than a total, and every rollup that touches it reports its
276
- * balance as a ceiling rather than a measurement. Use this — never a bare zero — whenever a runtime
277
- * cannot see what its worker spent.
278
- *
279
- * The same rule applies to dollars. A dollar-capped run must refuse an executor whose billed spend
280
- * is unknowable; `budgetExempt` does not authorize Runtime to relabel unknown spend as a measured
281
- * zero. Callers that require dollar accounting must use a backend with a trusted billing receipt.
282
- */
283
- function unmeteredSpend(ms) {
284
- return {
285
- iterations: 0,
286
- tokens: zeroTokenUsage(),
287
- tokensKnown: false,
288
- usd: 0,
289
- usdKnown: false,
290
- ms
291
- };
292
- }
293
- /** Copy a conserved spend without dropping a completeness marker or the catalog-priced part. */
294
- function cloneSpend(spend) {
295
- return {
296
- iterations: spend.iterations,
297
- tokens: cloneTokenUsage(spend.tokens),
298
- ...spend.tokensKnown === false ? { tokensKnown: false } : {},
299
- usd: spend.usd,
300
- ...spend.usdKnown === false ? { usdKnown: false } : {},
301
- ...spend.usdEstimated !== void 0 ? { usdEstimated: spend.usdEstimated } : {},
302
- ms: spend.ms,
303
- ...boxMinutesOf(spend)
304
- };
305
- }
306
- /**
307
- * Sum two conserved spends per channel. Tokens and dollars never fold into each other, an
308
- * incomplete input keeps the sum incomplete on that channel, and the catalog-priced part of the
309
- * dollar total is summed separately so `usd - usdEstimated` still names what a provider billed.
310
- */
311
- function addSpend(a, b) {
312
- const tokens = cloneTokenUsage(a.tokens);
313
- addTokenUsage(tokens, b.tokens);
314
- return {
315
- iterations: a.iterations + b.iterations,
316
- tokens,
317
- ...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
318
- usd: a.usd + b.usd,
319
- ...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
320
- ...usdEstimatedOf(a, b),
321
- ms: a.ms + b.ms,
322
- ...boxMinutesOf(a, b),
323
- ...tokensProvenanceOf(a, b)
324
- };
325
- }
326
- /**
327
- * Fold the token-provenance channel across spends, as a field to spread.
328
- *
329
- * Returns nothing when no contributor stated one, because ABSENT already means "the executor's own
330
- * live stream", which is what every path reported before a harness store was readable. A
331
- * contributor that stated one and a contributor that did not therefore disagree, and the sum is
332
- * `'mixed'` — the honest answer for a tree whose spend came from two kinds of receipt.
333
- */
334
- function tokensProvenanceOf(...spends) {
335
- let folded;
336
- let sawStated = false;
337
- for (const spend of spends) {
338
- const stated = spend.tokensProvenance ?? "stream-receipt";
339
- if (spend.tokensProvenance !== void 0) sawStated = true;
340
- folded = folded === void 0 || folded === stated ? stated : "mixed";
341
- }
342
- return !sawStated || folded === void 0 || folded === "stream-receipt" ? {} : { tokensProvenance: folded };
343
- }
344
- /**
345
- * Fold the platform box-time channel across spends, as a field to spread.
346
- *
347
- * Returns nothing when no input carried the channel, so a fold over router or cli-bridge work —
348
- * which runs no box at all — never gains a `boxMinutesProvenance: 'uncaptured'` that would claim
349
- * a box ran and went unmetered.
350
- *
351
- * Three rules, one per field:
352
- * - `boxMinutes` sums the numbers that are present, and stays ABSENT when none is. A missing
353
- * measurement never enters the sum as a zero.
354
- * - `boxMinutesKnown` is sticky-false, like `tokensKnown`: one contributor that could not state
355
- * its box time makes the whole sum a floor.
356
- * - `boxMinutesProvenance` degrades. `'observed'` survives only when every contributor that
357
- * carried a number was observed; one derived number makes the sum `'estimated'`; no number at
358
- * all is `'uncaptured'`.
359
- */
360
- function boxMinutesOf(...spends) {
361
- const present = spends.filter((spend) => spend.boxMinutesProvenance !== void 0);
362
- if (present.length === 0) return {};
363
- let minutes = 0;
364
- let sawNumber = false;
365
- let known = true;
366
- let observedOnly = true;
367
- for (const spend of present) {
368
- if (typeof spend.boxMinutes === "number" && Number.isFinite(spend.boxMinutes)) {
369
- minutes += spend.boxMinutes;
370
- sawNumber = true;
371
- if (spend.boxMinutesProvenance !== "observed") observedOnly = false;
372
- }
373
- if (spend.boxMinutesKnown !== true) known = false;
374
- }
375
- return {
376
- ...sawNumber ? { boxMinutes: roundBoxMinutes(minutes) } : {},
377
- boxMinutesKnown: known,
378
- boxMinutesProvenance: sawNumber ? observedOnly ? "observed" : "estimated" : "uncaptured"
379
- };
380
- }
381
- /**
382
- * Box minutes at four decimals — a tenth of a second.
383
- *
384
- * Fine enough that a box alive for two seconds reports `0.0333` instead of a zero that would read
385
- * as a measured free box, and coarse enough that summing floats does not print
386
- * `1.5000000000000002`.
387
- */
388
- function roundBoxMinutes(minutes) {
389
- return Number(minutes.toFixed(4));
390
- }
391
- /** Copy a token subtotal without dropping optional provider cache telemetry. */
392
- function cloneTokenUsage(usage) {
393
- const cacheBreakdownUnknown = usage.cacheBreakdownKnown === false || usage.input > 0 && !hasClassifiedCacheBreakdown(usage);
394
- return {
395
- input: usage.input,
396
- output: usage.output,
397
- ...usage.tokensKnown === false ? { tokensKnown: false } : {},
398
- ...usage.freshInput !== void 0 ? { freshInput: usage.freshInput } : {},
399
- ...usage.cacheRead !== void 0 ? { cacheRead: usage.cacheRead } : {},
400
- ...usage.cacheWrite !== void 0 ? { cacheWrite: usage.cacheWrite } : {},
401
- ...cacheBreakdownUnknown ? { cacheBreakdownKnown: false } : {}
402
- };
403
- }
404
- /** True only when every positive prompt token has a consistent cache classification. */
405
- function hasCompleteCacheBreakdown(usage) {
406
- if (usage.cacheBreakdownKnown === false) return false;
407
- return hasClassifiedCacheBreakdown(usage);
408
- }
409
- function hasClassifiedCacheBreakdown(usage) {
410
- if (usage.input === 0) return true;
411
- if (usage.freshInput === void 0 || usage.cacheRead === void 0 || usage.cacheWrite === void 0) return false;
412
- return usage.freshInput + usage.cacheRead + usage.cacheWrite === usage.input;
413
- }
414
- /**
415
- * The token charge for one observation: every token counted ONCE, at the moment it first enters
416
- * the context — `input − cacheRead + output`.
417
- *
418
- * `input` is a rolled-up prompt total that re-counts a cached prefix on every turn that reads it,
419
- * so charging `input` makes a 100K prefix read 40 times cost 4.1M for content authored once. A
420
- * cache read is content that was already charged when it was written, so it is the one class the
421
- * charge subtracts. Under a complete split the result is exactly `freshInput + cacheWrite`. No
422
- * price weight is involved: this counts work, and money is a separate channel.
423
- *
424
- * The subtraction form is what makes the charge ADDITIVE. `input` and `cacheRead` both accumulate
425
- * through `addTokenUsage`, so the charge on an aggregate equals the sum of the charges on the
426
- * records that built it — including an aggregate that mixes classified and unclassified turns,
427
- * where the unclassified remainder is charged in full and only the reported cache reads are
428
- * credited. A form that read `freshInput + cacheWrite` directly loses that: one unclassified turn
429
- * would drop the whole aggregate to `input + output` and over-charge every classified turn with it.
430
- *
431
- * The one arithmetic guarantee: a set of classes that does not FIT inside the prompt total it
432
- * partitions credits nothing, so the charge is never below `output` and a class that overflows its
433
- * own total buys no tokens. It is not a guarantee against a provider that reports a cache read it
434
- * never served — that provider can under-report `input` just as easily, and the token channel is an
435
- * accounting unit, not a trust boundary. Prompt tokens the provider left unclassified are charged
436
- * in full, so a spend with no readable split charges an upper bound on newly-presented work rather
437
- * than a measurement, and every caller that enforces a ceiling must report the difference
438
- * (`BudgetPool` does it through `readout().cacheBreakdownKnown`).
439
- */
440
- function chargedTokens(usage) {
441
- return usage.input - creditedCacheRead(usage) + usage.output;
442
- }
443
- /** `cacheRead` if the reported classes fit inside `input`, else nothing. */
444
- function creditedCacheRead(usage) {
445
- const { cacheRead } = usage;
446
- if (cacheRead === void 0) return 0;
447
- return classifiedTotal(usage, cacheRead) > usage.input ? 0 : cacheRead;
448
- }
449
- function classifiedTotal(usage, cacheRead) {
450
- return (usage.freshInput ?? 0) + cacheRead + (usage.cacheWrite ?? 0);
451
- }
452
- /**
453
- * Translate one provider prompt-cache report into the prompt token classes `addTokenUsage` folds
454
- * and `chargedTokens` credits.
455
- *
456
- * The input is the open `promptCache` record an `llm_call` carries, keyed by the `PromptCacheUsage`
457
- * vocabulary (`readTokens`, `writeTokens`). Providers report that vocabulary unevenly, and the
458
- * three outcomes are different facts that must not collapse into one another:
459
- *
460
- * - **Nothing reported** — no classes and no marker. The caller's own unknown handling decides,
461
- * because a provider that says nothing about caching has not said the split is unknowable.
462
- * - **Read and write both reported, and fitting inside `input`** — a complete partition, so
463
- * `freshInput` is the remainder and the split is known.
464
- * - **One counter reported, or classes that overflow `input`** — the counters that ARE measured
465
- * pass through and `cacheBreakdownKnown` is false. A read with no write counter (OpenAI reports
466
- * no write) still credits the read it measured, while declaring the rest unclassified so the
467
- * charge stays a declared upper bound. An overflowing claim buys nothing: it cannot be a
468
- * partition of a total it exceeds.
469
- *
470
- * A counter the provider did not report stays absent. A zero would claim the provider measured no
471
- * cache, which is a different fact from a provider that did not report.
472
- */
473
- function promptCacheTokenClasses(input, promptCache) {
474
- if (promptCache === void 0) return {};
475
- const read = tokenCount(promptCache.readTokens);
476
- const write = tokenCount(promptCache.writeTokens);
477
- if (read === void 0 && write === void 0) return {};
478
- if (input === void 0 || (read ?? 0) + (write ?? 0) > input) return { cacheBreakdownKnown: false };
479
- if (read !== void 0 && write !== void 0) return {
480
- freshInput: input - read - write,
481
- cacheRead: read,
482
- cacheWrite: write
483
- };
484
- return {
485
- ...read !== void 0 ? { cacheRead: read } : {},
486
- ...write !== void 0 ? { cacheWrite: write } : {},
487
- cacheBreakdownKnown: false
488
- };
489
- }
490
- function tokenCount(value) {
491
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
492
- }
493
- /**
494
- * Add the observed subtotal into `acc`; token and cache incompleteness are sticky.
495
- *
496
- * A delta whose classes do not FIT inside its own `input` contributes its prompt total and NO
497
- * classes. Folding them in would hide the overflow: the accumulator's larger `input` can absorb a
498
- * class total that overflowed the one delta that reported it, and `chargedTokens` would then credit
499
- * at the aggregate a cache read it refuses at the record. That is the one way the charge could come
500
- * out below the sum of the charges on the records that built it.
501
- */
502
- function addTokenUsage(acc, delta) {
503
- acc.input += delta.input ?? 0;
504
- acc.output += delta.output ?? 0;
505
- if (delta.tokensKnown === false) acc.tokensKnown = false;
506
- const input = delta.input ?? 0;
507
- const fits = classifiedTotal(delta, delta.cacheRead ?? 0) <= input;
508
- if (fits) {
509
- if (delta.freshInput !== void 0) acc.freshInput = (acc.freshInput ?? 0) + delta.freshInput;
510
- if (delta.cacheRead !== void 0) acc.cacheRead = (acc.cacheRead ?? 0) + delta.cacheRead;
511
- if (delta.cacheWrite !== void 0) acc.cacheWrite = (acc.cacheWrite ?? 0) + delta.cacheWrite;
512
- }
513
- const classified = fits && delta.freshInput !== void 0 && delta.cacheRead !== void 0 && delta.cacheWrite !== void 0 && delta.freshInput + delta.cacheRead + delta.cacheWrite === input;
514
- if (delta.cacheBreakdownKnown === false || input > 0 && !classified) acc.cacheBreakdownKnown = false;
515
- }
516
- /**
517
- * Map `items` through `fn` with at most `limit` calls in flight at once,
518
- * preserving input order in the result. On the first `fn` rejection no NEW
519
- * items are picked up; already-in-flight calls are awaited, then the first
520
- * error is rethrown. `limit` is clamped to ≥ 1.
521
- *
522
- * Used where a burst of provisioning (e.g. forking N child boxes) must respect
523
- * the loop's concurrency bound instead of firing all N at once.
524
- */
525
- async function mapWithConcurrency(items, limit, fn) {
526
- const bound = Math.max(1, Math.floor(limit));
527
- const results = new Array(items.length);
528
- let next = 0;
529
- let failed = false;
530
- const worker = async () => {
531
- while (!failed) {
532
- const i = next;
533
- next += 1;
534
- if (i >= items.length) return;
535
- try {
536
- results[i] = await fn(items[i], i);
537
- } catch (err) {
538
- failed = true;
539
- throw err;
540
- }
541
- }
542
- };
543
- const workerCount = Math.min(bound, items.length);
544
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
545
- return results;
546
- }
547
- //#endregion
548
- //#region src/runtime/tangle-sandbox-exact-process-provider.ts
549
- const exactProcessProviderMarker = "agent-runtime.exact-process-provider";
550
- const exactProcessEgressMarker = "agent-runtime.exact-process-egress";
551
- /**
552
- * Adapt Tangle Sandbox's managed control runtime to Runtime's exact-process provider.
553
- *
554
- * The adapter deliberately exposes no ordinary agent environment: an exact experiment
555
- * must start a fresh Sandbox with no managed agent and launch its declared argv directly.
556
- */
557
- function createTangleSandboxExactProcessProvider(client, options = {}) {
558
- const name = options.name ?? "tangle-sandbox-exact-process";
559
- if (!name.trim()) throw new Error("Tangle Sandbox exact-process provider name must not be empty");
560
- return {
561
- name,
562
- exactProcess: {
563
- async create(input) {
564
- throwIfAborted(input.signal);
565
- assertNoProviderOptions(input.providerOptions);
566
- const egressPolicy = sandboxEgressPolicy(input.egress);
567
- const metadata = exactMetadata(input.metadata, name, egressPolicy);
568
- const sandbox = await client.create({
569
- environment: requiredText(input.image, "exact-process image"),
570
- agent: false,
571
- bare: false,
572
- ephemeral: true,
573
- egressPolicy,
574
- resources: sandboxResources(input),
575
- maxLifetimeSeconds: sandboxLifetimeSeconds(input.maxLifetimeMs),
576
- metadata,
577
- idempotencyKey: requiredText(input.idempotencyKey, "exact-process idempotency key")
578
- }, {
579
- ...input.provisionTimeoutMs === void 0 ? {} : { timeoutMs: positiveInteger(input.provisionTimeoutMs, "provision timeout") },
580
- ...input.signal === void 0 ? {} : { signal: input.signal }
581
- });
582
- try {
583
- throwIfAborted(input.signal);
584
- assertExactSandboxMetadata(sandbox, metadata);
585
- await assertExactSandboxEgress(sandbox, egressPolicy);
586
- } catch (error) {
587
- await discardInvalidSandbox(sandbox, error);
588
- }
589
- return sandboxAsExactProcessEnvironment(sandbox, name);
590
- },
591
- async get(id) {
592
- const sandbox = await client.get(id);
593
- if (!sandbox || !isExactSandbox(sandbox, name)) return null;
594
- await assertExactSandboxEgress(sandbox, egressFromMetadata(sandbox.metadata));
595
- return sandboxAsExactProcessEnvironment(sandbox, name);
596
- },
597
- async list(query) {
598
- assertNoProviderOptions(query?.providerOptions);
599
- const sandboxes = await client.list();
600
- return Promise.all(sandboxes.filter((sandbox) => isExactSandbox(sandbox, name)).filter((sandbox) => metadataMatches(sandbox.metadata, query?.metadata)).map(async (sandbox) => {
601
- await assertExactSandboxEgress(sandbox, egressFromMetadata(sandbox.metadata));
602
- return sandboxAsExactProcessEnvironment(sandbox, name);
603
- }));
604
- }
605
- },
606
- capabilities: exactProcessOnlyCapabilities,
607
- async create() {
608
- throw new Error(`agent environment provider "${name}" supports exactProcess.create() only`);
609
- }
610
- };
611
- }
612
- function sandboxAsExactProcessEnvironment(sandbox, provider) {
613
- return {
614
- id: requiredText(sandbox.id, "Sandbox id"),
615
- provider,
616
- ...sandbox.metadata ? { metadata: sandbox.metadata } : {},
617
- process: {
618
- async list() {
619
- return (await sandbox.process.list()).map((status) => exactProcessStatus(status));
620
- },
621
- async get(pid) {
622
- const process = await sandbox.process.get(pid);
623
- return process ? sandboxProcessAsExactProcess(process) : null;
624
- },
625
- async spawn(input, options) {
626
- return sandboxProcessAsExactProcess(await afterAbortCheck(options?.signal, () => sandbox.process.spawnExact(input.executable, [...input.args], {
627
- cwd: input.cwd,
628
- env: { ...input.env },
629
- inheritEnv: false,
630
- ...input.stdin === void 0 ? {} : { stdin: input.stdin },
631
- timeoutMs: input.timeoutMs
632
- })));
633
- }
634
- },
635
- async writeFile(path, bytes, options) {
636
- if (sandbox.fs.supportsWriteMode !== true) throw new Error("Tangle Sandbox does not declare exact file-mode support");
637
- await afterAbortCheck(options.signal, () => sandbox.fs.write(path, Buffer.from(bytes).toString("base64"), {
638
- encoding: "base64",
639
- mode: fileMode(options.mode)
640
- }));
641
- },
642
- async readFile(path, options) {
643
- const maxBytes = nonnegativeInteger(options.maxBytes, "exact-process file read maximum");
644
- const initialSize = nonnegativeInteger((await afterAbortCheck(options.signal, () => sandbox.fs.stat(path))).size, `Tangle Sandbox file size for "${path}"`);
645
- if (initialSize > maxBytes) throw new Error(`Tangle Sandbox file "${path}" exceeds the ${maxBytes}-byte exact read maximum`);
646
- const result = await afterAbortCheck(options.signal, () => sandbox.fs.readBatch([path], { encoding: "base64" }));
647
- const failure = result.errors.find((entry) => entry.path === path);
648
- if (failure) throw new Error(`Tangle Sandbox failed to read "${path}": ${failure.error}`);
649
- const files = result.files.filter((entry) => entry.path === path);
650
- if (files.length !== 1) throw new Error(`Tangle Sandbox returned ${files.length} files for exact read "${path}"`);
651
- const file = files[0];
652
- if (file.encoding !== "base64") throw new Error(`Tangle Sandbox returned non-binary content for exact read "${path}"`);
653
- const size = nonnegativeInteger(file.size, `Tangle Sandbox file size for "${path}"`);
654
- if (size !== initialSize) throw new Error(`Tangle Sandbox file "${path}" changed during exact read`);
655
- if (size > maxBytes) throw new Error(`Tangle Sandbox file "${path}" exceeds the ${maxBytes}-byte exact read maximum`);
656
- const bytes = Buffer.from(file.content, "base64");
657
- if (bytes.byteLength !== size || bytes.toString("base64") !== file.content) throw new Error(`Tangle Sandbox returned an invalid binary payload for exact read "${path}"`);
658
- return Uint8Array.from(bytes);
659
- },
660
- async destroy() {
661
- await sandbox.delete();
662
- }
663
- };
664
- }
665
- function sandboxProcessAsExactProcess(process) {
666
- const pid = positiveInteger(process.pid, "Tangle Sandbox process pid");
667
- return {
668
- pid,
669
- async status() {
670
- return exactProcessStatus(await process.status(), pid);
671
- },
672
- async wait() {
673
- const waitExitCode = await process.wait();
674
- if (!Number.isSafeInteger(waitExitCode)) throw new Error("Tangle Sandbox process wait returned an invalid exit code");
675
- const status = exactProcessStatus(await process.status(), pid);
676
- if (status.running || !status.termination) throw new Error("Tangle Sandbox process wait returned before terminal status");
677
- if (status.termination.kind === "exit" && status.termination.exitCode !== waitExitCode) throw new Error("Tangle Sandbox process wait and status disagree on exit code");
678
- return status.termination;
679
- },
680
- async kill() {
681
- await process.kill("SIGKILL", { tree: true });
682
- },
683
- async *stdout() {
684
- yield* process.stdout();
685
- },
686
- async *stderr() {
687
- yield* process.stderr();
688
- }
689
- };
690
- }
691
- function exactProcessStatus(status, expectedPid) {
692
- const pid = positiveInteger(status.pid, "Tangle Sandbox process pid");
693
- if (expectedPid !== void 0 && pid !== expectedPid) throw new Error("Tangle Sandbox process status changed process identity");
694
- if (typeof status.running !== "boolean") throw new Error("Tangle Sandbox process status is missing running state");
695
- if (!Number.isSafeInteger(status.exitCode)) throw new Error("Tangle Sandbox process status is missing an integer exit code");
696
- const exitSignal = optionalText(status.exitSignal, "Tangle Sandbox process exit signal");
697
- if (status.running) {
698
- if (status.exitCode !== -1) throw new Error("Tangle Sandbox running process must report exit code -1");
699
- if (exitSignal) throw new Error("Tangle Sandbox running process must not report an exit signal");
700
- return {
701
- pid,
702
- running: true,
703
- exitCode: -1
704
- };
705
- }
706
- const termination = exitSignal ? {
707
- kind: "signal",
708
- signal: exitSignal
709
- } : status.exitCode >= 0 ? {
710
- kind: "exit",
711
- exitCode: status.exitCode
712
- } : void 0;
713
- if (!termination) throw new Error("Tangle Sandbox stopped process has no terminal reason");
714
- return {
715
- pid,
716
- running: false,
717
- exitCode: status.exitCode,
718
- ...exitSignal ? { exitSignal } : {},
719
- termination
720
- };
721
- }
722
- function exactMetadata(metadata, provider, egressPolicy) {
723
- for (const marker of [exactProcessProviderMarker, exactProcessEgressMarker]) if (Object.hasOwn(metadata, marker)) throw new Error(`exact-process metadata must not set "${marker}"`);
724
- return {
725
- ...metadata,
726
- [exactProcessProviderMarker]: provider,
727
- [exactProcessEgressMarker]: serializeEgressPolicy(egressPolicy)
728
- };
729
- }
730
- function isExactSandbox(sandbox, provider) {
731
- return sandbox.metadata?.[exactProcessProviderMarker] === provider;
732
- }
733
- function metadataMatches(metadata, expected) {
734
- return expected === void 0 || Object.entries(expected).every(([key, value]) => isDeepStrictEqual(metadata?.[key], value));
735
- }
736
- function assertExactSandboxMetadata(sandbox, expected) {
737
- if (!metadataMatches(sandbox.metadata, expected)) throw new Error("Tangle Sandbox did not persist exact-process metadata");
738
- }
739
- function egressFromMetadata(metadata) {
740
- const encoded = metadata?.[exactProcessEgressMarker];
741
- if (typeof encoded !== "string") throw new Error("Tangle Sandbox exact-process environment is missing egress metadata");
742
- try {
743
- const parsed = JSON.parse(encoded);
744
- if (!parsed || typeof parsed !== "object") throw new Error("expected egress object");
745
- const policy = parsed;
746
- if (policy.mode === "blocked" && policy.allowDomains === void 0) return { mode: "blocked" };
747
- if (policy.mode === "strict" && policy.includeImplicitDomains === false && Array.isArray(policy.allowDomains) && policy.allowDomains.every((domain) => typeof domain === "string")) {
748
- const allowDomains = sortedDomains(policy.allowDomains);
749
- if (new Set(allowDomains).size === allowDomains.length) return {
750
- mode: "strict",
751
- allowDomains,
752
- includeImplicitDomains: false
753
- };
754
- }
755
- } catch {
756
- throw new Error("Tangle Sandbox exact-process environment has invalid egress metadata");
757
- }
758
- throw new Error("Tangle Sandbox exact-process environment has invalid egress metadata");
759
- }
760
- async function assertExactSandboxEgress(sandbox, expected) {
761
- const actual = (await sandbox.egress.get()).policy;
762
- if (actual.mode !== expected.mode) throw new Error(`Tangle Sandbox egress mismatch: expected ${expected.mode}, received ${actual.mode}`);
763
- if (expected.mode === "blocked") {
764
- const allowDomains = actual.allowDomains;
765
- if (actual.mode === "blocked" && (allowDomains === void 0 || Array.isArray(allowDomains) && allowDomains.length === 0)) return;
766
- throw new Error("Tangle Sandbox blocked egress retained an allowlist");
767
- }
768
- if (actual.mode !== "strict") throw new Error("Tangle Sandbox did not return strict egress policy");
769
- const expectedDomains = sortedDomains(expected.allowDomains);
770
- if (!isDeepStrictEqual(sortedDomains(actual.allowDomains ?? []), expectedDomains)) throw new Error("Tangle Sandbox strict egress differs from the exact allowlist");
771
- if (actual.includeImplicitDomains !== false) throw new Error("Tangle Sandbox strict egress retained implicit domains");
772
- }
773
- function sandboxEgressPolicy(input) {
774
- if (input.mode === "blocked") return { mode: "blocked" };
775
- const allowDomains = input.allowDomains.map((domain) => requiredText(domain, "strict egress domain"));
776
- if (new Set(allowDomains).size !== allowDomains.length) throw new Error("strict egress domains must not contain duplicates");
777
- return {
778
- mode: "strict",
779
- allowDomains,
780
- includeImplicitDomains: false
781
- };
782
- }
783
- function serializeEgressPolicy(policy) {
784
- return JSON.stringify(policy.mode === "blocked" ? { mode: "blocked" } : {
785
- mode: "strict",
786
- allowDomains: sortedDomains(policy.allowDomains),
787
- includeImplicitDomains: false
788
- });
789
- }
790
- function sandboxResources(input) {
791
- const cpuCores = positiveNumber(input.resources.cpu, "exact-process cpu");
792
- const memoryMB = positiveInteger(input.resources.memoryMb, "exact-process memory");
793
- const diskMb = positiveInteger(input.resources.diskMb, "exact-process disk");
794
- if (diskMb % 1024 !== 0) throw new Error("Tangle Sandbox exact-process disk must be a whole GiB");
795
- return {
796
- cpuCores,
797
- memoryMB,
798
- diskGB: diskMb / 1024
799
- };
800
- }
801
- function sandboxLifetimeSeconds(maxLifetimeMs) {
802
- const milliseconds = positiveInteger(maxLifetimeMs, "exact-process maximum lifetime");
803
- if (milliseconds % 1e3 !== 0) throw new Error("Tangle Sandbox exact-process maximum lifetime must be a whole number of seconds");
804
- return milliseconds / 1e3;
805
- }
806
- function fileMode(value) {
807
- const mode = nonnegativeInteger(value, "exact-process file mode");
808
- if (mode > 4095) throw new Error("exact-process file mode exceeds POSIX mode bits");
809
- return mode;
810
- }
811
- function assertNoProviderOptions(providerOptions) {
812
- if (providerOptions && Object.keys(providerOptions).length > 0) throw new Error("Tangle Sandbox exact-process provider does not accept providerOptions");
813
- }
814
- function exactProcessOnlyCapabilities() {
815
- return {
816
- profile: {
817
- namedProfiles: false,
818
- systemPrompt: {
819
- replace: false,
820
- append: false
821
- },
822
- instructions: false,
823
- tools: false,
824
- permissions: false,
825
- mcp: false,
826
- subagents: false,
827
- resources: {
828
- files: false,
829
- instructions: false,
830
- tools: false,
831
- skills: false,
832
- agents: false,
833
- commands: false
834
- },
835
- hooks: false,
836
- modes: false,
837
- runtimeUpdate: false,
838
- validation: false
839
- },
840
- streaming: {
841
- live: false,
842
- replay: false,
843
- detach: false,
844
- turnIdempotency: false
845
- },
846
- sessions: {
847
- continue: false,
848
- list: false,
849
- messages: false
850
- },
851
- workspace: {
852
- read: false,
853
- write: false,
854
- exec: false,
855
- git: false,
856
- upload: false,
857
- download: false
858
- },
859
- branching: {
860
- checkpoint: false,
861
- fork: false
862
- },
863
- placement: false,
864
- usage: false,
865
- confidential: false,
866
- exactProcess: { egress: ["blocked", "strict"] }
867
- };
868
- }
869
- async function discardInvalidSandbox(sandbox, cause) {
870
- try {
871
- await sandbox.delete();
872
- } catch (destroyError) {
873
- throw new AggregateError([cause, destroyError], "Tangle Sandbox exact-process creation failed and cleanup failed");
874
- }
875
- throw cause;
876
- }
877
- function sortedDomains(domains) {
878
- return [...domains].sort((left, right) => left.localeCompare(right));
879
- }
880
- function requiredText(value, label) {
881
- if (typeof value !== "string" || !value.trim()) throw new Error(`${label} must be a non-empty string`);
882
- return value;
883
- }
884
- function optionalText(value, label) {
885
- if (value === void 0) return void 0;
886
- return requiredText(value, label);
887
- }
888
- function positiveNumber(value, label) {
889
- if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be positive`);
890
- return value;
891
- }
892
- function positiveInteger(value, label) {
893
- if (!Number.isSafeInteger(value) || value < 1) throw new Error(`${label} must be a positive integer`);
894
- return value;
895
- }
896
- function nonnegativeInteger(value, label) {
897
- if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label} must be a non-negative integer`);
898
- return value;
899
- }
900
- function throwIfAborted(signal) {
901
- signal?.throwIfAborted();
902
- }
903
- async function afterAbortCheck(signal, operation) {
904
- throwIfAborted(signal);
905
- const result = await operation();
906
- throwIfAborted(signal);
907
- return result;
908
- }
909
- //#endregion
910
- //#region src/runtime/environment-provider.ts
911
- var ValidationError = class extends Error {
912
- constructor(message, options) {
913
- super(message, options);
914
- this.name = "ValidationError";
915
- }
916
- };
917
- /** Create a registry that resolves provider names to concrete provider instances.
918
- * @experimental */
919
- function createAgentEnvironmentProviderRegistry(providers = []) {
920
- const entries = /* @__PURE__ */ new Map();
921
- const registry = {
922
- register(provider, options = {}) {
923
- if (!provider.name) throw new ValidationError("agent environment provider registry: provider.name required");
924
- if (!options.replace && entries.has(provider.name)) throw new ValidationError(`agent environment provider registry: provider "${provider.name}" already registered`);
925
- entries.set(provider.name, provider);
926
- },
927
- has(name) {
928
- return entries.has(name);
929
- },
930
- get(name) {
931
- return entries.get(name);
932
- },
933
- require(name) {
934
- const provider = entries.get(name);
935
- if (!provider) {
936
- const available = Array.from(entries.keys()).sort();
937
- throw new ValidationError(`agent environment provider registry: provider "${name}" is not registered${available.length > 0 ? `; available: ${available.join(", ")}` : ""}`);
938
- }
939
- return provider;
940
- },
941
- names() {
942
- return Array.from(entries.keys()).sort();
943
- },
944
- providers() {
945
- return registry.names().map((name) => registry.require(name));
946
- },
947
- async capabilities(name) {
948
- return registry.require(name).capabilities();
949
- }
950
- };
951
- for (const provider of providers) registry.register(provider);
952
- return registry;
953
- }
954
- /** Resolve a provider instance or registry name, failing loudly when a name is unknown.
955
- * @experimental */
956
- function resolveAgentEnvironmentProvider(provider, registry) {
957
- if (typeof provider !== "string") return provider;
958
- if (!registry) throw new ValidationError(`agent environment provider "${provider}" requires an AgentEnvironmentProviderRegistry`);
959
- return registry.require(provider);
960
- }
961
- /** Adapt a neutral environment provider to the `SandboxClient` interface used by existing loop paths.
962
- * @experimental */
963
- function providerAsSandboxClient(provider, options = {}) {
964
- return { async create(createOptions) {
965
- const defaults = options.defaults ?? {};
966
- const sandboxInput = createInputFromSandboxOptions(createOptions);
967
- const customInput = options.mapCreateOptions?.(createOptions) ?? {};
968
- const mapped = {
969
- ...defaults,
970
- ...sandboxInput,
971
- ...customInput,
972
- providerOptions: {
973
- ...defaults.providerOptions ?? {},
974
- ...sandboxInput.providerOptions ?? {},
975
- ...customInput.providerOptions ?? {}
976
- }
977
- };
978
- if (mapped.backend === void 0) delete mapped.backend;
979
- if (mapped.profile === void 0) throw new ValidationError(`providerAsSandboxClient(${provider.name}): profile required in defaults or CreateSandboxOptions.backend.profile`);
980
- if (options.requireSession) {
981
- const capabilities = await provider.capabilities();
982
- if (!capabilities.streaming.live || !capabilities.sessions.continue) throw new ValidationError(`providerAsSandboxClient(${provider.name}): live session continuation is required`);
983
- }
984
- const environment = await provider.create(mapped);
985
- if (options.requireSession && !environment.session) {
986
- await environment.destroy?.();
987
- throw new ValidationError(`providerAsSandboxClient(${provider.name}): session() is required`);
988
- }
989
- return environmentAsSandboxInstance(environment, { requireTerminalEvent: options.requireTerminalEvent ?? true });
990
- } };
991
- }
992
- /**
993
- * Adapt a `SandboxClient` into the shared `AgentEnvironmentProvider` contract.
994
- * The provider declares the public SDK contract before it creates an environment.
995
- * Each environment exposes interactive methods only when its deployment declares every required capability.
996
- * @experimental */
997
- function sandboxClientAsProvider(client, options = {}) {
998
- const providerName = options.name ?? "tangle-sandbox";
999
- const providerCapabilities = async () => {
1000
- const resolved = await (options.capabilities ? typeof options.capabilities === "function" ? options.capabilities() : options.capabilities : defaultTangleSandboxCapabilities({
1001
- namedProfiles: options.resolveProfile !== void 0,
1002
- rediscover: hasGet(client)
1003
- }));
1004
- if (hasGet(client)) return resolved;
1005
- const { interactiveAgent: _interactiveAgent, ...withoutInteractive } = resolved;
1006
- return withoutInteractive;
1007
- };
1008
- return {
1009
- name: providerName,
1010
- capabilities: providerCapabilities,
1011
- ...options.validateProfile ? { validateProfile: options.validateProfile } : {},
1012
- async create(input) {
1013
- const createOptions = options.mapCreateInput?.(input) ?? await sandboxOptionsFromCreateInput(input, options.defaultBackend ?? "opencode", options.resolveProfile, providerName);
1014
- const capabilities = await providerCapabilities();
1015
- return sandboxInstanceAsEnvironment(await client.create(createOptions, input.signal === void 0 ? void 0 : { signal: input.signal }), providerName, client, capabilities);
1016
- },
1017
- ...hasGet(client) ? { async get(id) {
1018
- const capabilities = await providerCapabilities();
1019
- const box = await client.get(id);
1020
- return box ? sandboxInstanceAsEnvironment(box, providerName, client, capabilities) : null;
1021
- } } : {},
1022
- ...hasList(client) ? { async list(query) {
1023
- return (await client.list(query?.providerOptions)).map((box) => ({
1024
- id: String(box.id),
1025
- provider: providerName,
1026
- name: typeof box.name === "string" ? box.name : void 0,
1027
- status: statusFromUnknown(readBoxStatus(box)),
1028
- metadata: readBoxMetadata(box)
1029
- }));
1030
- } } : {}
1031
- };
1032
- }
1033
- /**
1034
- * Lower the seam's prompt options onto turn fields, through the one mapper this module already
1035
- * uses to raise a sandbox prompt into a provider turn. Reusing it is what keeps the two directions
1036
- * from drifting: whatever `turnInputFromPrompt` decides a prompt option configures, the seam
1037
- * declares the same way.
1038
- */
1039
- function providerTurnDefaults(options, context) {
1040
- if (options === void 0) return void 0;
1041
- if (options.model !== void 0) throw new ValidationError(`${context}: promptOptions.model is refused — this executor's materialization names AgentProfile's model, so a turn-level override would record a model the provider did not run`);
1042
- const { prompt: _prompt, parts: _parts, model: _model, sessionId: _sessionId, signal: _signal, ...turn } = turnInputFromPrompt("", options);
1043
- return turn;
1044
- }
1045
- /**
1046
- * Merge the declared turn defaults under one mapped turn.
1047
- *
1048
- * `providerOptions` is merged one level rather than replaced: the defaults carry the caller's
1049
- * session credential and a `taskToTurn` that sets an unrelated provider option would otherwise
1050
- * remove it, and a run that silently loses its credential fails inside the environment as an
1051
- * authorization error that names nothing.
1052
- */
1053
- function providerTurnWithDefaults(defaults, turn, signal) {
1054
- if (defaults === void 0) return {
1055
- ...turn,
1056
- signal
1057
- };
1058
- const providerOptions = defaults.providerOptions === void 0 && turn.providerOptions === void 0 ? void 0 : {
1059
- ...defaults.providerOptions ?? {},
1060
- ...turn.providerOptions ?? {}
1061
- };
1062
- return {
1063
- ...defaults,
1064
- ...turn,
1065
- ...providerOptions === void 0 ? {} : { providerOptions },
1066
- signal
1067
- };
1068
- }
1069
- /** Adapt an environment provider into an `ExecutorFactory` for `createExecutor`.
1070
- *
1071
- * `createExecutor({ backend: 'provider', provider })` is the composition most callers want; it
1072
- * builds this factory and injects the seam. See `examples/provider-executor/`.
1073
- *
1074
- * Still `@experimental`: the entry point that consumes it, `createExecutor`, carries no stability
1075
- * tag and is therefore experimental by default, so a stable promise here would be reachable only
1076
- * through an experimental symbol.
1077
- *
1078
- * @experimental */
1079
- function providerAsExecutor(provider, options = {}) {
1080
- return (spec, ctx) => createProviderExecutor(provider, spec.profile, ctx, options);
1081
- }
1082
- function createProviderExecutor(provider, profile, ctx, options) {
1083
- const controller = linkAbort(ctx.signal);
1084
- let environment;
1085
- let artifact;
1086
- let destroyed = false;
1087
- const runtime = options.runtime ?? provider.name;
1088
- const createProfile = options.profileForCreate?.(profile) ?? profile;
1089
- const executionId = ctx.node?.nodeId ?? `provider-run-${randomUUID()}`;
1090
- const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
1091
- const providerModel = concreteProfileModel(createProfile);
1092
- const tokenLimits = enforceTokenLimits(profileModelExecutionSettings(createProfile, `providerAsExecutor(${provider.name})`).tokenLimits, "provider", `providerAsExecutor(${provider.name})`);
1093
- const plannedDeclaration = {
1094
- effectiveProfile: createProfile,
1095
- backend: provider.name,
1096
- model: providerModel ? {
1097
- status: "known",
1098
- id: providerModel
1099
- } : {
1100
- status: "unknown",
1101
- reason: "provider environment selected its default model"
1102
- },
1103
- execution: {
1104
- kind: "environment",
1105
- id: executionId
1106
- },
1107
- materializer: "environment-provider-create",
1108
- plan: {
1109
- kind: "agent-environment",
1110
- provider: provider.name,
1111
- destroyOnSettle: options.destroyOnSettle ?? true,
1112
- requireTerminalEvent: options.requireTerminalEvent ?? true,
1113
- tokenLimits,
1114
- environmentId: null
1115
- }
1116
- };
1117
- const plannedBinding = {
1118
- attemptId,
1119
- binding: {
1120
- provider: provider.name,
1121
- executionId,
1122
- model: providerModel ?? null
1123
- },
1124
- descriptor: {
1125
- kind: "agent-environment",
1126
- transport: "provider",
1127
- backend: provider.name
1128
- }
1129
- };
1130
- let executor;
1131
- executor = {
1132
- runtime,
1133
- execute(task, signal) {
1134
- return streamProviderExecutor({
1135
- provider,
1136
- profile,
1137
- createProfile,
1138
- task,
1139
- signal,
1140
- controller,
1141
- options,
1142
- onEnvironment: (env) => {
1143
- environment = env;
1144
- finalizeRuntimeOwnedPendingExecutor(executor, {
1145
- ...plannedDeclaration,
1146
- execution: {
1147
- kind: "environment",
1148
- id: env.id
1149
- },
1150
- plan: {
1151
- ...plannedDeclaration.plan,
1152
- environmentId: env.id
1153
- }
1154
- }, plannedBinding);
1155
- },
1156
- onArtifact: (next) => {
1157
- artifact = next;
1158
- },
1159
- onDestroyed: () => {
1160
- destroyed = true;
1161
- }
1162
- });
1163
- },
1164
- async cancel(request) {
1165
- controller.abort();
1166
- return {
1167
- status: "unknown",
1168
- effect: "cancel_requested",
1169
- observedAt: (/* @__PURE__ */ new Date()).toISOString(),
1170
- detail: `providerAsExecutor(${provider.name}): the streamed turn carries no durable run reference, so the provider acknowledged nothing`,
1171
- evidence: {
1172
- operationId: request.operationId,
1173
- ...environment ? { environmentId: environment.id } : {}
1174
- }
1175
- };
1176
- },
1177
- async teardown(_grace) {
1178
- controller.abort();
1179
- if (destroyed || environment === void 0) return { destroyed: true };
1180
- try {
1181
- await environment.destroy?.();
1182
- destroyed = true;
1183
- return { destroyed: true };
1184
- } catch (error) {
1185
- return {
1186
- destroyed: false,
1187
- detail: `providerAsExecutor(${provider.name}): environment.destroy() failed — ${error instanceof Error ? error.message : String(error)}`
1188
- };
1189
- }
1190
- },
1191
- resultArtifact() {
1192
- if (!artifact) throw new ValidationError(`providerAsExecutor(${provider.name}): resultArtifact() read before stream drained`);
1193
- return artifact;
1194
- }
1195
- };
1196
- return attestRuntimeOwnedPendingExecutor(executor, runtime, plannedDeclaration, plannedBinding);
1197
- }
1198
- async function* streamProviderExecutor(args) {
1199
- const started = Date.now();
1200
- const linked = linkAbort(args.signal, args.controller.signal).signal;
1201
- const environment = await args.provider.create({
1202
- ...args.options.defaults ?? {},
1203
- profile: args.createProfile,
1204
- signal: linked
1205
- });
1206
- args.onEnvironment(environment);
1207
- const turn = providerTurnWithDefaults(providerTurnDefaults(args.options.promptOptions, `providerAsExecutor(${args.provider.name})`), args.options.taskToTurn?.(args.task, args.profile) ?? taskToTurnInput(args.task, linked), linked);
1208
- const events = [];
1209
- const tokens = zeroTokenUsage();
1210
- let sawCompleteTokenReceipt = false;
1211
- let sawIncompleteTokenReceipt = false;
1212
- let usd = 0;
1213
- let text = "";
1214
- let terminal = false;
1215
- let explicitFailure = false;
1216
- let settled;
1217
- let failure;
1218
- let failed = false;
1219
- try {
1220
- const toolParts = createSandboxToolPartState();
1221
- const outcomeTracker = createAgentRunOutcomeTracker();
1222
- for await (const event of environment.stream(turn)) {
1223
- events.push(event);
1224
- text += textFromEnvironmentEvent(event);
1225
- const sandboxEvent = sandboxEventFromEnvironmentEvent(event);
1226
- const failureEvent = providerFailureEvent(event, sandboxEvent);
1227
- if (failureEvent) {
1228
- explicitFailure = true;
1229
- outcomeTracker.observe(failureEvent);
1230
- }
1231
- for (const progress of sandboxProgressEvents(sandboxEvent, toolParts)) yield {
1232
- kind: "progress",
1233
- progress
1234
- };
1235
- const usage = usageFromEnvironmentEvent(event);
1236
- if (usage.hasTokenReceipt) if (usage.hasInput && usage.hasOutput) sawCompleteTokenReceipt = true;
1237
- else sawIncompleteTokenReceipt = true;
1238
- if (usage.input || usage.output) {
1239
- tokens.input += usage.input;
1240
- tokens.output += usage.output;
1241
- yield {
1242
- kind: "tokens",
1243
- input: usage.input,
1244
- output: usage.output
1245
- };
1246
- }
1247
- if (usage.usd) {
1248
- usd += usage.usd;
1249
- yield {
1250
- kind: "cost",
1251
- usdKnown: false,
1252
- usd: usage.usd,
1253
- provenance: "uncaptured"
1254
- };
1255
- }
1256
- if (isTerminalEnvironmentEvent(event)) terminal = true;
1257
- }
1258
- if ((args.options.requireTerminalEvent ?? true) && !terminal) throw new ValidationError(`providerAsExecutor(${args.provider.name}): stream ended without a terminal result/done/status event`);
1259
- yield { kind: "iteration" };
1260
- const result = {
1261
- ...resultFromEvents(events, text),
1262
- ...explicitFailure ? { outcome: outcomeTracker.finish() } : {}
1263
- };
1264
- const spent = {
1265
- iterations: 1,
1266
- tokens,
1267
- ...sawCompleteTokenReceipt && !sawIncompleteTokenReceipt ? {} : { tokensKnown: false },
1268
- usd,
1269
- usdKnown: false,
1270
- ms: Date.now() - started
1271
- };
1272
- const verdict = await args.options.validator?.validate(result, {
1273
- iteration: 0,
1274
- box: environmentAsSandboxInstance(environment, { requireTerminalEvent: args.options.requireTerminalEvent ?? true }),
1275
- signal: linked
1276
- });
1277
- settled = {
1278
- outRef: contentRef(`provider:${args.provider.name}`, result),
1279
- out: result,
1280
- ...verdict ? { verdict } : {},
1281
- spent
1282
- };
1283
- args.onArtifact(settled);
1284
- } catch (error) {
1285
- failure = error;
1286
- failed = true;
1287
- } finally {
1288
- if (args.options.destroyOnSettle ?? true) try {
1289
- await environment.destroy?.();
1290
- args.onDestroyed();
1291
- } catch (error) {
1292
- if (settled !== void 0) args.onArtifact({
1293
- ...settled,
1294
- teardown: {
1295
- failed: true,
1296
- error: error instanceof Error ? error.message : String(error),
1297
- at: (/* @__PURE__ */ new Date()).toISOString()
1298
- }
1299
- });
1300
- else if (!failed) {
1301
- failure = error;
1302
- failed = true;
1303
- } else failure = failure instanceof Error ? Object.assign(failure, { cause: failure.cause ?? error }) : failure;
1304
- }
1305
- }
1306
- if (failed) throw failure;
1307
- }
1308
- function createInputFromSandboxOptions(options) {
1309
- const profile = options?.backend?.profile;
1310
- const backend = options?.backend?.type;
1311
- const cwd = options?.cwd === void 0 ? void 0 : canonicalWorkspaceCwd({
1312
- base: "repository",
1313
- path: options.cwd
1314
- });
1315
- const workspace = {
1316
- ...options?.environment ? { environment: options.environment } : {},
1317
- ...options?.git?.url ? { repoUrl: options.git.url } : {},
1318
- ...options?.git?.ref ? { gitRef: options.git.ref } : {},
1319
- ...cwd === void 0 ? {} : { cwd }
1320
- };
1321
- return {
1322
- ...profile !== void 0 ? { profile } : {},
1323
- ...backend ? { backend } : {},
1324
- ...Object.keys(workspace).length > 0 ? { workspace } : {},
1325
- ...options?.resources ? { resources: options.resources } : {},
1326
- ...options?.env ? { env: options.env } : {},
1327
- ...Array.isArray(options?.secrets) ? { secrets: options.secrets } : {},
1328
- ...options?.metadata ? { metadata: options.metadata } : {},
1329
- ...options?.name ? { name: options.name } : {},
1330
- ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {},
1331
- providerOptions: { sandboxCreateOptions: options ?? {} }
1332
- };
1333
- }
1334
- async function sandboxOptionsFromCreateInput(input, defaultBackend, resolveProfile, providerName = "tangle-sandbox") {
1335
- const backendType = input.backend ?? defaultBackend;
1336
- const workspace = WorkspaceRequestSchema.parse(input.workspace ?? {});
1337
- const environment = sandboxEnvironmentFromWorkspace(workspace);
1338
- const cwd = workspaceCwdPathForBase(workspace.cwd, "repository", providerName);
1339
- const providerOptions = input.providerOptions?.sandboxCreateOptions;
1340
- const base = providerOptions && typeof providerOptions === "object" ? { ...providerOptions } : {};
1341
- assertSandboxSecretNames(input.secrets);
1342
- assertSandboxSecretNames(base.secrets);
1343
- const profile = await sandboxProfileFromReference(input.profile, resolveProfile);
1344
- const { profile: _baseProfile, ...baseBackend } = base.backend ?? {};
1345
- return {
1346
- ...base,
1347
- ...environment ? { environment } : {},
1348
- ...workspace.repoUrl ? { git: {
1349
- url: workspace.repoUrl,
1350
- ref: workspace.gitRef
1351
- } } : {},
1352
- ...cwd === void 0 ? {} : { cwd },
1353
- ...input.resources ? { resources: input.resources } : {},
1354
- ...input.env ? { env: input.env } : {},
1355
- ...Array.isArray(input.secrets) ? { secrets: input.secrets } : {},
1356
- ...input.metadata ? { metadata: input.metadata } : {},
1357
- ...input.name ? { name: input.name } : {},
1358
- ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
1359
- backend: {
1360
- ...baseBackend,
1361
- type: backendType,
1362
- profile
1363
- }
1364
- };
1365
- }
1366
- function assertSandboxSecretNames(secrets) {
1367
- if (secrets !== void 0 && secrets !== "all" && (!Array.isArray(secrets) || secrets.some((secret) => typeof secret !== "string" || secret.trim().length === 0))) throw new ValidationError("Tangle Sandbox secret names must be non-empty strings; secret values are unsupported");
1368
- }
1369
- function sandboxEnvironmentFromWorkspace(workspace) {
1370
- if (workspace.environment !== void 0 && workspace.image !== void 0 && workspace.environment !== workspace.image) throw new ValidationError("Tangle Sandbox accepts one environment value; workspace.environment and workspace.image must match");
1371
- return workspace.environment ?? workspace.image;
1372
- }
1373
- async function sandboxProfileFromReference(profile, resolveProfile) {
1374
- if (typeof profile !== "string") return profile;
1375
- if (!resolveProfile) throw new ValidationError(`Tangle Sandbox requires an inline AgentProfile; named profile "${profile}" needs SandboxClientProviderOptions.resolveProfile`);
1376
- return resolveProfile(profile);
1377
- }
1378
- function environmentAsSandboxInstance(environment, options) {
1379
- return {
1380
- id: environment.id,
1381
- name: environment.name,
1382
- status: "running",
1383
- async refresh() {
1384
- await environment.refresh?.();
1385
- },
1386
- async *streamPrompt(message, promptOptions) {
1387
- let terminal = false;
1388
- const input = turnInputFromPrompt(message, promptOptions);
1389
- let cancellation;
1390
- let cancellationStarted = false;
1391
- let cancellationFailed = false;
1392
- let cancellationError;
1393
- const cancel = () => {
1394
- if (cancellationStarted || !input.sessionId || !environment.session) return;
1395
- cancellationStarted = true;
1396
- try {
1397
- const session = environment.session(input.sessionId, {
1398
- ...input.controlRef === void 0 ? {} : { controlRef: input.controlRef },
1399
- ...input.signal === void 0 ? {} : { signal: input.signal }
1400
- });
1401
- assertScopedSessionForInput(session, input);
1402
- cancellation = session.cancel().catch((error) => {
1403
- cancellationFailed = true;
1404
- cancellationError = error;
1405
- });
1406
- } catch (error) {
1407
- cancellationFailed = true;
1408
- cancellationError = error;
1409
- }
1410
- };
1411
- input.signal?.addEventListener("abort", cancel, { once: true });
1412
- if (input.signal?.aborted) cancel();
1413
- let streamFailed = false;
1414
- let streamError;
1415
- try {
1416
- for await (const event of environment.stream(input)) {
1417
- if (isTerminalEnvironmentEvent(event)) terminal = true;
1418
- const usageEvent = usageSandboxEvent(event);
1419
- if (usageEvent) yield usageEvent;
1420
- yield sandboxEventFromEnvironmentEvent(event);
1421
- }
1422
- } catch (error) {
1423
- streamFailed = true;
1424
- streamError = error;
1425
- } finally {
1426
- input.signal?.removeEventListener("abort", cancel);
1427
- if (input.signal?.aborted) {
1428
- cancel();
1429
- await cancellation;
1430
- }
1431
- }
1432
- if (input.signal?.aborted) {
1433
- const abortError = new DOMException("Provider session stream aborted", "AbortError");
1434
- const causes = [...streamFailed ? [streamError] : [], ...cancellationFailed ? [cancellationError] : []];
1435
- if (causes.length > 0) Object.defineProperty(abortError, "cause", { value: causes.length === 1 ? causes[0] : new AggregateError(causes, "Provider stream and cancellation failed") });
1436
- throw abortError;
1437
- }
1438
- if (streamFailed) throw streamError;
1439
- if (cancellationFailed) throw cancellationError;
1440
- if (options.requireTerminalEvent && !terminal) throw new ValidationError(`providerAsSandboxClient(${environment.provider}): stream ended without a terminal result/done/status event`);
1441
- },
1442
- async prompt(message, promptOptions) {
1443
- const events = [];
1444
- let text = "";
1445
- let usage;
1446
- let terminal = false;
1447
- for await (const event of environment.stream(turnInputFromPrompt(message, promptOptions))) {
1448
- events.push(event);
1449
- if (isTerminalEnvironmentEvent(event)) terminal = true;
1450
- text += textFromEnvironmentEvent(event);
1451
- usage = mergeTokenUsage(usage, event.usage);
1452
- }
1453
- if (options.requireTerminalEvent && !terminal) throw new ValidationError(`providerAsSandboxClient(${environment.provider}): prompt ended without a terminal result/done/status event`);
1454
- return {
1455
- response: resultFromEvents(events, text).content,
1456
- success: true,
1457
- status: "success",
1458
- durationMs: 0,
1459
- ...usage ? { usage } : {}
1460
- };
1461
- },
1462
- ...environment.dispatch && environment.session ? { async dispatchPrompt(message, promptOptions) {
1463
- const session = await environment.dispatch?.(turnInputFromPrompt(message, promptOptions));
1464
- if (!session) throw new ValidationError("providerAsSandboxClient: dispatch returned no session");
1465
- return sandboxDispatchResultFromSessionRef(session);
1466
- } } : {},
1467
- ...environment.session ? { session(id, sessionOptions) {
1468
- return sandboxSessionFromAgentSession(environment.session?.(id, sessionOptions), sessionOptions?.controlRef);
1469
- } } : {},
1470
- ...environment.read ? { read: environment.read.bind(environment) } : {},
1471
- ...environment.write ? { write: environment.write.bind(environment) } : {},
1472
- ...environment.exec ? { exec: environment.exec.bind(environment) } : {},
1473
- ...environment.checkpoint ? { async checkpoint(checkpointOptions) {
1474
- const checkpoint = await environment.checkpoint?.(checkpointOptions);
1475
- return {
1476
- checkpointId: checkpoint?.id,
1477
- id: checkpoint?.id
1478
- };
1479
- } } : {},
1480
- ...environment.fork ? { async fork(checkpointId, forkOptions) {
1481
- const forked = await environment.fork?.({ id: checkpointId }, forkOptions);
1482
- if (!forked) throw new ValidationError("providerAsSandboxClient: fork returned no environment");
1483
- return environmentAsSandboxInstance(forked, options);
1484
- } } : {},
1485
- async delete() {
1486
- await environment.destroy?.();
1487
- }
1488
- };
1489
- }
1490
- async function sandboxInstanceAsEnvironment(box, providerName, client, providerCapabilities) {
1491
- const capabilities = await sandboxEnvironmentCapabilities(box, providerCapabilities);
1492
- const interactiveAgent = capabilities.interactiveAgent;
1493
- return {
1494
- id: String(box.id),
1495
- provider: providerName,
1496
- capabilities,
1497
- ...typeof box.name === "string" ? { name: box.name } : {},
1498
- ...readBoxMetadata(box) ? { metadata: readBoxMetadata(box) } : {},
1499
- async status() {
1500
- await maybeRefresh(box);
1501
- return statusFromUnknown(readBoxStatus(box));
1502
- },
1503
- async *stream(input) {
1504
- for await (const event of box.streamPrompt(promptFromAgentTurnInput(input), promptOptionsFromAgentTurnInput(input))) yield environmentEventFromSandboxEvent(event);
1505
- },
1506
- ...hasDispatchPrompt(box) ? { async dispatch(input) {
1507
- return sessionRefFromSandboxDispatch(await box.dispatchPrompt(promptFromAgentTurnInput(input), promptOptionsFromAgentTurnInput(input)), providerName);
1508
- } } : {},
1509
- ...hasSession(box) ? {
1510
- session(id, sessionOptions) {
1511
- return sandboxSessionAsAgentSession(box.session(id), sessionOptions?.controlRef);
1512
- },
1513
- async respondToInteraction(command, options) {
1514
- return (await box.session(command.binding.sessionId).respondToInteraction(command, options)).acknowledgement;
1515
- }
1516
- } : {},
1517
- ...interactiveAgent ? {
1518
- async startInteractive(request, options) {
1519
- const exactRequest = AgentInteractiveSessionStartSchema.parse(request);
1520
- assertSandboxInteractiveBinding(exactRequest.run, box, providerName);
1521
- const result = await box.session(exactRequest.run.sessionId).interactive().start(exactRequest, options);
1522
- if (result.state !== "running") throw new ValidationError("sandbox interactive process settled before attachment");
1523
- const ref = detachedSnapshot(AgentInteractiveSessionRefSchema.parse(result.ref), "sandbox interactive start reference");
1524
- if (!agentInteractiveSessionRefMatchesStart(exactRequest, ref)) throw new ValidationError("sandbox interactive start returned a different exact process");
1525
- return ref;
1526
- },
1527
- interactive(ref) {
1528
- const exactRef = detachedSnapshot(AgentInteractiveSessionRefSchema.parse(ref), "sandbox interactive reference");
1529
- assertSandboxInteractiveBinding(exactRef.run, box, providerName);
1530
- const session = box.session(exactRef.run.sessionId).interactive({ ref: exactRef });
1531
- const controlledSessions = /* @__PURE__ */ new Map();
1532
- const controlledSession = (control) => {
1533
- const key = canonicalCandidateDigest(control);
1534
- const existing = controlledSessions.get(key);
1535
- if (existing) return existing;
1536
- const created = box.session(exactRef.run.sessionId).interactive({
1537
- ref: exactRef,
1538
- control
1539
- });
1540
- controlledSessions.set(key, created);
1541
- return created;
1542
- };
1543
- return {
1544
- ref: exactRef,
1545
- claimControl: (request, options) => session.claimControl(request, options),
1546
- async status(options) {
1547
- const status = await session.status(options);
1548
- if (status === null) throw new ValidationError(`sandbox interactive session "${exactRef.run.sessionId}" is unavailable`);
1549
- return status;
1550
- },
1551
- attach: (request, options) => {
1552
- const controlled = controlledSession(request.control);
1553
- if (typeof controlled.attachAgentTerminal !== "function") return Promise.reject(new ValidationError("sandbox interactive session does not expose the exact terminal adapter"));
1554
- return controlled.attachAgentTerminal(request, options);
1555
- },
1556
- sendPrompt: (command, options) => controlledSession(command.control).sendPrompt(command, options),
1557
- stop: (command, options) => controlledSession(command.control).stop(command, options)
1558
- };
1559
- }
1560
- } : {},
1561
- ...hasRead(box) ? { read: box.read.bind(box) } : {},
1562
- ...hasWrite(box) ? { write: box.write.bind(box) } : {},
1563
- ...hasExec(box) ? { async exec(command, options) {
1564
- return execResultFromSandboxExecResult(await box.exec(command, options));
1565
- } } : {},
1566
- async checkpoint(options) {
1567
- return {
1568
- id: (await box.snapshot({ ...options?.name ? { tags: [options.name] } : {} })).snapshotId,
1569
- provider: providerName,
1570
- ...options?.metadata ? { metadata: options.metadata } : {}
1571
- };
1572
- },
1573
- async fork(checkpoint, options) {
1574
- return sandboxInstanceAsEnvironment(await client.create({
1575
- fromSnapshot: checkpoint.id,
1576
- fromSandboxId: String(box.id),
1577
- ...options?.name ? { name: options.name } : {},
1578
- ...options?.metadata ? { metadata: options.metadata } : {}
1579
- }), providerName, client, providerCapabilities);
1580
- },
1581
- async placement() {
1582
- return placementInfoFromLoopPlacement(client.describePlacement?.(box), box);
1583
- },
1584
- async refresh() {
1585
- await maybeRefresh(box);
1586
- },
1587
- async destroy() {
1588
- await destroyBox(box);
1589
- }
1590
- };
1591
- }
1592
- function sandboxSessionAsAgentSession(session, expectedControlRef) {
1593
- const controlRef = resolveSessionControlRef(session.controlRef, expectedControlRef, { allowExpectedWhenActualAbsent: true });
1594
- return {
1595
- id: session.id,
1596
- ...controlRef === void 0 ? {} : { controlRef },
1597
- async status() {
1598
- const status = await session.status();
1599
- if (!status) return null;
1600
- assertSandboxStatusBinding(status, controlRef);
1601
- return sessionStatusFromUnknown(status.status);
1602
- },
1603
- async *events(options) {
1604
- const executionId = scopedExecutionId(controlRef, options?.executionId);
1605
- for await (const event of session.events({
1606
- ...options?.since === void 0 ? {} : { since: options.since },
1607
- ...executionId === void 0 ? {} : { executionId },
1608
- ...options?.signal === void 0 ? {} : { signal: options.signal }
1609
- })) yield environmentEventFromSandboxEvent(event);
1610
- },
1611
- async result(options) {
1612
- return agentTurnResultFromPromptResult(await awaitAbortable(Promise.resolve().then(() => session.result({ ...controlRef?.executionId === void 0 ? {} : { executionId: controlRef.executionId } })), options?.signal));
1613
- },
1614
- async prompt(input) {
1615
- return agentTurnResultFromPromptResult(await session.prompt(promptFromAgentTurnInput(input), promptOptionsFromAgentTurnInput(input)));
1616
- },
1617
- ...session.respondToInteraction ? { async respondToInteraction(command, options) {
1618
- assertInteractionCommandScope(command, controlRef);
1619
- return (await session.respondToInteraction(command, options)).acknowledgement;
1620
- } } : {},
1621
- ...session.cancelRun ? { async cancelRun(request, options) {
1622
- assertCancellationScope(request, controlRef);
1623
- return session.cancelRun(request, options);
1624
- } } : {},
1625
- cancel() {
1626
- return session.interrupt(controlRef?.executionId === void 0 ? void 0 : { executionId: controlRef.executionId }).then(() => void 0);
1627
- }
1628
- };
1629
- }
1630
- function sandboxSessionFromAgentSession(session, expectedControlRef) {
1631
- if (!session) throw new ValidationError("providerAsSandboxClient: session is unavailable");
1632
- const controlRef = resolveSessionControlRef(session.controlRef, expectedControlRef);
1633
- return {
1634
- id: session.id,
1635
- ...controlRef === void 0 ? {} : { controlRef },
1636
- async status() {
1637
- const status = await session.status();
1638
- if (!status) return null;
1639
- return {
1640
- id: session.id,
1641
- status: sandboxSessionStatusFromAgentSessionStatus(status)
1642
- };
1643
- },
1644
- async *events(options) {
1645
- const executionId = scopedExecutionId(controlRef, options?.executionId);
1646
- for await (const event of session.events({
1647
- ...options?.since === void 0 ? {} : { since: options.since },
1648
- ...executionId === void 0 ? {} : { executionId },
1649
- ...options?.signal === void 0 ? {} : { signal: options.signal }
1650
- })) yield sandboxEventFromEnvironmentEvent(event);
1651
- },
1652
- async result(options) {
1653
- scopedExecutionId(controlRef, options?.executionId);
1654
- return promptResultFromAgentTurnResult(await session.result());
1655
- },
1656
- async prompt(message, options) {
1657
- return promptResultFromAgentTurnResult(await session.prompt(turnInputFromPrompt(message, options)));
1658
- },
1659
- ...session.respondToInteraction ? { async respondToInteraction(command, options) {
1660
- assertInteractionCommandScope(command, controlRef);
1661
- return { acknowledgement: await session.respondToInteraction(command, options) };
1662
- } } : {},
1663
- ...session.cancelRun ? { async cancelRun(request, options) {
1664
- assertCancellationScope(request, controlRef);
1665
- return session.cancelRun(request, options);
1666
- } } : {},
1667
- async interrupt(options) {
1668
- scopedExecutionId(controlRef, options?.executionId);
1669
- await session.cancel();
1670
- return { cancelled: true };
1671
- }
1672
- };
1673
- }
1674
- function sandboxSessionStatusFromAgentSessionStatus(status) {
1675
- switch (status) {
1676
- case "pending":
1677
- case "provisioning": return "queued";
1678
- case "running": return "running";
1679
- case "completed": return "completed";
1680
- case "cancelled": return "cancelled";
1681
- case "stopped":
1682
- case "failed":
1683
- case "expired":
1684
- case "unknown": return "failed";
1685
- }
1686
- }
1687
- function promptResultFromAgentTurnResult(result) {
1688
- return {
1689
- response: result.text,
1690
- success: result.success,
1691
- status: result.success ? "success" : "failed",
1692
- durationMs: 0,
1693
- ...result.error ? { error: result.error } : {},
1694
- ...result.usage ? {
1695
- usage: {
1696
- inputTokens: result.usage.inputTokens,
1697
- outputTokens: result.usage.outputTokens
1698
- },
1699
- ...result.usage.cost === void 0 ? {} : { costUsd: result.usage.cost }
1700
- } : {}
1701
- };
1702
- }
1703
- function environmentEventFromSandboxEvent(event) {
1704
- const data = event.data && typeof event.data === "object" ? event.data : {};
1705
- const normalized = canonicalStreamEventFromSandboxEvent(event);
1706
- const usage = tokenUsageFromData(data);
1707
- const receipt = tokenUsageReceipt(tokenUsageRecord(data));
1708
- return {
1709
- type: String(event.type),
1710
- data,
1711
- ...event.id ? { id: event.id } : {},
1712
- ...normalized ? { normalized } : {},
1713
- ...usage && receipt.complete ? { usage } : {},
1714
- providerEvent: event
1715
- };
1716
- }
1717
- function sandboxEventFromEnvironmentEvent(event) {
1718
- const normalized = event.normalized;
1719
- const type = normalized?.type ?? event.type;
1720
- const baseData = normalized ? sandboxDataFromNormalizedEvent(event.data, normalized) : event.data;
1721
- const usage = event.usage ? tokenUsageData(event.usage) : void 0;
1722
- return {
1723
- type,
1724
- data: (() => {
1725
- if (!usage) return baseData;
1726
- if (isUsageType(type)) return {
1727
- ...baseData,
1728
- tokensIn: event.usage?.inputTokens,
1729
- tokensOut: event.usage?.outputTokens,
1730
- ...event.usage?.cost !== void 0 ? { costUsd: event.usage.cost } : {},
1731
- ...usage
1732
- };
1733
- if (isNestedUsageType(type)) return {
1734
- ...baseData,
1735
- usage
1736
- };
1737
- if (type === "done") return {
1738
- ...baseData,
1739
- tokenUsage: usage,
1740
- ...usage.totalCostUsd !== void 0 ? { totalCostUsd: usage.totalCostUsd } : {}
1741
- };
1742
- return baseData;
1743
- })(),
1744
- ...event.id ? { id: event.id } : {}
1745
- };
1746
- }
1747
- function providerFailureEvent(event, sandboxEvent) {
1748
- if (event.type === "error") {
1749
- const errorCode = failureCode(sandboxEvent.data);
1750
- return {
1751
- type: "error",
1752
- data: {
1753
- ...sandboxEvent.data,
1754
- ...errorCode ? { code: errorCode } : {}
1755
- }
1756
- };
1757
- }
1758
- const normalized = event.normalized;
1759
- const failedStatus = event.type === "status" && event.data.status === "failed" || normalized?.type === "status" && normalized.status === "failed";
1760
- const terminalFrame = sandboxEvent.type === "done" || sandboxEvent.type === "result";
1761
- const outcome = recordValue(sandboxEvent.data.outcome);
1762
- const result = recordValue(sandboxEvent.data.result);
1763
- const failedTerminal = terminalFrame && (sandboxEvent.data.success === false || sandboxEvent.data.status === "failed" || outcome?.type === "failed" || outcome?.status === "failed" || result?.status === "failed");
1764
- if (!failedStatus && !failedTerminal) return void 0;
1765
- const detail = failureDetail(sandboxEvent.data) ?? failureDetail(outcome) ?? failureDetail(result) ?? (normalized?.type === "status" ? normalized.detail : void 0);
1766
- const errorCode = failureCode(sandboxEvent.data) ?? failureCode(outcome) ?? failureCode(result);
1767
- return {
1768
- type: "result",
1769
- data: {
1770
- status: "failed",
1771
- ...detail ? { error: detail } : {},
1772
- ...errorCode ? { errorCode } : {}
1773
- }
1774
- };
1775
- }
1776
- function recordValue(value) {
1777
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1778
- }
1779
- function failureDetail(value) {
1780
- if (!value) return void 0;
1781
- const error = value.error;
1782
- if (typeof error === "string") return error;
1783
- const nestedError = recordValue(error);
1784
- if (typeof nestedError?.message === "string") return nestedError.message;
1785
- if (typeof value.message === "string") return value.message;
1786
- return typeof value.detail === "string" ? value.detail : void 0;
1787
- }
1788
- function failureCode(value) {
1789
- if (!value) return void 0;
1790
- const error = recordValue(value.error);
1791
- return stringValue(value.errorCode) ?? stringValue(value.code) ?? stringValue(error?.errorCode) ?? stringValue(error?.code);
1792
- }
1793
- function usageSandboxEvent(event) {
1794
- const type = event.normalized?.type ?? event.type;
1795
- if (!event.usage || isUsageType(type) || isNestedUsageType(type) || type === "done") return;
1796
- const usage = tokenUsageData(event.usage);
1797
- if (usage.inputTokens === void 0 && usage.outputTokens === void 0 && usage.totalCostUsd === void 0) return;
1798
- return {
1799
- type: "llm_call",
1800
- data: usage
1801
- };
1802
- }
1803
- function sandboxDataFromNormalizedEvent(rawData, normalized) {
1804
- const { type: _type, ...normalizedData } = normalized;
1805
- return {
1806
- ...rawData,
1807
- ...normalizedData,
1808
- ...normalized.type === "message.part.updated" && normalized.part.type === "text" ? { text: normalized.part.text } : {}
1809
- };
1810
- }
1811
- function tokenUsageData(usage) {
1812
- return {
1813
- inputTokens: usage.inputTokens,
1814
- outputTokens: usage.outputTokens,
1815
- ...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
1816
- ...usage.cacheReadInputTokens !== void 0 ? { cacheReadInputTokens: usage.cacheReadInputTokens } : {},
1817
- ...usage.cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens: usage.cacheCreationInputTokens } : {},
1818
- ...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {},
1819
- ...usage.cost !== void 0 ? { totalCostUsd: usage.cost } : {}
1820
- };
1821
- }
1822
- function turnInputFromPrompt(message, options) {
1823
- return {
1824
- ...typeof message === "string" ? { prompt: message } : { parts: message },
1825
- ...options?.sessionId ? { sessionId: options.sessionId } : {},
1826
- ...options?.model ? { model: options.model } : {},
1827
- ...options?.timeoutMs ? { timeoutMs: options.timeoutMs } : {},
1828
- ...options?.executionId ? { executionId: options.executionId } : {},
1829
- ...options?.lastEventId ? { lastEventId: options.lastEventId } : {},
1830
- ...options?.turnId ? { turnId: options.turnId } : {},
1831
- ...options?.detach !== void 0 ? { detach: options.detach } : {},
1832
- ...options?.context ? { context: options.context } : {},
1833
- ...options?.runControlRef ? { controlRef: options.runControlRef } : {},
1834
- ...options?.backend?.interactions ? { interactions: options.backend.interactions } : {},
1835
- ...options?.signal ? { signal: options.signal } : {},
1836
- ...options?.backend ? { providerOptions: { backend: options.backend } } : {}
1837
- };
1838
- }
1839
- function resolveSessionControlRef(actual, expected, options = {}) {
1840
- if (expected === void 0) {
1841
- if (actual === void 0) return void 0;
1842
- return AgentExactRunControlRefSchema.parse(actual);
1843
- }
1844
- const expectedExact = AgentExactRunControlRefSchema.parse(expected);
1845
- if (actual === void 0) {
1846
- if (options.allowExpectedWhenActualAbsent === true) return expectedExact;
1847
- throw new ValidationError("provider session omitted the required exact run control reference");
1848
- }
1849
- const actualExact = AgentExactRunControlRefSchema.safeParse(actual);
1850
- if (!actualExact.success) throw new ValidationError("provider session returned an invalid exact run control reference");
1851
- if (!sameControlCoordinates(actualExact.data, expectedExact)) throw new ValidationError("provider session returned a different exact run control reference");
1852
- return actualExact.data;
1853
- }
1854
- function assertSandboxStatusBinding(status, controlRef) {
1855
- if (!status || typeof status !== "object" || controlRef === void 0) return;
1856
- const record = status;
1857
- if (record.runControlRef !== void 0) {
1858
- if (!sameControlCoordinates(AgentExactRunControlRefSchema.parse(record.runControlRef), controlRef)) throw new ValidationError("sandbox status returned a different exact run control reference");
1859
- }
1860
- for (const key of ["activeExecutionId", "latestExecutionId"]) {
1861
- const executionId = record[key];
1862
- if (executionId !== void 0 && executionId !== controlRef.executionId) throw new ValidationError("sandbox status returned a different execution");
1863
- }
1864
- }
1865
- function assertInteractionCommandScope(command, controlRef) {
1866
- if (controlRef === void 0) return;
1867
- if (command.binding.runId !== controlRef.runId || command.binding.provider !== controlRef.provider || command.binding.environmentId !== controlRef.environmentId || command.binding.sessionId !== controlRef.sessionId || command.binding.executionId !== controlRef.executionId) throw new ValidationError("interaction response targeted a different execution");
1868
- }
1869
- function assertCancellationScope(request, controlRef) {
1870
- if (controlRef === void 0) throw new ValidationError("durable cancellation requires an exact wrapper run control reference");
1871
- if (!sameControlCoordinates(AgentRunCancellationRequestSchema.parse(request).run, controlRef)) throw new ValidationError("cancellation targeted a different execution");
1872
- }
1873
- function scopedExecutionId(controlRef, requested) {
1874
- if (controlRef?.executionId !== void 0 && requested !== void 0 && controlRef.executionId !== requested) throw new ValidationError("session operation targeted a different execution");
1875
- return controlRef?.executionId ?? requested;
1876
- }
1877
- function assertScopedSessionForInput(session, input) {
1878
- if (input.executionId === void 0 && input.controlRef === void 0) return;
1879
- const sessionControlRef = AgentExactRunControlRefSchema.safeParse(session.controlRef);
1880
- if (!sessionControlRef.success) throw new ValidationError("provider session did not expose an exact run control reference for scoped cancellation");
1881
- const expectedControlRef = input.controlRef === void 0 ? void 0 : AgentExactRunControlRefSchema.parse(input.controlRef);
1882
- if (input.executionId !== void 0 && sessionControlRef.data.executionId !== input.executionId || expectedControlRef !== void 0 && !sameControlCoordinates(sessionControlRef.data, expectedControlRef)) throw new ValidationError("provider session returned a different exact run control reference");
1883
- }
1884
- function taskToTurnInput(task, signal) {
1885
- return {
1886
- prompt: taskToPrompt(task),
1887
- signal
1888
- };
1889
- }
1890
- function taskToPrompt(task) {
1891
- if (typeof task === "string") return task;
1892
- if (task && typeof task === "object") {
1893
- const record = task;
1894
- for (const key of [
1895
- "prompt",
1896
- "content",
1897
- "task",
1898
- "message"
1899
- ]) if (typeof record[key] === "string") return record[key];
1900
- }
1901
- return JSON.stringify(task);
1902
- }
1903
- function resultFromEvents(events, fallbackText) {
1904
- for (let i = events.length - 1; i >= 0; i -= 1) {
1905
- const event = events[i];
1906
- const text = event ? resultTextFromData(event.data) : void 0;
1907
- if (text !== void 0) return {
1908
- content: text,
1909
- events
1910
- };
1911
- }
1912
- return {
1913
- content: fallbackText,
1914
- events
1915
- };
1916
- }
1917
- function textFromEnvironmentEvent(event) {
1918
- if (typeof event.normalized === "object" && event.normalized && event.normalized.type === "message.part.updated") return typeof event.normalized.delta === "string" ? event.normalized.delta : "";
1919
- const data = event.data;
1920
- for (const key of [
1921
- "delta",
1922
- "chunk",
1923
- "content",
1924
- "text"
1925
- ]) if (typeof data[key] === "string" && !isTerminalEnvironmentEvent(event)) return data[key];
1926
- return "";
1927
- }
1928
- function resultTextFromData(data) {
1929
- for (const key of [
1930
- "finalText",
1931
- "text",
1932
- "response",
1933
- "resultSummary",
1934
- "content"
1935
- ]) if (typeof data[key] === "string") return data[key];
1936
- }
1937
- function isTerminalEnvironmentEvent(event) {
1938
- if (isTerminalEventShape(event.type, event.data)) return true;
1939
- const normalized = event.normalized;
1940
- return normalized?.type === "status" && (normalized.status === "completed" || normalized.status === "failed");
1941
- }
1942
- function isTerminalEventShape(type, data) {
1943
- if (isSandboxTerminalEvent(type)) return true;
1944
- if (type.endsWith(".completed") || type.endsWith(".failed")) return true;
1945
- if (type !== "status") return false;
1946
- return data.status === "completed" || data.status === "failed" || data.status === "cancelled";
1947
- }
1948
- function isUsageType(type) {
1949
- return type === "llm_call" || type === "usage" || type === "cost.usage";
1950
- }
1951
- /** A terminal event whose usage rides `data.usage` — the nested shape this transport unwraps. */
1952
- function isNestedUsageType(type) {
1953
- return isSandboxTerminalEvent(type) && sandboxTerminalUsageField(type) === "usage";
1954
- }
1955
- function usageFromEnvironmentEvent(event) {
1956
- const receipt = tokenUsageReceipt(event.usage ?? tokenUsageRecord(event.data));
1957
- const usage = event.usage ?? tokenUsageFromData(event.data);
1958
- const input = finiteNumber(usage?.inputTokens);
1959
- const output = finiteNumber(usage?.outputTokens);
1960
- return {
1961
- input: input ?? 0,
1962
- output: (output ?? 0) + (finiteNumber(usage?.reasoningTokens) ?? 0),
1963
- usd: finiteNumber(usage?.cost) ?? finiteNumber(event.data.costUsd) ?? finiteNumber(event.data.totalCostUsd) ?? 0,
1964
- hasInput: receipt.hasInput,
1965
- hasOutput: receipt.hasOutput,
1966
- hasTokenReceipt: receipt.hasTokens
1967
- };
1968
- }
1969
- function tokenUsageFromData(data) {
1970
- const usageRecord = tokenUsageRecord(data);
1971
- const inputTokens = inputTokensFromUsage(usageRecord);
1972
- const outputTokens = outputTokensFromUsage(usageRecord);
1973
- const totalTokens = finiteNumber(usageRecord.totalTokens);
1974
- const cacheReadInputTokens = finiteNumber(usageRecord.cacheReadInputTokens);
1975
- const cacheCreationInputTokens = finiteNumber(usageRecord.cacheCreationInputTokens);
1976
- const reasoningTokens = finiteNumber(usageRecord.reasoningTokens);
1977
- const cost = finiteNumber(usageRecord.cost) ?? finiteNumber(usageRecord.costUsd) ?? finiteNumber(usageRecord.totalCostUsd) ?? finiteNumber(data.costUsd) ?? finiteNumber(data.totalCostUsd);
1978
- if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0 && cacheReadInputTokens === void 0 && cacheCreationInputTokens === void 0 && reasoningTokens === void 0 && cost === void 0) return void 0;
1979
- return {
1980
- inputTokens: inputTokens ?? 0,
1981
- outputTokens: outputTokens ?? 0,
1982
- ...totalTokens !== void 0 ? { totalTokens } : {},
1983
- ...cacheReadInputTokens !== void 0 ? { cacheReadInputTokens } : {},
1984
- ...cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens } : {},
1985
- ...reasoningTokens !== void 0 ? { reasoningTokens } : {},
1986
- ...cost !== void 0 ? { cost } : {}
1987
- };
1988
- }
1989
- function tokenUsageRecord(data) {
1990
- return recordValue(data.usage) ?? recordValue(data.tokenUsage) ?? data;
1991
- }
1992
- function inputTokensFromUsage(value) {
1993
- const usage = recordValue(value);
1994
- if (!usage) return void 0;
1995
- return finiteNumber(usage.inputTokens) ?? finiteNumber(usage.tokensIn) ?? finiteNumber(usage.prompt_tokens);
1996
- }
1997
- function outputTokensFromUsage(value) {
1998
- const usage = recordValue(value);
1999
- if (!usage) return void 0;
2000
- return finiteNumber(usage.outputTokens) ?? finiteNumber(usage.tokensOut) ?? finiteNumber(usage.completion_tokens);
2001
- }
2002
- function tokenUsageReceipt(value) {
2003
- const usage = recordValue(value);
2004
- const hasInput = inputTokensFromUsage(usage) !== void 0;
2005
- const hasOutput = outputTokensFromUsage(usage) !== void 0;
2006
- return {
2007
- hasInput,
2008
- hasOutput,
2009
- hasTokens: hasInput || hasOutput || finiteNumber(usage?.totalTokens) !== void 0 || finiteNumber(usage?.cacheReadInputTokens) !== void 0 || finiteNumber(usage?.cacheCreationInputTokens) !== void 0 || finiteNumber(usage?.reasoningTokens) !== void 0,
2010
- complete: hasInput && hasOutput
2011
- };
2012
- }
2013
- function mergeTokenUsage(left, right) {
2014
- if (!left) return right;
2015
- if (!right) return left;
2016
- return {
2017
- inputTokens: left.inputTokens + right.inputTokens,
2018
- outputTokens: left.outputTokens + right.outputTokens,
2019
- ...left.totalTokens !== void 0 || right.totalTokens !== void 0 ? { totalTokens: (left.totalTokens ?? 0) + (right.totalTokens ?? 0) } : {},
2020
- ...left.cacheReadInputTokens !== void 0 || right.cacheReadInputTokens !== void 0 ? { cacheReadInputTokens: (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0) } : {},
2021
- ...left.cacheCreationInputTokens !== void 0 || right.cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens: (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0) } : {},
2022
- ...left.reasoningTokens !== void 0 || right.reasoningTokens !== void 0 ? { reasoningTokens: (left.reasoningTokens ?? 0) + (right.reasoningTokens ?? 0) } : {},
2023
- ...left.cost !== void 0 || right.cost !== void 0 ? { cost: (left.cost ?? 0) + (right.cost ?? 0) } : {}
2024
- };
2025
- }
2026
- function finiteNumber(value) {
2027
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2028
- }
2029
- function stringValue(value) {
2030
- return typeof value === "string" && value.length > 0 ? value : void 0;
2031
- }
2032
- function agentTurnResultFromPromptResult(result) {
2033
- const record = result;
2034
- return {
2035
- text: typeof record.response === "string" ? record.response : typeof record.text === "string" ? record.text : typeof record.finalText === "string" ? record.finalText : "",
2036
- success: typeof record.success === "boolean" ? record.success : true,
2037
- ...typeof record.error === "string" ? { error: record.error } : {},
2038
- usage: tokenUsageFromData(record)
2039
- };
2040
- }
2041
- function sandboxDispatchResultFromSessionRef(session) {
2042
- const controlRef = session.controlRef === void 0 ? void 0 : AgentExactRunControlRefSchema.parse(session.controlRef);
2043
- const metadataExecutionId = optionalDispatchIdentity(session.metadata?.executionId, "executionId");
2044
- if (controlRef !== void 0) {
2045
- if (controlRef.sessionId !== session.id) throw new ValidationError("provider dispatch returned a control reference for another session");
2046
- if (session.provider !== void 0 && controlRef.provider !== session.provider) throw new ValidationError("provider dispatch returned a control reference for another provider");
2047
- if (metadataExecutionId !== void 0 && metadataExecutionId !== controlRef.executionId) throw new ValidationError("provider dispatch returned conflicting execution identities");
2048
- }
2049
- const status = session.metadata && Object.hasOwn(session.metadata, "status") ? sessionStatusFromUnknown(session.metadata?.status) : "running";
2050
- return {
2051
- sessionId: session.id,
2052
- status,
2053
- alreadyExisted: session.metadata?.alreadyExisted === true,
2054
- ...session.metadata?.dispatched === void 0 ? {} : { dispatched: session.metadata.dispatched === true },
2055
- ...controlRef === void 0 ? metadataExecutionId === void 0 ? {} : { executionId: metadataExecutionId } : {
2056
- executionId: controlRef.executionId,
2057
- runControlRef: controlRef
2058
- }
2059
- };
2060
- }
2061
- function sessionRefFromSandboxDispatch(dispatched, providerName) {
2062
- const record = dispatched && typeof dispatched === "object" ? dispatched : void 0;
2063
- const id = record?.sessionId ?? record?.id;
2064
- if (typeof id !== "string" || id.length === 0) throw new ValidationError("sandboxClientAsProvider: dispatch returned no session id");
2065
- if (!record) throw new ValidationError("sandboxClientAsProvider: dispatch returned no session record");
2066
- const executionId = optionalDispatchIdentity(record.executionId, "executionId");
2067
- const controlRef = record.runControlRef === void 0 ? void 0 : AgentExactRunControlRefSchema.parse(record.runControlRef);
2068
- if (controlRef !== void 0) {
2069
- if (controlRef.sessionId !== id || controlRef.provider !== providerName) throw new ValidationError("sandbox dispatch returned a control reference for another session");
2070
- if (executionId !== void 0 && controlRef.executionId !== executionId) throw new ValidationError("sandbox dispatch returned conflicting execution identities");
2071
- }
2072
- if (record.alreadyExisted !== void 0 && typeof record.alreadyExisted !== "boolean") throw new ValidationError("sandbox dispatch returned an invalid alreadyExisted flag");
2073
- if (record.dispatched !== void 0 && typeof record.dispatched !== "boolean") throw new ValidationError("sandbox dispatch returned an invalid dispatched flag");
2074
- if (record.status !== void 0 && !isSandboxSessionStatus(record.status)) throw new ValidationError("sandbox dispatch returned an invalid session status");
2075
- return {
2076
- id,
2077
- provider: providerName,
2078
- ...controlRef === void 0 ? {} : { controlRef },
2079
- metadata: {
2080
- ...record.status ? { status: record.status } : {},
2081
- ...executionId === void 0 ? {} : { executionId },
2082
- ...record.alreadyExisted !== void 0 ? { alreadyExisted: record.alreadyExisted } : {},
2083
- ...record.dispatched !== void 0 ? { dispatched: record.dispatched } : {}
2084
- }
2085
- };
2086
- }
2087
- function optionalDispatchIdentity(value, label) {
2088
- if (value === void 0) return void 0;
2089
- if (typeof value !== "string" || value.length === 0) throw new ValidationError(`sandbox dispatch returned an invalid ${label}`);
2090
- return value;
2091
- }
2092
- function isSandboxSessionStatus(value) {
2093
- return value === "queued" || value === "running" || value === "completed" || value === "failed" || value === "cancelled";
2094
- }
2095
- function execResultFromSandboxExecResult(result) {
2096
- const record = result;
2097
- const exitCode = finiteNumber(record.exitCode) ?? finiteNumber(record.code);
2098
- if (exitCode === void 0) throw new ValidationError("sandboxClientAsProvider: exec returned no exit code");
2099
- return {
2100
- exitCode,
2101
- stdout: typeof record.stdout === "string" ? record.stdout : "",
2102
- stderr: typeof record.stderr === "string" ? record.stderr : ""
2103
- };
2104
- }
2105
- function statusFromUnknown(status) {
2106
- if (status === "pending" || status === "provisioning" || status === "running") return status;
2107
- if (status === "stopped" || status === "failed" || status === "expired") return status;
2108
- if (status === "completed") return "stopped";
2109
- if (status === "cancelled") return "stopped";
2110
- return "unknown";
2111
- }
2112
- function sessionStatusFromUnknown(status) {
2113
- if (status === "completed" || status === "cancelled") return status;
2114
- return statusFromUnknown(status);
2115
- }
2116
- function readBoxStatus(box) {
2117
- return box.status;
2118
- }
2119
- function readBoxMetadata(box) {
2120
- const metadata = box.metadata;
2121
- return metadata && typeof metadata === "object" ? metadata : void 0;
2122
- }
2123
- async function maybeRefresh(box) {
2124
- const refresh = box.refresh;
2125
- if (typeof refresh === "function") await refresh.call(box);
2126
- }
2127
- async function destroyBox(box) {
2128
- const deleteBox = box.delete;
2129
- if (typeof deleteBox === "function") await deleteBox.call(box);
2130
- }
2131
- function placementInfoFromLoopPlacement(placement, box) {
2132
- if (!placement) return {
2133
- kind: "sandbox",
2134
- sandboxId: String(box.id)
2135
- };
2136
- return {
2137
- kind: placement.kind === "fleet" ? "fleet" : placement.kind === "in-process" ? "local" : "sandbox",
2138
- ...placement.sandboxId ? { sandboxId: placement.sandboxId } : { sandboxId: String(box.id) },
2139
- ...placement.fleetId ? { fleetId: placement.fleetId } : {},
2140
- ...placement.machineId ? { machineId: placement.machineId } : {}
2141
- };
2142
- }
2143
- const completeInteractiveAgentCapabilities = {
2144
- start: true,
2145
- control: true,
2146
- status: true,
2147
- attach: true,
2148
- reattach: true,
2149
- sendPrompt: true,
2150
- input: true,
2151
- resize: true,
2152
- stop: true
2153
- };
2154
- async function sandboxEnvironmentCapabilities(box, providerCapabilities) {
2155
- const { interactiveAgent: providerInteractive, ...baseCapabilities } = providerCapabilities;
2156
- if (!hasCompleteInteractiveAgentCapabilities(providerInteractive)) return baseCapabilities;
2157
- let deployed;
2158
- try {
2159
- deployed = await box.capabilities();
2160
- } catch {
2161
- return baseCapabilities;
2162
- }
2163
- if (!hasCompleteInteractiveAgentCapabilities(deployed?.interactiveAgent)) return baseCapabilities;
2164
- if (!hasExactInteractiveTerminalAdapter(box)) return baseCapabilities;
2165
- return {
2166
- ...baseCapabilities,
2167
- interactiveAgent: { ...completeInteractiveAgentCapabilities }
2168
- };
2169
- }
2170
- function hasExactInteractiveTerminalAdapter(box) {
2171
- if (!hasSession(box)) return false;
2172
- try {
2173
- const session = box.session("__runtime-capability-probe__");
2174
- const interactive = session.interactive;
2175
- if (typeof interactive !== "function") return false;
2176
- return typeof interactive.call(session).attachAgentTerminal === "function";
2177
- } catch {
2178
- return false;
2179
- }
2180
- }
2181
- function hasCompleteInteractiveAgentCapabilities(value) {
2182
- return value?.start === true && value.control === true && value.status === true && value.attach === true && value.reattach === true && value.sendPrompt === true && value.input === true && value.resize === true && value.stop === true;
2183
- }
2184
- function assertSandboxInteractiveBinding(run, box, providerName) {
2185
- if (run.provider !== providerName || run.environmentId !== String(box.id)) throw new ValidationError("sandbox interactive request targets another environment");
2186
- }
2187
- function defaultTangleSandboxCapabilities(options) {
2188
- return {
2189
- profile: {
2190
- namedProfiles: options.namedProfiles,
2191
- systemPrompt: { ...harnessSystemPromptIntents(void 0) },
2192
- instructions: true,
2193
- tools: true,
2194
- permissions: true,
2195
- mcp: true,
2196
- subagents: true,
2197
- resources: {
2198
- files: true,
2199
- instructions: true,
2200
- tools: true,
2201
- skills: true,
2202
- agents: true,
2203
- commands: true
2204
- },
2205
- hooks: true,
2206
- modes: true,
2207
- runtimeUpdate: true,
2208
- validation: true
2209
- },
2210
- streaming: {
2211
- live: true,
2212
- replay: true,
2213
- detach: true,
2214
- turnIdempotency: true
2215
- },
2216
- sessions: {
2217
- continue: true,
2218
- list: true,
2219
- messages: true
2220
- },
2221
- workspace: {
2222
- read: true,
2223
- write: true,
2224
- exec: true,
2225
- git: true,
2226
- upload: true,
2227
- download: true,
2228
- cwdBases: {
2229
- repository: true,
2230
- host: false
2231
- }
2232
- },
2233
- branching: {
2234
- checkpoint: false,
2235
- fork: false
2236
- },
2237
- ...options.rediscover ? { interactiveAgent: { ...completeInteractiveAgentCapabilities } } : {},
2238
- placement: true,
2239
- usage: true,
2240
- confidential: false
2241
- };
2242
- }
2243
- function contentRef(prefix, value) {
2244
- let str;
2245
- try {
2246
- str = JSON.stringify(value) ?? String(value);
2247
- } catch {
2248
- str = String(value);
2249
- }
2250
- let hash = 2166136261;
2251
- for (let i = 0; i < str.length; i += 1) {
2252
- hash ^= str.charCodeAt(i);
2253
- hash = Math.imul(hash, 16777619);
2254
- }
2255
- return `${prefix}:${(hash >>> 0).toString(16).padStart(8, "0")}`;
2256
- }
2257
- function hasGet(client) {
2258
- return typeof client.get === "function";
2259
- }
2260
- function hasList(client) {
2261
- return typeof client.list === "function";
2262
- }
2263
- function hasDispatchPrompt(box) {
2264
- return typeof box.dispatchPrompt === "function";
2265
- }
2266
- function hasSession(box) {
2267
- return typeof box.session === "function";
2268
- }
2269
- function hasRead(box) {
2270
- return typeof box.read === "function";
2271
- }
2272
- function hasWrite(box) {
2273
- return typeof box.write === "function";
2274
- }
2275
- function hasExec(box) {
2276
- return typeof box.exec === "function";
2277
- }
2278
- //#endregion
2279
- export { runAbortable as A, throwIfAborted$1 as C, zeroTokenUsage as D, zeroSpend as E, abortError$1 as O, throwAbort as S, withTimeout as T, randomSuffix as _, sandboxClientAsProvider as a, sleep as b, addTokenUsage as c, cloneTokenUsage as d, deleteBoxSafe as f, promptCacheTokenClasses as g, mapWithConcurrency as h, resolveAgentEnvironmentProvider as i, linkAbort as k, chargedTokens as l, isAbortError as m, providerAsExecutor as n, createTangleSandboxExactProcessProvider as o, hasCompleteCacheBreakdown as p, providerAsSandboxClient as r, addSpend as s, createAgentEnvironmentProviderRegistry as t, cloneSpend as u, randomUuid as v, unmeteredSpend as w, stringifySafe as x, roundBoxMinutes as y };
2280
-
2281
- //# sourceMappingURL=environment-provider-1fKZh2zl.js.map