@tangle-network/sandbox 0.9.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,6 +50,34 @@ If sandbox creation hangs or times out, do not generate fallback code that
50
50
  pretends to have run in a sandbox. Surface the failure, keep the `sandboxId` or
51
51
  request id, and retry with the same idempotency key when available.
52
52
 
53
+ ### Temporary GPU for evals
54
+
55
+ Keep the base sandbox cheap and attach a GPU only around the command that needs it.
56
+ Omit `provider` to let the platform choose the cheapest configured GPU cloud.
57
+ Every GPU lease needs a hard spend cap and lifetime.
58
+
59
+ ```typescript
60
+ const lease = await box.gpu.attach({
61
+ accelerator: { kind: "nvidia-3090", count: 1, memoryMB: 24_000 },
62
+ maxSpendUsd: 1,
63
+ maxLifetimeSeconds: 900,
64
+ idleTimeoutSeconds: 120,
65
+ });
66
+
67
+ try {
68
+ const result = await box.gpu.exec(lease.id, {
69
+ command: "python eval.py",
70
+ timeoutMs: 15 * 60 * 1000,
71
+ });
72
+ console.log(result.result.stdout);
73
+ } finally {
74
+ await box.gpu.detach(lease.id);
75
+ }
76
+ ```
77
+
78
+ The detached lease includes `billing.seconds`, `billing.customerCostUsd`, and provider cost basis fields once billing is finalized.
79
+ See `examples/gpu-eval-lease.ts` for the full create, run, detach, and sandbox cleanup flow.
80
+
53
81
  ## Installation
54
82
 
