@tangle-network/sandbox 0.9.1-develop.20260624210934.e2730e8 → 0.9.2-develop.20260625093856.35d47c0

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.
@@ -0,0 +1,2193 @@
1
+ import { f as encodePromptForWire, l as exportTraceBundle, p as parseSSEStream, s as normalizeConnection, t as SandboxInstance } from "./sandbox-joKtQ5y3.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";
3
+ //#region src/resources.ts
4
+ /**
5
+ * Compute tiers, ordered smallest → largest. Defined HERE (not imported from
6
+ * `@tangle-network/agent-interface`) so the SDK is self-contained: a consumer
7
+ * that type-checks the SDK SOURCE never has to resolve a cross-package export
8
+ * for the size vocabulary. `agent-interface` keeps its own identical copy for
9
+ * `Capability.recommendedSize`; a platform test guards the two against drift.
10
+ */
11
+ const SANDBOX_SIZE_PRESET_NAMES = [
12
+ "nano",
13
+ "small",
14
+ "medium",
15
+ "large"
16
+ ];
17
+ /**
18
+ * The concrete compute behind each {@link SandboxSizePreset} — the SINGLE source
19
+ * of truth for what a tier means. `@tangle-network/agent-interface` owns the
20
+ * tier NAMES (so the lowest layer can reference a size); this owns the numbers.
21
+ *
22
+ * Values stay within the sandbox-api default plan caps (8 vCPU / 16 GB / 100 GB)
23
+ * and keep memory at or above the driver's firecracker-snapshot floor (1536 MB),
24
+ * so a requested tier is never silently bumped. `diskGB` is in gigabytes;
25
+ * `memoryMB` in megabytes. The server still clamps every request to the owner's
26
+ * actual plan limits — these are the unclamped tier definitions, not guarantees.
27
+ */
28
+ const SANDBOX_SIZE_PRESETS = {
29
+ nano: {
30
+ cpuCores: 1,
31
+ memoryMB: 2048,
32
+ diskGB: 5
33
+ },
34
+ small: {
35
+ cpuCores: 2,
36
+ memoryMB: 4096,
37
+ diskGB: 10
38
+ },
39
+ medium: {
40
+ cpuCores: 4,
41
+ memoryMB: 8192,
42
+ diskGB: 20
43
+ },
44
+ large: {
45
+ cpuCores: 8,
46
+ memoryMB: 16384,
47
+ diskGB: 50
48
+ }
49
+ };
50
+ /**
51
+ * Default tier when a caller provisions without picking a size. The smallest
52
+ * tier that comfortably runs an agent harness (opencode + a repo clone + a
53
+ * build) — deliberately NOT a maxed box, so a thin task is cheap by default and
54
+ * a heavy one opts up explicitly.
55
+ */
56
+ const DEFAULT_SANDBOX_SIZE = "small";
57
+ /** The resources for a named tier, as a fresh object (never the shared preset).
58
+ * Fails loud on an unknown key rather than spreading `undefined` into an empty
59
+ * resources object — a silent zero would send no compute request downstream. */
60
+ function sandboxResourcesForSize(size) {
61
+ const preset = SANDBOX_SIZE_PRESETS[size];
62
+ if (!preset) throw new Error(`sandboxResourcesForSize: unknown size preset '${size}' (expected one of ${SANDBOX_SIZE_PRESET_NAMES.join(", ")})`);
63
+ return { ...preset };
64
+ }
65
+ /**
66
+ * Resolve a caller's size intent to concrete {@link SandboxResources}. Raw
67
+ * `resources` win (an explicit cpu/memory request is authoritative); else a
68
+ * named `size` expands to its preset; else `undefined` (the caller applies its
69
+ * own default). Raw and preset are intentionally exclusive at the schema layer —
70
+ * if both arrive here, the explicit numbers take precedence rather than being
71
+ * silently merged.
72
+ */
73
+ function resolveSandboxResources(opts) {
74
+ if (opts.resources) return opts.resources;
75
+ if (opts.size) return sandboxResourcesForSize(opts.size);
76
+ }
77
+ function normalizeSandboxResources(resources, driver) {
78
+ const legacyAccelerator = driver?.gpuRequired || driver?.gpuType || driver?.gpuCount ? {
79
+ kind: driver.gpuType ?? "gpu",
80
+ count: driver.gpuCount ?? 1
81
+ } : void 0;
82
+ if (!legacyAccelerator) return resources;
83
+ return {
84
+ ...resources,
85
+ accelerator: resources?.accelerator ?? legacyAccelerator
86
+ };
87
+ }
88
+ //#endregion
89
+ //#region src/fleet.ts
90
+ const FLEET_ID_META = "tangle.fleet.id";
91
+ const MACHINE_ID_META = "tangle.fleet.machine_id";
92
+ const MACHINE_ROLE_META = "tangle.fleet.machine_role";
93
+ const FLEET_CREATED_BY_META = "tangle.fleet.created_by";
94
+ const WORKSPACE_MODE_META = "tangle.fleet.workspace.mode";
95
+ const WORKSPACE_ID_META = "tangle.fleet.workspace.id";
96
+ const WORKSPACE_QUOTA_META = "tangle.fleet.workspace.quota_mb";
97
+ const WORKSPACE_MOUNT_META = "tangle.fleet.workspace.mount_path";
98
+ const RESERVED_METADATA_PREFIX = "tangle.";
99
+ const DEFAULT_MACHINE_LIFETIME_SECONDS = 3600;
100
+ const DEFAULT_MAX_CONCURRENT_CREATES = 4;
101
+ const MAX_CONCURRENT_ARTIFACT_READS = 8;
102
+ const DEFAULT_ARTIFACT_ROOT = "/workspace";
103
+ const DEFAULT_MAX_ARTIFACT_BYTES = 25 * 1024 * 1024;
104
+ const MAX_MACHINE_ID_LENGTH = 64;
105
+ const machineIdPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
106
+ function createFleetId() {
107
+ const crypto = globalThis.crypto;
108
+ if (crypto?.randomUUID) return `fleet_${crypto.randomUUID().replaceAll("-", "").slice(0, 24)}`;
109
+ if (crypto?.getRandomValues) {
110
+ const bytes = crypto.getRandomValues(new Uint8Array(12));
111
+ return `fleet_${Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
112
+ }
113
+ throw new ValidationError("Fleet id generation requires global crypto");
114
+ }
115
+ function assertSafeId(value, label) {
116
+ if (value.length === 0 || value.length > MAX_MACHINE_ID_LENGTH || !machineIdPattern.test(value)) throw new ValidationError(`${label} must be 1-${MAX_MACHINE_ID_LENGTH} characters and contain only letters, numbers, '.', '_' or '-'`, { [label]: "invalid" });
117
+ }
118
+ function assertUniqueMachineIds(machines) {
119
+ const seen = /* @__PURE__ */ new Set();
120
+ for (const machine of machines) {
121
+ assertSafeId(machine.machineId, "machineId");
122
+ if (seen.has(machine.machineId)) throw new ValidationError(`Duplicate machineId '${machine.machineId}' in fleet create request`, { machineId: "duplicate" });
123
+ seen.add(machine.machineId);
124
+ }
125
+ }
126
+ function assertNoReservedMetadata(metadata, label) {
127
+ if (!metadata) return;
128
+ const reserved = Object.keys(metadata).find((key) => key.startsWith(RESERVED_METADATA_PREFIX));
129
+ if (reserved) throw new ValidationError(`${label} metadata key '${reserved}' is reserved by Tangle`, { metadata: "reserved" });
130
+ }
131
+ function mergeCreateOptions(defaults, machine) {
132
+ const { machineId: _machineId, metadata: _metadata, name: _name, role: _role, ...rest } = machine;
133
+ const backend = defaults?.backend || machine.backend ? {
134
+ ...defaults?.backend,
135
+ ...machine.backend,
136
+ type: machine.backend?.type ?? defaults?.backend?.type ?? "opencode",
137
+ model: {
138
+ ...defaults?.backend?.model,
139
+ ...machine.backend?.model
140
+ }
141
+ } : void 0;
142
+ const driver = machine.driver ?? defaults?.driver;
143
+ return {
144
+ ...defaults,
145
+ ...rest,
146
+ resources: normalizeSandboxResources({
147
+ ...defaults?.resources,
148
+ ...machine.resources
149
+ }, driver),
150
+ env: {
151
+ ...defaults?.env,
152
+ ...machine.env
153
+ },
154
+ backend,
155
+ driver
156
+ };
157
+ }
158
+ function assertPolicy(machines, defaults, policy) {
159
+ const maxMachines = policy?.maxMachines ?? machines.length;
160
+ if (!Number.isInteger(maxMachines) || maxMachines <= 0) throw new ValidationError("policy.maxMachines must be a positive integer");
161
+ if (machines.length > maxMachines) throw new ValidationError(`Fleet create requested ${machines.length} machines but policy.maxMachines is ${maxMachines}`, { "policy.maxMachines": "exceeded" });
162
+ if (policy?.maxConcurrentCreates !== void 0 && (!Number.isInteger(policy.maxConcurrentCreates) || policy.maxConcurrentCreates <= 0)) throw new ValidationError("policy.maxConcurrentCreates must be a positive integer", { "policy.maxConcurrentCreates": "invalid" });
163
+ if (policy?.maxSpendUsd !== void 0 && (!Number.isFinite(policy.maxSpendUsd) || policy.maxSpendUsd < 0)) throw new ValidationError("policy.maxSpendUsd must be non-negative", { "policy.maxSpendUsd": "invalid" });
164
+ let totalCpu = 0;
165
+ let totalMemoryMb = 0;
166
+ let totalStorageMb = 0;
167
+ let totalAccelerators = 0;
168
+ for (const machine of machines) {
169
+ const resources = mergeCreateOptions(defaults, machine).resources;
170
+ totalCpu += resources?.cpuCores ?? 0;
171
+ totalMemoryMb += resources?.memoryMB ?? 0;
172
+ totalStorageMb += resources?.diskGB ? resources.diskGB * 1024 : 0;
173
+ totalAccelerators += resources?.accelerator?.count ?? (resources?.accelerator ? 1 : 0);
174
+ }
175
+ 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
+ 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
+ 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
+ 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" });
179
+ const maxLifetimeSeconds = Math.max(...machines.map((machine) => mergeCreateOptions(defaults, machine).maxLifetimeSeconds ?? DEFAULT_MACHINE_LIFETIME_SECONDS));
180
+ 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
+ for (const machine of machines) {
182
+ const options = mergeCreateOptions(defaults, machine);
183
+ assertMachineDescriptorPolicy(machine.machineId, options, policy);
184
+ }
185
+ }
186
+ function assertMachineDescriptorPolicy(machineId, options, policy) {
187
+ if (!policy) return;
188
+ 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" });
190
+ 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" });
192
+ 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" });
195
+ }
196
+ function calculateResourceTotals(machines, defaults) {
197
+ let totalCpu = 0;
198
+ let totalMemoryMb = 0;
199
+ let totalStorageMb = 0;
200
+ let totalAccelerators = 0;
201
+ let maxLifetimeSeconds = 0;
202
+ for (const machine of machines) {
203
+ const options = mergeCreateOptions(defaults, machine);
204
+ const resources = options.resources;
205
+ totalCpu += resources?.cpuCores ?? 0;
206
+ totalMemoryMb += resources?.memoryMB ?? 0;
207
+ totalStorageMb += resources?.diskGB ? resources.diskGB * 1024 : 0;
208
+ totalAccelerators += resources?.accelerator?.count ?? (resources?.accelerator ? 1 : 0);
209
+ maxLifetimeSeconds = Math.max(maxLifetimeSeconds, options.maxLifetimeSeconds ?? DEFAULT_MACHINE_LIFETIME_SECONDS);
210
+ }
211
+ return {
212
+ machines: machines.length,
213
+ totalCpu,
214
+ totalMemoryMb,
215
+ totalStorageMb,
216
+ totalAccelerators,
217
+ maxLifetimeSeconds
218
+ };
219
+ }
220
+ function workspaceMetadata(workspace) {
221
+ return {
222
+ [WORKSPACE_MODE_META]: workspace?.mode ?? "isolated",
223
+ ...workspace?.id ? { [WORKSPACE_ID_META]: workspace.id } : {},
224
+ ...workspace?.quotaMb ? { [WORKSPACE_QUOTA_META]: workspace.quotaMb } : {},
225
+ ...workspace?.mountPath ? { [WORKSPACE_MOUNT_META]: workspace.mountPath } : {}
226
+ };
227
+ }
228
+ function resolveMaxConcurrentCreates(value) {
229
+ const resolved = value ?? DEFAULT_MAX_CONCURRENT_CREATES;
230
+ if (!Number.isInteger(resolved) || resolved <= 0) throw new ValidationError("maxConcurrentCreates must be a positive integer");
231
+ return resolved;
232
+ }
233
+ function getMachineId(info) {
234
+ const value = info.metadata?.[MACHINE_ID_META];
235
+ return typeof value === "string" && value.length > 0 ? value : null;
236
+ }
237
+ function getFleetId(info) {
238
+ const value = info.metadata?.[FLEET_ID_META];
239
+ return typeof value === "string" && value.length > 0 ? value : null;
240
+ }
241
+ function dispatchResultsPath(fleetId, dispatchId, options) {
242
+ const params = new URLSearchParams();
243
+ if (options?.cursor) params.set("cursor", options.cursor);
244
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
245
+ for (const machine of options?.machines ?? []) params.append("machine", machine);
246
+ const query = params.toString();
247
+ const path = `/v1/fleets/${fleetId}/dispatch/${encodeURIComponent(dispatchId)}/results`;
248
+ return query ? `${path}?${query}` : path;
249
+ }
250
+ function execDispatchBody(command, options) {
251
+ return {
252
+ type: "exec",
253
+ command,
254
+ cwd: options?.cwd,
255
+ env: options?.env,
256
+ timeoutMs: options?.timeoutMs,
257
+ machines: options?.machines,
258
+ maxConcurrent: options?.maxConcurrent,
259
+ retry: options?.retry,
260
+ dispatchId: options?.dispatchId,
261
+ bufferResults: options?.bufferResults
262
+ };
263
+ }
264
+ function promptDispatchBody(message, options) {
265
+ return {
266
+ type: "prompt",
267
+ parts: encodePromptForWire(message),
268
+ sessionId: options?.sessionId,
269
+ model: options?.model,
270
+ backend: options?.backend,
271
+ context: options?.context,
272
+ timeoutMs: options?.timeoutMs,
273
+ machines: options?.machines,
274
+ maxConcurrent: options?.maxConcurrent,
275
+ retry: options?.retry,
276
+ dispatchId: options?.dispatchId,
277
+ bufferResults: options?.bufferResults
278
+ };
279
+ }
280
+ var SandboxFleet = class {
281
+ constructor(client, fleetId, machines) {
282
+ this.client = client;
283
+ this.fleetId = fleetId;
284
+ this.machines = machines;
285
+ }
286
+ get ids() {
287
+ return [...this.machines.keys()];
288
+ }
289
+ get(machineId) {
290
+ const machine = this.machines.get(machineId);
291
+ if (!machine) throw new ValidationError(`Unknown fleet machine '${machineId}'`);
292
+ return machine;
293
+ }
294
+ sandbox(machineId) {
295
+ return this.requireSandbox(machineId);
296
+ }
297
+ async collectArtifacts(artifacts) {
298
+ return mapConcurrent(artifacts, Math.min(MAX_CONCURRENT_ARTIFACT_READS, artifacts.length || 1), async (artifact) => {
299
+ assertArtifactPath(artifact.path);
300
+ const machine = this.get(artifact.machineId);
301
+ const content = await (await this.requireSandbox(artifact.machineId)).read(artifact.path);
302
+ assertArtifactSize(content, artifact);
303
+ return {
304
+ ...artifact,
305
+ sandboxId: machine.id,
306
+ content
307
+ };
308
+ });
309
+ }
310
+ async exec(machineId, command, options) {
311
+ const [result] = await this.dispatchExec(command, {
312
+ ...options,
313
+ machines: [machineId]
314
+ });
315
+ if (result?.ok && result.result) return result.result;
316
+ if (result?.error) throw new Error(result.error.message);
317
+ throw new Error(`Fleet exec produced no result for machine '${machineId}'`);
318
+ }
319
+ async dispatchExec(command, options) {
320
+ return (await this.dispatchExecDetailed(command, options)).results;
321
+ }
322
+ async dispatchExecDetailed(command, options) {
323
+ const response = await this.client.fetch(`/v1/fleets/${this.fleetId}/dispatch`, {
324
+ method: "POST",
325
+ body: JSON.stringify(execDispatchBody(command, options))
326
+ });
327
+ if (!response.ok) {
328
+ const body = await response.text();
329
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
330
+ }
331
+ return await response.json();
332
+ }
333
+ async prompt(machineId, message, options) {
334
+ const [result] = await this.dispatchPrompt(message, {
335
+ ...options,
336
+ machines: [machineId]
337
+ });
338
+ if (result?.prompt) return result.prompt;
339
+ if (result?.error) throw new Error(result.error.message);
340
+ throw new Error(`Fleet prompt produced no result for machine '${machineId}'`);
341
+ }
342
+ async dispatchPrompt(message, options) {
343
+ return (await this.dispatchPromptDetailed(message, options)).results;
344
+ }
345
+ async dispatchPromptDetailed(message, options) {
346
+ const response = await this.client.fetch(`/v1/fleets/${this.fleetId}/dispatch`, {
347
+ method: "POST",
348
+ body: JSON.stringify(promptDispatchBody(message, options))
349
+ });
350
+ if (!response.ok) {
351
+ const body = await response.text();
352
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
353
+ }
354
+ return await response.json();
355
+ }
356
+ manifest() {
357
+ return this.client.fleets.manifest(this.fleetId);
358
+ }
359
+ attachMachine(machine) {
360
+ return this.client.fleets.attachMachine(this.fleetId, machine);
361
+ }
362
+ detachMachine(machineId) {
363
+ return this.client.fleets.detachMachine(this.fleetId, machineId);
364
+ }
365
+ dispatchResults(dispatchId, options) {
366
+ return this.client.fleets.dispatchResults(this.fleetId, dispatchId, options);
367
+ }
368
+ cancelDispatch(dispatchId, reason) {
369
+ return this.client.fleets.cancelDispatch(this.fleetId, dispatchId, reason);
370
+ }
371
+ createWorkspaceSnapshot() {
372
+ return this.client.fleets.createWorkspaceSnapshot(this.fleetId);
373
+ }
374
+ restoreWorkspaceSnapshot(snapshotId) {
375
+ return this.client.fleets.restoreWorkspaceSnapshot(this.fleetId, snapshotId);
376
+ }
377
+ reconcileWorkspace() {
378
+ return this.client.fleets.reconcileWorkspace(this.fleetId);
379
+ }
380
+ dispatchExecStream(command, options) {
381
+ return this.client.fleets.dispatchExecStream(this.fleetId, command, options);
382
+ }
383
+ dispatchPromptStream(message, options) {
384
+ return this.client.fleets.dispatchPromptStream(this.fleetId, message, options);
385
+ }
386
+ async delete(options) {
387
+ const errors = [];
388
+ await Promise.all([...this.machines.values()].map(async (machine) => {
389
+ const box = await this.client.get(machine.id);
390
+ if (!box) return;
391
+ try {
392
+ await box.delete();
393
+ } catch (err) {
394
+ errors.push(err instanceof Error ? err : new Error(String(err)));
395
+ }
396
+ }));
397
+ await this.client.fleets.deleteRecord(this.fleetId);
398
+ if (errors.length > 0) {
399
+ if (!options?.continueOnError) throw errors[0];
400
+ }
401
+ }
402
+ async usage() {
403
+ const response = await this.client.fetch(`/v1/fleets/${this.fleetId}/usage`);
404
+ if (!response.ok) {
405
+ const body = await response.text();
406
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
407
+ }
408
+ return await response.json();
409
+ }
410
+ async trace(options = {}) {
411
+ return this.client.fleets.trace(this.fleetId, options);
412
+ }
413
+ async intelligence() {
414
+ const intelligence = (await this.trace({ includeIntelligence: true })).intelligence;
415
+ if (!intelligence) throw new Error("Fleet intelligence was not returned");
416
+ return intelligence;
417
+ }
418
+ async createIntelligenceReport(options = {}) {
419
+ const { dispatchId, window, compareTo, ...rest } = options;
420
+ return this.client.intelligence.createReport({
421
+ subject: {
422
+ type: "fleet",
423
+ id: this.fleetId,
424
+ dispatchId,
425
+ window,
426
+ compareTo
427
+ },
428
+ ...rest
429
+ });
430
+ }
431
+ async createAgenticIntelligenceReport(options) {
432
+ return this.createIntelligenceReport({
433
+ mode: "agentic",
434
+ budget: {
435
+ billTo: "customer",
436
+ maxUsd: options.maxUsd
437
+ },
438
+ metadata: options.metadata,
439
+ dispatchId: options.dispatchId,
440
+ window: options.window,
441
+ compareTo: options.compareTo
442
+ });
443
+ }
444
+ async exportTrace(sink) {
445
+ return exportTraceBundle(await this.trace(), sink);
446
+ }
447
+ async cost() {
448
+ return this.client.fleets.cost(this.fleetId);
449
+ }
450
+ async createToken(options) {
451
+ return this.client.fleets.createToken(this.fleetId, options);
452
+ }
453
+ async requireSandbox(machineId) {
454
+ const machine = this.get(machineId);
455
+ const box = await this.client.get(machine.id);
456
+ if (!box) throw new ValidationError(`Fleet machine '${machineId}' no longer exists`, { machineId: "not_found" });
457
+ return box;
458
+ }
459
+ };
460
+ var SandboxFleetClient = class {
461
+ constructor(client) {
462
+ this.client = client;
463
+ }
464
+ async capabilities() {
465
+ const response = await this.client.fetch("/v1/fleets/capabilities");
466
+ if (!response.ok) {
467
+ const body = await response.text();
468
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
469
+ }
470
+ return await response.json();
471
+ }
472
+ async operations() {
473
+ const response = await this.client.fetch("/v1/fleets/operations");
474
+ if (!response.ok) {
475
+ const body = await response.text();
476
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
477
+ }
478
+ return await response.json();
479
+ }
480
+ async reconcile(options) {
481
+ const response = await this.client.fetch("/v1/fleets/reconcile", {
482
+ method: "POST",
483
+ body: JSON.stringify(options ?? {})
484
+ });
485
+ if (!response.ok) {
486
+ const body = await response.text();
487
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
488
+ }
489
+ return await response.json();
490
+ }
491
+ async estimateCost(options) {
492
+ if (options.machines.length === 0) throw new ValidationError("Fleet cost estimate requires at least one machine");
493
+ assertUniqueMachineIds(options.machines);
494
+ assertPolicy(options.machines, options.defaults, options.policy);
495
+ const response = await this.client.fetch("/v1/fleets/estimate", {
496
+ method: "POST",
497
+ body: JSON.stringify({
498
+ machineCount: options.machines.length,
499
+ policy: options.policy,
500
+ resources: calculateResourceTotals(options.machines, options.defaults),
501
+ maxConcurrentCreates: options.maxConcurrentCreates
502
+ })
503
+ });
504
+ if (!response.ok) {
505
+ const body = await response.text();
506
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
507
+ }
508
+ return (await response.json()).estimate;
509
+ }
510
+ async create(options) {
511
+ const fleetId = options.fleetId ?? createFleetId();
512
+ assertSafeId(fleetId, "fleetId");
513
+ if (options.machines.length === 0) throw new ValidationError("Fleet create requires at least one machine");
514
+ assertUniqueMachineIds(options.machines);
515
+ assertNoReservedMetadata(options.metadata, "Fleet");
516
+ for (const machine of options.machines) assertNoReservedMetadata(machine.metadata, `Fleet machine '${machine.machineId}'`);
517
+ assertPolicy(options.machines, options.defaults, options.policy);
518
+ const maxConcurrentCreates = resolveMaxConcurrentCreates(options.maxConcurrentCreates);
519
+ if (options.policy?.maxConcurrentCreates !== void 0 && maxConcurrentCreates > options.policy.maxConcurrentCreates) throw new ValidationError(`Fleet create requested ${maxConcurrentCreates} concurrent creates but policy.maxConcurrentCreates is ${options.policy.maxConcurrentCreates}`, { "policy.maxConcurrentCreates": "exceeded" });
520
+ if (options.policy?.maxSpendUsd !== void 0) {
521
+ const estimate = await this.estimateCost({
522
+ defaults: options.defaults,
523
+ machines: options.machines,
524
+ policy: options.policy,
525
+ maxConcurrentCreates
526
+ });
527
+ if (estimate.estimatedMaxLifetimeUsd > options.policy.maxSpendUsd) throw new ValidationError(`Fleet estimated max lifetime cost $${estimate.estimatedMaxLifetimeUsd} exceeds policy.maxSpendUsd $${options.policy.maxSpendUsd}`, { "policy.maxSpendUsd": "exceeded" });
528
+ }
529
+ const created = [];
530
+ try {
531
+ for (let i = 0; i < options.machines.length; i += maxConcurrentCreates) {
532
+ const batch = options.machines.slice(i, i + maxConcurrentCreates);
533
+ const results = await Promise.allSettled(batch.map((machine) => this.createMachine(fleetId, machine, options)));
534
+ const rejected = results.find((result) => result.status === "rejected");
535
+ for (const result of results) if (result.status === "fulfilled") created.push(result.value);
536
+ if (rejected) throw rejected.reason;
537
+ }
538
+ } catch (err) {
539
+ if (options.cleanupOnFailure !== false) await Promise.allSettled(created.map(async (machine) => {
540
+ await (await this.client.get(machine.sandbox.id))?.delete();
541
+ }));
542
+ throw err;
543
+ }
544
+ try {
545
+ await this.persistFleet(fleetId, created, options);
546
+ } catch (err) {
547
+ if (options.cleanupOnFailure !== false) await Promise.allSettled(created.map(async (machine) => {
548
+ await (await this.client.get(machine.sandbox.id))?.delete();
549
+ }));
550
+ throw err;
551
+ }
552
+ return this.fromInfo({
553
+ fleetId,
554
+ machines: created
555
+ });
556
+ }
557
+ async createWithCoordinator(options) {
558
+ const { coordinator: coordinatorOptions, workers, ...createOptions } = options;
559
+ const coordinator = {
560
+ ...coordinatorOptions,
561
+ machineId: coordinatorOptions?.machineId ?? "coordinator",
562
+ role: "coordinator"
563
+ };
564
+ return this.create({
565
+ ...createOptions,
566
+ machines: [coordinator, ...workers.map((worker) => ({
567
+ ...worker,
568
+ role: worker.role ?? "worker"
569
+ }))]
570
+ });
571
+ }
572
+ async createMachine(fleetId, machine, options) {
573
+ const merged = mergeCreateOptions(options.defaults, machine);
574
+ const sandbox = await this.client.create({
575
+ ...merged,
576
+ name: machine.name ?? `${fleetId}-${machine.machineId}`,
577
+ maxLifetimeSeconds: merged.maxLifetimeSeconds ?? DEFAULT_MACHINE_LIFETIME_SECONDS,
578
+ metadata: {
579
+ ...options.metadata,
580
+ ...machine.metadata,
581
+ [FLEET_ID_META]: fleetId,
582
+ [MACHINE_ID_META]: machine.machineId,
583
+ [MACHINE_ROLE_META]: machine.role ?? "worker",
584
+ [FLEET_CREATED_BY_META]: "@tangle-network/sandbox",
585
+ ...workspaceMetadata(options.workspace)
586
+ }
587
+ });
588
+ return {
589
+ machineId: machine.machineId,
590
+ sandbox: sandbox.toJSON(),
591
+ role: machine.role
592
+ };
593
+ }
594
+ async list(options) {
595
+ assertSafeId(options.fleetId, "fleetId");
596
+ const serverFleet = await this.getServerFleet(options.fleetId);
597
+ if (serverFleet) return this.fromInfo(await this.hydrateServerFleet(serverFleet, options));
598
+ return this.listBySandboxMetadata(options);
599
+ }
600
+ async delete(fleetId, options) {
601
+ await (await this.list({
602
+ ...options,
603
+ fleetId
604
+ })).delete({ continueOnError: options?.continueOnError });
605
+ }
606
+ fromInfo(info) {
607
+ const machines = /* @__PURE__ */ new Map();
608
+ for (const machine of info.machines) machines.set(machine.machineId, machine.sandbox);
609
+ return new SandboxFleet(this.client, info.fleetId, machines);
610
+ }
611
+ async persistFleet(fleetId, machines, options) {
612
+ const response = await this.client.fetch("/v1/fleets", {
613
+ method: "POST",
614
+ headers: { "Idempotency-Key": options.idempotencyKey ?? fleetId },
615
+ body: JSON.stringify({
616
+ id: fleetId,
617
+ metadata: {
618
+ ...options.metadata,
619
+ ...workspaceMetadata(options.workspace)
620
+ },
621
+ workspace: options.workspace,
622
+ policy: options.policy,
623
+ resources: calculateResourceTotals(options.machines, options.defaults),
624
+ maxConcurrentCreates: options.maxConcurrentCreates,
625
+ machines: machines.map((machine) => ({
626
+ machineId: machine.machineId,
627
+ sandboxId: machine.sandbox.id,
628
+ status: machine.sandbox.status,
629
+ role: machine.role,
630
+ ...getMachineDescriptor(options, machine.machineId)
631
+ }))
632
+ })
633
+ });
634
+ if (!response.ok) {
635
+ const body = await response.text();
636
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
637
+ }
638
+ }
639
+ async getServerFleet(fleetId) {
640
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}`);
641
+ if (response.status === 404) return null;
642
+ if (!response.ok) {
643
+ const body = await response.text();
644
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
645
+ }
646
+ return (await response.json()).fleet ?? null;
647
+ }
648
+ async deleteRecord(fleetId) {
649
+ assertSafeId(fleetId, "fleetId");
650
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}`, { method: "DELETE" });
651
+ if (response.status === 404) return;
652
+ if (!response.ok) {
653
+ const body = await response.text();
654
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
655
+ }
656
+ }
657
+ async usage(fleetId) {
658
+ assertSafeId(fleetId, "fleetId");
659
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/usage`);
660
+ if (!response.ok) {
661
+ const body = await response.text();
662
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
663
+ }
664
+ return await response.json();
665
+ }
666
+ async trace(fleetId, options = {}) {
667
+ assertSafeId(fleetId, "fleetId");
668
+ const query = new URLSearchParams();
669
+ if (options.includeIntelligence === true) query.set("includeIntelligence", "true");
670
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/trace${query.size ? `?${query}` : ""}`);
671
+ if (!response.ok) {
672
+ const body = await response.text();
673
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
674
+ }
675
+ return await response.json();
676
+ }
677
+ async intelligence(fleetId) {
678
+ const intelligence = (await this.trace(fleetId, { includeIntelligence: true })).intelligence;
679
+ if (!intelligence) throw new Error("Fleet intelligence was not returned");
680
+ return intelligence;
681
+ }
682
+ async createIntelligenceReport(fleetId, options = {}) {
683
+ return this.client.intelligence.createReport({
684
+ subject: {
685
+ type: "fleet",
686
+ id: fleetId
687
+ },
688
+ ...options
689
+ });
690
+ }
691
+ async createAgenticIntelligenceReport(fleetId, options) {
692
+ return this.createIntelligenceReport(fleetId, {
693
+ mode: "agentic",
694
+ budget: {
695
+ billTo: "customer",
696
+ maxUsd: options.maxUsd
697
+ },
698
+ metadata: options.metadata
699
+ });
700
+ }
701
+ async exportTrace(fleetId, sink) {
702
+ return exportTraceBundle(await this.trace(fleetId), sink);
703
+ }
704
+ async cost(fleetId) {
705
+ assertSafeId(fleetId, "fleetId");
706
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/cost`);
707
+ if (!response.ok) {
708
+ const body = await response.text();
709
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
710
+ }
711
+ return (await response.json()).estimate;
712
+ }
713
+ async createToken(fleetId, options) {
714
+ assertSafeId(fleetId, "fleetId");
715
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/token`, {
716
+ method: "POST",
717
+ body: JSON.stringify(options ?? {})
718
+ });
719
+ if (!response.ok) {
720
+ const body = await response.text();
721
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
722
+ }
723
+ return await response.json();
724
+ }
725
+ async manifest(fleetId) {
726
+ assertSafeId(fleetId, "fleetId");
727
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/manifest`);
728
+ if (!response.ok) {
729
+ const body = await response.text();
730
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
731
+ }
732
+ return await response.json();
733
+ }
734
+ async attachMachine(fleetId, machine) {
735
+ assertSafeId(fleetId, "fleetId");
736
+ assertSafeId(machine.machineId, "machineId");
737
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/machines`, {
738
+ method: "POST",
739
+ body: JSON.stringify(machine)
740
+ });
741
+ if (!response.ok) {
742
+ const body = await response.text();
743
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
744
+ }
745
+ return (await response.json()).machine;
746
+ }
747
+ async detachMachine(fleetId, machineId) {
748
+ assertSafeId(fleetId, "fleetId");
749
+ assertSafeId(machineId, "machineId");
750
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/machines/${encodeURIComponent(machineId)}`, { method: "DELETE" });
751
+ if (!response.ok) {
752
+ const body = await response.text();
753
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
754
+ }
755
+ return await response.json();
756
+ }
757
+ async dispatchResults(fleetId, dispatchId, options) {
758
+ assertSafeId(fleetId, "fleetId");
759
+ assertSafeId(dispatchId, "dispatchId");
760
+ const response = await this.client.fetch(dispatchResultsPath(fleetId, dispatchId, options));
761
+ if (!response.ok) {
762
+ const body = await response.text();
763
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
764
+ }
765
+ return await response.json();
766
+ }
767
+ async cancelDispatch(fleetId, dispatchId, reason) {
768
+ assertSafeId(fleetId, "fleetId");
769
+ assertSafeId(dispatchId, "dispatchId");
770
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/dispatch/${encodeURIComponent(dispatchId)}/cancel`, {
771
+ method: "POST",
772
+ body: JSON.stringify(reason ? { reason } : {})
773
+ });
774
+ if (!response.ok) {
775
+ const body = await response.text();
776
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
777
+ }
778
+ return await response.json();
779
+ }
780
+ async createWorkspaceSnapshot(fleetId) {
781
+ assertSafeId(fleetId, "fleetId");
782
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/workspace/snapshots`, { method: "POST" });
783
+ if (!response.ok) {
784
+ const body = await response.text();
785
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
786
+ }
787
+ return await response.json();
788
+ }
789
+ async restoreWorkspaceSnapshot(fleetId, snapshotId) {
790
+ assertSafeId(fleetId, "fleetId");
791
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/workspace/snapshots/${encodeURIComponent(snapshotId)}/restore`, { method: "POST" });
792
+ if (!response.ok) {
793
+ const body = await response.text();
794
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
795
+ }
796
+ return await response.json();
797
+ }
798
+ async reconcileWorkspace(fleetId) {
799
+ assertSafeId(fleetId, "fleetId");
800
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/workspace/reconcile`, { method: "POST" });
801
+ if (!response.ok) {
802
+ const body = await response.text();
803
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
804
+ }
805
+ return await response.json();
806
+ }
807
+ async *dispatchExecStream(fleetId, command, options) {
808
+ assertSafeId(fleetId, "fleetId");
809
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/dispatch/stream`, {
810
+ method: "POST",
811
+ headers: { Accept: "text/event-stream" },
812
+ signal: options?.signal,
813
+ body: JSON.stringify(execDispatchBody(command, options))
814
+ });
815
+ if (!response.ok) {
816
+ const body = await response.text();
817
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
818
+ }
819
+ yield* parseSSEStream(response.body, { signal: options?.signal });
820
+ }
821
+ async *dispatchPromptStream(fleetId, message, options) {
822
+ assertSafeId(fleetId, "fleetId");
823
+ const response = await this.client.fetch(`/v1/fleets/${fleetId}/dispatch/stream`, {
824
+ method: "POST",
825
+ headers: { Accept: "text/event-stream" },
826
+ signal: options?.signal,
827
+ body: JSON.stringify(promptDispatchBody(message, options))
828
+ });
829
+ if (!response.ok) {
830
+ const body = await response.text();
831
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
832
+ }
833
+ yield* parseSSEStream(response.body, { signal: options?.signal });
834
+ }
835
+ async reapExpired(options) {
836
+ const response = await this.client.fetch("/v1/fleets/reap-expired", {
837
+ method: "POST",
838
+ body: JSON.stringify(options ?? {})
839
+ });
840
+ if (!response.ok) {
841
+ const body = await response.text();
842
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
843
+ }
844
+ return await response.json();
845
+ }
846
+ async hydrateServerFleet(fleet, options) {
847
+ const machines = await Promise.all(fleet.machines.map(async (machine) => {
848
+ const box = await this.client.get(machine.sandboxId);
849
+ if (!box) throw new ValidationError(`Fleet machine '${machine.machineId}' points at missing sandbox '${machine.sandboxId}'`, { machineId: "missing_sandbox" });
850
+ return {
851
+ machineId: machine.machineId,
852
+ sandbox: box.toJSON(),
853
+ role: machine.role
854
+ };
855
+ }));
856
+ const filtered = options.status ? machines.filter((machine) => machine.sandbox.status === options.status) : machines;
857
+ return {
858
+ fleetId: fleet.id,
859
+ machines: filtered
860
+ };
861
+ }
862
+ async listBySandboxMetadata(options) {
863
+ const machines = (await this.client.list(options)).map((box) => box.toJSON()).filter((info) => getFleetId(info) === options.fleetId).flatMap((info) => {
864
+ const machineId = getMachineId(info);
865
+ return machineId ? [{
866
+ machineId,
867
+ sandbox: info,
868
+ role: getMachineRole(info)
869
+ }] : [];
870
+ });
871
+ return this.fromInfo({
872
+ fleetId: options.fleetId,
873
+ machines
874
+ });
875
+ }
876
+ };
877
+ function getMachineDescriptor(options, machineId) {
878
+ const spec = options.machines.find((machine) => machine.machineId === machineId);
879
+ if (!spec) return {};
880
+ const merged = mergeCreateOptions(options.defaults, spec);
881
+ return {
882
+ driverType: merged.driver?.type,
883
+ image: merged.image,
884
+ environment: merged.environment,
885
+ templateId: merged.templateId,
886
+ publicTemplateId: merged.publicTemplateId,
887
+ acceleratorCount: merged.resources?.accelerator?.count ?? (merged.resources?.accelerator ? 1 : void 0)
888
+ };
889
+ }
890
+ async function mapConcurrent(items, concurrency, fn) {
891
+ const results = new Array(items.length);
892
+ let index = 0;
893
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
894
+ while (index < items.length) {
895
+ const current = index;
896
+ index += 1;
897
+ const item = items[current];
898
+ if (item === void 0) throw new Error(`Missing fleet item at index ${current}`);
899
+ results[current] = await fn(item);
900
+ }
901
+ });
902
+ await Promise.all(workers);
903
+ return results;
904
+ }
905
+ function assertArtifactPath(path) {
906
+ if (path !== DEFAULT_ARTIFACT_ROOT && !path.startsWith(`${DEFAULT_ARTIFACT_ROOT}/`)) throw new ValidationError(`Fleet artifact path '${path}' must be under ${DEFAULT_ARTIFACT_ROOT}`, { path: "outside_workspace" });
907
+ if (path.includes("\0") || path.split("/").includes("..")) throw new ValidationError(`Fleet artifact path '${path}' must not contain traversal segments`, { path: "invalid" });
908
+ }
909
+ function assertArtifactSize(content, artifact) {
910
+ const maxBytes = artifact.maxBytes ?? DEFAULT_MAX_ARTIFACT_BYTES;
911
+ if (!Number.isFinite(maxBytes) || maxBytes <= 0) throw new ValidationError("artifact.maxBytes must be a positive number", { maxBytes: "invalid" });
912
+ if (new TextEncoder().encode(content).byteLength > maxBytes) throw new ValidationError(`Fleet artifact '${artifact.path}' exceeded ${maxBytes} bytes`, { path: "too_large" });
913
+ }
914
+ function getMachineRole(info) {
915
+ const value = info.metadata?.[MACHINE_ROLE_META];
916
+ return value === "coordinator" || value === "worker" ? value : void 0;
917
+ }
918
+ //#endregion
919
+ //#region src/client.ts
920
+ /**
921
+ * Sandbox Client
922
+ *
923
+ * Main client class for interacting with the Sandbox API.
924
+ */
925
+ const DEFAULT_TIMEOUT_MS = 3e4;
926
+ /**
927
+ * Default time budget for `create()` — the full create-and-reach-running
928
+ * flow. Cold provisions (image pull + container create + sidecar boot)
929
+ * routinely take ~25–40s, well past the generic 30s request timeout, so
930
+ * the create POST and the poll-to-running ride this higher ceiling
931
+ * instead. A POST that rides the deadline polls the box to `running`
932
+ * rather than re-POSTing (the idempotency key already makes a re-POST
933
+ * safe server-side, but polling is cheaper and avoids duplicate work).
934
+ */
935
+ const DEFAULT_CREATE_TIMEOUT_MS = 12e4;
936
+ /**
937
+ * Extra grace the SDK's client-side batch deadline gets on top of
938
+ * the server-side `timeoutMs`. The server's clock starts when the
939
+ * HTTP request lands on the server; the SDK's `AbortSignal.timeout`
940
+ * clock starts before the TCP handshake. Without a grace buffer,
941
+ * the two deadlines line up almost exactly — and a batch that legitimately
942
+ * runs to the full `timeoutMs` on the server will race the SDK
943
+ * deadline firing as the final `batch.completed` SSE event is still
944
+ * in transit, making the caller throw `AbortError` and drop the
945
+ * result.
946
+ *
947
+ * The client deadline is purely a safety valve against hung
948
+ * connections, not a batch correctness timer — the server enforces
949
+ * the real `timeoutMs`. 30s is enough to cover TCP/TLS setup, the
950
+ * server-side batch bookkeeping, and SSE teardown, while still
951
+ * shutting down quickly on a truly hung stream.
952
+ */
953
+ const CLIENT_DEADLINE_GRACE_MS = 3e4;
954
+ function isLocalSandboxEndpoint(baseUrl) {
955
+ try {
956
+ const { hostname } = new URL(baseUrl);
957
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
958
+ } catch {
959
+ return false;
960
+ }
961
+ }
962
+ function generateIdempotencyKey() {
963
+ const c = globalThis.crypto;
964
+ if (c?.randomUUID) return c.randomUUID();
965
+ if (c?.getRandomValues) {
966
+ const bytes = c.getRandomValues(new Uint8Array(16));
967
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
968
+ }
969
+ throw new Error("idempotency key generation requires global crypto");
970
+ }
971
+ function withRequestContextHeaders(response, path, method) {
972
+ const headers = new Headers(response.headers);
973
+ headers.set("x-tangle-request-path", path);
974
+ if (method) headers.set("x-tangle-request-method", method);
975
+ return new Response(response.body, {
976
+ status: response.status,
977
+ statusText: response.statusText,
978
+ headers
979
+ });
980
+ }
981
+ async function resolveLocalCliAuthFiles(backendType) {
982
+ try {
983
+ const { discoverLocalCliAuthFiles } = await importLocalCliRegistry();
984
+ return discoverLocalCliAuthFiles(backendType);
985
+ } catch {
986
+ return discoverLocalCliAuthFilesFallback(backendType);
987
+ }
988
+ }
989
+ async function discoverLocalCliAuthFilesFallback(backendType) {
990
+ try {
991
+ const [{ existsSync, readFileSync }, { homedir }, { join }] = await Promise.all([
992
+ import("node:fs"),
993
+ import("node:os"),
994
+ import("node:path")
995
+ ]);
996
+ const homeDir = homedir();
997
+ const authFiles = (backendType === "codex" ? [".codex/auth.json"] : [".claude/.credentials.json", ".claude/settings.json"]).flatMap((path) => {
998
+ const absolutePath = join(homeDir, path);
999
+ if (!existsSync(absolutePath)) return [];
1000
+ return [{
1001
+ path,
1002
+ content: readFileSync(absolutePath, "utf8"),
1003
+ mode: 384
1004
+ }];
1005
+ });
1006
+ return authFiles.length ? authFiles : void 0;
1007
+ } catch {
1008
+ return;
1009
+ }
1010
+ }
1011
+ async function describeLocalCliAuthExpectation(backendType) {
1012
+ try {
1013
+ return (await importLocalCliRegistry()).describeLocalCliAuthExpectation(backendType);
1014
+ } catch {
1015
+ return backendType === "codex" ? "~/.codex/auth.json" : "~/.claude/.credentials.json or ~/.claude/settings.json";
1016
+ }
1017
+ }
1018
+ async function importLocalCliRegistry() {
1019
+ return await import(
1020
+ /* @vite-ignore */
1021
+ ["@tangle-network", "cli-agent-registry"].join("/")
1022
+ );
1023
+ }
1024
+ async function hydrateLocalCliBackendAuth(backend, baseUrl, trustLocalCliAuth) {
1025
+ if (!backend) return void 0;
1026
+ if (!isLocalSandboxEndpoint(baseUrl)) return backend;
1027
+ if (backend.type !== "codex" && backend.type !== "claude-code") return backend;
1028
+ if (backend.model?.authFiles?.length) return backend;
1029
+ if (backend.model?.apiKey) return backend;
1030
+ if (backend.model?.authMode === "api-key") return backend;
1031
+ if (!trustLocalCliAuth) {
1032
+ const cliName = backend.type === "codex" ? "codex" : "claude";
1033
+ throw new AuthError(`Local ${backend.type} backend requested for ${baseUrl}, but the SDK will not read CLI credentials from the host home directory unless you opt in by constructing the client with \`new Sandbox({ ..., trustLocalCliAuth: true })\`. Only opt in when the localhost endpoint is one you control. Otherwise, pass \`backend.model.apiKey\` / \`backend.model.authFiles\` explicitly, or run \`${cliName} login\` inside the sandbox.`);
1034
+ }
1035
+ const authFiles = await resolveLocalCliAuthFiles(backend.type);
1036
+ if (!authFiles?.length) {
1037
+ const cliName = backend.type === "codex" ? "codex" : "claude";
1038
+ const expectation = await describeLocalCliAuthExpectation(backend.type);
1039
+ throw new AuthError(`Local ${backend.type} backend requested for ${baseUrl}, but no local CLI auth was found. Run \`${cliName} login\` on the host or provide backend.model.apiKey/authFiles explicitly. Expected auth under ${expectation}.`);
1040
+ }
1041
+ return {
1042
+ ...backend,
1043
+ model: {
1044
+ ...backend.model,
1045
+ authMode: backend.model?.authMode ?? "oauth",
1046
+ authFiles
1047
+ }
1048
+ };
1049
+ }
1050
+ /**
1051
+ * Client for the Tangle Sandbox platform.
1052
+ *
1053
+ * @example
1054
+ * ```typescript
1055
+ * import { Sandbox } from "@tangle-network/sandbox";
1056
+ *
1057
+ * const client = new Sandbox({
1058
+ * apiKey: "sk_sandbox_...",
1059
+ * baseUrl: "https://your-sandbox-api.example.com",
1060
+ * });
1061
+ *
1062
+ * // Create a sandbox
1063
+ * const box = await client.create({
1064
+ * name: "my-project",
1065
+ * sshEnabled: true,
1066
+ * });
1067
+ *
1068
+ * // Execute commands
1069
+ * const result = await box.exec("npm install");
1070
+ *
1071
+ * // Clean up
1072
+ * await box.delete();
1073
+ * ```
1074
+ */
1075
+ var SandboxClient = class {
1076
+ baseUrl;
1077
+ apiKey;
1078
+ timeoutMs;
1079
+ trustLocalCliAuth;
1080
+ _secrets = null;
1081
+ _sshKeys = null;
1082
+ _fleets = null;
1083
+ _intelligence = null;
1084
+ constructor(config) {
1085
+ if (!config.apiKey) throw new AuthError("API key is required");
1086
+ if (!config.baseUrl) throw new ValidationError("Base URL is required");
1087
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
1088
+ this.apiKey = config.apiKey;
1089
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1090
+ this.trustLocalCliAuth = config.trustLocalCliAuth === true;
1091
+ }
1092
+ /**
1093
+ * Fleet operations for agent-managed groups of sandboxes.
1094
+ *
1095
+ * Fleets are an SDK-level convenience over the existing sandbox lifecycle
1096
+ * API. Each worker is a normal sandbox tagged with stable fleet metadata,
1097
+ * so older clients and dashboards can still inspect and delete them.
1098
+ */
1099
+ get fleets() {
1100
+ if (!this._fleets) this._fleets = new SandboxFleetClient(this);
1101
+ return this._fleets;
1102
+ }
1103
+ get intelligence() {
1104
+ if (!this._intelligence) this._intelligence = new IntelligenceClient(this);
1105
+ return this._intelligence;
1106
+ }
1107
+ getApiKey() {
1108
+ return this.apiKey;
1109
+ }
1110
+ /**
1111
+ * Access the secrets manager for storing and retrieving encrypted secrets.
1112
+ *
1113
+ * @example
1114
+ * ```typescript
1115
+ * // Create a secret
1116
+ * await client.secrets.create("HF_TOKEN", "hf_xxx");
1117
+ *
1118
+ * // List secrets (names only)
1119
+ * const secrets = await client.secrets.list();
1120
+ *
1121
+ * // Get secret value
1122
+ * const value = await client.secrets.get("HF_TOKEN");
1123
+ *
1124
+ * // Update secret
1125
+ * await client.secrets.update("HF_TOKEN", "hf_new_value");
1126
+ *
1127
+ * // Delete secret
1128
+ * await client.secrets.delete("HF_TOKEN");
1129
+ * ```
1130
+ */
1131
+ get secrets() {
1132
+ if (!this._secrets) this._secrets = new SecretsManagerImpl(this);
1133
+ return this._secrets;
1134
+ }
1135
+ get sshKeys() {
1136
+ if (!this._sshKeys) this._sshKeys = new SshKeysManagerImpl(this);
1137
+ return this._sshKeys;
1138
+ }
1139
+ _environments = null;
1140
+ /**
1141
+ * Access available development environments.
1142
+ *
1143
+ * @example
1144
+ * ```typescript
1145
+ * const envs = await client.environments.list();
1146
+ * // → [{ id: "universal", description: "Universal environment with all toolchains via Nix", ... }]
1147
+ *
1148
+ * const sandbox = await client.create({ environment: "universal" });
1149
+ * ```
1150
+ */
1151
+ get environments() {
1152
+ if (!this._environments) this._environments = new EnvironmentsClient(this);
1153
+ return this._environments;
1154
+ }
1155
+ _teams = null;
1156
+ _publicTemplates = null;
1157
+ /**
1158
+ * Access team management (collaboration groups, invitations,
1159
+ * member roles). Teams scope shared sandboxes — pass `teamId` to
1160
+ * `client.create()` to create a sandbox accessible to a team's
1161
+ * members.
1162
+ *
1163
+ * @example
1164
+ * ```typescript
1165
+ * const teams = await client.teams.list();
1166
+ * const invite = await client.teams.invite(teams[0].id, {
1167
+ * email: "alice@example.com",
1168
+ * role: "member",
1169
+ * });
1170
+ * ```
1171
+ */
1172
+ get teams() {
1173
+ if (!this._teams) this._teams = new TeamsClient(this);
1174
+ return this._teams;
1175
+ }
1176
+ get publicTemplates() {
1177
+ if (!this._publicTemplates) this._publicTemplates = new PublicTemplatesClient(this);
1178
+ return this._publicTemplates;
1179
+ }
1180
+ /**
1181
+ * Create a new sandbox.
1182
+ *
1183
+ * @param options - Configuration for the new sandbox
1184
+ * @returns A SandboxInstance representing the created sandbox
1185
+ *
1186
+ * @example
1187
+ * ```typescript
1188
+ * const box = await client.create({
1189
+ * name: "my-project",
1190
+ * environment: "universal",
1191
+ * sshEnabled: true,
1192
+ * env: { NODE_ENV: "development" },
1193
+ * });
1194
+ * ```
1195
+ */
1196
+ async create(options, requestOptions) {
1197
+ const timeoutMs = requestOptions?.timeoutMs ?? DEFAULT_CREATE_TIMEOUT_MS;
1198
+ const idempotencyKey = options?.idempotencyKey ?? generateIdempotencyKey();
1199
+ const deadline = Date.now() + timeoutMs;
1200
+ const git = options?.git ? {
1201
+ url: options.git.url,
1202
+ ref: options.git.ref,
1203
+ depth: options.git.depth,
1204
+ sparse: options.git.sparse,
1205
+ auth: options.git.auth
1206
+ } : void 0;
1207
+ const resolvedEnvironment = options?.environment ?? options?.image;
1208
+ const backend = await hydrateLocalCliBackendAuth(options?.backend, this.baseUrl, this.trustLocalCliAuth);
1209
+ const resolvedBackend = backend ? {
1210
+ ...backend,
1211
+ type: backend.type ?? "opencode"
1212
+ } : void 0;
1213
+ const resources = normalizeSandboxResources(options?.resources, options?.driver);
1214
+ const postDeadline = AbortSignal.timeout(timeoutMs);
1215
+ const postSignal = requestOptions?.signal ? AbortSignal.any([requestOptions.signal, postDeadline]) : postDeadline;
1216
+ let response;
1217
+ try {
1218
+ response = await this.fetch("/v1/sandboxes", {
1219
+ method: "POST",
1220
+ headers: { "Idempotency-Key": idempotencyKey },
1221
+ signal: postSignal,
1222
+ body: JSON.stringify({
1223
+ name: options?.name,
1224
+ ...resolvedEnvironment !== void 0 ? {
1225
+ environment: resolvedEnvironment,
1226
+ image: resolvedEnvironment
1227
+ } : {},
1228
+ git,
1229
+ tools: options?.tools,
1230
+ bare: options?.bare,
1231
+ driver: options?.driver,
1232
+ backend: resolvedBackend,
1233
+ permissions: options?.permissions,
1234
+ env: options?.env,
1235
+ resources,
1236
+ sshEnabled: options?.sshEnabled,
1237
+ sshPublicKeys: options?.sshPublicKeys ?? (options?.sshPublicKey ? [options.sshPublicKey] : void 0),
1238
+ sshKeyIds: options?.sshKeyIds,
1239
+ webTerminalEnabled: options?.webTerminalEnabled,
1240
+ maxLifetimeSeconds: options?.maxLifetimeSeconds,
1241
+ idleTimeoutSeconds: options?.idleTimeoutSeconds,
1242
+ storage: options?.storage,
1243
+ fromSnapshot: options?.fromSnapshot,
1244
+ fromSandboxId: options?.fromSandboxId,
1245
+ templateId: options?.templateId,
1246
+ publicTemplateId: options?.publicTemplateId,
1247
+ publicTemplateVersionId: options?.publicTemplateVersionId,
1248
+ secrets: options?.secrets,
1249
+ confidential: options?.confidential,
1250
+ metadata: options?.metadata,
1251
+ teamId: options?.teamId,
1252
+ capabilities: options?.capabilities,
1253
+ privacy: options?.privacy,
1254
+ egressPolicy: options?.egressPolicy,
1255
+ idempotencyKey
1256
+ })
1257
+ });
1258
+ } catch (err) {
1259
+ if (requestOptions?.signal?.aborted) throw err;
1260
+ if (err instanceof TimeoutError || err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) throw new TimeoutError(timeoutMs, `Sandbox create did not complete within ${timeoutMs}ms. The provision may still be running server-side; retry create() with idempotencyKey="${idempotencyKey}" to join it.`);
1261
+ throw err;
1262
+ }
1263
+ if (!response.ok) {
1264
+ const body = await response.text();
1265
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1266
+ }
1267
+ const data = await response.json();
1268
+ const instance = new SandboxInstance(this, this.parseInfo(data), resolvedBackend);
1269
+ if (instance.status === "provisioning" || instance.status === "pending") {
1270
+ const remainingMs = Math.max(0, deadline - Date.now());
1271
+ await instance.waitFor("running", {
1272
+ timeoutMs: remainingMs,
1273
+ signal: requestOptions?.signal
1274
+ });
1275
+ }
1276
+ return instance;
1277
+ }
1278
+ /**
1279
+ * Poll a sandbox to `running` by id. Surfaces a real provision failure
1280
+ * as a {@link StateError} (carrying the sandbox's error message) rather
1281
+ * than an opaque timeout, and throws {@link NotFoundError} if the id is
1282
+ * unknown. Use after a {@link create} that timed out client-side but may
1283
+ * have continued provisioning server-side under its idempotency key.
1284
+ */
1285
+ async waitForRunning(id, options) {
1286
+ const instance = await this.get(id);
1287
+ if (!instance) throw new NotFoundError("Sandbox", id, {
1288
+ endpoint: `/v1/sandboxes/${id}`,
1289
+ origin: "sandbox-api"
1290
+ });
1291
+ await instance.waitFor("running", {
1292
+ timeoutMs: options?.timeoutMs ?? DEFAULT_CREATE_TIMEOUT_MS,
1293
+ signal: options?.signal
1294
+ });
1295
+ return instance;
1296
+ }
1297
+ /**
1298
+ * List all sandboxes.
1299
+ *
1300
+ * @param options - Filtering and pagination options
1301
+ * @returns Array of SandboxInstance objects
1302
+ *
1303
+ * @example
1304
+ * ```typescript
1305
+ * // List all running sandboxes
1306
+ * const running = await client.list({ status: "running" });
1307
+ *
1308
+ * // List with pagination
1309
+ * const page = await client.list({ limit: 10, offset: 0 });
1310
+ * ```
1311
+ */
1312
+ async list(options) {
1313
+ const params = new URLSearchParams();
1314
+ if (options?.status) {
1315
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
1316
+ for (const s of statuses) params.append("status", s);
1317
+ }
1318
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
1319
+ if (options?.offset !== void 0) params.set("offset", String(options.offset));
1320
+ if (options?.scope !== void 0) params.set("scope", options.scope);
1321
+ const query = params.toString();
1322
+ const path = query ? `/v1/sandboxes?${query}` : "/v1/sandboxes";
1323
+ const response = await this.fetch(path);
1324
+ if (!response.ok) {
1325
+ const body = await response.text();
1326
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1327
+ }
1328
+ const data = await response.json();
1329
+ return (Array.isArray(data) ? data : data.sandboxes ?? []).map((item) => new SandboxInstance(this, this.parseInfo(item)));
1330
+ }
1331
+ /**
1332
+ * Get a sandbox by ID.
1333
+ *
1334
+ * @param id - The sandbox ID
1335
+ * @returns A SandboxInstance or null if not found
1336
+ *
1337
+ * @example
1338
+ * ```typescript
1339
+ * const box = await client.get("sandbox_abc123");
1340
+ * if (box) {
1341
+ * console.log(box.status);
1342
+ * }
1343
+ * ```
1344
+ */
1345
+ async get(id) {
1346
+ const response = await this.fetch(`/v1/sandboxes/${encodeURIComponent(id)}`);
1347
+ if (response.status === 404) return null;
1348
+ if (!response.ok) {
1349
+ const body = await response.text();
1350
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1351
+ }
1352
+ const data = await response.json();
1353
+ return new SandboxInstance(this, this.parseInfo(data));
1354
+ }
1355
+ /**
1356
+ * Get usage information for the account.
1357
+ *
1358
+ * @returns Usage statistics for the current billing period
1359
+ *
1360
+ * @example
1361
+ * ```typescript
1362
+ * const usage = await client.usage();
1363
+ * console.log(`Active sandboxes: ${usage.activeSandboxes}`);
1364
+ * console.log(`Compute minutes: ${usage.computeMinutes}`);
1365
+ * ```
1366
+ */
1367
+ async usage() {
1368
+ const response = await this.fetch("/usage");
1369
+ if (!response.ok) {
1370
+ const body = await response.text();
1371
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1372
+ }
1373
+ const data = await response.json();
1374
+ return {
1375
+ computeMinutes: data.computeMinutes ?? 0,
1376
+ activeSandboxes: data.activeSandboxes ?? 0,
1377
+ totalSandboxes: data.totalSandboxes ?? 0,
1378
+ periodStart: new Date(data.periodStart),
1379
+ periodEnd: new Date(data.periodEnd)
1380
+ };
1381
+ }
1382
+ /**
1383
+ * Get subscription and billing information for the account.
1384
+ *
1385
+ * @returns Subscription details including plan, credits, and limits
1386
+ *
1387
+ * @example
1388
+ * ```typescript
1389
+ * const sub = await client.subscription();
1390
+ * console.log(`Plan: ${sub.plan}`);
1391
+ * console.log(`Credits: $${sub.creditsAvailableUsd.toFixed(2)}`);
1392
+ * ```
1393
+ */
1394
+ async subscription() {
1395
+ const response = await this.fetch("/subscription");
1396
+ if (!response.ok) {
1397
+ const body = await response.text();
1398
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1399
+ }
1400
+ const data = await response.json();
1401
+ return {
1402
+ plan: data.plan ?? "free",
1403
+ status: data.status ?? "active",
1404
+ creditsAvailableUsd: data.creditsAvailableUsd ?? 0,
1405
+ creditsUsedUsd: data.creditsUsedUsd ?? 0,
1406
+ monthlyBalanceUsd: data.monthlyBalanceUsd ?? 0,
1407
+ maxConcurrentSandboxes: data.maxConcurrentSandboxes ?? 0,
1408
+ overageAllowed: data.overageAllowed ?? false,
1409
+ limits: data.limits ?? {
1410
+ maxCpuCores: 1,
1411
+ maxRamGB: 2,
1412
+ maxStorageGB: 50
1413
+ },
1414
+ currentPeriodEnd: data.currentPeriodEnd ?? 0
1415
+ };
1416
+ }
1417
+ /**
1418
+ * Check if the Sandbox API is available.
1419
+ *
1420
+ * @returns true if the API is healthy, false otherwise
1421
+ */
1422
+ async health() {
1423
+ try {
1424
+ return (await this.fetch("/health")).ok;
1425
+ } catch {
1426
+ return false;
1427
+ }
1428
+ }
1429
+ /**
1430
+ * Check if CRIU checkpointing is available on the platform.
1431
+ *
1432
+ * CRIU enables memory preservation for true pause/resume and fork operations.
1433
+ * It requires specific host configuration.
1434
+ *
1435
+ * @returns CRIU availability status
1436
+ *
1437
+ * @example
1438
+ * ```typescript
1439
+ * const status = await client.criuStatus();
1440
+ * if (status.available) {
1441
+ * console.log(`CRIU ${status.criuVersion} available`);
1442
+ * } else {
1443
+ * console.log(`CRIU not available: ${status.reason}`);
1444
+ * }
1445
+ * ```
1446
+ */
1447
+ async criuStatus() {
1448
+ const response = await this.fetch("/v1/system/criu-status");
1449
+ if (!response.ok) {
1450
+ const body = await response.text();
1451
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1452
+ }
1453
+ return await response.json();
1454
+ }
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) {
1485
+ const results = [];
1486
+ let totalRetries = 0;
1487
+ for await (const event of this.streamBatch(tasks, options)) {
1488
+ if (event.type === "task.completed") {
1489
+ const data = event.data;
1490
+ const usageTotal = (data.usage?.inputTokens ?? 0) + (data.usage?.outputTokens ?? 0);
1491
+ results.push({
1492
+ taskId: data.taskId,
1493
+ success: true,
1494
+ response: data.resultSummary ?? data.response,
1495
+ durationMs: data.durationMs ?? 0,
1496
+ retries: data.retries ?? 0,
1497
+ tokensUsed: data.tokensUsed ?? (usageTotal > 0 ? usageTotal : void 0)
1498
+ });
1499
+ totalRetries += data.retries ?? 0;
1500
+ }
1501
+ if (event.type === "task.failed") {
1502
+ const data = event.data;
1503
+ results.push({
1504
+ taskId: data.taskId,
1505
+ success: false,
1506
+ error: data.error,
1507
+ durationMs: data.durationMs ?? 0,
1508
+ retries: data.retries ?? 0
1509
+ });
1510
+ totalRetries += data.retries ?? 0;
1511
+ }
1512
+ if (event.type === "batch.failed") {
1513
+ const data = event.data;
1514
+ throw new Error(data.error ?? "Batch failed");
1515
+ }
1516
+ }
1517
+ const succeeded = results.filter((r) => r.success).length;
1518
+ const failed = results.filter((r) => !r.success).length;
1519
+ return {
1520
+ totalTasks: tasks.length,
1521
+ succeeded,
1522
+ failed,
1523
+ totalRetries,
1524
+ successRate: tasks.length > 0 ? succeeded / tasks.length * 100 : 0,
1525
+ results
1526
+ };
1527
+ }
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) {
1554
+ const url = `${this.baseUrl}/batch/run`;
1555
+ const resolvedTimeoutMs = options?.timeoutMs ?? 3e5;
1556
+ const requestBody = JSON.stringify({
1557
+ tasks,
1558
+ backend: options?.backend ? {
1559
+ ...options.backend,
1560
+ type: options.backend.type ?? "opencode"
1561
+ } : { type: "opencode" },
1562
+ timeoutMs: resolvedTimeoutMs,
1563
+ scalingMode: options?.scalingMode ?? "balanced",
1564
+ persistent: options?.persistent ?? false,
1565
+ graceMs: options?.graceMs ?? 0
1566
+ });
1567
+ const deadline = AbortSignal.timeout(resolvedTimeoutMs + CLIENT_DEADLINE_GRACE_MS);
1568
+ const combinedSignal = options?.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
1569
+ let response;
1570
+ try {
1571
+ response = await globalThis.fetch(url, {
1572
+ method: "POST",
1573
+ headers: {
1574
+ Authorization: `Bearer ${this.apiKey}`,
1575
+ "Content-Type": "application/json",
1576
+ Accept: "text/event-stream"
1577
+ },
1578
+ body: requestBody,
1579
+ signal: combinedSignal
1580
+ });
1581
+ } catch (err) {
1582
+ if (options?.signal?.aborted) throw err instanceof Error && err.name === "AbortError" ? err : new DOMException("Batch aborted", "AbortError");
1583
+ if (err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) throw new TimeoutError(resolvedTimeoutMs, "Batch request timed out before the first SSE event");
1584
+ throw new NetworkError(`Failed to connect to Sandbox API: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : void 0, {
1585
+ endpoint: "/batch/run",
1586
+ origin: "sandbox-api"
1587
+ });
1588
+ }
1589
+ if (!response.ok) {
1590
+ const errBody = await response.text();
1591
+ throw parseErrorResponse(response.status, errBody, void 0, response.headers);
1592
+ }
1593
+ yield* parseSSEStream(response.body, { signal: combinedSignal });
1594
+ }
1595
+ /**
1596
+ * Make an authenticated HTTP request to the API.
1597
+ * This is exposed for use by SandboxInstance.
1598
+ *
1599
+ * `fetchOptions.timeoutMs` overrides the client-wide `this.timeoutMs`
1600
+ * for a single request. Slow data-plane operations (snapshot create
1601
+ * blocks the server up to 600s while the host-agent compresses and
1602
+ * uploads the rootfs) must pass a higher ceiling, otherwise the
1603
+ * default client timeout aborts mid-operation with a `TimeoutError`
1604
+ * even though the server is still working.
1605
+ */
1606
+ async fetch(path, options, fetchOptions) {
1607
+ const url = `${this.baseUrl}${path}`;
1608
+ const requestTimeoutMs = typeof fetchOptions?.timeoutMs === "number" && fetchOptions.timeoutMs > 0 ? fetchOptions.timeoutMs : this.timeoutMs;
1609
+ const controller = new AbortController();
1610
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);
1611
+ try {
1612
+ const headers = new Headers(options?.headers);
1613
+ headers.set("Authorization", `Bearer ${this.apiKey}`);
1614
+ const isFormDataBody = typeof FormData !== "undefined" && options?.body instanceof FormData;
1615
+ if (!headers.has("Content-Type") && !isFormDataBody) headers.set("Content-Type", "application/json");
1616
+ return withRequestContextHeaders(await globalThis.fetch(url, {
1617
+ ...options,
1618
+ headers,
1619
+ signal: options?.signal ?? controller.signal
1620
+ }), path, options?.method);
1621
+ } catch (err) {
1622
+ if (err instanceof Error && err.name === "AbortError") throw new TimeoutError(requestTimeoutMs);
1623
+ throw new NetworkError(`Failed to connect to Sandbox API: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : void 0, {
1624
+ endpoint: path,
1625
+ origin: "sandbox-api"
1626
+ });
1627
+ } finally {
1628
+ clearTimeout(timeoutId);
1629
+ }
1630
+ }
1631
+ parseInfo(data) {
1632
+ return {
1633
+ id: data.id ?? "",
1634
+ name: data.name,
1635
+ status: data.status ?? "pending",
1636
+ connection: normalizeConnection(data.connection),
1637
+ metadata: data.metadata,
1638
+ createdAt: data.createdAt ? new Date(data.createdAt) : /* @__PURE__ */ new Date(),
1639
+ startedAt: data.startedAt ? new Date(data.startedAt) : void 0,
1640
+ lastActivityAt: data.lastActivityAt ? new Date(data.lastActivityAt) : void 0,
1641
+ expiresAt: data.expiresAt ? new Date(data.expiresAt) : void 0,
1642
+ error: data.error
1643
+ };
1644
+ }
1645
+ _tokenRefreshHandlers = /* @__PURE__ */ new Set();
1646
+ /**
1647
+ * Register a handler invoked when the SDK transparently refreshes a
1648
+ * sandbox bearer after a 401 retry. Returns a function that
1649
+ * unregisters the handler when called.
1650
+ */
1651
+ onTokenRefresh(handler) {
1652
+ this._tokenRefreshHandlers.add(handler);
1653
+ return () => {
1654
+ this._tokenRefreshHandlers.delete(handler);
1655
+ };
1656
+ }
1657
+ /** @internal — fired by the runtime-fetch retry path after a successful
1658
+ * refresh. Errors thrown by handlers are caught and surfaced as
1659
+ * console warnings so one bad subscriber does not poison the rest. */
1660
+ _emitTokenRefresh(sandboxId, newToken) {
1661
+ for (const h of this._tokenRefreshHandlers) try {
1662
+ h(sandboxId, newToken);
1663
+ } catch (err) {
1664
+ console.warn(`[sandbox-sdk] tokenRefresh handler threw: ${err instanceof Error ? err.message : String(err)}`);
1665
+ }
1666
+ }
1667
+ };
1668
+ var SecretsManagerImpl = class {
1669
+ constructor(client) {
1670
+ this.client = client;
1671
+ }
1672
+ async create(name, value) {
1673
+ const response = await this.client.fetch("/v1/secrets", {
1674
+ method: "POST",
1675
+ body: JSON.stringify({
1676
+ name,
1677
+ value
1678
+ })
1679
+ });
1680
+ if (response.status === 409) {
1681
+ const body = await response.text();
1682
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1683
+ }
1684
+ if (response.status === 400) {
1685
+ const body = await response.text();
1686
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1687
+ }
1688
+ if (!response.ok) {
1689
+ const body = await response.text();
1690
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1691
+ }
1692
+ const data = await response.json();
1693
+ return {
1694
+ name: data.name,
1695
+ createdAt: new Date(data.createdAt),
1696
+ updatedAt: new Date(data.updatedAt)
1697
+ };
1698
+ }
1699
+ async list() {
1700
+ const response = await this.client.fetch("/v1/secrets");
1701
+ if (!response.ok) {
1702
+ const body = await response.text();
1703
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1704
+ }
1705
+ return ((await response.json()).secrets ?? []).map((s) => ({
1706
+ name: s.name,
1707
+ createdAt: new Date(s.createdAt),
1708
+ updatedAt: new Date(s.updatedAt)
1709
+ }));
1710
+ }
1711
+ async get(name) {
1712
+ const response = await this.client.fetch(`/v1/secrets/${encodeURIComponent(name)}`);
1713
+ if (response.status === 404) throw new NotFoundError("Secret", name);
1714
+ if (!response.ok) {
1715
+ const body = await response.text();
1716
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1717
+ }
1718
+ return (await response.json()).value;
1719
+ }
1720
+ async update(name, value) {
1721
+ const response = await this.client.fetch(`/v1/secrets/${encodeURIComponent(name)}`, {
1722
+ method: "PATCH",
1723
+ body: JSON.stringify({ value })
1724
+ });
1725
+ if (response.status === 404) throw new NotFoundError("Secret", name);
1726
+ if (response.status === 400) {
1727
+ const body = await response.text();
1728
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1729
+ }
1730
+ if (!response.ok) {
1731
+ const body = await response.text();
1732
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1733
+ }
1734
+ const data = await response.json();
1735
+ return {
1736
+ name: data.name,
1737
+ createdAt: new Date(data.createdAt),
1738
+ updatedAt: new Date(data.updatedAt)
1739
+ };
1740
+ }
1741
+ async delete(name) {
1742
+ const response = await this.client.fetch(`/v1/secrets/${encodeURIComponent(name)}`, { method: "DELETE" });
1743
+ if (response.status === 404) throw new NotFoundError("Secret", name);
1744
+ if (!response.ok) {
1745
+ const body = await response.text();
1746
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1747
+ }
1748
+ }
1749
+ };
1750
+ var SshKeysManagerImpl = class {
1751
+ constructor(client) {
1752
+ this.client = client;
1753
+ }
1754
+ async create(name, publicKey) {
1755
+ const response = await this.client.fetch("/v1/ssh-keys", {
1756
+ method: "POST",
1757
+ body: JSON.stringify({
1758
+ name,
1759
+ publicKey
1760
+ })
1761
+ });
1762
+ if (!response.ok) {
1763
+ const body = await response.text();
1764
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1765
+ }
1766
+ return toSshKeyInfo((await response.json()).sshKey);
1767
+ }
1768
+ async list() {
1769
+ const response = await this.client.fetch("/v1/ssh-keys");
1770
+ if (!response.ok) {
1771
+ const body = await response.text();
1772
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1773
+ }
1774
+ return ((await response.json()).sshKeys ?? []).map(toSshKeyInfo);
1775
+ }
1776
+ async delete(id) {
1777
+ const response = await this.client.fetch(`/v1/ssh-keys/${encodeURIComponent(id)}`, { method: "DELETE" });
1778
+ if (response.status === 404) throw new NotFoundError("SSH key", id);
1779
+ if (!response.ok) {
1780
+ const body = await response.text();
1781
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1782
+ }
1783
+ }
1784
+ };
1785
+ function toSshKeyInfo(key) {
1786
+ return {
1787
+ ...key,
1788
+ createdAt: new Date(key.createdAt),
1789
+ updatedAt: new Date(key.updatedAt)
1790
+ };
1791
+ }
1792
+ /**
1793
+ * Client for the Intelligence Reports API.
1794
+ *
1795
+ * Two analysis modes:
1796
+ *
1797
+ * - `deterministic` (default): free, rule-based platform analysis over
1798
+ * the subject's trace evidence. Returns immediately.
1799
+ * - `agentic`: routes to the **Tangle Trace Analyst**, an LLM-driven
1800
+ * reasoning loop that reads OTLP-shaped trace evidence and emits
1801
+ * findings, evidence references, recommended actions, and a
1802
+ * validation plan. Billed against `budget.maxUsd`; the platform
1803
+ * never spends past the budget the caller sets.
1804
+ *
1805
+ * Subjects: `sandbox` or `fleet`. A fleet subject can be narrowed to
1806
+ * a single coordinated command via `subject.dispatchId`.
1807
+ */
1808
+ var IntelligenceClient = class {
1809
+ constructor(client) {
1810
+ this.client = client;
1811
+ }
1812
+ /**
1813
+ * Create an intelligence report over the given subject.
1814
+ *
1815
+ * Use `mode: "agentic"` to engage the **Tangle Trace Analyst** with a
1816
+ * spending budget. The budget is enforced server-side before billing
1817
+ * fires — a request whose computed cost exceeds `budget.maxUsd`
1818
+ * returns 402 without charging the customer.
1819
+ */
1820
+ async createReport(options) {
1821
+ const response = await this.client.fetch("/v1/intelligence/reports", {
1822
+ method: "POST",
1823
+ body: JSON.stringify(options)
1824
+ });
1825
+ const body = await response.text();
1826
+ if (!response.ok) throw parseErrorResponse(response.status, body, void 0, response.headers);
1827
+ return JSON.parse(body).report;
1828
+ }
1829
+ async createAgenticReport(options) {
1830
+ return this.createReport({
1831
+ subject: options.subject,
1832
+ mode: "agentic",
1833
+ budget: {
1834
+ billTo: "customer",
1835
+ maxUsd: options.maxUsd
1836
+ },
1837
+ metadata: options.metadata
1838
+ });
1839
+ }
1840
+ /**
1841
+ * Estimate the cost of a prospective report without creating one.
1842
+ * Verifies subject ownership the same way `createReport` does, so it
1843
+ * never becomes an existence oracle for foreign subjects.
1844
+ */
1845
+ async estimateReport(options) {
1846
+ const response = await this.client.fetch("/v1/intelligence/reports/estimate", {
1847
+ method: "POST",
1848
+ body: JSON.stringify(options)
1849
+ });
1850
+ const body = await response.text();
1851
+ if (!response.ok) throw parseErrorResponse(response.status, body, void 0, response.headers);
1852
+ return JSON.parse(body).estimate;
1853
+ }
1854
+ async getReport(jobId) {
1855
+ const response = await this.client.fetch(`/v1/intelligence/reports/${encodeURIComponent(jobId)}`);
1856
+ const body = await response.text();
1857
+ if (!response.ok) throw parseErrorResponse(response.status, body, void 0, response.headers);
1858
+ return JSON.parse(body).report;
1859
+ }
1860
+ async listReports(options = {}) {
1861
+ const query = new URLSearchParams();
1862
+ if (options.subjectType) query.set("subjectType", options.subjectType);
1863
+ if (options.subjectId) query.set("subjectId", options.subjectId);
1864
+ if (options.limit !== void 0) query.set("limit", String(options.limit));
1865
+ const suffix = query.size > 0 ? `?${query}` : "";
1866
+ const response = await this.client.fetch(`/v1/intelligence/reports${suffix}`);
1867
+ const body = await response.text();
1868
+ if (!response.ok) throw parseErrorResponse(response.status, body, void 0, response.headers);
1869
+ return JSON.parse(body).reports;
1870
+ }
1871
+ async waitForReport(jobId, options = {}) {
1872
+ const deadline = Date.now() + (options.timeoutMs ?? 6e4);
1873
+ const pollMs = options.pollMs ?? 1e3;
1874
+ while (true) {
1875
+ const report = await this.getReport(jobId);
1876
+ if (report.status === "completed" || report.status === "failed") return report;
1877
+ if (Date.now() >= deadline) throw new TimeoutError(options.timeoutMs ?? 6e4);
1878
+ await new Promise((resolve) => setTimeout(resolve, Math.min(pollMs, Math.max(0, deadline - Date.now()))));
1879
+ }
1880
+ }
1881
+ };
1882
+ /**
1883
+ * Client for browsing available development environments.
1884
+ */
1885
+ var EnvironmentsClient = class {
1886
+ constructor(client) {
1887
+ this.client = client;
1888
+ }
1889
+ /**
1890
+ * List available development environments.
1891
+ *
1892
+ * @returns Array of available environments with metadata
1893
+ *
1894
+ * @example
1895
+ * ```typescript
1896
+ * const envs = await client.environments.list();
1897
+ * for (const env of envs) {
1898
+ * console.log(`${env.id}: ${env.description}`);
1899
+ * }
1900
+ * ```
1901
+ */
1902
+ async list() {
1903
+ const response = await this.client.fetch("/v1/environments");
1904
+ if (!response.ok) {
1905
+ const body = await response.text();
1906
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1907
+ }
1908
+ return (await response.json()).environments ?? [];
1909
+ }
1910
+ /**
1911
+ * Get details for a specific environment.
1912
+ *
1913
+ * @param id - Environment identifier (e.g., "universal")
1914
+ */
1915
+ async get(id) {
1916
+ return (await this.list()).find((e) => e.id === id) ?? null;
1917
+ }
1918
+ };
1919
+ var PublicTemplatesClient = class {
1920
+ constructor(client) {
1921
+ this.client = client;
1922
+ }
1923
+ async list(options) {
1924
+ const params = new URLSearchParams();
1925
+ if (options?.query) params.set("q", options.query);
1926
+ if (options?.tag) params.set("tag", options.tag);
1927
+ if (options?.featuredOnly) params.set("featured", "true");
1928
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
1929
+ if (options?.offset !== void 0) params.set("offset", String(options.offset));
1930
+ const suffix = params.toString();
1931
+ const response = await this.client.fetch(`/v1/public-templates${suffix ? `?${suffix}` : ""}`);
1932
+ if (!response.ok) {
1933
+ const body = await response.text();
1934
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1935
+ }
1936
+ return (await response.json()).templates ?? [];
1937
+ }
1938
+ async featured() {
1939
+ const response = await this.client.fetch("/v1/public-templates/featured");
1940
+ if (!response.ok) {
1941
+ const body = await response.text();
1942
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1943
+ }
1944
+ return (await response.json()).templates ?? [];
1945
+ }
1946
+ async get(idOrSlug) {
1947
+ const response = await this.client.fetch(`/v1/public-templates/${encodeURIComponent(idOrSlug)}`);
1948
+ if (!response.ok) {
1949
+ const body = await response.text();
1950
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1951
+ }
1952
+ return (await response.json()).template;
1953
+ }
1954
+ async versions(idOrSlug) {
1955
+ const response = await this.client.fetch(`/v1/public-templates/${encodeURIComponent(idOrSlug)}/versions`);
1956
+ if (!response.ok) {
1957
+ const body = await response.text();
1958
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1959
+ }
1960
+ return (await response.json()).versions ?? [];
1961
+ }
1962
+ async publish(options) {
1963
+ const response = await this.client.fetch("/v1/public-templates", {
1964
+ method: "POST",
1965
+ body: JSON.stringify(options)
1966
+ });
1967
+ if (!response.ok) {
1968
+ const body = await response.text();
1969
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1970
+ }
1971
+ return (await response.json()).template;
1972
+ }
1973
+ async publishVersion(idOrSlug, options) {
1974
+ const response = await this.client.fetch(`/v1/public-templates/${encodeURIComponent(idOrSlug)}/versions`, {
1975
+ method: "POST",
1976
+ body: JSON.stringify(options)
1977
+ });
1978
+ if (!response.ok) {
1979
+ const body = await response.text();
1980
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
1981
+ }
1982
+ return (await response.json()).version;
1983
+ }
1984
+ };
1985
+ /**
1986
+ * Client for managing teams and team-scoped sharing.
1987
+ *
1988
+ * Team resources are scoped to the authenticated user's membership —
1989
+ * the client never needs to pass an org id explicitly.
1990
+ */
1991
+ var TeamsClient = class {
1992
+ constructor(client) {
1993
+ this.client = client;
1994
+ }
1995
+ async list() {
1996
+ const response = await this.client.fetch("/v1/teams");
1997
+ if (!response.ok) {
1998
+ const body = await response.text();
1999
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2000
+ }
2001
+ return (await response.json()).teams ?? [];
2002
+ }
2003
+ async create(options) {
2004
+ const response = await this.client.fetch("/v1/teams", {
2005
+ method: "POST",
2006
+ body: JSON.stringify(options)
2007
+ });
2008
+ if (!response.ok) {
2009
+ const body = await response.text();
2010
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2011
+ }
2012
+ return (await response.json()).team;
2013
+ }
2014
+ async get(teamId) {
2015
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}`);
2016
+ if (!response.ok) {
2017
+ const body = await response.text();
2018
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2019
+ }
2020
+ return (await response.json()).team;
2021
+ }
2022
+ async update(teamId, updates) {
2023
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}`, {
2024
+ method: "PATCH",
2025
+ body: JSON.stringify(updates)
2026
+ });
2027
+ if (!response.ok) {
2028
+ const body = await response.text();
2029
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2030
+ }
2031
+ return (await response.json()).team;
2032
+ }
2033
+ async delete(teamId) {
2034
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}`, { method: "DELETE" });
2035
+ if (!response.ok) {
2036
+ const body = await response.text();
2037
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2038
+ }
2039
+ }
2040
+ async leave(teamId) {
2041
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/leave`, { method: "POST" });
2042
+ if (!response.ok) {
2043
+ const body = await response.text();
2044
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2045
+ }
2046
+ }
2047
+ async transferOwnership(teamId, newOwnerCustomerId) {
2048
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/transfer`, {
2049
+ method: "POST",
2050
+ body: JSON.stringify({ newOwnerCustomerId })
2051
+ });
2052
+ if (!response.ok) {
2053
+ const body = await response.text();
2054
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2055
+ }
2056
+ }
2057
+ async listMembers(teamId) {
2058
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/members`);
2059
+ if (!response.ok) {
2060
+ const body = await response.text();
2061
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2062
+ }
2063
+ return (await response.json()).members ?? [];
2064
+ }
2065
+ async updateMember(teamId, memberId, updates) {
2066
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(memberId)}`, {
2067
+ method: "PATCH",
2068
+ body: JSON.stringify(updates)
2069
+ });
2070
+ if (!response.ok) {
2071
+ const body = await response.text();
2072
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2073
+ }
2074
+ return (await response.json()).member;
2075
+ }
2076
+ async removeMember(teamId, memberId) {
2077
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(memberId)}`, { method: "DELETE" });
2078
+ if (!response.ok) {
2079
+ const body = await response.text();
2080
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2081
+ }
2082
+ }
2083
+ async listInvitations(teamId) {
2084
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/invitations`);
2085
+ if (!response.ok) {
2086
+ const body = await response.text();
2087
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2088
+ }
2089
+ return (await response.json()).invitations ?? [];
2090
+ }
2091
+ async invite(teamId, options) {
2092
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/invitations`, {
2093
+ method: "POST",
2094
+ body: JSON.stringify(options)
2095
+ });
2096
+ if (!response.ok) {
2097
+ const body = await response.text();
2098
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2099
+ }
2100
+ return (await response.json()).invitation;
2101
+ }
2102
+ async revokeInvitation(invitationId) {
2103
+ const response = await this.client.fetch(`/v1/teams/invitations/${encodeURIComponent(invitationId)}`, { method: "DELETE" });
2104
+ if (!response.ok) {
2105
+ const body = await response.text();
2106
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2107
+ }
2108
+ }
2109
+ async acceptInvitation(token) {
2110
+ const response = await this.client.fetch(`/v1/teams/invitations/${encodeURIComponent(token)}/accept`, { method: "POST" });
2111
+ if (!response.ok) {
2112
+ const body = await response.text();
2113
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2114
+ }
2115
+ return (await response.json()).member;
2116
+ }
2117
+ async getInvitation(token) {
2118
+ const response = await this.client.fetch(`/v1/teams/invitations/${encodeURIComponent(token)}`);
2119
+ if (!response.ok) {
2120
+ const body = await response.text();
2121
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2122
+ }
2123
+ return (await response.json()).invitation;
2124
+ }
2125
+ async listSecrets(teamId) {
2126
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/secrets`);
2127
+ if (!response.ok) {
2128
+ const body = await response.text();
2129
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2130
+ }
2131
+ return (await response.json()).secrets ?? [];
2132
+ }
2133
+ async upsertSecret(teamId, name, value) {
2134
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/secrets/${encodeURIComponent(name)}`, {
2135
+ method: "PUT",
2136
+ body: JSON.stringify({ value })
2137
+ });
2138
+ if (!response.ok) {
2139
+ const body = await response.text();
2140
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2141
+ }
2142
+ return (await response.json()).secret;
2143
+ }
2144
+ async deleteSecret(teamId, name) {
2145
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/secrets/${encodeURIComponent(name)}`, { method: "DELETE" });
2146
+ if (!response.ok) {
2147
+ const body = await response.text();
2148
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2149
+ }
2150
+ }
2151
+ async revealSecret(teamId, name) {
2152
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/secrets/${encodeURIComponent(name)}/reveal`, { method: "POST" });
2153
+ if (!response.ok) {
2154
+ const body = await response.text();
2155
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2156
+ }
2157
+ return await response.json();
2158
+ }
2159
+ async listTemplates(teamId) {
2160
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/templates`);
2161
+ if (!response.ok) {
2162
+ const body = await response.text();
2163
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2164
+ }
2165
+ return (await response.json()).templates ?? [];
2166
+ }
2167
+ async createTemplate(teamId, options) {
2168
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/templates`, {
2169
+ method: "POST",
2170
+ body: JSON.stringify({
2171
+ name: options.name,
2172
+ snapshotId: options.snapshotId,
2173
+ description: options.description,
2174
+ environment: options.environment,
2175
+ config: options.config
2176
+ })
2177
+ });
2178
+ if (!response.ok) {
2179
+ const body = await response.text();
2180
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2181
+ }
2182
+ return (await response.json()).template;
2183
+ }
2184
+ async deleteTemplate(teamId, templateId) {
2185
+ const response = await this.client.fetch(`/v1/teams/${encodeURIComponent(teamId)}/templates/${encodeURIComponent(templateId)}`, { method: "DELETE" });
2186
+ if (!response.ok) {
2187
+ const body = await response.text();
2188
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
2189
+ }
2190
+ }
2191
+ };
2192
+ //#endregion
2193
+ export { DEFAULT_SANDBOX_SIZE as a, resolveSandboxResources as c, SandboxFleetClient as i, sandboxResourcesForSize as l, SandboxClient as n, SANDBOX_SIZE_PRESETS as o, SandboxFleet as r, SANDBOX_SIZE_PRESET_NAMES as s, IntelligenceClient as t };