@agent-relay/sandbox 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +226 -5
  2. package/dist/agent37/client.d.ts +115 -0
  3. package/dist/agent37/client.d.ts.map +1 -0
  4. package/dist/agent37/client.js +270 -0
  5. package/dist/agent37/client.js.map +1 -0
  6. package/dist/agent37/runtime.d.ts +415 -0
  7. package/dist/agent37/runtime.d.ts.map +1 -0
  8. package/dist/agent37/runtime.js +855 -0
  9. package/dist/agent37/runtime.js.map +1 -0
  10. package/dist/agentcore/bench.d.ts +93 -0
  11. package/dist/agentcore/bench.d.ts.map +1 -0
  12. package/dist/agentcore/bench.js +125 -0
  13. package/dist/agentcore/bench.js.map +1 -0
  14. package/dist/agentcore/capabilities.d.ts +169 -0
  15. package/dist/agentcore/capabilities.d.ts.map +1 -0
  16. package/dist/agentcore/capabilities.js +153 -0
  17. package/dist/agentcore/capabilities.js.map +1 -0
  18. package/dist/agentcore/config.d.ts +156 -0
  19. package/dist/agentcore/config.d.ts.map +1 -0
  20. package/dist/agentcore/config.js +12 -0
  21. package/dist/agentcore/config.js.map +1 -0
  22. package/dist/agentcore/internal/sdk.d.ts +181 -0
  23. package/dist/agentcore/internal/sdk.d.ts.map +1 -0
  24. package/dist/agentcore/internal/sdk.js +223 -0
  25. package/dist/agentcore/internal/sdk.js.map +1 -0
  26. package/dist/agentcore/runtime.d.ts +177 -0
  27. package/dist/agentcore/runtime.d.ts.map +1 -0
  28. package/dist/agentcore/runtime.js +706 -0
  29. package/dist/agentcore/runtime.js.map +1 -0
  30. package/dist/daytona/runtime.d.ts +23 -0
  31. package/dist/daytona/runtime.d.ts.map +1 -1
  32. package/dist/daytona/runtime.js +167 -5
  33. package/dist/daytona/runtime.js.map +1 -1
  34. package/dist/daytona/wire-supplement.d.ts +25 -0
  35. package/dist/daytona/wire-supplement.d.ts.map +1 -0
  36. package/dist/daytona/wire-supplement.js +16 -0
  37. package/dist/daytona/wire-supplement.js.map +1 -0
  38. package/dist/e2b/runtime.d.ts +107 -72
  39. package/dist/e2b/runtime.d.ts.map +1 -1
  40. package/dist/e2b/runtime.js +675 -190
  41. package/dist/e2b/runtime.js.map +1 -1
  42. package/dist/freestyle/capabilities.d.ts +28 -0
  43. package/dist/freestyle/capabilities.d.ts.map +1 -0
  44. package/dist/freestyle/capabilities.js +42 -0
  45. package/dist/freestyle/capabilities.js.map +1 -0
  46. package/dist/freestyle/config.d.ts +36 -0
  47. package/dist/freestyle/config.d.ts.map +1 -0
  48. package/dist/freestyle/config.js +2 -0
  49. package/dist/freestyle/config.js.map +1 -0
  50. package/dist/freestyle/internal/sdk.d.ts +69 -0
  51. package/dist/freestyle/internal/sdk.d.ts.map +1 -0
  52. package/dist/freestyle/internal/sdk.js +19 -0
  53. package/dist/freestyle/internal/sdk.js.map +1 -0
  54. package/dist/freestyle/runtime.d.ts +163 -0
  55. package/dist/freestyle/runtime.d.ts.map +1 -0
  56. package/dist/freestyle/runtime.js +798 -0
  57. package/dist/freestyle/runtime.js.map +1 -0
  58. package/dist/index.d.ts +35 -4
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +16 -2
  61. package/dist/index.js.map +1 -1
  62. package/dist/microsandbox/runtime.d.ts +793 -0
  63. package/dist/microsandbox/runtime.d.ts.map +1 -0
  64. package/dist/microsandbox/runtime.js +2303 -0
  65. package/dist/microsandbox/runtime.js.map +1 -0
  66. package/dist/modal/bench.d.ts +199 -0
  67. package/dist/modal/bench.d.ts.map +1 -0
  68. package/dist/modal/bench.js +450 -0
  69. package/dist/modal/bench.js.map +1 -0
  70. package/dist/modal/capabilities.d.ts +180 -0
  71. package/dist/modal/capabilities.d.ts.map +1 -0
  72. package/dist/modal/capabilities.js +159 -0
  73. package/dist/modal/capabilities.js.map +1 -0
  74. package/dist/modal/config.d.ts +192 -0
  75. package/dist/modal/config.d.ts.map +1 -0
  76. package/dist/modal/config.js +169 -0
  77. package/dist/modal/config.js.map +1 -0
  78. package/dist/modal/internal/sdk.d.ts +141 -0
  79. package/dist/modal/internal/sdk.d.ts.map +1 -0
  80. package/dist/modal/internal/sdk.js +51 -0
  81. package/dist/modal/internal/sdk.js.map +1 -0
  82. package/dist/modal/runtime.d.ts +271 -0
  83. package/dist/modal/runtime.d.ts.map +1 -0
  84. package/dist/modal/runtime.js +871 -0
  85. package/dist/modal/runtime.js.map +1 -0
  86. package/dist/mount-script.d.ts +16 -1
  87. package/dist/mount-script.d.ts.map +1 -1
  88. package/dist/mount-script.js +76 -15
  89. package/dist/mount-script.js.map +1 -1
  90. package/dist/orchestrator.d.ts +15 -0
  91. package/dist/orchestrator.d.ts.map +1 -1
  92. package/dist/orchestrator.js +13 -1
  93. package/dist/orchestrator.js.map +1 -1
  94. package/dist/port.d.ts +144 -6
  95. package/dist/port.d.ts.map +1 -1
  96. package/dist/port.js +27 -5
  97. package/dist/port.js.map +1 -1
  98. package/dist/types.d.ts +50 -1
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/vercel/bench.d.ts +169 -0
  101. package/dist/vercel/bench.d.ts.map +1 -0
  102. package/dist/vercel/bench.js +337 -0
  103. package/dist/vercel/bench.js.map +1 -0
  104. package/dist/vercel/capabilities.d.ts +106 -0
  105. package/dist/vercel/capabilities.d.ts.map +1 -0
  106. package/dist/vercel/capabilities.js +128 -0
  107. package/dist/vercel/capabilities.js.map +1 -0
  108. package/dist/vercel/config.d.ts +119 -0
  109. package/dist/vercel/config.d.ts.map +1 -0
  110. package/dist/vercel/config.js +8 -0
  111. package/dist/vercel/config.js.map +1 -0
  112. package/dist/vercel/internal/sdk.d.ts +173 -0
  113. package/dist/vercel/internal/sdk.d.ts.map +1 -0
  114. package/dist/vercel/internal/sdk.js +117 -0
  115. package/dist/vercel/internal/sdk.js.map +1 -0
  116. package/dist/vercel/runtime.d.ts +305 -0
  117. package/dist/vercel/runtime.d.ts.map +1 -0
  118. package/dist/vercel/runtime.js +1169 -0
  119. package/dist/vercel/runtime.js.map +1 -0
  120. package/docs/freestyle.md +110 -0
  121. package/package.json +35 -4