55
83
  ```bash
@@ -1,5 +1,5 @@
1
- import { G as CodeExecutionOptions, K as CodeExecutionResult, Y as CodeResultPart, n as SandboxInstance, q as CodeLanguage } from "../sandbox-DcOyvnIy.js";
2
- import { i as SandboxClient } from "../client-DUY6Pdwf.js";
1
+ import { $ as CodeExecutionResult, Q as CodeExecutionOptions, et as CodeLanguage, n as SandboxInstance, nt as CodeResultPart } from "../sandbox-Bm2C9Phd.js";
2
+ import { i as SandboxClient } from "../client-cfdZckhU.js";
3
3
  import * as _$_modelcontextprotocol_sdk_server_index_js0 from "@modelcontextprotocol/sdk/server/index.js";
4
4
 
5
5
  //#region src/agent/tools/_specs.d.ts
@@ -1,39 +1,78 @@
1
- import { createHmac, timingSafeEqual } from "node:crypto";
2
1
  //#region src/auth/tokens.ts
2
+ const textEncoder = new TextEncoder();
3
+ const textDecoder = new TextDecoder();
3
4
  /**
4
- * JWT Token Utilities
5
- *
6
- * Token generation and verification using HMAC-SHA256. Server-only
7
- * (uses Node.js `crypto`).
8
- */
9
- /**
10
- * Base64URL encode (RFC 7515).
5
+ * Base64URL encode (RFC 7515). Isomorphic: `btoa` + `TextEncoder` exist in
6
+ * Node and browsers, so — unlike `Buffer` — this never blanks a browser page
7
+ * that transitively imports the module.
11
8
  */
12
9
  function base64UrlEncode(data) {
13
- return (typeof data === "string" ? Buffer.from(data) : data).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
10
+ const bytes = typeof data === "string" ? textEncoder.encode(data) : data;
11
+ let binary = "";
12
+ for (const byte of bytes) binary += String.fromCharCode(byte);
13
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
14
14
  }
15
15
  /**
16
- * Base64URL decode (RFC 7515) to raw bytes. Returns `null` if the
17
- * input contains characters outside the base64url alphabet.
16
+ * Base64URL decode (RFC 7515) to raw bytes. Returns `null` if the input is
17
+ * not valid base64url (characters outside the alphabet, or a length that
18
+ * cannot decode).
18
19
  */
19
- function decodeBase64UrlToBuffer(input) {
20
+ function decodeBase64UrlToBytes(input) {
20
21
  if (!/^[A-Za-z0-9_-]*$/.test(input)) return null;
21
22
  const padded = input + "=".repeat((4 - input.length % 4) % 4);
22
- return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64");
23
+ let binary;
24
+ try {
25
+ binary = atob(padded.replace(/-/g, "+").replace(/_/g, "/"));
26
+ } catch {
27
+ return null;
28
+ }
29
+ const bytes = new Uint8Array(binary.length);
30
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
31
+ return bytes;
32
+ }
33
+ /**
34
+ * Base64URL decode to a UTF-8 string, or `null` if the input is not valid
35
+ * base64url.
36
+ */
37
+ function decodeBase64UrlToString(input) {
38
+ const bytes = decodeBase64UrlToBytes(input);
39
+ return bytes === null ? null : textDecoder.decode(bytes);
40
+ }
41
+ let nodeCrypto;
42
+ /**
43
+ * Resolve Node's `crypto` lazily and synchronously. Signing and verification
44
+ * are server-only (HMAC-SHA256), but this module must stay importable in a
45
+ * browser bundle — a static `import "node:crypto"` forces bundlers to resolve
46
+ * the builtin at load and blanks the page. `process.getBuiltinModule` exists
47
+ * only on a Node runtime (Node >= 22.3); in a browser `globalThis.process` is
48
+ * undefined, so callers get a clear server-only error instead of a crash.
49
+ */
50
+ function getNodeCrypto() {
51
+ if (nodeCrypto) return nodeCrypto;
52
+ const resolved = globalThis.process?.getBuiltinModule?.("node:crypto");
53
+ if (!resolved) throw new Error("@tangle-network/sandbox/auth: token signing and verification are server-only (HMAC-SHA256 via Node.js crypto) and cannot run in a browser.");
54
+ nodeCrypto = resolved;
55
+ return nodeCrypto;
23
56
  }
24
57
  /**
25
58
  * Create HMAC-SHA256 signature.
26
59
  */
27
60
  function createSignature(data, secret) {
61
+ const { createHmac } = getNodeCrypto();
28
62
  return base64UrlEncode(createHmac("sha256", secret).update(data).digest());
29
63
  }
30
64
  /**
31
- * JWT header (always the same for our use case).
65
+ * Base64URL-encoded JWT header `{"alg":"HS256","typ":"JWT"}`. Computed on
66
+ * first use (not at module load) and memoized.
32
67
  */
33
- const JWT_HEADER = base64UrlEncode(JSON.stringify({
34
- alg: "HS256",
35
- typ: "JWT"
36
- }));
68
+ let jwtHeaderCache;
69
+ function jwtHeader() {
70
+ if (jwtHeaderCache === void 0) jwtHeaderCache = base64UrlEncode(JSON.stringify({
71
+ alg: "HS256",
72
+ typ: "JWT"
73
+ }));
74
+ return jwtHeaderCache;
75
+ }
37
76
  function issueToken(signingSecret, payload, ttlMinutes) {
38
77
  const now = Math.floor(Date.now() / 1e3);
39
78
  const fullPayload = {
@@ -41,7 +80,8 @@ function issueToken(signingSecret, payload, ttlMinutes) {
41
80
  iat: now,
42
81
  exp: now + ttlMinutes * 60
43
82
  };
44
- const data = `${JWT_HEADER}.${base64UrlEncode(JSON.stringify(fullPayload))}`;
83
+ const encodedPayload = base64UrlEncode(JSON.stringify(fullPayload));
84
+ const data = `${jwtHeader()}.${encodedPayload}`;
45
85
  return `${data}.${createSignature(data, signingSecret)}`;
46
86
  }
47
87
  /**
@@ -119,8 +159,8 @@ function unsafeDecodeToken(token) {
119
159
  if (parts.length !== 3) return null;
120
160
  const payload = parts[1];
121
161
  if (payload === void 0) return null;
122
- const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
123
- const decoded = Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString();
162
+ const decoded = decodeBase64UrlToString(payload);
163
+ if (decoded === null) return null;
124
164
  return JSON.parse(decoded);
125
165
  } catch {
126
166
  return null;
@@ -145,20 +185,21 @@ function verifyToken(token, signingSecret, options = {}) {
145
185
  if (parts.length !== 3) return null;
146
186
  const [headerB64, payloadB64, signatureB64] = parts;
147
187
  if (!headerB64 || !payloadB64 || !signatureB64) return null;
188
+ const headerJson = decodeBase64UrlToString(headerB64);
189
+ if (headerJson === null) return null;
148
190
  let header;
149
191
  try {
150
- const headerPadded = headerB64 + "=".repeat((4 - headerB64.length % 4) % 4);
151
- const headerJson = Buffer.from(headerPadded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString();
152
192
  header = JSON.parse(headerJson);
153
193
  } catch {
154
194
  return null;
155
195
  }
156
196
  if (header.alg !== "HS256") return null;
157
197
  const expectedSig = createSignature(`${headerB64}.${payloadB64}`, signingSecret);
158
- const providedRaw = decodeBase64UrlToBuffer(signatureB64);
159
- const expectedRaw = decodeBase64UrlToBuffer(expectedSig);
198
+ const providedRaw = decodeBase64UrlToBytes(signatureB64);
199
+ const expectedRaw = decodeBase64UrlToBytes(expectedSig);
160
200
  if (!providedRaw || !expectedRaw) return null;
161
201
  if (providedRaw.length !== expectedRaw.length) return null;
202
+ const { timingSafeEqual } = getNodeCrypto();
162
203
  if (!timingSafeEqual(providedRaw, expectedRaw)) return null;
163
204
  const payload = unsafeDecodeToken(token);
164
205
  if (!payload) return null;
@@ -1,5 +1,5 @@
1
- import { f as encodePromptForWire, l as exportTraceBundle, p as parseSSEStream, s as normalizeConnection, t as SandboxInstance } from "./sandbox-UQrmff8d.js";
2
- import { d as ValidationError, f as parseErrorResponse, i as NotFoundError, r as NetworkError, t as AuthError, u as TimeoutError } from "./errors-DZsfJUuc.js";
1
+ import { d as exportTraceBundle, g as parseSSEStream, l as normalizeConnection, m as encodePromptForWire, n as normalizeStartupDiagnostics, t as SandboxInstance } from "./sandbox-CeimsfC8.js";
2
+ import { a as NotFoundError, d as TimeoutError, f as ValidationError, i as NetworkError, p as parseErrorResponse, t as AuthError } from "./errors-DSz87Rkk.js";
3
3
  //#region src/resources.ts
4
4
  /**
5
5
  * Compute tiers, ordered smallest → largest. Defined HERE (not imported from
@@ -140,6 +140,7 @@ function mergeCreateOptions(defaults, machine) {
140
140
  }
141
141
  } : void 0;
142
142
  const driver = machine.driver ?? defaults?.driver;
143
+ const gpuLease = mergeGpuLeaseOptions(defaults?.gpuLease, machine.gpuLease);
143
144
  return {
144
145
  ...defaults,
145
146
  ...rest,
@@ -152,7 +153,26 @@ function mergeCreateOptions(defaults, machine) {
152
153
  ...machine.env
153
154
  },
154
155
  backend,
155
- driver
156
+ driver,
157
+ gpuLease
158
+ };
159
+ }
160
+ function mergeGpuLeaseOptions(defaults, machine) {
161
+ if (!defaults && !machine) return void 0;
162
+ const maxSpendUsd = machine?.maxSpendUsd ?? defaults?.maxSpendUsd;
163
+ const maxLifetimeSeconds = machine?.maxLifetimeSeconds ?? defaults?.maxLifetimeSeconds;
164
+ if (maxSpendUsd === void 0 || maxLifetimeSeconds === void 0) return;
165
+ const env = defaults?.env || machine?.env ? {
166
+ ...defaults?.env,
167
+ ...machine?.env
168
+ } : void 0;
169
+ return {
170
+ ...machine?.provider ?? defaults?.provider ? { provider: machine?.provider ?? defaults?.provider } : {},
171
+ ...machine?.image ?? defaults?.image ? { image: machine?.image ?? defaults?.image } : {},
172
+ ...env ? { env } : {},
173
+ maxSpendUsd,
174
+ maxLifetimeSeconds,
175
+ ...machine?.idleTimeoutSeconds ?? defaults?.idleTimeoutSeconds ? { idleTimeoutSeconds: machine?.idleTimeoutSeconds ?? defaults?.idleTimeoutSeconds } : {}
156
176
  };
157
177
  }
158
178
  function assertPolicy(machines, defaults, policy) {
@@ -165,17 +185,21 @@ function assertPolicy(machines, defaults, policy) {
165
185
  let totalMemoryMb = 0;
166
186
  let totalStorageMb = 0;
167
187
  let totalAccelerators = 0;
188
+ let totalGpuLeaseMaxSpendUsd = 0;
168
189
  for (const machine of machines) {
169
- const resources = mergeCreateOptions(defaults, machine).resources;
190
+ const options = mergeCreateOptions(defaults, machine);
191
+ const resources = options.resources;
170
192
  totalCpu += resources?.cpuCores ?? 0;
171
193
  totalMemoryMb += resources?.memoryMB ?? 0;
172
194
  totalStorageMb += resources?.diskGB ? resources.diskGB * 1024 : 0;
173
195
  totalAccelerators += resources?.accelerator?.count ?? (resources?.accelerator ? 1 : 0);
196
+ if (options.gpuLease) totalGpuLeaseMaxSpendUsd += options.gpuLease.maxSpendUsd;
174
197
  }
175
198
  if (policy?.maxTotalCpu !== void 0 && totalCpu > policy.maxTotalCpu) throw new ValidationError(`Fleet create requested ${totalCpu} CPU cores but policy.maxTotalCpu is ${policy.maxTotalCpu}`, { "policy.maxTotalCpu": "exceeded" });
176
199
  if (policy?.maxTotalMemoryMb !== void 0 && totalMemoryMb > policy.maxTotalMemoryMb) throw new ValidationError(`Fleet create requested ${totalMemoryMb} MB RAM but policy.maxTotalMemoryMb is ${policy.maxTotalMemoryMb}`, { "policy.maxTotalMemoryMb": "exceeded" });
177
200
  if (policy?.maxTotalStorageMb !== void 0 && totalStorageMb > policy.maxTotalStorageMb) throw new ValidationError(`Fleet create requested ${totalStorageMb} MB storage but policy.maxTotalStorageMb is ${policy.maxTotalStorageMb}`, { "policy.maxTotalStorageMb": "exceeded" });
178
201
  if (policy?.maxTotalAccelerators !== void 0 && totalAccelerators > policy.maxTotalAccelerators) throw new ValidationError(`Fleet create requested ${totalAccelerators} accelerator devices but policy.maxTotalAccelerators is ${policy.maxTotalAccelerators}`, { "policy.maxTotalAccelerators": "exceeded" });
202
+ if (policy?.maxSpendUsd !== void 0 && totalGpuLeaseMaxSpendUsd > policy.maxSpendUsd) throw new ValidationError(`Fleet GPU lease max spend $${totalGpuLeaseMaxSpendUsd} exceeds policy.maxSpendUsd $${policy.maxSpendUsd}`, { "policy.maxSpendUsd": "exceeded" });
179
203
  const maxLifetimeSeconds = Math.max(...machines.map((machine) => mergeCreateOptions(defaults, machine).maxLifetimeSeconds ?? DEFAULT_MACHINE_LIFETIME_SECONDS));
180
204
  if (policy?.maxLifetimeSeconds !== void 0 && maxLifetimeSeconds > policy.maxLifetimeSeconds) throw new ValidationError(`Fleet create requested ${maxLifetimeSeconds}s max lifetime but policy.maxLifetimeSeconds is ${policy.maxLifetimeSeconds}`, { "policy.maxLifetimeSeconds": "exceeded" });
181
205
  for (const machine of machines) {
@@ -184,14 +208,20 @@ function assertPolicy(machines, defaults, policy) {
184
208
  }
185
209
  }
186
210
  function assertMachineDescriptorPolicy(machineId, options, policy) {
187
- if (!policy) return;
188
211
  const driverType = options.driver?.type;
189
- if (policy.allowedDrivers && driverType && !policy.allowedDrivers.includes(driverType)) throw new ValidationError(`Fleet machine '${machineId}' requested driver '${driverType}' outside policy.allowedDrivers`, { "policy.allowedDrivers": "exceeded" });
212
+ if (policy?.allowedDrivers && driverType && !policy.allowedDrivers.includes(driverType)) throw new ValidationError(`Fleet machine '${machineId}' requested driver '${driverType}' outside policy.allowedDrivers`, { "policy.allowedDrivers": "exceeded" });
190
213
  const image = options.environment ?? options.image;
191
- if (policy.allowedImages && image && !policy.allowedImages.includes(image)) throw new ValidationError(`Fleet machine '${machineId}' requested image '${image}' outside policy.allowedImages`, { "policy.allowedImages": "exceeded" });
214
+ if (policy?.allowedImages && image && !policy.allowedImages.includes(image)) throw new ValidationError(`Fleet machine '${machineId}' requested image '${image}' outside policy.allowedImages`, { "policy.allowedImages": "exceeded" });
192
215
  const templateId = options.templateId ?? options.publicTemplateId;
193
- if (policy.allowedTemplateIds && templateId && !policy.allowedTemplateIds.includes(templateId)) throw new ValidationError(`Fleet machine '${machineId}' requested template '${templateId}' outside policy.allowedTemplateIds`, { "policy.allowedTemplateIds": "exceeded" });
194
- if (policy.allowAccelerators === false && (options.resources?.accelerator?.count ?? (options.resources?.accelerator ? 1 : 0)) > 0) throw new ValidationError(`Fleet machine '${machineId}' requested accelerators but policy.allowAccelerators is false`, { "policy.allowAccelerators": "exceeded" });
216
+ if (policy?.allowedTemplateIds && templateId && !policy.allowedTemplateIds.includes(templateId)) throw new ValidationError(`Fleet machine '${machineId}' requested template '${templateId}' outside policy.allowedTemplateIds`, { "policy.allowedTemplateIds": "exceeded" });
217
+ if (policy?.allowAccelerators === false && (options.resources?.accelerator?.count ?? (options.resources?.accelerator ? 1 : 0)) > 0) throw new ValidationError(`Fleet machine '${machineId}' requested accelerators but policy.allowAccelerators is false`, { "policy.allowAccelerators": "exceeded" });
218
+ const acceleratorCount = options.resources?.accelerator?.count ?? (options.resources?.accelerator ? 1 : 0);
219
+ if (acceleratorCount > 0 && !options.gpuLease) throw new ValidationError(`Fleet machine '${machineId}' requested accelerators but gpuLease is missing`, { gpuLease: "required" });
220
+ if (acceleratorCount === 0 && options.gpuLease) throw new ValidationError(`Fleet machine '${machineId}' set gpuLease without resources.accelerator`, { gpuLease: "unexpected" });
221
+ if (options.gpuLease) {
222
+ if (!Number.isFinite(options.gpuLease.maxSpendUsd) || options.gpuLease.maxSpendUsd <= 0) throw new ValidationError(`Fleet machine '${machineId}' gpuLease.maxSpendUsd must be positive`, { "gpuLease.maxSpendUsd": "invalid" });
223
+ if (!Number.isInteger(options.gpuLease.maxLifetimeSeconds) || options.gpuLease.maxLifetimeSeconds <= 0) throw new ValidationError(`Fleet machine '${machineId}' gpuLease.maxLifetimeSeconds must be a positive integer`, { "gpuLease.maxLifetimeSeconds": "invalid" });
224
+ }
195
225
  }
196
226
  function calculateResourceTotals(machines, defaults) {
197
227
  let totalCpu = 0;
@@ -933,6 +963,7 @@ const DEFAULT_TIMEOUT_MS = 3e4;
933
963
  * safe server-side, but polling is cheaper and avoids duplicate work).
934
964
  */
935
965
  const DEFAULT_CREATE_TIMEOUT_MS = 12e4;
966
+ const DEFAULT_GPU_CREATE_TIMEOUT_MS = 900 * 1e3;
936
967
  /**
937
968
  * Extra grace the SDK's client-side batch deadline gets on top of
938
969
  * the server-side `timeoutMs`. The server's clock starts when the
@@ -1136,6 +1167,15 @@ var SandboxClient = class {
1136
1167
  if (!this._sshKeys) this._sshKeys = new SshKeysManagerImpl(this);
1137
1168
  return this._sshKeys;
1138
1169
  }
1170
+ /** List agent profiles registered by the Sandbox API. */
1171
+ async listProfiles() {
1172
+ const response = await this.fetch("/profiles");
1173
+ if (!response.ok) {
1174
+ const body = await response.text();
1175
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1176
+ }
1177
+ return (await response.json()).profiles ?? [];
1178
+ }
1139
1179
  _environments = null;
1140
1180
  /**
1141
1181
  * Access available development environments.
@@ -1194,7 +1234,7 @@ var SandboxClient = class {
1194
1234
  * ```
