@tangle-network/sandbox 0.9.7 → 0.10.1

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-B6qbvsur.js";
2
- import { i as SandboxClient } from "../client-DWkQOg1k.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-BgKAw8YA.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,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-B6qbvsur.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
  /**
@@ -185,6 +185,20 @@ declare class SandboxFleetClient {
185
185
  private listBySandboxMetadata;
186
186
  }
187
187
  //#endregion
188
+ //#region src/session-events.d.ts
189
+ /** Event delivered to a product session through the Sandbox session gateway. */
190
+ interface SessionBroadcastEvent {
191
+ type: string;
192
+ id?: string;
193
+ data?: unknown;
194
+ channel?: string;
195
+ }
196
+ /** Result returned after Sandbox accepts or rejects a session event. */
197
+ interface SessionBroadcastResult {
198
+ success: boolean;
199
+ sessionId: string;
200
+ }
201
+ //#endregion
188
202
  //#region src/client.d.ts
189
203
  /**
190
204
  * Client for the Tangle Sandbox platform.
@@ -254,6 +268,10 @@ declare class SandboxClient implements HttpClient {
254
268
  */
255
269
  get secrets(): SecretsManager;
256
270
  get sshKeys(): SshKeysManager;
271
+ /** List agent profiles registered by the Sandbox API. */
272
+ listProfiles(): Promise<SandboxProfileSummary[]>;
273
+ /** Deliver a server-originated event to a mapped product session. */
274
+ broadcastSessionEvent(sessionId: string, event: SessionBroadcastEvent): Promise<SessionBroadcastResult>;
257
275
  private _environments;
258
276
  /**
259
277
  * Access available development environments.
@@ -435,6 +453,7 @@ declare class SandboxClient implements HttpClient {
435
453
  * console.log(`Success rate: ${result.successRate}%`);
436
454
  * ```
437
455
  */
456
+ runBatch(request: BatchRunRequest, options?: BatchRunOptions): Promise<BatchResult>;
438
457
  runBatch(tasks: BatchTask[], options?: BatchOptions): Promise<BatchResult>;
439
458
  /**
440
459
  * Stream events from a batch execution.
@@ -461,6 +480,7 @@ declare class SandboxClient implements HttpClient {
461
480
  * }
462
481
  * ```
463
482
  */
483
+ streamBatch(request: BatchRunRequest, options?: BatchRunOptions): AsyncGenerator<BatchEvent>;
464
484
  streamBatch(tasks: BatchTask[], options?: BatchOptions): AsyncGenerator<BatchEvent>;
465
485
  /**
466
486
  * Make an authenticated HTTP request to the API.
@@ -712,4 +732,4 @@ declare class TeamsClient {
712
732
  * Alias for SandboxClient for cleaner imports.
713
733
  */
714
734
  //#endregion
715
- export { Team as a, SandboxFleet as c, ParsedSSEEvent as d, parseSSEStream as f, SandboxClient as i, SandboxFleetClient as l, IntelligenceClient as n, TeamInvitation as o, InviteTeamMemberOptions as r, TeamMember as s, CreateTeamOptions as t, ParseSSEStreamOptions as u };
735
+ export { Team as a, SessionBroadcastEvent as c, SandboxFleetClient as d, ParseSSEStreamOptions as f, SandboxClient as i, SessionBroadcastResult as l, parseSSEStream as m, IntelligenceClient as n, TeamInvitation as o, ParsedSSEEvent as p, InviteTeamMemberOptions as r, TeamMember as s, CreateTeamOptions as t, SandboxFleet as u };
@@ -1,5 +1,5 @@
1
- import { f as encodePromptForWire, l as exportTraceBundle, p as parseSSEStream, s as normalizeConnection, t as SandboxInstance } from "./sandbox-bDWrUu8i.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;
@@ -916,6 +946,25 @@ function getMachineRole(info) {
916
946
  return value === "coordinator" || value === "worker" ? value : void 0;
917
947
  }
918
948
  //#endregion