@@ -0,0 +1,450 @@
1
+ /**
2
+ * Modal benchmark harness: ledger, cost guards, and measurement.
3
+ *
4
+ * This module is deliberately separate from any live credential. It contains
5
+ * no I/O of its own — it drives a `SandboxRuntime` that the caller supplies —
6
+ * so every guard below is unit-testable against a fake runtime, and the guards
7
+ * are proven *before* they are trusted with a real account.
8
+ *
9
+ * The safety model has three independent layers, and each one alone is
10
+ * sufficient to stop a runaway:
11
+ *
12
+ * 1. **Ledger before use.** Every sandbox is recorded as *intended* before the
13
+ * create call is made, not after it returns. A create that times out or
14
+ * crashes mid-flight still leaves a record, so a sandbox that was actually
15
+ * provisioned can never be invisible to cleanup.
16
+ * 2. **Bounded cost.** A projected cost is computed for each sandbox from
17
+ * Modal's published rates and charged against a hard budget *before* the
18
+ * create. The run aborts rather than exceeding it.
19
+ * 3. **Verified cleanup.** Teardown runs in a `finally`, and afterwards each
20
+ * ledger entry is re-checked against the provider. Anything still present
21
+ * is reported as a leak rather than being assumed gone.
22
+ */
23
+ import { MODAL_MIN_CPU_CORES } from "./config.js";
24
+ // --- published rates -------------------------------------------------------
25
+ /**
26
+ * Modal's published per-second rates, read from <https://modal.com/pricing> on
27
+ * 2026-08-21.
28
+ *
29
+ * Note the two cards. Modal bills **Sandboxes at roughly 3× its own Functions**,
30
+ * and the pricing page presents both in one table, so quoting the Function rate
31
+ * for a sandbox workload understates it threefold. Only the Sandbox card is
32
+ * correct for this adapter; the Function card is kept solely so the ratio can
33
+ * be asserted in a test rather than restated as a comment.
34
+ *
35
+ * Provider pricing is time-unstable. Re-verify before these decide anything.
36
+ */
37
+ export const MODAL_RATES = {
38
+ /** Sandbox/Notebook rate, USD per **physical core** per second. */
39
+ sandboxCpuCoreSecondUsd: 0.00003942,
40
+ /** Sandbox/Notebook rate, USD per GiB of memory per second. */
41
+ sandboxMemoryGiBSecondUsd: 0.00000667,
42
+ /** Ordinary Function rate, USD per physical core per second. */
43
+ functionCpuCoreSecondUsd: 0.0000131,
44
+ /** Ordinary Function rate, USD per GiB of memory per second. */
45
+ functionMemoryGiBSecondUsd: 0.00000222,
46
+ /** Volume storage, USD per GiB per month. Not charged per second. */
47
+ volumeStorageGiBMonthUsd: 0.09,
48
+ /** Modal counts one physical core as this many vCPU. */
49
+ vcpuPerPhysicalCore: 2,
50
+ };
51
+ /**
52
+ * Projected USD cost of one sandbox at Modal's **Sandbox** rate.
53
+ *
54
+ * Deliberately not clamped or rounded: a budget guard that quietly rounds a
55
+ * projection down is a budget guard that can be walked past.
56
+ */
57
+ export function estimateModalSandboxCostUsd(input) {
58
+ requireCpuAtLeastFloor(input.cpuCores);
59
+ requireFinitePositive(input.memoryGiB, "memoryGiB");
60
+ requireFiniteNonNegative(input.seconds, "seconds");
61
+ return (input.cpuCores * MODAL_RATES.sandboxCpuCoreSecondUsd
62
+ + input.memoryGiB * MODAL_RATES.sandboxMemoryGiBSecondUsd) * input.seconds;
63
+ }
64
+ /** The same shape billed as an ordinary Function, for ratio reporting only. */
65
+ export function estimateModalFunctionCostUsd(input) {
66
+ requireCpuAtLeastFloor(input.cpuCores);
67
+ requireFinitePositive(input.memoryGiB, "memoryGiB");
68
+ requireFiniteNonNegative(input.seconds, "seconds");
69
+ return (input.cpuCores * MODAL_RATES.functionCpuCoreSecondUsd
70
+ + input.memoryGiB * MODAL_RATES.functionMemoryGiBSecondUsd) * input.seconds;
71
+ }
72
+ /**
73
+ * A record of every sandbox this harness intended to create.
74
+ *
75
+ * "Intended" is the important word. Recording only successful creates would
76
+ * lose exactly the sandboxes most likely to leak: those whose create call
77
+ * timed out or was interrupted after the provider had already provisioned
78
+ * them. An entry is written before the attempt and updated after.
79
+ */
80
+ export class ModalBenchLedger {
81
+ entries = [];
82
+ budgetUsd;
83
+ constructor(options) {
84
+ requireFinitePositive(options.budgetUsd, "budgetUsd");
85
+ this.budgetUsd = options.budgetUsd;
86
+ }
87
+ /** Total projected cost of everything recorded so far. */
88
+ committedUsd() {
89
+ return this.entries.reduce((sum, entry) => sum + entry.projectedCostUsd, 0);
90
+ }
91
+ remainingUsd() {
92
+ return this.budgetUsd - this.committedUsd();
93
+ }
94
+ /**
95
+ * Reserve budget and record intent. Throws *before* any provider call if the
96
+ * projection would breach the cap, so the guard cannot be raced by the
97
+ * create it is guarding.
98
+ */
99
+ recordIntent(label, projectedCostUsd) {
100
+ requireFiniteNonNegative(projectedCostUsd, "projectedCostUsd");
101
+ const wouldCommit = this.committedUsd() + projectedCostUsd;
102
+ if (wouldCommit > this.budgetUsd) {
103
+ throw new ModalBudgetExceededError({
104
+ budgetUsd: this.budgetUsd,
105
+ committedUsd: this.committedUsd(),
106
+ requestedUsd: projectedCostUsd,
107
+ label,
108
+ });
109
+ }
110
+ const entry = { label, state: "intended", projectedCostUsd };
111
+ this.entries.push(entry);
112
+ return entry;
113
+ }
114
+ all() {
115
+ return this.entries;
116
+ }
117
+ /** Entries that may still correspond to a live provider resource. */
118
+ outstanding() {
119
+ return this.entries.filter((entry) => entry.state !== "verified-gone"
120
+ && entry.state !== "create-failed");
121
+ }
122
+ leaked() {
123
+ return this.entries.filter((entry) => entry.state === "leaked");
124
+ }
125
+ }
126
+ /** A create was refused because it would breach the run's cost cap. */
127
+ export class ModalBudgetExceededError extends Error {
128
+ budgetUsd;
129
+ committedUsd;
130
+ requestedUsd;
131
+ constructor(options) {
132
+ super(`Modal benchmark refused to create "${options.label}": projected $${options.requestedUsd.toFixed(6)} `
133
+ + `on top of $${options.committedUsd.toFixed(6)} would exceed the $${options.budgetUsd.toFixed(6)} cap`);
134
+ this.name = "ModalBudgetExceededError";
135
+ this.budgetUsd = options.budgetUsd;
136
+ this.committedUsd = options.committedUsd;
137
+ this.requestedUsd = options.requestedUsd;
138
+ }
139
+ }
140
+ /** Teardown finished with provider resources still present. */
141
+ export class ModalLeakedSandboxError extends Error {
142
+ leaked;
143
+ constructor(leaked) {
144
+ super(`Modal benchmark leaked ${leaked.length} sandbox(es) that are still present after teardown: `
145
+ + leaked.map((entry) => `${entry.label}=${entry.sandboxId ?? "unknown-id"}`).join(", "));
146
+ this.name = "ModalLeakedSandboxError";
147
+ this.leaked = leaked;
148
+ }
149
+ }
150
+ /**
151
+ * Linear-interpolated percentile over a sorted copy of `values`.
152
+ *
153
+ * Returns `null` for an empty input rather than 0, because "no samples" and
154
+ * "zero milliseconds" are different claims and the second one is a lie.
155
+ */
156
+ export function percentile(values, p) {
157
+ if (values.length === 0) {
158
+ return null;
159
+ }
160
+ if (!Number.isFinite(p) || p < 0 || p > 100) {
161
+ throw new Error(`percentile p must be within [0, 100]; got ${String(p)}`);
162
+ }
163
+ const sorted = [...values].sort((a, b) => a - b);
164
+ if (sorted.length === 1) {
165
+ return sorted[0];
166
+ }
167
+ const rank = (p / 100) * (sorted.length - 1);
168
+ const lower = Math.floor(rank);
169
+ const upper = Math.ceil(rank);
170
+ if (lower === upper) {
171
+ return sorted[lower];
172
+ }
173
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * (rank - lower);
174
+ }
175
+ /**
176
+ * Run the benchmark.
177
+ *
178
+ * Cleanup is unconditional and verified. Even when the run aborts on the
179
+ * budget guard or a create throws, teardown still runs over everything the
180
+ * ledger recorded, and the returned report names anything that survived.
181
+ */
182
+ export async function runModalBenchmark(runtime, options) {
183
+ // Reject a non-integer instead of silently truncating: a caller who passes
184
+ // `count: 2.7` almost certainly typed a wrong number, and running 2 without
185
+ // saying so buys a benchmark for a shape that was never requested.
186
+ if (!Number.isFinite(options.count)
187
+ || !Number.isInteger(options.count)
188
+ || options.count < 1) {
189
+ throw new Error(`Modal benchmark count must be a positive integer; got ${String(options.count)}`);
190
+ }
191
+ const count = options.count;
192
+ // A zero-lifetime projection reserves $0 in the ledger but still creates
193
+ // sandboxes with a positive provider lifetime, so the budget guard cannot
194
+ // stop the run before it bills. Guard here where the value crosses into the
195
+ // guard rather than deep in the estimator.
196
+ if (!Number.isFinite(options.projectedLifetimeSeconds)
197
+ || options.projectedLifetimeSeconds <= 0) {
198
+ throw new Error("Modal benchmark projectedLifetimeSeconds must be a finite, positive number of seconds "
199
+ + "(and should cover the runtime's configured maxLifetimeMs, because a leaked "
200
+ + `sandbox bills for its whole lifetime); got ${String(options.projectedLifetimeSeconds)}`);
201
+ }
202
+ const now = options.now ?? (() => Date.now());
203
+ const ledger = new ModalBenchLedger({ budgetUsd: options.budgetUsd });
204
+ const perSandboxUsd = estimateModalSandboxCostUsd({
205
+ cpuCores: options.shape.cpuCores,
206
+ memoryGiB: options.shape.memoryGiB,
207
+ seconds: options.projectedLifetimeSeconds,
208
+ });
209
+ const samples = [];
210
+ const labels = options.labels ?? {};
211
+ // Only sweep-by-label if the caller supplied labels *and* the runtime can
212
+ // list by them. Without both, a stray sandbox cannot be attributed to this
213
+ // run and the entry stays `create-failed` — the pre-existing shape.
214
+ const canReconcileByLabel = Object.keys(labels).length > 0
215
+ && typeof runtime.findAllByLabels === "function";
216
+ const acquire = async (index) => {
217
+ const label = `bench-${index}`;
218
+ // Budget check and ledger write happen together, before any provider call.
219
+ const entry = ledger.recordIntent(label, perSandboxUsd);
220
+ const startedAt = now();
221
+ let handle;
222
+ try {
223
+ handle = await runtime.launch({ name: label, labels });
224
+ }
225
+ catch (error) {
226
+ entry.error = errorMessage(error);
227
+ // Leave the entry `intended` when a label sweep can reconcile it:
228
+ // `launch` throwing does not prove the provider created nothing, and
229
+ // marking `create-failed` here would hide any sandbox that materialised
230
+ // after the client-side deadline fired. Teardown's sweep either
231
+ // reattaches the stray and destroys it, or promotes the entry to
232
+ // `create-failed` once absence has been checked.
233
+ if (!canReconcileByLabel) {
234
+ entry.state = "create-failed";
235
+ }
236
+ samples.push({ label, createMs: now() - startedAt, error: entry.error });
237
+ return;
238
+ }
239
+ const createMs = now() - startedAt;
240
+ entry.state = "created";
241
+ entry.sandboxId = handle.id;
242
+ let firstExecMs;
243
+ let execError;
244
+ if (options.measureFirstExec) {
245
+ const execStartedAt = now();
246
+ try {
247
+ const result = await runtime.runScript(handle, {
248
+ command: options.firstExecCommand ?? "echo modal-bench-ready",
249
+ });
250
+ // A canary that exits nonzero is a failure, not a latency sample.
251
+ // Recording `firstExecMs` for a failed command would let the harness
252
+ // report a green measurement for a command that never worked.
253
+ if (result.exitCode === 0) {
254
+ firstExecMs = now() - execStartedAt;
255
+ }
256
+ else {
257
+ execError = `first exec exited with code ${String(result.exitCode ?? "unknown")}`;
258
+ }
259
+ }
260
+ catch (error) {
261
+ execError = errorMessage(error);
262
+ }
263
+ }
264
+ samples.push({
265
+ label,
266
+ createMs,
267
+ sandboxId: handle.id,
268
+ ...(firstExecMs === undefined ? {} : { firstExecMs }),
269
+ ...(execError === undefined ? {} : { error: execError }),
270
+ });
271
+ };
272
+ let abortError;
273
+ try {
274
+ if (options.concurrent) {
275
+ // `allSettled`, not `all`: one create failing must not skip teardown for
276
+ // the others, which would be exactly how a concurrency test leaks.
277
+ const results = await Promise.allSettled(Array.from({ length: count }, (_unused, index) => acquire(index)));
278
+ const rejected = results.find((result) => result.status === "rejected");
279
+ if (rejected && rejected.status === "rejected") {
280
+ abortError = rejected.reason;
281
+ }
282
+ }
283
+ else {
284
+ for (let index = 0; index < count; index += 1) {
285
+ await acquire(index);
286
+ }
287
+ }
288
+ }
289
+ catch (error) {
290
+ // A budget breach aborts the run but must never skip teardown.
291
+ abortError = error;
292
+ }
293
+ finally {
294
+ await teardown(runtime, ledger, labels);
295
+ }
296
+ const leaked = ledger.leaked();
297
+ const report = {
298
+ requested: count,
299
+ succeeded: samples.filter((sample) => sample.sandboxId !== undefined && !sample.error).length,
300
+ failed: samples.filter((sample) => sample.error !== undefined).length,
301
+ samples,
302
+ createMsP50: percentile(successfulCreates(samples), 50),
303
+ createMsP95: percentile(successfulCreates(samples), 95),
304
+ firstExecMsP50: percentile(firstExecs(samples), 50),
305
+ firstExecMsP95: percentile(firstExecs(samples), 95),
306
+ projectedCostUsd: ledger.committedUsd(),
307
+ ledger: ledger.all(),
308
+ leaked,
309
+ };
310
+ if (leaked.length > 0) {
311
+ // Loud by design. A silent leak on a metered provider is the one failure
312
+ // mode this harness exists to prevent.
313
+ throw Object.assign(new ModalLeakedSandboxError(leaked), { report });
314
+ }
315
+ if (abortError !== undefined) {
316
+ throw Object.assign(toError(abortError), { report });
317
+ }
318
+ return report;
319
+ }
320
+ /**
321
+ * Destroy everything the ledger knows about, then prove it is gone.
322
+ *
323
+ * Every entry is attempted even if an earlier one throws, because stopping at
324
+ * the first failure would strand the rest.
325
+ */
326
+ async function teardown(runtime, ledger, labels) {
327
+ // Pass 1: destroy sandboxes we already have ids for.
328
+ for (const entry of ledger.outstanding()) {
329
+ if (entry.sandboxId === undefined) {
330
+ continue;
331
+ }
332
+ try {
333
+ await runtime.destroy({ id: entry.sandboxId });
334
+ entry.state = "destroyed";
335
+ }
336
+ catch (error) {
337
+ entry.state = "destroy-failed";
338
+ entry.error = errorMessage(error);
339
+ }
340
+ }
341
+ // Pass 2: reconcile launches that threw without returning a handle. A
342
+ // `launch` rejection does not prove the provider created nothing — a
343
+ // sandbox that landed just after the client-side deadline fired would
344
+ // otherwise leak invisibly. Sweep by the caller's labels, associate any
345
+ // stray with an outstanding no-id entry, and destroy it. This is why the
346
+ // acquire loop leaves such entries `intended` rather than `create-failed`.
347
+ const unaccounted = ledger.outstanding().filter((entry) => entry.sandboxId === undefined);
348
+ const canSweep = unaccounted.length > 0
349
+ && Object.keys(labels).length > 0
350
+ && typeof runtime.findAllByLabels === "function";
351
+ if (canSweep) {
352
+ const knownIds = new Set(ledger.all()
353
+ .map((entry) => entry.sandboxId)
354
+ .filter((id) => id !== undefined));
355
+ let strays = [];
356
+ try {
357
+ strays = (await runtime.findAllByLabels(labels))
358
+ .filter((handle) => !knownIds.has(handle.id));
359
+ }
360
+ catch (error) {
361
+ // A sweep failure is not fatal, but it does mean any remaining no-id
362
+ // entries have to fall through as unverified — see below.
363
+ for (const entry of unaccounted) {
364
+ entry.error = errorMessage(error);
365
+ }
366
+ }
367
+ for (const entry of unaccounted) {
368
+ const match = strays.shift();
369
+ if (match === undefined) {
370
+ // Sweep ran and no matching stray exists: this create truly failed.
371
+ entry.state = "create-failed";
372
+ entry.error ??= "create rejected and no stray sandbox was found by label sweep";
373
+ continue;
374
+ }
375
+ entry.sandboxId = match.id;
376
+ try {
377
+ await runtime.destroy(match);
378
+ entry.state = "destroyed";
379
+ }
380
+ catch (error) {
381
+ entry.state = "destroy-failed";
382
+ entry.error = errorMessage(error);
383
+ }
384
+ }
385
+ }
386
+ else {
387
+ for (const entry of unaccounted) {
388
+ entry.state = "leaked";
389
+ entry.error ??= "create outcome unknown: no sandbox id was ever observed "
390
+ + "and no label sweep was possible";
391
+ }
392
+ }
393
+ // Pass 3: verify. `destroy` returning is not proof; a re-lookup reporting
394
+ // absence is.
395
+ for (const entry of ledger.outstanding()) {
396
+ if (entry.sandboxId === undefined) {
397
+ continue;
398
+ }
399
+ if (typeof runtime.getById !== "function") {
400
+ // Without reattach there is no way to prove absence, so it is not
401
+ // claimed. The entry stays outstanding rather than being upgraded.
402
+ continue;
403
+ }
404
+ try {
405
+ const found = await runtime.getById(entry.sandboxId);
406
+ entry.state = found === null ? "verified-gone" : "leaked";
407
+ }
408
+ catch (error) {
409
+ entry.state = "leaked";
410
+ entry.error = errorMessage(error);
411
+ }
412
+ }
413
+ }
414
+ // --- helpers ---------------------------------------------------------------
415
+ function successfulCreates(samples) {
416
+ return samples.filter((s) => s.sandboxId !== undefined).map((s) => s.createMs);
417
+ }
418
+ function firstExecs(samples) {
419
+ return samples
420
+ .map((s) => s.firstExecMs)
421
+ .filter((value) => value !== undefined);
422
+ }
423
+ function errorMessage(error) {
424
+ return error instanceof Error ? error.message : String(error);
425
+ }
426
+ function toError(error) {
427
+ return error instanceof Error ? error : new Error(String(error));
428
+ }
429
+ function requireFinitePositive(value, field) {
430
+ if (!Number.isFinite(value) || value <= 0) {
431
+ throw new Error(`Modal benchmark ${field} must be a finite, positive number; got ${String(value)}`);
432
+ }
433
+ }
434
+ function requireFiniteNonNegative(value, field) {
435
+ if (!Number.isFinite(value) || value < 0) {
436
+ throw new Error(`Modal benchmark ${field} must be a finite, non-negative number; got ${String(value)}`);
437
+ }
438
+ }
439
+ /**
440
+ * Modal charges as if the reservation were at least its per-container floor.
441
+ * Accepting a below-floor CPU value in the estimator would understate spend
442
+ * and let the budget guard be walked past.
443
+ */
444
+ function requireCpuAtLeastFloor(cpuCores) {
445
+ if (!Number.isFinite(cpuCores) || cpuCores < MODAL_MIN_CPU_CORES) {
446
+ throw new Error(`Modal benchmark cpuCores must be a finite number >= ${MODAL_MIN_CPU_CORES} `
447
+ + `physical cores (Modal's per-container floor); got ${String(cpuCores)}`);
448
+ }
449
+ }
450
+ //# sourceMappingURL=bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bench.js","sourceRoot":"","sources":["../../src/modal/bench.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * SDK-free capability metadata for the Modal adapter.
3
+ *
4
+ * House rule: a behavioral claim stays `false` until a live probe against the
5
+ * pinned SDK establishes it. "The SDK has a method for it" is not evidence —
6
+ * a method can exist and degrade, and a capability means *reachable through
7
+ * this package's port*, not *present in the vendor surface*.
8
+ */
9
+ export declare const modalSandboxCapabilities: {
10
+ /**
11
+ * Modal genuinely has server-side label search: `sandboxes.list({ tags })`
12
+ * returns only sandboxes carrying all the given tags, and the adapter routes
13
+ * every lookup through it. That is a real warm-lease implementation, unlike
14
+ * providers where `findAllByLabels` degrades to `[]`.
15
+ *
16
+ * It is nevertheless declared `false` until the live canary proves that a
17
+ * tagged sandbox is actually returned by a tag-filtered list. Promoting it
18
+ * on the strength of a type signature is exactly the inference this field
19
+ * exists to prevent, and `false` fails in the safe direction: the
20
+ * orchestrator declines to rely on warm leases rather than leaking them.
21
+ */
22
+ readonly warmLease: false;
23
+ /**
24
+ * Permanently false, and not pending verification.
25
+ *
26
+ * Modal exposes no stop/start for a Sandbox. `terminate()` is the only
27
+ * lifecycle transition and it is terminal — there is no suspend, resume, or
28
+ * wake. The adapter therefore omits `start`/`stop` entirely rather than
29
+ * shipping no-ops, and this flag records the structural fact for the
30
+ * capability resolver, which cannot see an absent method.
31
+ */
32
+ readonly lifecycle: false;
33
+ };
34
+ /**
35
+ * Bootstrap-plane capabilities. Note this is `RuntimeCapabilities` from
36
+ * `../types.js` — the live in-sandbox plane — and NOT the orchestration-plane
37
+ * `SandboxRuntimeCapabilities`. The two are deliberately distinct.
38
+ */
39
+ export declare const modalWorkflowCapabilities: {
40
+ /**
41
+ * Modal supports PTY on both `create` and `exec` (`pty: true`, which
42
+ * multiplexes stderr into stdout). This package's port exposes no PTY
43
+ * operation, so it is not reachable here.
44
+ */
45
+ readonly pty: false;
46
+ /**
47
+ * Modal exposes real filesystem snapshots — `snapshotFilesystem()` and
48
+ * `snapshotDirectory(path)`, both returning an `Image` reusable as a create
49
+ * source (30-day default TTL, `ttlMs: null` to retain indefinitely). This
50
+ * package's port exposes no snapshot operation, so the capability is not
51
+ * reachable through it. Documented here so the surface is not lost.
52
+ */
53
+ readonly snapshots: false;
54
+ /** Modal Sandboxes are gVisor-isolated containers with their own filesystem. */
55
+ readonly isolation: "strong";
56
+ /** A sandbox id survives the client: `sandboxes.fromId(id)` re-resolves it. */
57
+ readonly persistentHandle: true;
58
+ /**
59
+ * `exec()` returns live `stdout`/`stderr` as `ReadableStream`s, so Modal can
60
+ * genuinely stream. The adapter drains them to completion to satisfy the
61
+ * port's buffered `RunScriptResult`, and the port exposes no streaming
62
+ * surface, so this is false *for this port* while true of the provider.
63
+ */
64
+ readonly streamingLogs: false;
65
+ };
66
+ /**
67
+ * Cells proven by a live run against Modal. Everything starts `false` and is
68
+ * promoted only by an observation recorded in the adapter's docs, never by
69
+ * reading the SDK's types.
70
+ */
71
+ export type ModalObservedCapabilities = {
72
+ /** A terminated sandbox verifiably left the account, checked by re-lookup. */
73
+ cleanupVerified: boolean;
74
+ /** `snapshotFilesystem` produced an Image that booted a new sandbox. */
75
+ snapshotCapture: boolean;
76
+ /** Tag-filtered `list()` returned a sandbox we tagged at create. */
77
+ warmLease: boolean;
78
+ /** `fromId` re-resolved a sandbox created by a different client instance. */
79
+ reattach: boolean;
80
+ /** stop -> settled -> start -> exec succeeded. Structurally impossible on Modal. */
81
+ lifecycle: boolean;
82
+ /**
83
+ * A sandbox can be kept alive indefinitely with no termination deadline.
84
+ *
85
+ * Structurally false on Modal, like {@link lifecycle}, and for the same kind
86
+ * of reason: *every* Modal Sandbox carries a maximum lifetime. It defaults to
87
+ * five minutes, it can be raised only as far as 24 hours, and the provider
88
+ * terminates the sandbox when it elapses. There is no "no deadline" setting.
89
+ * Past 24 hours Modal's own guidance is to snapshot the filesystem and
90
+ * restore it into a *new* sandbox, which is a rebuild rather than a
91
+ * continuation. No live run can promote this.
92
+ */
93
+ neverIdle: boolean;
94
+ /** A sandbox outlived Modal's 5-minute default because we set maxLifetimeMs. */
95
+ lifetimeOverride: boolean;
96
+ /** Concurrent creates all reached ready without provider-side throttling. */
97
+ concurrencyCeiling: boolean;
98
+ };
99
+ /** Nothing promoted yet: no live run has happened. Awaiting credentials. */
100
+ export declare const modalObservedCapabilities: ModalObservedCapabilities;
101
+ /**
102
+ * Cells that are **settled structural facts**, not pending observations.
103
+ *
104
+ * The distinction matters for review: everything else in
105
+ * {@link modalObservedCapabilities} is `false` only because no live run has
106
+ * happened yet and will flip when one does. These two are `false` because the
107
+ * provider cannot do the thing at all, so a future canary must not "promote"
108
+ * them and a future reader must not try to "fix" them.
109
+ */
110
+ export declare const MODAL_STRUCTURALLY_FALSE: readonly ["lifecycle", "neverIdle"];
111
+ /**
112
+ * Structured capability modes for the Modal adapter.
113
+ *
114
+ * Every cell below restates, in the type system, something this file already
115
+ * documented in prose. That is the whole reason to fill them in: the booleans
116
+ * above cannot distinguish "Modal cannot do this" from "Modal can, but our port
117
+ * exposes no operation reaching it" from "nobody has checked yet", and this
118
+ * adapter is the one that had to work around that by hand — see
119
+ * {@link MODAL_STRUCTURALLY_FALSE}.
120
+ *
121
+ * Note what is deliberately *not* here: `warmLease` stays a `false` boolean
122
+ * pending the live canary. Modes describe the shape of a capability, not its
123
+ * verification state, so a mode is not a back door for promoting a cell that
124
+ * the house rule keeps unproven.
125
+ */
126
+ export declare const modalCapabilityModes: {
127
+ /**
128
+ * `buffered`, not `separate-streams`.
129
+ *
130
+ * Modal's `exec()` does return live `stdout`/`stderr` as separate
131
+ * `ReadableStream`s, and `runScript` does hand back separated `stdout` and
132
+ * `stderr` fields. But both streaming members of the union require output to
133
+ * be *streamed live*, and the adapter drains both pipes to completion with
134
+ * `readText()` before it returns anything. Separated-after-the-fact is still
135
+ * buffered. Declaring `separate-streams` would promise callers an incremental
136
+ * channel this port does not have.
137
+ */
138
+ readonly outputStreams: "buffered";
139
+ /**
140
+ * Anything written is lost when the sandbox goes away, and on Modal it always
141
+ * goes away: `terminate()` is the only lifecycle transition and it is
142
+ * terminal. There is no stop/start pair for state to survive across, so
143
+ * `persistent` cannot apply.
144
+ */
145
+ readonly filesystem: "ephemeral";
146
+ /**
147
+ * The structural fact behind `neverIdle: false` in
148
+ * {@link MODAL_STRUCTURALLY_FALSE}, now stated in the type rather than in a
149
+ * hand-maintained list. *Every* Modal Sandbox carries a maximum lifetime —
150
+ * five minutes by default, 24 hours at the ceiling (`MODAL_MAX_LIFETIME_MS`),
151
+ * and the provider terminates it when that elapses. There is no "no deadline"
152
+ * setting, so no live run can ever move this.
153
+ *
154
+ * `deadline` says that on its own; per the union's own note, a provider that
155
+ * always terminates at a deadline cannot offer a never-idle tier, so this
156
+ * needs no separate `unsupported`.
157
+ */
158
+ readonly lifetime: "deadline";
159
+ /**
160
+ * `not-exposed`, emphatically not `unsupported`: Modal supports PTY on both
161
+ * `create` and `exec` (`pty: true`). This package's port declares no PTY
162
+ * operation, so it is unreachable *here*. It moves if someone adds one — and
163
+ * `isPendingEvidence()` correctly reports false for it, because no canary
164
+ * against Modal can change a fact about our own port surface.
165
+ */
166
+ readonly interactive: "not-exposed";
167
+ /**
168
+ * Also `not-exposed`. Modal has real filesystem snapshots —
169
+ * `snapshotFilesystem()` and `snapshotDirectory(path)`, both returning an
170
+ * `Image` reusable as a create source. The port exposes no snapshot
171
+ * operation, so the capability is not reachable through it.
172
+ *
173
+ * This is exactly the cell the absence vocabulary was added to protect:
174
+ * `modalObservedCapabilities.snapshotCapture` is a *pending* observation
175
+ * about the provider, while this is a *settled* fact about our port. A canary
176
+ * may promote that one; it must never promote this one.
177
+ */
178
+ readonly snapshots: "not-exposed";
179
+ };
180
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/modal/capabilities.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB;IACnC;;;;;;;;;;;OAWG;;IAEH;;;;;;;;OAQG;;CAEkD,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB;IACpC;;;;OAIG;;IAEH;;;;;;OAMG;;IAEH,gFAAgF;;IAEhF,+EAA+E;;IAE/E;;;;;OAKG;;CAEmC,CAAC;AAEzC;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,eAAe,EAAE,OAAO,CAAC;IACzB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,CAAC;IAClB,oFAAoF;IACpF,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;;;;OAUG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,gFAAgF;IAChF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,6EAA6E;IAC7E,kBAAkB,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,EAAE,yBASvC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,qCAG8B,CAAC;AAEpE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB;IAC/B;;;;;;;;;;OAUG;;IAEH;;;;;OAKG;;IAEH;;;;;;;;;;;OAWG;;IAEH;;;;;;OAMG;;IAEH;;;;;;;;;;OAUG;;CAEsC,CAAC"}