1195
1235
  */
1196
1236
  async create(options, requestOptions) {
1197
- const timeoutMs = requestOptions?.timeoutMs ?? DEFAULT_CREATE_TIMEOUT_MS;
1237
+ const timeoutMs = requestOptions?.timeoutMs ?? (options?.gpuLease ? DEFAULT_GPU_CREATE_TIMEOUT_MS : DEFAULT_CREATE_TIMEOUT_MS);
1198
1238
  const idempotencyKey = options?.idempotencyKey ?? generateIdempotencyKey();
1199
1239
  const deadline = Date.now() + timeoutMs;
1200
1240
  const git = options?.git ? {
@@ -1228,11 +1268,13 @@ var SandboxClient = class {
1228
1268
  git,
1229
1269
  tools: options?.tools,
1230
1270
  bare: options?.bare,
1271
+ ...options?.publicEdge === false ? { publicEdge: false } : {},
1231
1272
  driver: options?.driver,
1232
1273
  backend: resolvedBackend,
1233
1274
  permissions: options?.permissions,
1234
1275
  env: options?.env,
1235
1276
  resources,
1277
+ gpuLease: options?.gpuLease,
1236
1278
  sshEnabled: options?.sshEnabled,
1237
1279
  sshPublicKeys: options?.sshPublicKeys ?? (options?.sshPublicKey ? [options.sshPublicKey] : void 0),
1238
1280
  sshKeyIds: options?.sshKeyIds,
@@ -1240,6 +1282,7 @@ var SandboxClient = class {
1240
1282
  maxLifetimeSeconds: options?.maxLifetimeSeconds,
1241
1283
  idleTimeoutSeconds: options?.idleTimeoutSeconds,
1242
1284
  storage: options?.storage,
1285
+ ephemeral: options?.ephemeral,
1243
1286
  fromSnapshot: options?.fromSnapshot,
1244
1287
  fromSandboxId: options?.fromSandboxId,
1245
1288
  templateId: options?.templateId,
@@ -1373,6 +1416,9 @@ var SandboxClient = class {
1373
1416
  const data = await response.json();
1374
1417
  return {
1375
1418
  computeMinutes: data.computeMinutes ?? 0,
1419
+ gpuSeconds: data.gpuSeconds ?? 0,
1420
+ gpuCostUsd: data.gpuCostUsd ?? 0,
1421
+ gpuProviderCostUsd: data.gpuProviderCostUsd ?? 0,
1376
1422
  activeSandboxes: data.activeSandboxes ?? 0,
1377
1423
  totalSandboxes: data.totalSandboxes ?? 0,
1378
1424
  periodStart: new Date(data.periodStart),
@@ -1452,39 +1498,12 @@ var SandboxClient = class {
1452
1498
  }
1453
1499
  return await response.json();
1454
1500
  }
1455
- /**
1456
- * Run multiple tasks in parallel across sandboxes.
1457
- * Returns the aggregated results after all tasks complete.
1458
- *
1459
- * @param tasks - Array of tasks to execute
1460
- * @param options - Batch execution options
1461
- * @returns Aggregated batch results
1462
- *
1463
- * @throws {@link TimeoutError} if the server hasn't begun responding
1464
- * before the deadline (pre-stream timeout).
1465
- * @throws {@link ValidationError} / {@link QuotaError} / other typed
1466
- * SDK errors if the server rejects the request before streaming.
1467
- * @throws `DOMException` with `name === "AbortError"` if
1468
- * `options.signal` fires OR the safety-valve deadline fires
1469
- * AFTER streaming has begun — parseSSEStream can't distinguish
1470
- * the source mid-stream. Callers that need to tell cancel from
1471
- * timeout should check `options.signal?.aborted` in their catch.
1472
- * @throws `Error` with the server message if the stream emits a
1473
- * `batch.failed` event.
1474
- *
1475
- * @example
1476
- * ```typescript
1477
- * const result = await client.runBatch([
1478
- * { id: "task-1", message: "Analyze this file" },
1479
- * { id: "task-2", message: "Generate a summary" },
1480
- * ]);
1481
- * console.log(`Success rate: ${result.successRate}%`);
1482
- * ```
1483
- */
1484
- async runBatch(tasks, options) {
1501
+ async runBatch(input, options) {
1502
+ const tasks = Array.isArray(input) ? input : input.tasks;
1485
1503
  const results = [];
1486
1504
  let totalRetries = 0;
1487
- for await (const event of this.streamBatch(tasks, options)) {
1505
+ const stream = Array.isArray(input) ? this.streamBatch(input, options) : this.streamBatch(input, options);
1506
+ for await (const event of stream) {
1488
1507
  if (event.type === "task.completed") {
1489
1508
  const data = event.data;
1490
1509
  const usageTotal = (data.usage?.inputTokens ?? 0) + (data.usage?.outputTokens ?? 0);
@@ -1494,7 +1513,12 @@ var SandboxClient = class {
1494
1513
  response: data.resultSummary ?? data.response,
1495
1514
  durationMs: data.durationMs ?? 0,
1496
1515
  retries: data.retries ?? 0,
1497
- tokensUsed: data.tokensUsed ?? (usageTotal > 0 ? usageTotal : void 0)
1516
+ tokensUsed: data.tokensUsed ?? (usageTotal > 0 ? usageTotal : void 0),
1517
+ backend: data.backend,
1518
+ backendLabel: data.backendLabel,
1519
+ backendModel: data.backendModel,
1520
+ sessionId: data.sessionId,
1521
+ sandboxId: data.sidecarId
1498
1522
  });
1499
1523
  totalRetries += data.retries ?? 0;
1500
1524
  }
@@ -1505,7 +1529,11 @@ var SandboxClient = class {
1505
1529
  success: false,
1506
1530
  error: data.error,
1507
1531
  durationMs: data.durationMs ?? 0,
1508
- retries: data.retries ?? 0
1532
+ retries: data.retries ?? 0,
1533
+ backend: data.backend,
1534
+ backendLabel: data.backendLabel,
1535
+ backendModel: data.backendModel,
1536
+ sandboxId: data.sidecarId
1509
1537
  });
1510
1538
  totalRetries += data.retries ?? 0;
1511
1539
  }
@@ -1521,51 +1549,36 @@ var SandboxClient = class {
1521
1549
  succeeded,
1522
1550
  failed,
1523
1551
  totalRetries,
1524
- successRate: tasks.length > 0 ? succeeded / tasks.length * 100 : 0,
1552
+ successRate: results.length > 0 ? succeeded / results.length * 100 : 0,
1525
1553
  results
1526
1554
  };
1527
1555
  }
1528
- /**
1529
- * Stream events from a batch execution.
1530
- * Use this for real-time progress updates during batch processing.
1531
- *
1532
- * @param tasks - Array of tasks to execute
1533
- * @param options - Batch execution options
1534
- *
1535
- * @throws {@link TimeoutError} if the server hasn't begun responding
1536
- * before the deadline (pre-stream timeout).
1537
- * @throws {@link ValidationError} / {@link QuotaError} / other typed
1538
- * SDK errors if the server rejects the request before streaming.
1539
- * @throws `DOMException` with `name === "AbortError"` if
1540
- * `options.signal` fires OR the safety-valve deadline fires
1541
- * AFTER streaming has begun. Distinguish via
1542
- * `options.signal?.aborted` in the consumer's catch.
1543
- *
1544
- * @example
1545
- * ```typescript
1546
- * for await (const event of client.streamBatch(tasks)) {
1547
- * if (event.type === "task.completed") {
1548
- * console.log(`Task ${event.data.taskId} completed`);
1549
- * }
1550
- * }
1551
- * ```
1552
- */
1553
- async *streamBatch(tasks, options) {
1556
+ async *streamBatch(input, options) {
1557
+ const { signal: legacySignal, ...legacyRequestOptions } = (Array.isArray(input) ? options : void 0) ?? {};
1558
+ const request = Array.isArray(input) ? {
1559
+ tasks: input,
1560
+ ...legacyRequestOptions
1561
+ } : input;
1562
+ const userSignal = Array.isArray(input) ? legacySignal : options?.signal;
1554
1563
  const url = `${this.baseUrl}/batch/run`;
1555
- const resolvedTimeoutMs = options?.timeoutMs ?? 3e5;
1564
+ const resolvedTimeoutMs = request.timeoutMs ?? 3e5;
1556
1565
  const requestBody = JSON.stringify({
1557
- tasks,
1558
- backend: options?.backend ? {
1559
- ...options.backend,
1560
- type: options.backend.type ?? "opencode"
1561
- } : { type: "opencode" },
1566
+ ...request,
1567
+ backend: request.backend ? {
1568
+ ...request.backend,
1569
+ type: request.backend.type ?? "opencode"
1570
+ } : request.backends ? void 0 : { type: "opencode" },
1571
+ backends: request.backends?.map((backend) => ({
1572
+ ...backend,
1573
+ type: backend.type ?? "opencode"
1574
+ })),
1562
1575
  timeoutMs: resolvedTimeoutMs,
1563
- scalingMode: options?.scalingMode ?? "balanced",
1564
- persistent: options?.persistent ?? false,
1565
- graceMs: options?.graceMs ?? 0
1576
+ scalingMode: request.scalingMode ?? "balanced",
1577
+ persistent: request.persistent ?? false,
1578
+ graceMs: request.graceMs ?? 0
1566
1579
  });
1567
1580
  const deadline = AbortSignal.timeout(resolvedTimeoutMs + CLIENT_DEADLINE_GRACE_MS);
1568
- const combinedSignal = options?.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
1581
+ const combinedSignal = userSignal ? AbortSignal.any([userSignal, deadline]) : deadline;
1569
1582
  let response;
1570
1583
  try {
1571
1584
  response = await globalThis.fetch(url, {
@@ -1579,7 +1592,7 @@ var SandboxClient = class {
1579
1592
  signal: combinedSignal
1580
1593
  });
1581
1594
  } catch (err) {
1582
- if (options?.signal?.aborted) throw err instanceof Error && err.name === "AbortError" ? err : new DOMException("Batch aborted", "AbortError");
1595
+ if (userSignal?.aborted) throw err instanceof Error && err.name === "AbortError" ? err : new DOMException("Batch aborted", "AbortError");
1583
1596
  if (err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) throw new TimeoutError(resolvedTimeoutMs, "Batch request timed out before the first SSE event");
1584
1597
  throw new NetworkError(`Failed to connect to Sandbox API: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : void 0, {
1585
1598
  endpoint: "/batch/run",
@@ -1590,7 +1603,7 @@ var SandboxClient = class {
1590
1603
  const errBody = await response.text();
1591
1604
  throw parseErrorResponse(response.status, errBody, void 0, response.headers);
1592
1605
  }
1593
- yield* parseSSEStream(response.body, { signal: combinedSignal });
1606
+ for await (const event of parseSSEStream(response.body, { signal: combinedSignal })) yield event;
1594
1607
  }
1595
1608
  /**
1596
1609
  * Make an authenticated HTTP request to the API.
@@ -1639,7 +1652,9 @@ var SandboxClient = class {
1639
1652
  startedAt: data.startedAt ? new Date(data.startedAt) : void 0,
1640
1653
  lastActivityAt: data.lastActivityAt ? new Date(data.lastActivityAt) : void 0,
1641
1654
  expiresAt: data.expiresAt ? new Date(data.expiresAt) : void 0,
1642
- error: data.error
1655
+ error: data.error,
1656
+ gpuLease: data.gpuLease,
1657
+ startupDiagnostics: normalizeStartupDiagnostics(data.startupDiagnostics) ?? void 0
1643
1658
  };
1644
1659
  }
1645
1660
  _tokenRefreshHandlers = /* @__PURE__ */ new Set();
@@ -1,4 +1,4 @@
1
- import { $ as CreateSandboxFleetOptions, $n as SandboxFleetTraceBundle, A as AttachSandboxFleetMachineOptions, B as BatchTask, Bn as SandboxFleetDriverCapability, Cn as PublishPublicTemplateVersionOptions, Ct as FleetDispatchStreamOptions, Dn as ReconcileSandboxFleetsResult, Dt as FleetPromptDispatchOptions, En as ReconcileSandboxFleetsOptions, Et as FleetMachineId, Fn as SandboxFleetArtifact, Gr as TokenRefreshHandler, Gt as IntelligenceReportWindow, Hn as SandboxFleetInfo, Ht as IntelligenceReportBudget, In as SandboxFleetArtifactSpec, Jn as SandboxFleetManifest, Jt as ListSandboxFleetOptions, Kn as SandboxFleetMachineRecord, L as BatchEvent, Ln as SandboxFleetCostEstimate, Lr as SubscriptionInfo, Nn as SandboxEnvironment, Nr as SshKeysManager, Ot as FleetPromptDispatchResult, Q as CreateRequestOptions, Qn as SandboxFleetToken, R as BatchOptions, Sn as PublishPublicTemplateOptions, St as FleetDispatchResultBufferOptions, Tn as ReapExpiredSandboxFleetsResult, Tt as FleetExecDispatchResult, Ut as IntelligenceReportCompareTo, Vt as IntelligenceReport, Xn as SandboxFleetOperationsSummary, Yt as ListSandboxOptions, Z as CreateIntelligenceReportOptions, Zr as UsageInfo, _t as ExecResult, a as TraceExportSink, ar as SandboxFleetWorkspaceReconcileResult, bn as PublicTemplateInfo, bt as FleetDispatchCancelResult, cr as SandboxInfo, et as CreateSandboxFleetTokenOptions, gt as ExecOptions, hn as PromptResult, i as TraceExportResult, jn as SandboxClientConfig, mn as PromptOptions, n as SandboxInstance, nr as SandboxFleetTraceOptions, nt as CreateSandboxOptions, or as SandboxFleetWorkspaceRestoreResult, pn as PromptInputPart, rr as SandboxFleetUsage, sr as SandboxFleetWorkspaceSnapshotResult, t as HttpClient, tt as CreateSandboxFleetWithCoordinatorOptions, wn as ReapExpiredSandboxFleetsOptions, wr as SecretsManager, wt as FleetExecDispatchOptions, xn as PublicTemplateVersionInfo, xt as FleetDispatchResultBuffer, z as BatchResult, zn as SandboxFleetDispatchResponse } from "./sandbox-DcOyvnIy.js";
1
+ import { $n as ReconcileSandboxFleetsResult, Bn as PromptOptions, Bt as FleetDispatchResultBuffer, Dt as ExecOptions, G as BatchRunOptions, Gt as FleetMachineId, Ht as FleetDispatchStreamOptions, J as BatchTask, Jn as PublishPublicTemplateOptions, K as BatchRunRequest, Kn as PublicTemplateInfo, Kt as FleetPromptDispatchOptions, M as AttachSandboxFleetMachineOptions, Mr as SandboxFleetWorkspaceRestoreResult, Nr as SandboxFleetWorkspaceSnapshotResult, Or as SandboxFleetTraceOptions, Ot as ExecResult, Pr as SandboxInfo, Qn as ReconcileSandboxFleetsOptions, Ri as TokenRefreshHandler, Sr as SandboxFleetOperationsSummary, Ti as SubscriptionInfo, Tr as SandboxFleetTraceBundle, U as BatchOptions, Ut as FleetExecDispatchOptions, V as BatchEvent, Vn as PromptResult, Vt as FleetDispatchResultBufferOptions, W as BatchResult, Wi as UsageInfo, Wt as FleetExecDispatchResult, Xn as ReapExpiredSandboxFleetsOptions, Yn as PublishPublicTemplateVersionOptions, Zn as ReapExpiredSandboxFleetsResult, _n as IntelligenceReportWindow, a as TraceExportSink, ar as SandboxEnvironment, bn as ListSandboxFleetOptions, br as SandboxFleetManifest, cr as SandboxFleetArtifactSpec, ct as CreateSandboxFleetOptions, dr as SandboxFleetDispatchResponse, dt as CreateSandboxOptions, fr as SandboxFleetDriverCapability, hn as IntelligenceReportCompareTo, i as TraceExportResult, jr as SandboxFleetWorkspaceReconcileResult, kr as SandboxFleetUsage, lr as SandboxFleetCostEstimate, lt as CreateSandboxFleetTokenOptions, mn as IntelligenceReportBudget, mr as SandboxFleetInfo, n as SandboxInstance, oi as SecretsManager, ot as CreateIntelligenceReportOptions, pn as IntelligenceReport, qn as PublicTemplateVersionInfo, qt as FleetPromptDispatchResult, rr as SandboxClientConfig, sr as SandboxFleetArtifact, st as CreateRequestOptions, t as HttpClient, ut as CreateSandboxFleetWithCoordinatorOptions, vr as SandboxFleetMachineRecord, wr as SandboxFleetToken, xi as SshKeysManager, xn as ListSandboxOptions, zn as PromptInputPart, zr as SandboxProfileSummary, zt as FleetDispatchCancelResult } from "./sandbox-Bm2C9Phd.js";
2
2
 
3
3
  //#region src/lib/sse-parser.d.ts
4
4
  /**
@@ -254,6 +254,8 @@ declare class SandboxClient implements HttpClient {
254
254
  */
255
255
  get secrets(): SecretsManager;
256
256
  get sshKeys(): SshKeysManager;
257
+ /** List agent profiles registered by the Sandbox API. */
258
+ listProfiles(): Promise<SandboxProfileSummary[]>;
257
259
  private _environments;
258
260
  /**
259
261
  * Access available development environments.
@@ -435,6 +437,7 @@ declare class SandboxClient implements HttpClient {
435
437
  * console.log(`Success rate: ${result.successRate}%`);
436
438
  * ```
437
439
  */
440
+ runBatch(request: BatchRunRequest, options?: BatchRunOptions): Promise<BatchResult>;
438
441
  runBatch(tasks: BatchTask[], options?: BatchOptions): Promise<BatchResult>;
439
442
  /**
440
443
  * Stream events from a batch execution.
@@ -461,6 +464,7 @@ declare class SandboxClient implements HttpClient {
461
464
  * }
462
465
  * ```
463
466
  */
467
+ streamBatch(request: BatchRunRequest, options?: BatchRunOptions): AsyncGenerator<BatchEvent>;
464
468
  streamBatch(tasks: BatchTask[], options?: BatchOptions): AsyncGenerator<BatchEvent>;
465
469
  /**
466
470
  * Make an authenticated HTTP request to the API.
@@ -1,2 +1,2 @@
1
- import { a as CollaborationClient, i as parseCollaborationDocumentId, n as buildCollaborationDocumentId, r as normalizeCollaborationPath, t as CollaborationFileBridge } from "../collaboration-BxlfZ2Uh.js";
1
+ import { a as CollaborationClient, i as parseCollaborationDocumentId, n as buildCollaborationDocumentId, r as normalizeCollaborationPath, t as CollaborationFileBridge } from "../collaboration-D17lnOJX.js";
2
2
  export { CollaborationClient, CollaborationFileBridge, buildCollaborationDocumentId, normalizeCollaborationPath, parseCollaborationDocumentId };
@@ -1,4 +1,4 @@
1
- import { f as parseErrorResponse, r as NetworkError, u as TimeoutError } from "./errors-DZsfJUuc.js";
1
+ import { d as TimeoutError, i as NetworkError, p as parseErrorResponse } from "./errors-DSz87Rkk.js";
2
2
  //#region src/collaboration/client.ts
3
3
  const DEFAULT_TIMEOUT_MS = 3e4;
4
4
  function normalizeBaseUrl(url) {
package/dist/core.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $t as NetworkConfig, _t as ExecResult, an as PreviewLinkManager, cr as SandboxInfo, gt as ExecOptions, in as PreviewLinkInfo, jn as SandboxClientConfig, n as SandboxInstance, nt as CreateSandboxOptions, pr as SandboxStatus } from "./sandbox-DcOyvnIy.js";
2
- import { i as SandboxClient } from "./client-DUY6Pdwf.js";
3
- import { a as PartialFailureError, c as SandboxErrorJson, d as StateError, f as TimeoutError, i as NotFoundError, l as SandboxFailureDetail, n as CapabilityError, o as QuotaError, p as ValidationError, r as NetworkError, s as SandboxError, t as AuthError, u as ServerError } from "./errors-1Se5ATyZ.js";
1
+ import { An as PreviewLinkInfo, Dt as ExecOptions, Gr as SandboxStatus, Ot as ExecResult, Pr as SandboxInfo, Tn as NetworkConfig, dt as CreateSandboxOptions, jn as PreviewLinkManager, n as SandboxInstance, rr as SandboxClientConfig } from "./sandbox-Bm2C9Phd.js";
2
+ import { i as SandboxClient } from "./client-cfdZckhU.js";
3
+ import { a as NotFoundError, c as SandboxError, d as ServerError, f as StateError, i as NetworkError, l as SandboxErrorJson, m as ValidationError, n as CapabilityError, o as PartialFailureError, p as TimeoutError, s as QuotaError, t as AuthError, u as SandboxFailureDetail } from "./errors-H9268M_g.js";
4
4
  export { AuthError, CapabilityError, type CreateSandboxOptions, type ExecOptions, type ExecResult, type NetworkConfig, NetworkError, NotFoundError, PartialFailureError, type PreviewLinkInfo, type PreviewLinkManager, QuotaError, SandboxClient as Sandbox, SandboxClient, type SandboxClientConfig, SandboxError, type SandboxErrorJson, type SandboxFailureDetail, type SandboxInfo, SandboxInstance, type SandboxStatus, ServerError, StateError, TimeoutError, ValidationError };
package/dist/core.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as SandboxInstance } from "./sandbox-UQrmff8d.js";
2
- import { a as PartialFailureError, c as ServerError, d as ValidationError, i as NotFoundError, l as StateError, n as CapabilityError, o as QuotaError, r as NetworkError, s as SandboxError, t as AuthError, u as TimeoutError } from "./errors-DZsfJUuc.js";
3
- import { n as SandboxClient } from "./client-vBERsxhj.js";
1
+ import { t as SandboxInstance } from "./sandbox-CeimsfC8.js";
2
+ import { a as NotFoundError, c as SandboxError, d as TimeoutError, f as ValidationError, i as NetworkError, l as ServerError, n as CapabilityError, o as PartialFailureError, s as QuotaError, t as AuthError, u as StateError } from "./errors-DSz87Rkk.js";
3
+ import { n as SandboxClient } from "./client-InX98Zxk.js";
4
4
  export { AuthError, CapabilityError, NetworkError, NotFoundError, PartialFailureError, QuotaError, SandboxClient as Sandbox, SandboxClient, SandboxError, SandboxInstance, ServerError, StateError, TimeoutError, ValidationError };
@@ -130,6 +130,14 @@ var StateError = class extends SandboxError {
130
130
  this.requiredState = requiredState;
131
131
  }
132
132
  };
133
+ var EgressProxyRecoveryError = class extends SandboxError {
134
+ phase;
135
+ constructor(message, metadata) {
136
+ super(message, "EGRESS_PROXY_RECOVERY_REQUIRED", 409, metadata);
137
+ this.name = "EgressProxyRecoveryError";
138
+ this.phase = "egress_proxy_recovery";
139
+ }
140
+ };
133
141
  /**
134
142
  * The request timed out.
135
143
  */
@@ -245,6 +253,7 @@ function parseErrorResponse(status, body, context, headers) {
245
253
  if (isQuotaCode(code)) return new QuotaError(data.quotaType || "rate_limit", message, data.current, data.limit, metadata, status);
246
254
  if (isCapabilityCode(code)) return new CapabilityError(message, code, status, metadata, readCapability(data, errorObj));
247
255
  if (isPartialFailureCode(code)) return new PartialFailureError(message, code, status, metadata, readFailures(data, errorObj));
256
+ if (code === "EGRESS_PROXY_RECOVERY_REQUIRED") return new EgressProxyRecoveryError(message, metadata);
248
257
  switch (status) {
249
258
  case 400: return new ValidationError(message, data.fields, metadata);
250
259
  case 401: return new AuthError(message, metadata);
@@ -259,4 +268,4 @@ function parseErrorResponse(status, body, context, headers) {
259
268
  }
260
269
  }
261
270
  //#endregion
262
- export { PartialFailureError as a, ServerError as c, ValidationError as d, parseErrorResponse as f, NotFoundError as i, StateError as l, CapabilityError as n, QuotaError as o, NetworkError as r, SandboxError as s, AuthError as t, TimeoutError as u };
271
+ export { NotFoundError as a, SandboxError as c, TimeoutError as d, ValidationError as f, NetworkError as i, ServerError as l, CapabilityError as n, PartialFailureError as o, parseErrorResponse as p, EgressProxyRecoveryError as r, QuotaError as s, AuthError as t, StateError as u };
@@ -102,6 +102,10 @@ declare class StateError extends SandboxError {
102
102
  readonly requiredState?: string;
103
103
  constructor(message: string, currentState: string, requiredState?: string, metadata?: SandboxErrorMetadata);
104
104
  }
105
+ declare class EgressProxyRecoveryError extends SandboxError {
106
+ readonly phase: "egress_proxy_recovery";
107
+ constructor(message: string, metadata?: SandboxErrorMetadata);
108
+ }
105
109
  /**
106
110
  * The request timed out.
107
111
  */
@@ -125,4 +129,4 @@ declare class ServerError extends SandboxError {
125
129
  constructor(message: string, status?: number, metadata?: SandboxErrorMetadata);
126
130
  }
127
131
  //#endregion
128
- export { PartialFailureError as a, SandboxErrorJson as c, StateError as d, TimeoutError as f, NotFoundError as i, SandboxFailureDetail as l, CapabilityError as n, QuotaError as o, ValidationError as p, NetworkError as r, SandboxError as s, AuthError as t, ServerError as u };
132
+ export { NotFoundError as a, SandboxError as c, ServerError as d, StateError as f, NetworkError as i, SandboxErrorJson as l, ValidationError as m, CapabilityError as n, PartialFailureError as o, TimeoutError as p, EgressProxyRecoveryError as r, QuotaError as s, AuthError as t, SandboxFailureDetail as u };