949
+ //#region src/session-events.ts
950
+ async function broadcastSessionEvent(client, sessionId, event) {
951
+ if (!sessionId.trim()) throw new ValidationError("Session ID is required");
952
+ if (!event.type.trim()) throw new ValidationError("Session event type is required");
953
+ const response = await client.fetch(`/v1/session/${encodeURIComponent(sessionId)}/broadcast`, {
954
+ method: "POST",
955
+ body: JSON.stringify(event)
956
+ });
957
+ if (!response.ok) {
958
+ const body = await response.text();
959
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
960
+ }
961
+ const result = await response.json();
962
+ return {
963
+ success: result.success,
964
+ sessionId: result.sessionId
965
+ };
966
+ }
967
+ //#endregion
919
968
  //#region src/client.ts
920
969
  /**
921
970
  * Sandbox Client
@@ -933,6 +982,7 @@ const DEFAULT_TIMEOUT_MS = 3e4;
933
982
  * safe server-side, but polling is cheaper and avoids duplicate work).
934
983
  */
935
984
  const DEFAULT_CREATE_TIMEOUT_MS = 12e4;
985
+ const DEFAULT_GPU_CREATE_TIMEOUT_MS = 900 * 1e3;
936
986
  /**
937
987
  * Extra grace the SDK's client-side batch deadline gets on top of
938
988
  * the server-side `timeoutMs`. The server's clock starts when the
@@ -1136,6 +1186,19 @@ var SandboxClient = class {
1136
1186
  if (!this._sshKeys) this._sshKeys = new SshKeysManagerImpl(this);
1137
1187
  return this._sshKeys;
1138
1188
  }
1189
+ /** List agent profiles registered by the Sandbox API. */
1190
+ async listProfiles() {
1191
+ const response = await this.fetch("/profiles");
1192
+ if (!response.ok) {
1193
+ const body = await response.text();
1194
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1195
+ }
1196
+ return (await response.json()).profiles ?? [];
1197
+ }
1198
+ /** Deliver a server-originated event to a mapped product session. */
1199
+ async broadcastSessionEvent(sessionId, event) {
1200
+ return broadcastSessionEvent(this, sessionId, event);
1201
+ }
1139
1202
  _environments = null;
1140
1203
  /**
1141
1204
  * Access available development environments.
@@ -1194,7 +1257,7 @@ var SandboxClient = class {
1194
1257
  * ```
1195
1258
  */
