@agentuity/sandbox 3.0.12 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/AGENTS.md +3 -3
  2. package/dist/api-reference.d.ts +1221 -0
  3. package/dist/api-reference.d.ts.map +1 -0
  4. package/dist/api-reference.js +1046 -0
  5. package/dist/api-reference.js.map +1 -0
  6. package/dist/base64.d.ts +2 -0
  7. package/dist/base64.d.ts.map +1 -0
  8. package/dist/base64.js +14 -0
  9. package/dist/base64.js.map +1 -0
  10. package/dist/client.d.ts +431 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +632 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/create.d.ts +203 -0
  15. package/dist/create.d.ts.map +1 -0
  16. package/dist/create.js +235 -0
  17. package/dist/create.js.map +1 -0
  18. package/dist/destroy.d.ts +23 -0
  19. package/dist/destroy.d.ts.map +1 -0
  20. package/dist/destroy.js +30 -0
  21. package/dist/destroy.js.map +1 -0
  22. package/dist/disk-checkpoint.d.ts +108 -0
  23. package/dist/disk-checkpoint.d.ts.map +1 -0
  24. package/dist/disk-checkpoint.js +124 -0
  25. package/dist/disk-checkpoint.js.map +1 -0
  26. package/dist/events.d.ts +56 -0
  27. package/dist/events.d.ts.map +1 -0
  28. package/dist/events.js +54 -0
  29. package/dist/events.js.map +1 -0
  30. package/dist/execute.d.ts +99 -0
  31. package/dist/execute.d.ts.map +1 -0
  32. package/dist/execute.js +138 -0
  33. package/dist/execute.js.map +1 -0
  34. package/dist/execution.d.ts +150 -0
  35. package/dist/execution.d.ts.map +1 -0
  36. package/dist/execution.js +120 -0
  37. package/dist/execution.js.map +1 -0
  38. package/dist/files.d.ts +283 -0
  39. package/dist/files.d.ts.map +1 -0
  40. package/dist/files.js +471 -0
  41. package/dist/files.js.map +1 -0
  42. package/dist/get.d.ts +288 -0
  43. package/dist/get.d.ts.map +1 -0
  44. package/dist/get.js +256 -0
  45. package/dist/get.js.map +1 -0
  46. package/dist/getStatus.d.ts +23 -0
  47. package/dist/getStatus.d.ts.map +1 -0
  48. package/dist/getStatus.js +53 -0
  49. package/dist/getStatus.js.map +1 -0
  50. package/dist/index.d.ts +42 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +22 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/job.d.ts +227 -0
  55. package/dist/job.d.ts.map +1 -0
  56. package/dist/job.js +109 -0
  57. package/dist/job.js.map +1 -0
  58. package/dist/list.d.ts +330 -0
  59. package/dist/list.d.ts.map +1 -0
  60. package/dist/list.js +209 -0
  61. package/dist/list.js.map +1 -0
  62. package/dist/pause.d.ts +39 -0
  63. package/dist/pause.d.ts.map +1 -0
  64. package/dist/pause.js +48 -0
  65. package/dist/pause.js.map +1 -0
  66. package/dist/resolve.d.ts +75 -0
  67. package/dist/resolve.d.ts.map +1 -0
  68. package/dist/resolve.js +76 -0
  69. package/dist/resolve.js.map +1 -0
  70. package/dist/resume.d.ts +23 -0
  71. package/dist/resume.d.ts.map +1 -0
  72. package/dist/resume.js +30 -0
  73. package/dist/resume.js.map +1 -0
  74. package/dist/run.d.ts +73 -0
  75. package/dist/run.d.ts.map +1 -0
  76. package/dist/run.js +568 -0
  77. package/dist/run.js.map +1 -0
  78. package/dist/runtime.d.ts +94 -0
  79. package/dist/runtime.d.ts.map +1 -0
  80. package/dist/runtime.js +82 -0
  81. package/dist/runtime.js.map +1 -0
  82. package/dist/snapshot-build.d.ts +48 -0
  83. package/dist/snapshot-build.d.ts.map +1 -0
  84. package/dist/snapshot-build.js +72 -0
  85. package/dist/snapshot-build.js.map +1 -0
  86. package/dist/snapshot.d.ts +596 -0
  87. package/dist/snapshot.d.ts.map +1 -0
  88. package/dist/snapshot.js +612 -0
  89. package/dist/snapshot.js.map +1 -0
  90. package/dist/types.d.ts +1010 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +853 -0
  93. package/dist/types.js.map +1 -0
  94. package/dist/util.d.ts +296 -0
  95. package/dist/util.d.ts.map +1 -0
  96. package/dist/util.js +234 -0
  97. package/dist/util.js.map +1 -0
  98. package/package.json +7 -2
  99. package/src/api-reference.ts +1094 -0
  100. package/src/base64.ts +14 -0
  101. package/src/client.ts +998 -0
  102. package/src/create.ts +273 -0
  103. package/src/destroy.ts +43 -0
  104. package/src/disk-checkpoint.ts +184 -0
  105. package/src/events.ts +72 -0
  106. package/src/execute.ts +167 -0
  107. package/src/execution.ts +152 -0
  108. package/src/files.ts +637 -0
  109. package/src/get.ts +291 -0
  110. package/src/getStatus.ts +72 -0
  111. package/src/index.ts +252 -18
  112. package/src/job.ts +161 -0
  113. package/src/list.ts +239 -0
  114. package/src/pause.ts +75 -0
  115. package/src/resolve.ts +96 -0
  116. package/src/resume.ts +41 -0
  117. package/src/run.ts +783 -0
  118. package/src/runtime.ts +106 -0
  119. package/src/snapshot-build.ts +94 -0
  120. package/src/snapshot.ts +791 -0
  121. package/src/types.ts +1033 -0
  122. package/src/util.ts +280 -0
