@agentuity/server 0.0.110 → 0.0.112

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 (48) hide show
  1. package/dist/api/api.d.ts +5 -0
  2. package/dist/api/api.d.ts.map +1 -1
  3. package/dist/api/api.js +23 -4
  4. package/dist/api/api.js.map +1 -1
  5. package/dist/api/project/deploy.d.ts +37 -1
  6. package/dist/api/project/deploy.d.ts.map +1 -1
  7. package/dist/api/project/deploy.js +49 -1
  8. package/dist/api/project/deploy.js.map +1 -1
  9. package/dist/api/sandbox/create.d.ts.map +1 -1
  10. package/dist/api/sandbox/create.js +7 -0
  11. package/dist/api/sandbox/create.js.map +1 -1
  12. package/dist/api/sandbox/files.d.ts +120 -0
  13. package/dist/api/sandbox/files.d.ts.map +1 -1
  14. package/dist/api/sandbox/files.js +287 -0
  15. package/dist/api/sandbox/files.js.map +1 -1
  16. package/dist/api/sandbox/get.d.ts.map +1 -1
  17. package/dist/api/sandbox/get.js +14 -0
  18. package/dist/api/sandbox/get.js.map +1 -1
  19. package/dist/api/sandbox/index.d.ts +2 -2
  20. package/dist/api/sandbox/index.d.ts.map +1 -1
  21. package/dist/api/sandbox/index.js +1 -1
  22. package/dist/api/sandbox/index.js.map +1 -1
  23. package/dist/api/sandbox/snapshot.d.ts +0 -1
  24. package/dist/api/sandbox/snapshot.d.ts.map +1 -1
  25. package/dist/api/sandbox/snapshot.js +6 -8
  26. package/dist/api/sandbox/snapshot.js.map +1 -1
  27. package/dist/config.d.ts +7 -1
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +17 -4
  30. package/dist/config.js.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +3 -1
  37. package/dist/schema.js.map +1 -1
  38. package/package.json +5 -5
  39. package/src/api/api.ts +40 -4
  40. package/src/api/project/deploy.ts +97 -1
  41. package/src/api/sandbox/create.ts +7 -0
  42. package/src/api/sandbox/files.ts +450 -0
  43. package/src/api/sandbox/get.ts +15 -0
  44. package/src/api/sandbox/index.ts +27 -2
  45. package/src/api/sandbox/snapshot.ts +6 -10
  46. package/src/config.ts +21 -4
  47. package/src/index.ts +1 -1
  48. package/src/schema.ts +3 -1
package/dist/schema.js CHANGED
@@ -12,7 +12,9 @@ export const toJSONSchema = (schema) => {
12
12
  return agentuityToJSONSchema(schema);
13
13
  }
14
14
  // Check if it's a Zod schema