1196
1259
  async create(options, requestOptions) {
1197
- const timeoutMs = requestOptions?.timeoutMs ?? DEFAULT_CREATE_TIMEOUT_MS;
1260
+ const timeoutMs = requestOptions?.timeoutMs ?? (options?.gpuLease ? DEFAULT_GPU_CREATE_TIMEOUT_MS : DEFAULT_CREATE_TIMEOUT_MS);
1198
1261
  const idempotencyKey = options?.idempotencyKey ?? generateIdempotencyKey();
1199
1262
  const deadline = Date.now() + timeoutMs;
1200
1263
  const git = options?.git ? {
@@ -1228,11 +1291,13 @@ var SandboxClient = class {
1228
1291
  git,
1229
1292
  tools: options?.tools,
1230
1293
  bare: options?.bare,
1294
+ ...options?.publicEdge === false ? { publicEdge: false } : {},
1231
1295
  driver: options?.driver,
1232
1296
  backend: resolvedBackend,
1233
1297
  permissions: options?.permissions,
1234
1298
  env: options?.env,
1235
1299
  resources,
1300
+ gpuLease: options?.gpuLease,
1236
1301
  sshEnabled: options?.sshEnabled,
1237
1302
  sshPublicKeys: options?.sshPublicKeys ?? (options?.sshPublicKey ? [options.sshPublicKey] : void 0),
1238
1303
  sshKeyIds: options?.sshKeyIds,
@@ -1240,6 +1305,7 @@ var SandboxClient = class {
1240
1305
  maxLifetimeSeconds: options?.maxLifetimeSeconds,
1241
1306
  idleTimeoutSeconds: options?.idleTimeoutSeconds,
1242
1307
  storage: options?.storage,
1308
+ ephemeral: options?.ephemeral,
1243
1309
  fromSnapshot: options?.fromSnapshot,
1244
1310
  fromSandboxId: options?.fromSandboxId,
1245
1311
  templateId: options?.templateId,
@@ -1373,6 +1439,9 @@ var SandboxClient = class {
1373
1439
  const data = await response.json();
1374
1440
  return {
1375
1441
  computeMinutes: data.computeMinutes ?? 0,
1442
+ gpuSeconds: data.gpuSeconds ?? 0,
1443
+ gpuCostUsd: data.gpuCostUsd ?? 0,
1444
+ gpuProviderCostUsd: data.gpuProviderCostUsd ?? 0,
1376
1445
  activeSandboxes: data.activeSandboxes ?? 0,
1377
1446
  totalSandboxes: data.totalSandboxes ?? 0,
1378
1447
  periodStart: new Date(data.periodStart),
@@ -1452,39 +1521,12 @@ var SandboxClient = class {
1452
1521
  }
1453
1522
  return await response.json();
1454
1523
  }
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) {
1524
+ async runBatch(input, options) {
1525
+ const tasks = Array.isArray(input) ? input : input.tasks;
1485
1526
  const results = [];
1486
1527
  let totalRetries = 0;
1487
- for await (const event of this.streamBatch(tasks, options)) {
1528
+ const stream = Array.isArray(input) ? this.streamBatch(input, options) : this.streamBatch(input, options);
1529
+ for await (const event of stream) {
1488
1530
  if (event.type === "task.completed") {
1489
1531
  const data = event.data;
1490
1532
  const usageTotal = (data.usage?.inputTokens ?? 0) + (data.usage?.outputTokens ?? 0);
@@ -1494,7 +1536,12 @@ var SandboxClient = class {
1494
1536
  response: data.resultSummary ?? data.response,
1495
1537
  durationMs: data.durationMs ?? 0,
1496
1538
  retries: data.retries ?? 0,
1497
- tokensUsed: data.tokensUsed ?? (usageTotal > 0 ? usageTotal : void 0)
1539
+ tokensUsed: data.tokensUsed ?? (usageTotal > 0 ? usageTotal : void 0),
1540
+ backend: data.backend,
1541
+ backendLabel: data.backendLabel,
1542
+ backendModel: data.backendModel,
1543
+ sessionId: data.sessionId,
1544
+ sandboxId: data.sidecarId
1498
1545
  });
1499
1546
  totalRetries += data.retries ?? 0;
1500
1547
  }
@@ -1505,7 +1552,11 @@ var SandboxClient = class {
1505
1552
  success: false,
1506
1553
  error: data.error,
1507
1554
  durationMs: data.durationMs ?? 0,
1508
- retries: data.retries ?? 0
1555
+ retries: data.retries ?? 0,
1556
+ backend: data.backend,
1557
+ backendLabel: data.backendLabel,
1558
+ backendModel: data.backendModel,
1559
+ sandboxId: data.sidecarId
1509
1560
  });
1510
1561
  totalRetries += data.retries ?? 0;
1511
1562
  }
@@ -1521,51 +1572,36 @@ var SandboxClient = class {
1521
1572
  succeeded,
1522
1573
  failed,
1523
1574
  totalRetries,
1524
- successRate: tasks.length > 0 ? succeeded / tasks.length * 100 : 0,
1575
+ successRate: results.length > 0 ? succeeded / results.length * 100 : 0,
1525
1576
  results
1526
1577
  };
1527
1578
  }
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) {
1579
+ async *streamBatch(input, options) {
1580
+ const { signal: legacySignal, ...legacyRequestOptions } = (Array.isArray(input) ? options : void 0) ?? {};
1581
+ const request = Array.isArray(input) ? {
1582
+ tasks: input,
1583
+ ...legacyRequestOptions
1584
+ } : input;
1585
+ const userSignal = Array.isArray(input) ? legacySignal : options?.signal;
1554
1586
  const url = `${this.baseUrl}/batch/run`;
1555
- const resolvedTimeoutMs = options?.timeoutMs ?? 3e5;
1587
+ const resolvedTimeoutMs = request.timeoutMs ?? 3e5;
1556
1588
  const requestBody = JSON.stringify({
1557
- tasks,
1558
- backend: options?.backend ? {
1559
- ...options.backend,
1560
- type: options.backend.type ?? "opencode"
1561
- } : { type: "opencode" },
1589
+ ...request,
1590
+ backend: request.backend ? {
1591
+ ...request.backend,
1592
+ type: request.backend.type ?? "opencode"
1593
+ } : request.backends ? void 0 : { type: "opencode" },
1594
+ backends: request.backends?.map((backend) => ({
1595
+ ...backend,
1596
+ type: backend.type ?? "opencode"
1597
+ })),
1562
1598
  timeoutMs: resolvedTimeoutMs,
1563
- scalingMode: options?.scalingMode ?? "balanced",
1564
- persistent: options?.persistent ?? false,
1565
- graceMs: options?.graceMs ?? 0
1599
+ scalingMode: request.scalingMode ?? "balanced",
1600
+ persistent: request.persistent ?? false,
1601
+ graceMs: request.graceMs ?? 0
1566
1602
  });
1567
1603
  const deadline = AbortSignal.timeout(resolvedTimeoutMs + CLIENT_DEADLINE_GRACE_MS);
1568
- const combinedSignal = options?.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
1604
+ const combinedSignal = userSignal ? AbortSignal.any([userSignal, deadline]) : deadline;
1569
1605
  let response;
1570
1606
  try {
1571
1607
  response = await globalThis.fetch(url, {
@@ -1579,7 +1615,7 @@ var SandboxClient = class {
1579
1615
  signal: combinedSignal
1580
1616
  });
1581
1617
  } catch (err) {
1582
- if (options?.signal?.aborted) throw err instanceof Error && err.name === "AbortError" ? err : new DOMException("Batch aborted", "AbortError");
1618
+ if (userSignal?.aborted) throw err instanceof Error && err.name === "AbortError" ? err : new DOMException("Batch aborted", "AbortError");
1583
1619
  if (err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) throw new TimeoutError(resolvedTimeoutMs, "Batch request timed out before the first SSE event");
1584
1620
  throw new NetworkError(`Failed to connect to Sandbox API: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : void 0, {
1585
1621
  endpoint: "/batch/run",
@@ -1590,7 +1626,7 @@ var SandboxClient = class {
1590
1626
  const errBody = await response.text();
1591
1627
  throw parseErrorResponse(response.status, errBody, void 0, response.headers);
1592
1628
  }
1593
- yield* parseSSEStream(response.body, { signal: combinedSignal });
1629
+ for await (const event of parseSSEStream(response.body, { signal: combinedSignal })) yield event;
1594
1630
  }
1595
1631
  /**
1596
1632
  * Make an authenticated HTTP request to the API.
@@ -1639,7 +1675,9 @@ var SandboxClient = class {
1639
1675
  startedAt: data.startedAt ? new Date(data.startedAt) : void 0,
1640
1676
  lastActivityAt: data.lastActivityAt ? new Date(data.lastActivityAt) : void 0,
1641
1677
  expiresAt: data.expiresAt ? new Date(data.expiresAt) : void 0,
1642
- error: data.error
1678
+ error: data.error,
1679
+ gpuLease: data.gpuLease,
1680
+ startupDiagnostics: normalizeStartupDiagnostics(data.startupDiagnostics) ?? void 0
1643
1681
  };
1644
1682
  }
1645
1683
  _tokenRefreshHandlers = /* @__PURE__ */ new Set();
@@ -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-B6qbvsur.js";
2
- import { i as SandboxClient } from "./client-DWkQOg1k.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-BgKAw8YA.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-bDWrUu8i.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-BZSkXIEp.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-DeHvk634.js";
4
4
  export { AuthError, CapabilityError, NetworkError, NotFoundError, PartialFailureError, QuotaError, SandboxClient as Sandbox, SandboxClient, SandboxError, SandboxInstance, ServerError, StateError, TimeoutError, ValidationError };