package/src/files.ts ADDED
@@ -0,0 +1,637 @@
1
+ import { z } from 'zod';
2
+ import { APIClient } from '@agentuity/api';
3
+ import { SandboxResponseError, throwSandboxError } from './util.ts';
4
+ import type { FileToWrite } from './types.ts';
5
+ import { base64Encode } from './base64.ts';
6
+
7
+ export const FileToWriteSchema = z.object({
8
+ path: z.string().describe('Path to the file relative to the sandbox workspace'),
9
+ content: z.string().describe('Base64-encoded file content'),
10
+ });
11
+
12
+ export const WriteFilesRequestSchema = z
13
+ .object({
14
+ files: z.array(FileToWriteSchema).describe('Array of files to write'),
15
+ })
16
+ .describe('Request body for writing files to a sandbox');
17
+
18
+ export const WriteFilesDataSchema = z
19
+ .object({
20
+ filesWritten: z.number().describe('Number of files successfully written'),
21
+ })
22
+ .describe('Response data from writing files');
23
+
24
+ export const WriteFilesResponseSchema = z.discriminatedUnion('success', [
25
+ z.object({
26
+ success: z.literal<false>(false),
27
+ message: z.string().describe('the error message'),
28
+ }),
29
+ z.object({
30
+ success: z.literal<true>(true),
31
+ data: WriteFilesDataSchema.optional(),
32
+ filesWritten: z.number().optional(),
33
+ }),
34
+ ]);
35
+
36
+ export const WriteFilesParamsSchema = z.object({
37
+ sandboxId: z.string().describe('Sandbox ID to write files into'),
38
+ files: z.array(z.custom<FileToWrite>()).describe('Files to write to the sandbox workspace'),
39
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
40
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
41
+ });
42
+
43
+ export type WriteFilesParams = z.infer<typeof WriteFilesParamsSchema>;
44
+ export type WriteFilesResult = z.infer<typeof WriteFilesDataSchema>;
45
+
46
+ /**
47
+ * Writes files to a sandbox workspace.
48
+ *
49
+ * @param client - The API client to use for the request
50
+ * @param params - Parameters including sandbox ID and files to write
51
+ * @returns The result including number of files written
52
+ * @throws {SandboxResponseError} If the write request fails
53
+ */
54
+ export async function sandboxWriteFiles(
55
+ client: APIClient,
56
+ params: WriteFilesParams
57
+ ): Promise<WriteFilesResult> {
58
+ const { sandboxId, files, orgId, signal } = params;
59
+
60
+ const body: z.infer<typeof WriteFilesRequestSchema> = {
61
+ files: files.map((f) => ({
62
+ path: f.path,
63
+ content: base64Encode(f.content),
64
+ })),
65
+ };
66
+
67
+ const queryParams = new URLSearchParams();
68
+ if (orgId) {
69
+ queryParams.set('orgId', orgId);
70
+ }
71
+ const queryString = queryParams.toString();
72
+ const url = `/fs/${sandboxId}${queryString ? `?${queryString}` : ''}`;
73
+
74
+ const resp = await client.post<z.infer<typeof WriteFilesResponseSchema>>(
75
+ url,
76
+ body,
77
+ WriteFilesResponseSchema,
78
+ WriteFilesRequestSchema,
79
+ signal
80
+ );
81
+
82
+ if (resp.success) {
83
+ return {
84
+ filesWritten: resp.data?.filesWritten ?? resp.filesWritten ?? 0,
85
+ };
86
+ }
87
+
88
+ throwSandboxError(resp, { sandboxId });
89
+ }
90
+
91
+ export const ReadFileParamsSchema = z.object({
92
+ sandboxId: z.string().describe('Sandbox ID to read a file from'),
93
+ path: z.string().describe('Path to the file to read'),
94
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
95
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
96
+ });
97
+
98
+ export type ReadFileParams = z.infer<typeof ReadFileParamsSchema>;
99
+
100
+ /**
101
+ * Reads a file from a sandbox workspace.
102
+ *
103
+ * @param client - The API client to use for the request
104
+ * @param params - Parameters including sandbox ID and file path
105
+ * @returns A ReadableStream of the file contents
106
+ * @throws {SandboxResponseError} If the read request fails
107
+ */
108
+ export async function sandboxReadFile(
109
+ client: APIClient,
110
+ params: ReadFileParams
111
+ ): Promise<ReadableStream<Uint8Array>> {
112
+ const { sandboxId, path, orgId, signal } = params;
113
+
114
+ const queryParams = new URLSearchParams();
115
+ queryParams.set('path', path);
116
+ if (orgId) {
117
+ queryParams.set('orgId', orgId);
118
+ }
119
+ const queryString = queryParams.toString();
120
+ const url = `/fs/${sandboxId}?${queryString}`;
121
+
122
+ const response = await client.rawGet(url, signal);
123
+ const sessionId = response.headers.get('x-session-id');
124
+
125
+ if (!response.ok) {
126
+ const text = await response.text().catch(() => 'Unknown error');
127
+ throw new SandboxResponseError({
128
+ message: `Failed to read file "${path}": ${response.status} ${text}`,
129
+ sandboxId,
130
+ sessionId,
131
+ });
132
+ }
133
+
134
+ if (!response.body) {
135
+ throw new SandboxResponseError({
136
+ message: 'No response body',
137
+ sandboxId,
138
+ sessionId,
139
+ });
140
+ }
141
+
142
+ return response.body;
143
+ }
144
+
145
+ export const MkDirRequestSchema = z
146
+ .object({
147
+ path: z.string().describe('Path to the directory to create'),
148
+ recursive: z.boolean().optional().describe('Create parent directories if needed'),
149
+ })
150
+ .describe('Request body for creating a directory');
151
+
152
+ export const MkDirResponseSchema = z.discriminatedUnion('success', [
153
+ z.object({
154
+ success: z.literal<false>(false),
155
+ message: z.string().describe('the error message'),
156
+ }),
157
+ z.object({
158
+ success: z.literal<true>(true),
159
+ }),
160
+ ]);
161
+
162
+ export const MkDirParamsSchema = z.object({
163
+ sandboxId: z.string().describe('Sandbox ID where directory should be created'),
164
+ path: z.string().describe('Directory path to create'),
165
+ recursive: z.boolean().optional().describe('Create parent directories when true'),
166
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
167
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
168
+ });
169
+
170
+ export type MkDirParams = z.infer<typeof MkDirParamsSchema>;
171
+
172
+ /**
173
+ * Creates a directory in a sandbox workspace.
174
+ *
175
+ * @param client - The API client to use for the request
176
+ * @param params - Parameters including sandbox ID, path, and recursive flag
177
+ * @throws {SandboxResponseError} If the mkdir request fails
178
+ */
179
+ export async function sandboxMkDir(client: APIClient, params: MkDirParams): Promise<void> {
180
+ const { sandboxId, path, recursive, orgId, signal } = params;
181
+
182
+ const body: z.infer<typeof MkDirRequestSchema> = {
183
+ path,
184
+ recursive: recursive ?? false,
185
+ };
186
+
187
+ const queryParams = new URLSearchParams();
188
+ if (orgId) {
189
+ queryParams.set('orgId', orgId);
190
+ }
191
+ const queryString = queryParams.toString();
192
+ const url = `/fs/mkdir/${sandboxId}${queryString ? `?${queryString}` : ''}`;
193
+
194
+ const resp = await client.post<z.infer<typeof MkDirResponseSchema>>(
195
+ url,
196
+ body,
197
+ MkDirResponseSchema,
198
+ MkDirRequestSchema,
199
+ signal
200
+ );
201
+
202
+ if (!resp.success) {
203
+ throwSandboxError(resp, { sandboxId });
204
+ }
205
+ }
206
+
207
+ export const RmDirRequestSchema = z
208
+ .object({
209
+ path: z.string().describe('Path to the directory to remove'),
210
+ recursive: z.boolean().optional().describe('Remove directory and all contents'),
211
+ })
212
+ .describe('Request body for removing a directory');
213
+
214
+ export const RmDirResponseSchema = z.discriminatedUnion('success', [
215
+ z.object({
216
+ success: z.literal<false>(false),
217
+ message: z.string().describe('the error message'),
218
+ }),
219
+ z.object({
220
+ success: z.literal<true>(true),
221
+ found: z
222
+ .boolean()
223
+ .optional()
224
+ .default(true)
225
+ .describe(
226
+ 'Whether the directory existed before removal. False when already removed (idempotent).'
227
+ ),
228
+ }),
229
+ ]);
230
+
231
+ export const RmDirParamsSchema = z.object({
232
+ sandboxId: z.string().describe('Sandbox ID containing the directory to remove'),
233
+ path: z.string().describe('Directory path to remove'),
234
+ recursive: z.boolean().optional().describe('Remove directory contents recursively when true'),
235
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
236
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
237
+ });
238
+
239
+ export type RmDirParams = z.infer<typeof RmDirParamsSchema>;
240
+
241
+ /**
242
+ * Removes a directory from a sandbox workspace.
243
+ *
244
+ * @param client - The API client to use for the request
245
+ * @param params - Parameters including sandbox ID, path, and recursive flag
246
+ * @returns Object with `found` indicating whether the directory existed before removal
247
+ * @throws {SandboxResponseError} If the rmdir request fails
248
+ */
249
+ export async function sandboxRmDir(
250
+ client: APIClient,
251
+ params: RmDirParams
252
+ ): Promise<{ found: boolean }> {
253
+ const { sandboxId, path, recursive, orgId, signal } = params;
254
+
255
+ const body: z.infer<typeof RmDirRequestSchema> = {
256
+ path,
257
+ recursive: recursive ?? false,
258
+ };
259
+
260
+ const queryParams = new URLSearchParams();
261
+ if (orgId) {
262
+ queryParams.set('orgId', orgId);
263
+ }
264
+ const queryString = queryParams.toString();
265
+ const url = `/fs/rmdir/${sandboxId}${queryString ? `?${queryString}` : ''}`;
266
+
267
+ const resp = await client.post<z.infer<typeof RmDirResponseSchema>>(
268
+ url,
269
+ body,
270
+ RmDirResponseSchema,
271
+ RmDirRequestSchema,
272
+ signal
273
+ );
274
+
275
+ if (!resp.success) {
276
+ throwSandboxError(resp, { sandboxId });
277
+ }
278
+
279
+ return { found: resp.found };
280
+ }
281
+
282
+ export const RmFileRequestSchema = z
283
+ .object({
284
+ path: z.string().describe('Path to the file to remove'),
285
+ })
286
+ .describe('Request body for removing a file');
287
+
288
+ export const RmFileResponseSchema = z.discriminatedUnion('success', [
289
+ z.object({
290
+ success: z.literal<false>(false),
291
+ message: z.string().describe('the error message'),
292
+ }),
293
+ z.object({
294
+ success: z.literal<true>(true),
295
+ found: z
296
+ .boolean()
297
+ .optional()
298
+ .default(true)
299
+ .describe(
300
+ 'Whether the file existed before removal. False when already removed (idempotent).'
301
+ ),
302
+ }),
303
+ ]);
304
+
305
+ export const RmFileParamsSchema = z.object({
306
+ sandboxId: z.string().describe('Sandbox ID containing the file to remove'),
307
+ path: z.string().describe('File path to remove'),
308
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
309
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
310
+ });
311
+
312
+ export type RmFileParams = z.infer<typeof RmFileParamsSchema>;
313
+
314
+ /**
315
+ * Removes a file from a sandbox workspace.
316
+ *
317
+ * @param client - The API client to use for the request
318
+ * @param params - Parameters including sandbox ID and path
319
+ * @returns Object with `found` indicating whether the file existed before removal
320
+ * @throws {SandboxResponseError} If the rm request fails
321
+ */
322
+ export async function sandboxRmFile(
323
+ client: APIClient,
324
+ params: RmFileParams
325
+ ): Promise<{ found: boolean }> {
326
+ const { sandboxId, path, orgId, signal } = params;
327
+
328
+ const body: z.infer<typeof RmFileRequestSchema> = {
329
+ path,
330
+ };
331
+
332
+ const queryParams = new URLSearchParams();
333
+ if (orgId) {
334
+ queryParams.set('orgId', orgId);
335
+ }
336
+ const queryString = queryParams.toString();
337
+ const url = `/fs/rm/${sandboxId}${queryString ? `?${queryString}` : ''}`;
338
+
339
+ const resp = await client.post<z.infer<typeof RmFileResponseSchema>>(
340
+ url,
341
+ body,
342
+ RmFileResponseSchema,
343
+ RmFileRequestSchema,
344
+ signal
345
+ );
346
+
347
+ if (!resp.success) {
348
+ throwSandboxError(resp, { sandboxId });
349
+ }
350
+
351
+ return { found: resp.found };
352
+ }
353
+
354
+ export const FileInfoSchema = z.object({
355
+ path: z.string().describe('File path relative to the listed directory'),
356
+ size: z.number().describe('File size in bytes'),
357
+ isDir: z.boolean().describe('Whether the entry is a directory'),
358
+ isSymlink: z.boolean().optional().describe('Whether the entry is a symbolic link'),
359
+ linkTarget: z.string().optional().describe('Target path of the symbolic link'),
360
+ mode: z.string().describe('Unix permissions as octal string (e.g., "0644")'),
361
+ modTime: z.string().describe('Modification time in RFC3339 format'),
362
+ });
363
+
364
+ export const ListFilesDataSchema = z.object({
365
+ files: z.array(FileInfoSchema).describe('Array of file information'),
366
+ });
367
+
368
+ export const ListFilesResponseSchema = z.discriminatedUnion('success', [
369
+ z.object({
370
+ success: z.literal<false>(false),
371
+ message: z.string().describe('the error message'),
372
+ }),
373
+ z.object({
374
+ success: z.literal<true>(true),
375
+ data: ListFilesDataSchema,
376
+ }),
377
+ ]);
378
+
379
+ export type FileInfo = z.infer<typeof FileInfoSchema>;
380
+
381
+ export const ListFilesParamsSchema = z.object({
382
+ sandboxId: z.string().describe('Sandbox ID to list files from'),
383
+ path: z.string().optional().describe('Optional directory path to list'),
384
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
385
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
386
+ });
387
+
388
+ export type ListFilesParams = z.infer<typeof ListFilesParamsSchema>;
389
+ export type ListFilesResult = z.infer<typeof ListFilesDataSchema>;
390
+
391
+ /**
392
+ * Lists files in a sandbox workspace directory.
393
+ *
394
+ * @param client - The API client to use for the request
395
+ * @param params - Parameters including sandbox ID and optional path
396
+ * @returns The list of files and directories
397
+ * @throws {SandboxResponseError} If the list request fails
398
+ */
399
+ export async function sandboxListFiles(
400
+ client: APIClient,
401
+ params: ListFilesParams
402
+ ): Promise<ListFilesResult> {
403
+ const { sandboxId, path, orgId, signal } = params;
404
+
405
+ const queryParams = new URLSearchParams();
406
+ if (path) {
407
+ queryParams.set('path', path);
408
+ }
409
+ if (orgId) {
410
+ queryParams.set('orgId', orgId);
411
+ }
412
+ const queryString = queryParams.toString();
413
+ const url = `/fs/list/${sandboxId}${queryString ? `?${queryString}` : ''}`;
414
+
415
+ const resp = await client.get<z.infer<typeof ListFilesResponseSchema>>(
416
+ url,
417
+ ListFilesResponseSchema,
418
+ signal
419
+ );
420
+
421
+ if (resp.success) {
422
+ return {
423
+ files: resp.data.files,
424
+ };
425
+ }
426
+
427
+ throwSandboxError(resp, { sandboxId });
428
+ }
429
+
430
+ export type ArchiveFormat = 'zip' | 'tar.gz';
431
+
432
+ export const DownloadArchiveParamsSchema = z.object({
433
+ sandboxId: z.string().describe('Sandbox ID to download archive contents from'),
434
+ path: z.string().optional().describe('Optional path inside sandbox workspace to archive'),
435
+ format: z.enum(['zip', 'tar.gz']).optional().describe('Archive format to return'),
436
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
437
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
438
+ });
439
+
440
+ export type DownloadArchiveParams = z.infer<typeof DownloadArchiveParamsSchema>;
441
+
442
+ /**
443
+ * Downloads files from a sandbox as a compressed archive.
444
+ *
445
+ * @param client - The API client to use for the request
446
+ * @param params - Parameters including sandbox ID, path, and format
447
+ * @returns A ReadableStream of the archive contents
448
+ * @throws {SandboxResponseError} If the download request fails
449
+ */
450
+ export async function sandboxDownloadArchive(
451
+ client: APIClient,
452
+ params: DownloadArchiveParams
453
+ ): Promise<ReadableStream<Uint8Array>> {
454
+ const { sandboxId, path, format, orgId, signal } = params;
455
+
456
+ const queryParams = new URLSearchParams();
457
+ if (path) {
458
+ queryParams.set('path', path);
459
+ }
460
+ if (format) {
461
+ queryParams.set('format', format);
462
+ }
463
+ if (orgId) {
464
+ queryParams.set('orgId', orgId);
465
+ }
466
+ const queryString = queryParams.toString();
467
+ const url = `/fs/download/${sandboxId}${queryString ? `?${queryString}` : ''}`;
468
+
469
+ const response = await client.rawGet(url, signal);
470
+ const sessionId = response.headers.get('x-session-id');
471
+
472
+ if (!response.ok) {
473
+ const text = await response.text().catch(() => 'Unknown error');
474
+ throw new SandboxResponseError({
475
+ message: `Failed to download archive: ${response.status} ${text}`,
476
+ sandboxId,
477
+ sessionId,
478
+ });
479
+ }
480
+
481
+ if (!response.body) {
482
+ throw new SandboxResponseError({
483
+ message: 'No response body',
484
+ sandboxId,
485
+ sessionId,
486
+ });
487
+ }
488
+
489
+ return response.body;
490
+ }
491
+
492
+ export const UploadArchiveParamsSchema = z.object({
493
+ sandboxId: z.string().describe('Sandbox ID to upload archive contents to'),
494
+ archive: z
495
+ .union([
496
+ z.custom<Uint8Array>((v) => v instanceof Uint8Array),
497
+ z.instanceof(ArrayBuffer),
498
+ z.custom<ReadableStream<Uint8Array>>((v) => v instanceof ReadableStream),
499
+ ])
500
+ .describe('Archive bytes or stream to upload'),
501
+ path: z.string().optional().describe('Optional destination path for archive extraction'),
502
+ format: z
503
+ .union([z.enum(['zip', 'tar.gz']), z.literal('')])
504
+ .optional()
505
+ .describe('Optional archive format hint'),
506
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
507
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
508
+ });
509
+
510
+ export type UploadArchiveParams = z.infer<typeof UploadArchiveParamsSchema>;
511
+
512
+ export const UploadArchiveResponseSchema = z.discriminatedUnion('success', [
513
+ z.object({
514
+ success: z.literal<false>(false),
515
+ message: z.string().describe('the error message'),
516
+ }),
517
+ z.object({
518
+ success: z.literal<true>(true),
519
+ }),
520
+ ]);
521
+
522
+ /**
523
+ * Uploads a compressed archive to a sandbox and extracts it.
524
+ *
525
+ * @param client - The API client to use for the request
526
+ * @param params - Parameters including sandbox ID, archive data, path, and optional format
527
+ * @throws {SandboxResponseError} If the upload request fails
528
+ */
529
+ export async function sandboxUploadArchive(
530
+ client: APIClient,
531
+ params: UploadArchiveParams
532
+ ): Promise<void> {
533
+ const { sandboxId, archive, path, format, orgId, signal } = params;
534
+
535
+ const queryParams = new URLSearchParams();
536
+ if (path) {
537
+ queryParams.set('path', path);
538
+ }
539
+ if (format) {
540
+ queryParams.set('format', format);
541
+ }
542
+ if (orgId) {
543
+ queryParams.set('orgId', orgId);
544
+ }
545
+ const queryString = queryParams.toString();
546
+ const url = `/fs/upload/${sandboxId}${queryString ? `?${queryString}` : ''}`;
547
+
548
+ const response = await client.rawPost(url, archive, 'application/octet-stream', signal);
549
+ const sessionId = response.headers.get('x-session-id');
550
+
551
+ if (!response.ok) {
552
+ const text = await response.text().catch(() => 'Unknown error');
553
+ throw new SandboxResponseError({
554
+ message: `Failed to upload archive: ${response.status} ${text}`,
555
+ sandboxId,
556
+ sessionId,
557
+ });
558
+ }
559
+
560
+ const body = await response.json();
561
+ const result = UploadArchiveResponseSchema.parse(body);
562
+
563
+ if (!result.success) {
564
+ throwSandboxError(result, { sandboxId, sessionId });
565
+ }
566
+ }
567
+
568
+ export const SetEnvRequestSchema = z.object({
569
+ env: z
570
+ .record(z.string(), z.string().nullable())
571
+ .describe('Environment variables to set (null to delete)'),
572
+ });
573
+
574
+ export const SetEnvDataSchema = z.object({
575
+ env: z.record(z.string(), z.string()).describe('Current environment variables after update'),
576
+ });
577
+
578
+ export const SetEnvResponseSchema = z.discriminatedUnion('success', [
579
+ z.object({
580
+ success: z.literal<false>(false),
581
+ message: z.string().describe('the error message'),
582
+ }),
583
+ z.object({
584
+ success: z.literal<true>(true),
585
+ data: SetEnvDataSchema,
586
+ }),
587
+ ]);
588
+
589
+ export const SetEnvParamsSchema = z.object({
590
+ sandboxId: z.string().describe('Sandbox ID where environment should be updated'),
591
+ env: z.record(z.string(), z.string().nullable()).describe('Environment variable updates'),
592
+ orgId: z.string().optional().describe('Optional org id for CLI auth context'),
593
+ signal: z.custom<AbortSignal>().optional().describe('Optional abort signal for cancellation'),
594
+ });
595
+
596
+ export type SetEnvParams = z.infer<typeof SetEnvParamsSchema>;
597
+ export type SetEnvResult = z.infer<typeof SetEnvDataSchema>;
598
+
599
+ /**
600
+ * Sets environment variables on a sandbox. Pass null to delete a variable.
601
+ *
602
+ * @param client - The API client to use for the request
603
+ * @param params - Parameters including sandbox ID and env key/value pairs
604
+ * @returns The current environment variables after the update
605
+ * @throws {SandboxResponseError} If the request fails
606
+ */
607
+ export async function sandboxSetEnv(
608
+ client: APIClient,
609
+ params: SetEnvParams
610
+ ): Promise<SetEnvResult> {
611
+ const { sandboxId, env, orgId, signal } = params;
612
+
613
+ const body: z.infer<typeof SetEnvRequestSchema> = { env };
614
+
615
+ const queryParams = new URLSearchParams();
616
+ if (orgId) {
617
+ queryParams.set('orgId', orgId);
618
+ }
619
+ const queryString = queryParams.toString();
620
+ const url = `/sandbox/env/${sandboxId}${queryString ? `?${queryString}` : ''}`;
621
+
622
+ const resp = await client.patch<z.infer<typeof SetEnvResponseSchema>>(
623
+ url,
624
+ body,
625
+ SetEnvResponseSchema,
626
+ SetEnvRequestSchema,
627
+ signal
628
+ );
629
+
630
+ if (resp.success) {
631
+ return {
632
+ env: resp.data.env,
633
+ };
634
+ }
635
+
636
+ throwSandboxError(resp, { sandboxId });
637
+ }