15
- if (schema?._def?.typeName) {
15
+ // Zod 3 uses _def.typeName (e.g., "ZodObject")
16
+ // Zod 4 uses _def.type (e.g., "object")
17
+ if (schema?._def?.typeName || schema?._def?.type) {
16
18
  try {
17
19
  return z.toJSONSchema(schema);
18
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAmB,MAAM,mBAAmB,CAAC;AAE3F;;;;GAIG;AACH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAW,EAAc,EAAE;IACvD,gEAAgE;IAChE,IAAI,MAAM,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;QACnD,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,6BAA6B;IAC7B,IAAI,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC;YACJ,OAAO,CAAC,CAAC,YAAY,CAAC,MAAM,CAAe,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC;IACD,sBAAsB;IACtB,OAAO,EAAE,CAAC;AACX,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAmB,MAAM,mBAAmB,CAAC;AAE3F;;;;GAIG;AACH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAW,EAAc,EAAE;IACvD,gEAAgE;IAChE,IAAI,MAAM,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;QACnD,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,6BAA6B;IAC7B,+CAA+C;IAC/C,wCAAwC;IACxC,IAAI,MAAM,EAAE,IAAI,EAAE,QAAQ,IAAI,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAClD,IAAI,CAAC;YACJ,OAAO,CAAC,CAAC,YAAY,CAAC,MAAM,CAAe,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC;IACD,sBAAsB;IACtB,OAAO,EAAE,CAAC;AACX,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentuity/server",
3
- "version": "0.0.110",
3
+ "version": "0.0.112",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Agentuity employees and contributors",
6
6
  "type": "module",
@@ -25,12 +25,12 @@
25
25
  "prepublishOnly": "bun run clean && bun run build"
26
26
  },
27
27
  "dependencies": {
28
- "@agentuity/core": "0.0.110",
29
- "@agentuity/schema": "0.0.110",
30
- "zod": "^4.1.12"
28
+ "@agentuity/core": "0.0.112",
29
+ "@agentuity/schema": "0.0.112",
30
+ "zod": "^4.3.5"
31
31
  },
32
32
  "devDependencies": {
33
- "@agentuity/test-utils": "0.0.110",
33
+ "@agentuity/test-utils": "0.0.112",
34
34
  "@types/bun": "latest",
35
35
  "@types/node": "^22.0.0",
36
36
  "bun-types": "latest",
package/src/api/api.ts CHANGED
@@ -186,6 +186,19 @@ export class APIClient {
186
186
  return this.#makeRequest('GET', endpoint, undefined, signal);
187
187
  }
188
188
 
189
+ /**
190
+ * Raw POST request that returns the Response object directly.
191
+ * Useful for binary uploads where you need to pass raw body data.
192
+ */
193
+ async rawPost(
194
+ endpoint: string,
195
+ body: Uint8Array | ArrayBuffer | ReadableStream<Uint8Array> | string,
196
+ contentType: string,
197
+ signal?: AbortSignal
198
+ ): Promise<Response> {
199
+ return this.#makeRequest('POST', endpoint, body, signal, contentType);
200
+ }
201
+
189
202
  /**
190
203
  * Generic request method (prefer HTTP verb methods: get, post, put, delete, patch)
191
204
  */
@@ -249,7 +262,8 @@ export class APIClient {
249
262
  method: string,
250
263
  endpoint: string,
251
264
  body?: unknown,
252
- signal?: AbortSignal
265
+ signal?: AbortSignal,
266
+ contentType?: string
253
267
  ): Promise<Response> {
254
268
  this.#logger.trace('sending %s to %s%s', method, this.#baseUrl, endpoint);
255
269
 
@@ -260,10 +274,14 @@ export class APIClient {
260
274
  try {
261
275
  const url = `${this.#baseUrl}${endpoint}`;
262
276
  const headers: Record<string, string> = {
263
- 'Content-Type': 'application/json',
264
- Accept: 'application/json',
277
+ 'Content-Type': contentType ?? 'application/json',
265
278
  };
266
279
 
280
+ // Only set Accept header for JSON requests (not binary uploads)
281
+ if (!contentType || contentType === 'application/json') {
282
+ headers['Accept'] = 'application/json';
283
+ }
284
+
267
285
  if (this.#config?.userAgent) {
268
286
  headers['User-Agent'] = this.#config.userAgent;
269
287
  }
@@ -281,10 +299,28 @@ export class APIClient {
281
299
  let response: Response;
282
300
 
283
301
  try {
302
+ let requestBody:
303
+ | Uint8Array
304
+ | ArrayBuffer
305
+ | ReadableStream<Uint8Array>
306
+ | string
307
+ | undefined;
308
+ if (body !== undefined) {
309
+ if (contentType && contentType !== 'application/json') {
310
+ requestBody = body as
311
+ | Uint8Array
312
+ | ArrayBuffer
313
+ | ReadableStream<Uint8Array>
314
+ | string;
315
+ } else {
316
+ requestBody = JSON.stringify(body);
317
+ }
318
+ }
319
+
284
320
  response = await fetch(url, {
285
321
  method,
286
322
  headers,
287
- body: body !== undefined ? JSON.stringify(body) : undefined,
323
+ body: requestBody,
288
324
  signal,
289
325
  });
290
326
  } catch (ex) {
@@ -33,7 +33,7 @@ const BaseFileFields = {
33
33
  const EvalSchema = z.object({
34
34
  ...BaseFileFields,
35
35
  id: z.string().describe('the unique calculated id for the eval'),
36
- evalId: z.string().describe('the unique id for eval for the project across deployments'),
36
+ identifier: z.string().describe('the unique id for eval for the project across deployments'),
37
37
  name: z.string().describe('the name of the eval'),
38
38
  description: z.string().optional().describe('the eval description'),
39
39
  agentIdentifier: z.string().describe('the identifier of the agent'),
@@ -164,6 +164,10 @@ const CreateProjectDeployment = z.object({
164
164
  id: z.string().describe('the unique id for the deployment'),
165
165
  orgId: z.string().describe('the organization id'),
166
166
  publicKey: z.string().describe('the public key to use for encrypting the deployment'),
167
+ buildLogsStreamURL: z
168
+ .string()
169
+ .optional()
170
+ .describe('the URL for streaming build logs (PUT to write, GET to read)'),
167
171
  });
168
172
 
169
173
  const CreateProjectDeploymentSchema = APIResponseSchema(CreateProjectDeployment);
@@ -314,3 +318,95 @@ export async function projectDeploymentStatus(
314
318
  }
315
319
  throw new ProjectResponseError({ message: resp.message });
316
320
  }
321
+
322
+ export interface ClientDiagnosticsError {
323
+ type: 'file' | 'general';
324
+ scope: 'typescript' | 'ast' | 'build' | 'bundler' | 'validation' | 'deploy';
325
+ path?: string;
326
+ line?: number;
327
+ column?: number;
328
+ message: string;
329
+ code?: string;
330
+ }
331
+
332
+ export interface ClientDiagnosticsTiming {
333
+ name: string;
334
+ startedAt: string;
335
+ completedAt: string;
336
+ durationMs: number;
337
+ }
338
+
339
+ export interface ClientDiagnostics {
340
+ success: boolean;
341
+ errors: ClientDiagnosticsError[];
342
+ warnings: ClientDiagnosticsError[];
343
+ diagnostics: ClientDiagnosticsTiming[];
344
+ error?: string;
345
+ }
346
+
347
+ export interface DeploymentFailPayload {
348
+ error?: string;
349
+ diagnostics?: ClientDiagnostics;
350
+ }
351
+
352
+ const ClientDiagnosticsErrorSchema = z.object({
353
+ type: z.enum(['file', 'general']),
354
+ scope: z.enum(['typescript', 'ast', 'build', 'bundler', 'validation', 'deploy']),
355
+ path: z.string().optional(),
356
+ line: z.number().optional(),
357
+ column: z.number().optional(),
358
+ message: z.string(),
359
+ code: z.string().optional(),
360
+ });
361
+
362
+ const ClientDiagnosticsTimingSchema = z.object({
363
+ name: z.string(),
364
+ startedAt: z.string(),
365
+ completedAt: z.string(),
366
+ durationMs: z.number(),
367
+ });
368
+
369
+ const ClientDiagnosticsSchema = z.object({
370
+ success: z.boolean(),
371
+ errors: z.array(ClientDiagnosticsErrorSchema),
372
+ warnings: z.array(ClientDiagnosticsErrorSchema),
373
+ diagnostics: z.array(ClientDiagnosticsTimingSchema),
374
+ error: z.string().optional(),
375
+ });
376
+
377
+ const DeploymentFailPayloadSchema = z.object({
378
+ error: z.string().optional(),
379
+ diagnostics: ClientDiagnosticsSchema.optional(),
380
+ });
381
+
382
+ const DeploymentFailResponseObject = z.object({
383
+ state: z.literal('failed'),
384
+ });
385
+
386
+ const DeploymentFailResponseSchema = APIResponseSchema(DeploymentFailResponseObject);
387
+ type DeploymentFailResponse = z.infer<typeof DeploymentFailResponseSchema>;
388
+
389
+ /**
390
+ * Report a deployment failure from the client
391
+ *
392
+ * @param client
393
+ * @param deploymentId
394
+ * @param payload - Error message and/or structured diagnostics
395
+ * @returns
396
+ */
397
+ export async function projectDeploymentFail(
398
+ client: APIClient,
399
+ deploymentId: string,
400
+ payload: DeploymentFailPayload
401
+ ): Promise<void> {
402
+ const resp = await client.request<DeploymentFailResponse, DeploymentFailPayload>(
403
+ 'POST',
404
+ `/cli/deploy/1/fail/${deploymentId}`,
405
+ DeploymentFailResponseSchema,
406
+ payload,
407
+ DeploymentFailPayloadSchema
408
+ );
409
+ if (!resp.success) {
410
+ throw new ProjectResponseError({ message: resp.message });
411
+ }
412
+ }
@@ -65,6 +65,10 @@ const SandboxCreateRequestSchema = z
65
65
  .array(z.string())
66
66
  .optional()
67
67
  .describe('Apt packages to install when creating the sandbox'),
68
+ metadata: z
69
+ .record(z.string(), z.unknown())
70
+ .optional()
71
+ .describe('Optional user-defined metadata to associate with the sandbox'),
68
72
  })
69
73
  .describe('Request body for creating a new sandbox');
70
74
 
@@ -143,6 +147,9 @@ export async function sandboxCreate(
143
147
  if (options.dependencies && options.dependencies.length > 0) {
144
148
  body.dependencies = options.dependencies;
145
149
  }
150
+ if (options.metadata) {
151
+ body.metadata = options.metadata;
152
+ }
146
153
 
147
154
  const queryParams = new URLSearchParams();
148
155
  if (orgId) {
@@ -136,3 +136,453 @@ export async function sandboxReadFile(
136
136
 
137
137
  return response.body;
138
138
  }
139
+
140
+ const MkDirRequestSchema = z
141
+ .object({
142
+ path: z.string().describe('Path to the directory to create'),
143
+ recursive: z.boolean().optional().describe('Create parent directories if needed'),
144
+ })
145
+ .describe('Request body for creating a directory');
146
+
147
+ const MkDirResponseSchema = z.discriminatedUnion('success', [
148
+ z.object({
149
+ success: z.literal<false>(false),
150
+ message: z.string().describe('the error message'),
151
+ }),
152
+ z.object({
153
+ success: z.literal<true>(true),
154
+ }),
155
+ ]);
156
+
157
+ export interface MkDirParams {
158
+ sandboxId: string;
159
+ path: string;
160
+ recursive?: boolean;
161
+ orgId?: string;
162
+ signal?: AbortSignal;
163
+ }
164
+
165
+ /**
166
+ * Creates a directory in a sandbox workspace.
167
+ *
168
+ * @param client - The API client to use for the request
169
+ * @param params - Parameters including sandbox ID, path, and recursive flag
170
+ * @throws {SandboxResponseError} If the mkdir request fails
171
+ */
172
+ export async function sandboxMkDir(client: APIClient, params: MkDirParams): Promise<void> {
173
+ const { sandboxId, path, recursive, orgId, signal } = params;
174
+
175
+ const body: z.infer<typeof MkDirRequestSchema> = {
176
+ path,
177
+ recursive: recursive ?? false,
178
+ };
179
+
180
+ const queryParams = new URLSearchParams();
181
+ if (orgId) {
182
+ queryParams.set('orgId', orgId);
183
+ }
184
+ const queryString = queryParams.toString();
185
+ const url = `/fs/${API_VERSION}/mkdir/${sandboxId}${queryString ? `?${queryString}` : ''}`;
186
+
187
+ const resp = await client.post<z.infer<typeof MkDirResponseSchema>>(
188
+ url,
189
+ body,
190
+ MkDirResponseSchema,
191
+ MkDirRequestSchema,
192
+ signal
193
+ );
194
+
195
+ if (!resp.success) {
196
+ throw new SandboxResponseError({ message: resp.message, sandboxId });
197
+ }
198
+ }
199
+
200
+ const RmDirRequestSchema = z
201
+ .object({
202
+ path: z.string().describe('Path to the directory to remove'),
203
+ recursive: z.boolean().optional().describe('Remove directory and all contents'),
204
+ })
205
+ .describe('Request body for removing a directory');
206
+
207
+ const RmDirResponseSchema = z.discriminatedUnion('success', [
208
+ z.object({
209
+ success: z.literal<false>(false),
210
+ message: z.string().describe('the error message'),
211
+ }),
212
+ z.object({
213
+ success: z.literal<true>(true),
214
+ }),
215
+ ]);
216
+
217
+ export interface RmDirParams {
218
+ sandboxId: string;
219
+ path: string;
220
+ recursive?: boolean;
221
+ orgId?: string;
222
+ signal?: AbortSignal;
223
+ }
224
+
225
+ /**
226
+ * Removes a directory from a sandbox workspace.
227
+ *
228
+ * @param client - The API client to use for the request
229
+ * @param params - Parameters including sandbox ID, path, and recursive flag
230
+ * @throws {SandboxResponseError} If the rmdir request fails
231
+ */
232
+ export async function sandboxRmDir(client: APIClient, params: RmDirParams): Promise<void> {
233
+ const { sandboxId, path, recursive, orgId, signal } = params;
234
+
235
+ const body: z.infer<typeof RmDirRequestSchema> = {
236
+ path,
237
+ recursive: recursive ?? false,
238
+ };
239
+
240
+ const queryParams = new URLSearchParams();
241
+ if (orgId) {
242
+ queryParams.set('orgId', orgId);
243
+ }
244
+ const queryString = queryParams.toString();
245
+ const url = `/fs/${API_VERSION}/rmdir/${sandboxId}${queryString ? `?${queryString}` : ''}`;
246
+
247
+ const resp = await client.post<z.infer<typeof RmDirResponseSchema>>(
248
+ url,
249
+ body,
250
+ RmDirResponseSchema,
251
+ RmDirRequestSchema,
252
+ signal
253
+ );
254
+
255
+ if (!resp.success) {
256
+ throw new SandboxResponseError({ message: resp.message, sandboxId });
257
+ }
258
+ }
259
+
260
+ const RmFileRequestSchema = z
261
+ .object({
262
+ path: z.string().describe('Path to the file to remove'),
263
+ })
264
+ .describe('Request body for removing a file');
265
+
266
+ const RmFileResponseSchema = z.discriminatedUnion('success', [
267
+ z.object({
268
+ success: z.literal<false>(false),
269
+ message: z.string().describe('the error message'),
270
+ }),
271
+ z.object({
272
+ success: z.literal<true>(true),
273
+ }),
274
+ ]);
275
+
276
+ export interface RmFileParams {
277
+ sandboxId: string;
278
+ path: string;
279
+ orgId?: string;
280
+ signal?: AbortSignal;
281
+ }
282
+
283
+ /**
284
+ * Removes a file from a sandbox workspace.
285
+ *
286
+ * @param client - The API client to use for the request
287
+ * @param params - Parameters including sandbox ID and path
288
+ * @throws {SandboxResponseError} If the rm request fails
289
+ */
290
+ export async function sandboxRmFile(client: APIClient, params: RmFileParams): Promise<void> {
291
+ const { sandboxId, path, orgId, signal } = params;
292
+
293
+ const body: z.infer<typeof RmFileRequestSchema> = {
294
+ path,
295
+ };
296
+
297
+ const queryParams = new URLSearchParams();
298
+ if (orgId) {
299
+ queryParams.set('orgId', orgId);
300
+ }
301
+ const queryString = queryParams.toString();
302
+ const url = `/fs/${API_VERSION}/rm/${sandboxId}${queryString ? `?${queryString}` : ''}`;
303
+
304
+ const resp = await client.post<z.infer<typeof RmFileResponseSchema>>(
305
+ url,
306
+ body,
307
+ RmFileResponseSchema,
308
+ RmFileRequestSchema,
309
+ signal
310
+ );
311
+
312
+ if (!resp.success) {
313
+ throw new SandboxResponseError({ message: resp.message, sandboxId });
314
+ }
315
+ }
316
+
317
+ const FileInfoSchema = z.object({
318
+ path: z.string().describe('File path relative to the listed directory'),
319
+ size: z.number().describe('File size in bytes'),
320
+ isDir: z.boolean().describe('Whether the entry is a directory'),
321
+ mode: z.string().describe('Unix permissions as octal string (e.g., "0644")'),
322
+ modTime: z.string().describe('Modification time in RFC3339 format'),
323
+ });
324
+
325
+ const ListFilesDataSchema = z.object({
326
+ files: z.array(FileInfoSchema).describe('Array of file information'),
327
+ });
328
+
329
+ const ListFilesResponseSchema = z.discriminatedUnion('success', [
330
+ z.object({
331
+ success: z.literal<false>(false),
332
+ message: z.string().describe('the error message'),
333
+ }),
334
+ z.object({
335
+ success: z.literal<true>(true),
336
+ data: ListFilesDataSchema,
337
+ }),
338
+ ]);
339
+
340
+ export interface FileInfo {
341
+ path: string;
342
+ size: number;
343
+ isDir: boolean;
344
+ mode: string;
345
+ modTime: string;
346
+ }
347
+
348
+ export interface ListFilesParams {
349
+ sandboxId: string;
350
+ path?: string;
351
+ orgId?: string;
352
+ signal?: AbortSignal;
353
+ }
354
+
355
+ export interface ListFilesResult {
356
+ files: FileInfo[];
357
+ }
358
+
359
+ /**
360
+ * Lists files in a sandbox workspace directory.
361
+ *
362
+ * @param client - The API client to use for the request
363
+ * @param params - Parameters including sandbox ID and optional path
364
+ * @returns The list of files and directories
365
+ * @throws {SandboxResponseError} If the list request fails
366
+ */
367
+ export async function sandboxListFiles(
368
+ client: APIClient,
369
+ params: ListFilesParams
370
+ ): Promise<ListFilesResult> {
371
+ const { sandboxId, path, orgId, signal } = params;
372
+
373
+ const queryParams = new URLSearchParams();
374
+ if (path) {
375
+ queryParams.set('path', path);
376
+ }
377
+ if (orgId) {
378
+ queryParams.set('orgId', orgId);
379
+ }
380
+ const queryString = queryParams.toString();
381
+ const url = `/fs/${API_VERSION}/list/${sandboxId}${queryString ? `?${queryString}` : ''}`;
382
+
383
+ const resp = await client.get<z.infer<typeof ListFilesResponseSchema>>(
384
+ url,
385
+ ListFilesResponseSchema,
386
+ signal
387
+ );
388
+
389
+ if (resp.success) {
390
+ return {
391
+ files: resp.data.files,
392
+ };
393
+ }
394
+
395
+ throw new SandboxResponseError({ message: resp.message, sandboxId });
396
+ }
397
+
398
+ export type ArchiveFormat = 'zip' | 'tar.gz';
399
+
400
+ export interface DownloadArchiveParams {
401
+ sandboxId: string;
402
+ path?: string;
403
+ format?: ArchiveFormat;
404
+ orgId?: string;
405
+ signal?: AbortSignal;
406
+ }
407
+
408
+ /**
409
+ * Downloads files from a sandbox as a compressed archive.
410
+ *
411
+ * @param client - The API client to use for the request
412
+ * @param params - Parameters including sandbox ID, path, and format
413
+ * @returns A ReadableStream of the archive contents
414
+ * @throws {SandboxResponseError} If the download request fails
415
+ */
416
+ export async function sandboxDownloadArchive(
417
+ client: APIClient,
418
+ params: DownloadArchiveParams
419
+ ): Promise<ReadableStream<Uint8Array>> {
420
+ const { sandboxId, path, format, orgId, signal } = params;
421
+
422
+ const queryParams = new URLSearchParams();
423
+ if (path) {
424
+ queryParams.set('path', path);
425
+ }
426
+ if (format) {
427
+ queryParams.set('format', format);
428
+ }
429
+ if (orgId) {
430
+ queryParams.set('orgId', orgId);
431
+ }
432
+ const queryString = queryParams.toString();
433
+ const url = `/fs/${API_VERSION}/download/${sandboxId}${queryString ? `?${queryString}` : ''}`;
434
+
435
+ const response = await client.rawGet(url, signal);
436
+
437
+ if (!response.ok) {
438
+ const text = await response.text().catch(() => 'Unknown error');
439
+ throw new SandboxResponseError({
440
+ message: `Failed to download archive: ${response.status} ${text}`,
441
+ sandboxId,
442
+ });
443
+ }
444
+
445
+ if (!response.body) {
446
+ throw new SandboxResponseError({
447
+ message: 'No response body',
448
+ sandboxId,
449
+ });
450
+ }
451
+
452
+ return response.body;
453
+ }
454
+
455
+ export interface UploadArchiveParams {
456
+ sandboxId: string;
457
+ archive: Uint8Array | ArrayBuffer | ReadableStream<Uint8Array>;
458
+ path?: string;
459
+ format?: ArchiveFormat | '';
460
+ orgId?: string;
461
+ signal?: AbortSignal;
462
+ }
463
+
464
+ const UploadArchiveResponseSchema = z.discriminatedUnion('success', [
465
+ z.object({
466
+ success: z.literal<false>(false),
467
+ message: z.string().describe('the error message'),
468
+ }),
469
+ z.object({
470
+ success: z.literal<true>(true),
471
+ }),
472
+ ]);
473
+
474
+ /**
475
+ * Uploads a compressed archive to a sandbox and extracts it.
476
+ *
477
+ * @param client - The API client to use for the request
478
+ * @param params - Parameters including sandbox ID, archive data, path, and optional format
479
+ * @throws {SandboxResponseError} If the upload request fails
480
+ */
481
+ export async function sandboxUploadArchive(
482
+ client: APIClient,
483
+ params: UploadArchiveParams
484
+ ): Promise<void> {
485
+ const { sandboxId, archive, path, format, orgId, signal } = params;
486
+
487
+ const queryParams = new URLSearchParams();
488
+ if (path) {
489
+ queryParams.set('path', path);
490
+ }
491
+ if (format) {
492
+ queryParams.set('format', format);
493
+ }
494
+ if (orgId) {
495
+ queryParams.set('orgId', orgId);
496
+ }
497
+ const queryString = queryParams.toString();
498
+ const url = `/fs/${API_VERSION}/upload/${sandboxId}${queryString ? `?${queryString}` : ''}`;
499
+
500
+ const response = await client.rawPost(url, archive, 'application/octet-stream', signal);
501
+
502
+ if (!response.ok) {
503
+ const text = await response.text().catch(() => 'Unknown error');
504
+ throw new SandboxResponseError({
505
+ message: `Failed to upload archive: ${response.status} ${text}`,
506
+ sandboxId,
507
+ });
508
+ }
509
+
510
+ const body = await response.json();
511
+ const result = UploadArchiveResponseSchema.parse(body);
512
+
513
+ if (!result.success) {
514
+ throw new SandboxResponseError({ message: result.message, sandboxId });
515
+ }
516
+ }
517
+
518
+ const SetEnvRequestSchema = z.object({
519
+ env: z
520
+ .record(z.string(), z.string().nullable())
521
+ .describe('Environment variables to set (null to delete)'),
522
+ });
523
+
524
+ const SetEnvDataSchema = z.object({
525
+ env: z.record(z.string(), z.string()).describe('Current environment variables after update'),
526
+ });
527
+
528
+ const SetEnvResponseSchema = z.discriminatedUnion('success', [
529
+ z.object({
530
+ success: z.literal<false>(false),
531
+ message: z.string().describe('the error message'),
532
+ }),
533
+ z.object({
534
+ success: z.literal<true>(true),
535
+ data: SetEnvDataSchema,
536
+ }),
537
+ ]);
538
+
539
+ export interface SetEnvParams {
540
+ sandboxId: string;
541
+ env: Record<string, string | null>;
542
+ orgId?: string;
543
+ signal?: AbortSignal;
544
+ }
545
+
546
+ export interface SetEnvResult {
547
+ env: Record<string, string>;
548
+ }
549
+
550
+ /**
551
+ * Sets environment variables on a sandbox. Pass null to delete a variable.
552
+ *
553
+ * @param client - The API client to use for the request
554
+ * @param params - Parameters including sandbox ID and env key/value pairs
555
+ * @returns The current environment variables after the update
556
+ * @throws {SandboxResponseError} If the request fails
557
+ */
558
+ export async function sandboxSetEnv(
559
+ client: APIClient,
560
+ params: SetEnvParams
561
+ ): Promise<SetEnvResult> {
562
+ const { sandboxId, env, orgId, signal } = params;
563
+
564
+ const body: z.infer<typeof SetEnvRequestSchema> = { env };
565
+
566
+ const queryParams = new URLSearchParams();
567
+ if (orgId) {
568
+ queryParams.set('orgId', orgId);
569
+ }
570
+ const queryString = queryParams.toString();
571
+ const url = `/sandbox/env/${API_VERSION}/${sandboxId}${queryString ? `?${queryString}` : ''}`;
572
+
573
+ const resp = await client.patch<z.infer<typeof SetEnvResponseSchema>>(
574
+ url,
575
+ body,
576
+ SetEnvResponseSchema,
577
+ SetEnvRequestSchema,
578
+ signal
579
+ );
580
+
581
+ if (resp.success) {
582
+ return {
583
+ env: resp.data.env,
584
+ };
585
+ }
586
+
587
+ throw new SandboxResponseError({ message: resp.message, sandboxId });
588
+ }