langsmith 0.5.21 → 0.5.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.cjs +327 -10
- package/dist/client.d.ts +90 -1
- package/dist/client.js +330 -13
- package/dist/evaluation/_runner.cjs +1 -4
- package/dist/evaluation/_runner.js +1 -4
- package/dist/experimental/sandbox/client.cjs +102 -427
- package/dist/experimental/sandbox/client.d.ts +68 -159
- package/dist/experimental/sandbox/client.js +104 -429
- package/dist/experimental/sandbox/errors.cjs +1 -2
- package/dist/experimental/sandbox/errors.d.ts +1 -2
- package/dist/experimental/sandbox/errors.js +1 -2
- package/dist/experimental/sandbox/helpers.cjs +8 -98
- package/dist/experimental/sandbox/helpers.d.ts +0 -29
- package/dist/experimental/sandbox/helpers.js +9 -95
- package/dist/experimental/sandbox/index.cjs +6 -1
- package/dist/experimental/sandbox/index.d.ts +7 -2
- package/dist/experimental/sandbox/index.js +6 -1
- package/dist/experimental/sandbox/sandbox.cjs +3 -11
- package/dist/experimental/sandbox/sandbox.d.ts +3 -5
- package/dist/experimental/sandbox/sandbox.js +3 -11
- package/dist/experimental/sandbox/types.d.ts +32 -149
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/schemas.d.ts +54 -0
- package/dist/utils/error.cjs +7 -0
- package/dist/utils/error.d.ts +1 -0
- package/dist/utils/error.js +6 -0
- package/dist/utils/fast-safe-stringify/index.cjs +228 -0
- package/dist/utils/fast-safe-stringify/index.d.ts +33 -0
- package/dist/utils/fast-safe-stringify/index.js +227 -0
- package/dist/utils/prompts.cjs +7 -2
- package/dist/utils/prompts.d.ts +6 -1
- package/dist/utils/prompts.js +6 -1
- package/dist/wrappers/openai_agents.cjs +849 -0
- package/dist/wrappers/openai_agents.d.ts +92 -0
- package/dist/wrappers/openai_agents.js +845 -0
- package/package.json +22 -6
- package/wrappers/openai_agents.cjs +1 -0
- package/wrappers/openai_agents.d.cts +1 -0
- package/wrappers/openai_agents.d.ts +1 -0
- package/wrappers/openai_agents.js +1 -0
|
@@ -5,8 +5,8 @@ import { getLangSmithEnvironmentVariable } from "../../utils/env.js";
|
|
|
5
5
|
import { _getFetchImplementation } from "../../singletons/fetch.js";
|
|
6
6
|
import { AsyncCaller } from "../../utils/async_caller.js";
|
|
7
7
|
import { Sandbox } from "./sandbox.js";
|
|
8
|
-
import { LangSmithResourceCreationError, LangSmithResourceNameConflictError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithSandboxAPIError, } from "./errors.js";
|
|
9
|
-
import { handleClientHttpError,
|
|
8
|
+
import { LangSmithResourceCreationError, LangSmithResourceNameConflictError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithSandboxAPIError, LangSmithValidationError, } from "./errors.js";
|
|
9
|
+
import { handleClientHttpError, handleSandboxCreationError, validateTtl, } from "./helpers.js";
|
|
10
10
|
/**
|
|
11
11
|
* Sleep that can be interrupted by an AbortSignal.
|
|
12
12
|
* Resolves after `ms` milliseconds or rejects immediately if the signal fires.
|
|
@@ -47,7 +47,7 @@ function getDefaultApiKey() {
|
|
|
47
47
|
/**
|
|
48
48
|
* Client for interacting with the Sandbox Server API.
|
|
49
49
|
*
|
|
50
|
-
* This client provides a simple interface for managing sandboxes and
|
|
50
|
+
* This client provides a simple interface for managing sandboxes and snapshots.
|
|
51
51
|
*
|
|
52
52
|
* @example
|
|
53
53
|
* ```typescript
|
|
@@ -62,8 +62,13 @@ function getDefaultApiKey() {
|
|
|
62
62
|
* apiKey: "your-api-key",
|
|
63
63
|
* });
|
|
64
64
|
*
|
|
65
|
-
* //
|
|
66
|
-
* const
|
|
65
|
+
* // Build a snapshot, then create a sandbox from it
|
|
66
|
+
* const snapshot = await client.createSnapshot(
|
|
67
|
+
* "python",
|
|
68
|
+
* "python:3.12-slim",
|
|
69
|
+
* 1_073_741_824 // 1 GiB
|
|
70
|
+
* );
|
|
71
|
+
* const sandbox = await client.createSandbox(snapshot.id);
|
|
67
72
|
* try {
|
|
68
73
|
* const result = await sandbox.run("python --version");
|
|
69
74
|
* console.log(result.stdout);
|
|
@@ -153,421 +158,40 @@ export class SandboxClient {
|
|
|
153
158
|
return response;
|
|
154
159
|
}
|
|
155
160
|
// =========================================================================
|
|
156
|
-
// Volume Operations
|
|
157
|
-
// =========================================================================
|
|
158
|
-
/**
|
|
159
|
-
* Create a new persistent volume.
|
|
160
|
-
*
|
|
161
|
-
* Creates a persistent storage volume that can be referenced in templates.
|
|
162
|
-
*
|
|
163
|
-
* @param name - Volume name.
|
|
164
|
-
* @param options - Creation options including size and optional timeout.
|
|
165
|
-
* @returns Created Volume.
|
|
166
|
-
* @throws SandboxCreationError if volume provisioning fails.
|
|
167
|
-
* @throws ResourceTimeoutError if volume doesn't become ready within timeout.
|
|
168
|
-
*/
|
|
169
|
-
async createVolume(name, options) {
|
|
170
|
-
const { size, timeout = 60 } = options;
|
|
171
|
-
const url = `${this._baseUrl}/volumes`;
|
|
172
|
-
const payload = {
|
|
173
|
-
name,
|
|
174
|
-
size,
|
|
175
|
-
wait_for_ready: true,
|
|
176
|
-
timeout,
|
|
177
|
-
};
|
|
178
|
-
const response = await this._fetch(url, {
|
|
179
|
-
method: "POST",
|
|
180
|
-
headers: { "Content-Type": "application/json" },
|
|
181
|
-
body: JSON.stringify(payload),
|
|
182
|
-
signal: AbortSignal.timeout((timeout + 30) * 1000),
|
|
183
|
-
});
|
|
184
|
-
if (!response.ok) {
|
|
185
|
-
await handleVolumeCreationError(response);
|
|
186
|
-
}
|
|
187
|
-
return (await response.json());
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Get a volume by name.
|
|
191
|
-
*
|
|
192
|
-
* @param name - Volume name.
|
|
193
|
-
* @returns Volume.
|
|
194
|
-
* @throws LangSmithResourceNotFoundError if volume not found.
|
|
195
|
-
*/
|
|
196
|
-
async getVolume(name) {
|
|
197
|
-
const url = `${this._baseUrl}/volumes/${encodeURIComponent(name)}`;
|
|
198
|
-
const response = await this._fetch(url);
|
|
199
|
-
if (!response.ok) {
|
|
200
|
-
if (response.status === 404) {
|
|
201
|
-
throw new LangSmithResourceNotFoundError(`Volume '${name}' not found`, "volume");
|
|
202
|
-
}
|
|
203
|
-
await handleClientHttpError(response);
|
|
204
|
-
}
|
|
205
|
-
return (await response.json());
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* List all volumes.
|
|
209
|
-
*
|
|
210
|
-
* @returns List of Volumes.
|
|
211
|
-
*/
|
|
212
|
-
async listVolumes() {
|
|
213
|
-
const url = `${this._baseUrl}/volumes`;
|
|
214
|
-
const response = await this._fetch(url);
|
|
215
|
-
if (!response.ok) {
|
|
216
|
-
if (response.status === 404) {
|
|
217
|
-
throw new LangSmithSandboxAPIError(`API endpoint not found: ${url}. Check that apiEndpoint is correct.`);
|
|
218
|
-
}
|
|
219
|
-
await handleClientHttpError(response);
|
|
220
|
-
}
|
|
221
|
-
const data = await response.json();
|
|
222
|
-
return (data.volumes ?? []);
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Delete a volume.
|
|
226
|
-
*
|
|
227
|
-
* @param name - Volume name.
|
|
228
|
-
* @throws LangSmithResourceNotFoundError if volume not found.
|
|
229
|
-
* @throws ResourceInUseError if volume is referenced by templates.
|
|
230
|
-
*/
|
|
231
|
-
async deleteVolume(name) {
|
|
232
|
-
const url = `${this._baseUrl}/volumes/${encodeURIComponent(name)}`;
|
|
233
|
-
const response = await this._fetch(url, { method: "DELETE" });
|
|
234
|
-
if (!response.ok) {
|
|
235
|
-
if (response.status === 404) {
|
|
236
|
-
throw new LangSmithResourceNotFoundError(`Volume '${name}' not found`, "volume");
|
|
237
|
-
}
|
|
238
|
-
if (response.status === 409) {
|
|
239
|
-
await handleResourceInUseError(response, "volume");
|
|
240
|
-
}
|
|
241
|
-
await handleClientHttpError(response);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Update a volume's name and/or size.
|
|
246
|
-
*
|
|
247
|
-
* You can update the display name, size, or both in a single request.
|
|
248
|
-
* Only storage size increases are allowed (storage backend limitation).
|
|
249
|
-
*
|
|
250
|
-
* @param name - Current volume name.
|
|
251
|
-
* @param options - Update options.
|
|
252
|
-
* @returns Updated Volume.
|
|
253
|
-
* @throws LangSmithResourceNotFoundError if volume not found.
|
|
254
|
-
* @throws ValidationError if storage decrease attempted.
|
|
255
|
-
* @throws LangSmithResourceNameConflictError if newName is already in use.
|
|
256
|
-
*/
|
|
257
|
-
async updateVolume(name, options) {
|
|
258
|
-
const { newName, size } = options;
|
|
259
|
-
if (newName === undefined && size === undefined) {
|
|
260
|
-
// Nothing to update, just return the current volume
|
|
261
|
-
return this.getVolume(name);
|
|
262
|
-
}
|
|
263
|
-
const url = `${this._baseUrl}/volumes/${encodeURIComponent(name)}`;
|
|
264
|
-
const payload = {};
|
|
265
|
-
if (newName !== undefined) {
|
|
266
|
-
payload.name = newName;
|
|
267
|
-
}
|
|
268
|
-
if (size !== undefined) {
|
|
269
|
-
payload.size = size;
|
|
270
|
-
}
|
|
271
|
-
const response = await this._fetch(url, {
|
|
272
|
-
method: "PATCH",
|
|
273
|
-
headers: { "Content-Type": "application/json" },
|
|
274
|
-
body: JSON.stringify(payload),
|
|
275
|
-
});
|
|
276
|
-
if (!response.ok) {
|
|
277
|
-
if (response.status === 404) {
|
|
278
|
-
throw new LangSmithResourceNotFoundError(`Volume '${name}' not found`, "volume");
|
|
279
|
-
}
|
|
280
|
-
if (response.status === 409) {
|
|
281
|
-
await handleConflictError(response, "volume");
|
|
282
|
-
}
|
|
283
|
-
await handleClientHttpError(response);
|
|
284
|
-
}
|
|
285
|
-
return (await response.json());
|
|
286
|
-
}
|
|
287
|
-
// =========================================================================
|
|
288
|
-
// Template Operations
|
|
289
|
-
// =========================================================================
|
|
290
|
-
/**
|
|
291
|
-
* Create a new SandboxTemplate.
|
|
292
|
-
*
|
|
293
|
-
* Only the container image, resource limits, and volume mounts can be
|
|
294
|
-
* configured. All other container details are handled by the server.
|
|
295
|
-
*
|
|
296
|
-
* @param name - Template name.
|
|
297
|
-
* @param options - Creation options including image and resource limits.
|
|
298
|
-
* @returns Created SandboxTemplate.
|
|
299
|
-
*/
|
|
300
|
-
async createTemplate(name, options) {
|
|
301
|
-
const { image, cpu = "500m", memory = "512Mi", storage, volumeMounts, } = options;
|
|
302
|
-
const url = `${this._baseUrl}/templates`;
|
|
303
|
-
const payload = {
|
|
304
|
-
name,
|
|
305
|
-
image,
|
|
306
|
-
resources: {
|
|
307
|
-
cpu,
|
|
308
|
-
memory,
|
|
309
|
-
},
|
|
310
|
-
};
|
|
311
|
-
if (storage) {
|
|
312
|
-
payload.resources.storage = storage;
|
|
313
|
-
}
|
|
314
|
-
if (volumeMounts && volumeMounts.length > 0) {
|
|
315
|
-
payload.volume_mounts = volumeMounts.map((vm) => ({
|
|
316
|
-
volume_name: vm.volume_name,
|
|
317
|
-
mount_path: vm.mount_path,
|
|
318
|
-
}));
|
|
319
|
-
}
|
|
320
|
-
const response = await this._fetch(url, {
|
|
321
|
-
method: "POST",
|
|
322
|
-
headers: { "Content-Type": "application/json" },
|
|
323
|
-
body: JSON.stringify(payload),
|
|
324
|
-
});
|
|
325
|
-
if (!response.ok) {
|
|
326
|
-
await handleClientHttpError(response);
|
|
327
|
-
}
|
|
328
|
-
return (await response.json());
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Get a SandboxTemplate by name.
|
|
332
|
-
*
|
|
333
|
-
* @param name - Template name.
|
|
334
|
-
* @returns SandboxTemplate.
|
|
335
|
-
* @throws LangSmithResourceNotFoundError if template not found.
|
|
336
|
-
*/
|
|
337
|
-
async getTemplate(name) {
|
|
338
|
-
const url = `${this._baseUrl}/templates/${encodeURIComponent(name)}`;
|
|
339
|
-
const response = await this._fetch(url);
|
|
340
|
-
if (!response.ok) {
|
|
341
|
-
if (response.status === 404) {
|
|
342
|
-
throw new LangSmithResourceNotFoundError(`Template '${name}' not found`, "template");
|
|
343
|
-
}
|
|
344
|
-
await handleClientHttpError(response);
|
|
345
|
-
}
|
|
346
|
-
return (await response.json());
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* List all SandboxTemplates.
|
|
350
|
-
*
|
|
351
|
-
* @returns List of SandboxTemplates.
|
|
352
|
-
*/
|
|
353
|
-
async listTemplates() {
|
|
354
|
-
const url = `${this._baseUrl}/templates`;
|
|
355
|
-
const response = await this._fetch(url);
|
|
356
|
-
if (!response.ok) {
|
|
357
|
-
if (response.status === 404) {
|
|
358
|
-
throw new LangSmithSandboxAPIError(`API endpoint not found: ${url}. Check that apiEndpoint is correct.`);
|
|
359
|
-
}
|
|
360
|
-
await handleClientHttpError(response);
|
|
361
|
-
}
|
|
362
|
-
const data = await response.json();
|
|
363
|
-
return (data.templates ?? []);
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Update a template.
|
|
367
|
-
*
|
|
368
|
-
* @param name - Current template name.
|
|
369
|
-
* @param options - Update options (e.g., newName).
|
|
370
|
-
* @returns Updated SandboxTemplate.
|
|
371
|
-
* @throws LangSmithResourceNotFoundError if template not found.
|
|
372
|
-
* @throws LangSmithResourceNameConflictError if newName is already in use.
|
|
373
|
-
*/
|
|
374
|
-
async updateTemplate(name, options) {
|
|
375
|
-
const { newName } = options;
|
|
376
|
-
if (newName === undefined) {
|
|
377
|
-
// Nothing to update, just return the current template
|
|
378
|
-
return this.getTemplate(name);
|
|
379
|
-
}
|
|
380
|
-
const url = `${this._baseUrl}/templates/${encodeURIComponent(name)}`;
|
|
381
|
-
const payload = { name: newName };
|
|
382
|
-
const response = await this._fetch(url, {
|
|
383
|
-
method: "PATCH",
|
|
384
|
-
headers: { "Content-Type": "application/json" },
|
|
385
|
-
body: JSON.stringify(payload),
|
|
386
|
-
});
|
|
387
|
-
if (!response.ok) {
|
|
388
|
-
if (response.status === 404) {
|
|
389
|
-
throw new LangSmithResourceNotFoundError(`Template '${name}' not found`, "template");
|
|
390
|
-
}
|
|
391
|
-
if (response.status === 409) {
|
|
392
|
-
await handleConflictError(response, "template");
|
|
393
|
-
}
|
|
394
|
-
await handleClientHttpError(response);
|
|
395
|
-
}
|
|
396
|
-
return (await response.json());
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Delete a SandboxTemplate.
|
|
400
|
-
*
|
|
401
|
-
* @param name - Template name.
|
|
402
|
-
* @throws LangSmithResourceNotFoundError if template not found.
|
|
403
|
-
* @throws ResourceInUseError if template is referenced by sandboxes or pools.
|
|
404
|
-
*/
|
|
405
|
-
async deleteTemplate(name) {
|
|
406
|
-
const url = `${this._baseUrl}/templates/${encodeURIComponent(name)}`;
|
|
407
|
-
const response = await this._fetch(url, { method: "DELETE" });
|
|
408
|
-
if (!response.ok) {
|
|
409
|
-
if (response.status === 404) {
|
|
410
|
-
throw new LangSmithResourceNotFoundError(`Template '${name}' not found`, "template");
|
|
411
|
-
}
|
|
412
|
-
if (response.status === 409) {
|
|
413
|
-
await handleResourceInUseError(response, "template");
|
|
414
|
-
}
|
|
415
|
-
await handleClientHttpError(response);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
// =========================================================================
|
|
419
|
-
// Pool Operations
|
|
420
|
-
// =========================================================================
|
|
421
|
-
/**
|
|
422
|
-
* Create a new Sandbox Pool.
|
|
423
|
-
*
|
|
424
|
-
* Pools pre-provision sandboxes from a template for faster startup.
|
|
425
|
-
*
|
|
426
|
-
* @param name - Pool name (lowercase letters, numbers, hyphens; max 63 chars).
|
|
427
|
-
* @param options - Creation options including templateName, replicas, and optional timeout.
|
|
428
|
-
* @returns Created Pool.
|
|
429
|
-
* @throws LangSmithResourceNotFoundError if template not found.
|
|
430
|
-
* @throws ValidationError if template has volumes attached.
|
|
431
|
-
* @throws ResourceAlreadyExistsError if pool with this name already exists.
|
|
432
|
-
* @throws ResourceTimeoutError if pool doesn't reach ready state within timeout.
|
|
433
|
-
* @throws QuotaExceededError if organization quota is exceeded.
|
|
434
|
-
*/
|
|
435
|
-
async createPool(name, options) {
|
|
436
|
-
const { templateName, replicas, timeout = 30 } = options;
|
|
437
|
-
const url = `${this._baseUrl}/pools`;
|
|
438
|
-
const payload = {
|
|
439
|
-
name,
|
|
440
|
-
template_name: templateName,
|
|
441
|
-
replicas,
|
|
442
|
-
wait_for_ready: true,
|
|
443
|
-
timeout,
|
|
444
|
-
};
|
|
445
|
-
const response = await this._fetch(url, {
|
|
446
|
-
method: "POST",
|
|
447
|
-
headers: { "Content-Type": "application/json" },
|
|
448
|
-
body: JSON.stringify(payload),
|
|
449
|
-
signal: AbortSignal.timeout((timeout + 30) * 1000),
|
|
450
|
-
});
|
|
451
|
-
if (!response.ok) {
|
|
452
|
-
await handlePoolError(response);
|
|
453
|
-
}
|
|
454
|
-
return (await response.json());
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* Get a Pool by name.
|
|
458
|
-
*
|
|
459
|
-
* @param name - Pool name.
|
|
460
|
-
* @returns Pool.
|
|
461
|
-
* @throws LangSmithResourceNotFoundError if pool not found.
|
|
462
|
-
*/
|
|
463
|
-
async getPool(name) {
|
|
464
|
-
const url = `${this._baseUrl}/pools/${encodeURIComponent(name)}`;
|
|
465
|
-
const response = await this._fetch(url);
|
|
466
|
-
if (!response.ok) {
|
|
467
|
-
if (response.status === 404) {
|
|
468
|
-
throw new LangSmithResourceNotFoundError(`Pool '${name}' not found`, "pool");
|
|
469
|
-
}
|
|
470
|
-
await handleClientHttpError(response);
|
|
471
|
-
}
|
|
472
|
-
return (await response.json());
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* List all Pools.
|
|
476
|
-
*
|
|
477
|
-
* @returns List of Pools.
|
|
478
|
-
*/
|
|
479
|
-
async listPools() {
|
|
480
|
-
const url = `${this._baseUrl}/pools`;
|
|
481
|
-
const response = await this._fetch(url);
|
|
482
|
-
if (!response.ok) {
|
|
483
|
-
if (response.status === 404) {
|
|
484
|
-
throw new LangSmithSandboxAPIError(`API endpoint not found: ${url}. Check that apiEndpoint is correct.`);
|
|
485
|
-
}
|
|
486
|
-
await handleClientHttpError(response);
|
|
487
|
-
}
|
|
488
|
-
const data = await response.json();
|
|
489
|
-
return (data.pools ?? []);
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Update a Pool's name and/or replica count.
|
|
493
|
-
*
|
|
494
|
-
* You can update the display name, replica count, or both.
|
|
495
|
-
* The template reference cannot be changed after creation.
|
|
496
|
-
*
|
|
497
|
-
* @param name - Current pool name.
|
|
498
|
-
* @param options - Update options.
|
|
499
|
-
* @returns Updated Pool.
|
|
500
|
-
* @throws LangSmithResourceNotFoundError if pool not found.
|
|
501
|
-
* @throws ValidationError if template was deleted.
|
|
502
|
-
* @throws LangSmithResourceNameConflictError if newName is already in use.
|
|
503
|
-
* @throws QuotaExceededError if quota exceeded when scaling up.
|
|
504
|
-
*/
|
|
505
|
-
async updatePool(name, options) {
|
|
506
|
-
const { newName, replicas } = options;
|
|
507
|
-
if (newName === undefined && replicas === undefined) {
|
|
508
|
-
// Nothing to update, just return the current pool
|
|
509
|
-
return this.getPool(name);
|
|
510
|
-
}
|
|
511
|
-
const url = `${this._baseUrl}/pools/${encodeURIComponent(name)}`;
|
|
512
|
-
const payload = {};
|
|
513
|
-
if (newName !== undefined) {
|
|
514
|
-
payload.name = newName;
|
|
515
|
-
}
|
|
516
|
-
if (replicas !== undefined) {
|
|
517
|
-
payload.replicas = replicas;
|
|
518
|
-
}
|
|
519
|
-
const response = await this._fetch(url, {
|
|
520
|
-
method: "PATCH",
|
|
521
|
-
headers: { "Content-Type": "application/json" },
|
|
522
|
-
body: JSON.stringify(payload),
|
|
523
|
-
});
|
|
524
|
-
if (!response.ok) {
|
|
525
|
-
if (response.status === 404) {
|
|
526
|
-
throw new LangSmithResourceNotFoundError(`Pool '${name}' not found`, "pool");
|
|
527
|
-
}
|
|
528
|
-
if (response.status === 409) {
|
|
529
|
-
await handleConflictError(response, "pool");
|
|
530
|
-
}
|
|
531
|
-
await handlePoolError(response);
|
|
532
|
-
}
|
|
533
|
-
return (await response.json());
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* Delete a Pool.
|
|
537
|
-
*
|
|
538
|
-
* This will terminate all sandboxes in the pool.
|
|
539
|
-
*
|
|
540
|
-
* @param name - Pool name.
|
|
541
|
-
* @throws LangSmithResourceNotFoundError if pool not found.
|
|
542
|
-
*/
|
|
543
|
-
async deletePool(name) {
|
|
544
|
-
const url = `${this._baseUrl}/pools/${encodeURIComponent(name)}`;
|
|
545
|
-
const response = await this._fetch(url, { method: "DELETE" });
|
|
546
|
-
if (!response.ok) {
|
|
547
|
-
if (response.status === 404) {
|
|
548
|
-
throw new LangSmithResourceNotFoundError(`Pool '${name}' not found`, "pool");
|
|
549
|
-
}
|
|
550
|
-
await handleClientHttpError(response);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
// =========================================================================
|
|
554
161
|
// Sandbox Operations
|
|
555
162
|
// =========================================================================
|
|
556
163
|
/**
|
|
557
|
-
* Create a new Sandbox.
|
|
164
|
+
* Create a new Sandbox from a snapshot.
|
|
558
165
|
*
|
|
559
166
|
* Remember to call `sandbox.delete()` when done to clean up resources.
|
|
560
167
|
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
168
|
+
* Exactly one of `snapshotId` (positional) or `options.snapshotName` must
|
|
169
|
+
* be provided. When `snapshotName` is used, the server resolves it to a
|
|
170
|
+
* snapshot owned by the caller's tenant.
|
|
171
|
+
*
|
|
172
|
+
* @param snapshotId - ID of the snapshot to boot from. Create one with
|
|
173
|
+
* `createSnapshot()` or `captureSnapshot()`, or pass an existing snapshot ID.
|
|
174
|
+
* Pass `undefined` when booting by name via `options.snapshotName`.
|
|
175
|
+
* @param options - Creation options. Use `options.snapshotName` to boot
|
|
176
|
+
* by snapshot name instead of ID.
|
|
563
177
|
* @returns Created Sandbox.
|
|
564
178
|
* @throws ResourceTimeoutError if timeout waiting for sandbox to be ready.
|
|
565
179
|
* @throws SandboxCreationError if sandbox creation fails.
|
|
566
|
-
* @throws LangSmithValidationError if TTL values are invalid
|
|
180
|
+
* @throws LangSmithValidationError if TTL values are invalid, or if neither
|
|
181
|
+
* (or both) of `snapshotId` / `options.snapshotName` are provided.
|
|
567
182
|
*
|
|
568
183
|
* @example
|
|
569
184
|
* ```typescript
|
|
570
|
-
* const
|
|
185
|
+
* const snapshot = await client.createSnapshot(
|
|
186
|
+
* "python",
|
|
187
|
+
* "python:3.12-slim",
|
|
188
|
+
* 1_073_741_824
|
|
189
|
+
* );
|
|
190
|
+
* const sandbox = await client.createSandbox(snapshot.id);
|
|
191
|
+
* // Or, resolve by snapshot name:
|
|
192
|
+
* const sandbox = await client.createSandbox(undefined, {
|
|
193
|
+
* snapshotName: "python",
|
|
194
|
+
* });
|
|
571
195
|
* try {
|
|
572
196
|
* const result = await sandbox.run("echo hello");
|
|
573
197
|
* console.log(result.stdout);
|
|
@@ -576,13 +200,10 @@ export class SandboxClient {
|
|
|
576
200
|
* }
|
|
577
201
|
* ```
|
|
578
202
|
*/
|
|
579
|
-
async createSandbox(
|
|
580
|
-
const {
|
|
581
|
-
if (
|
|
582
|
-
throw new
|
|
583
|
-
}
|
|
584
|
-
if (templateName && snapshotId) {
|
|
585
|
-
throw new Error("Cannot specify both templateName and snapshotId");
|
|
203
|
+
async createSandbox(snapshotId, options = {}) {
|
|
204
|
+
const { snapshotName, name, timeout = 30, waitForReady = true, ttlSeconds, idleTtlSeconds, vCpus, memBytes, fsCapacityBytes, proxyConfig, } = options;
|
|
205
|
+
if (!!snapshotId === !!snapshotName) {
|
|
206
|
+
throw new LangSmithValidationError("Exactly one of snapshotId or options.snapshotName must be set", "snapshotId");
|
|
586
207
|
}
|
|
587
208
|
validateTtl(ttlSeconds, "ttlSeconds");
|
|
588
209
|
validateTtl(idleTtlSeconds, "idleTtlSeconds");
|
|
@@ -590,12 +211,12 @@ export class SandboxClient {
|
|
|
590
211
|
const payload = {
|
|
591
212
|
wait_for_ready: waitForReady,
|
|
592
213
|
};
|
|
593
|
-
if (templateName) {
|
|
594
|
-
payload.template_name = templateName;
|
|
595
|
-
}
|
|
596
214
|
if (snapshotId) {
|
|
597
215
|
payload.snapshot_id = snapshotId;
|
|
598
216
|
}
|
|
217
|
+
if (snapshotName) {
|
|
218
|
+
payload.snapshot_name = snapshotName;
|
|
219
|
+
}
|
|
599
220
|
if (waitForReady) {
|
|
600
221
|
payload.timeout = timeout;
|
|
601
222
|
}
|
|
@@ -765,7 +386,7 @@ export class SandboxClient {
|
|
|
765
386
|
*
|
|
766
387
|
* @example
|
|
767
388
|
* ```typescript
|
|
768
|
-
* const sandbox = await client.createSandbox(
|
|
389
|
+
* const sandbox = await client.createSandbox(snapshot.id, { waitForReady: false });
|
|
769
390
|
* // ... do other work ...
|
|
770
391
|
* const readySandbox = await client.waitForSandbox(sandbox.name);
|
|
771
392
|
* ```
|
|
@@ -861,16 +482,13 @@ export class SandboxClient {
|
|
|
861
482
|
*
|
|
862
483
|
* @param sandboxName - Name of the sandbox to capture from.
|
|
863
484
|
* @param name - Snapshot name.
|
|
864
|
-
* @param options - Capture options (
|
|
485
|
+
* @param options - Capture options (timeout).
|
|
865
486
|
* @returns Snapshot in "ready" status.
|
|
866
487
|
*/
|
|
867
488
|
async captureSnapshot(sandboxName, name, options = {}) {
|
|
868
|
-
const {
|
|
489
|
+
const { timeout = 60, signal } = options;
|
|
869
490
|
const url = `${this._baseUrl}/boxes/${encodeURIComponent(sandboxName)}/snapshot`;
|
|
870
491
|
const payload = { name };
|
|
871
|
-
if (checkpoint !== undefined) {
|
|
872
|
-
payload.checkpoint = checkpoint;
|
|
873
|
-
}
|
|
874
492
|
const response = await this._postJson(url, payload, { signal });
|
|
875
493
|
const snapshot = (await response.json());
|
|
876
494
|
return this.waitForSnapshot(snapshot.id, { timeout, signal });
|
|
@@ -893,13 +511,50 @@ export class SandboxClient {
|
|
|
893
511
|
return (await response.json());
|
|
894
512
|
}
|
|
895
513
|
/**
|
|
896
|
-
* List
|
|
514
|
+
* List snapshots, optionally filtered and paginated server-side.
|
|
897
515
|
*
|
|
898
|
-
*
|
|
516
|
+
* The backend always paginates this endpoint. When `limit` is omitted the
|
|
517
|
+
* server applies a default page size (currently 50), so a single call is
|
|
518
|
+
* not guaranteed to return every snapshot visible to the tenant. To iterate
|
|
519
|
+
* through all results, repeat the call with increasing `offset` values (or
|
|
520
|
+
* an explicit `limit`) until fewer than `limit` snapshots come back.
|
|
521
|
+
*
|
|
522
|
+
* @param options - Optional filter/pagination options.
|
|
523
|
+
* - `nameContains`: case-insensitive substring match on snapshot name.
|
|
524
|
+
* - `limit`: page size; must be between 1 and 500 (inclusive). Defaults
|
|
525
|
+
* to 50 server-side when omitted.
|
|
526
|
+
* - `offset`: number of snapshots to skip; must be `>= 0`.
|
|
527
|
+
*
|
|
528
|
+
* Values outside those ranges are rejected by the server.
|
|
529
|
+
* @returns A single page of Snapshots matching the provided filters.
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```typescript
|
|
533
|
+
* const firstPage = await client.listSnapshots();
|
|
534
|
+
* const page = await client.listSnapshots({
|
|
535
|
+
* nameContains: "python",
|
|
536
|
+
* limit: 100,
|
|
537
|
+
* offset: 0,
|
|
538
|
+
* });
|
|
539
|
+
* ```
|
|
899
540
|
*/
|
|
900
|
-
async listSnapshots() {
|
|
901
|
-
const
|
|
902
|
-
const
|
|
541
|
+
async listSnapshots(options = {}) {
|
|
542
|
+
const { nameContains, limit, offset, signal } = options;
|
|
543
|
+
const params = new URLSearchParams();
|
|
544
|
+
if (nameContains !== undefined) {
|
|
545
|
+
params.set("name_contains", nameContains);
|
|
546
|
+
}
|
|
547
|
+
if (limit !== undefined) {
|
|
548
|
+
params.set("limit", String(limit));
|
|
549
|
+
}
|
|
550
|
+
if (offset !== undefined) {
|
|
551
|
+
params.set("offset", String(offset));
|
|
552
|
+
}
|
|
553
|
+
const query = params.toString();
|
|
554
|
+
const url = query
|
|
555
|
+
? `${this._baseUrl}/snapshots?${query}`
|
|
556
|
+
: `${this._baseUrl}/snapshots`;
|
|
557
|
+
const response = await this._fetch(url, { signal });
|
|
903
558
|
if (!response.ok) {
|
|
904
559
|
await handleClientHttpError(response);
|
|
905
560
|
}
|
|
@@ -949,4 +604,24 @@ export class SandboxClient {
|
|
|
949
604
|
}
|
|
950
605
|
throw new LangSmithResourceTimeoutError(`Snapshot '${snapshotId}' did not become ready within ${timeout}s`, "snapshot", lastStatus);
|
|
951
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* Returns a string representation of the SandboxClient instance.
|
|
609
|
+
* This method is called when the object is converted to a string
|
|
610
|
+
* or logged, ensuring sensitive information like API keys is not exposed.
|
|
611
|
+
*
|
|
612
|
+
* @returns A string representation of the SandboxClient.
|
|
613
|
+
*/
|
|
614
|
+
toString() {
|
|
615
|
+
return `[LangSmithSandboxClient apiEndpoint=${JSON.stringify(this._baseUrl)}]`;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Custom inspect method for Node.js.
|
|
619
|
+
* This method is called when the object is inspected in the Node.js REPL
|
|
620
|
+
* or with console.log, ensuring sensitive information like API keys is not exposed.
|
|
621
|
+
*
|
|
622
|
+
* @returns A string representation of the SandboxClient for inspection.
|
|
623
|
+
*/
|
|
624
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
625
|
+
return this.toString();
|
|
626
|
+
}
|
|
952
627
|
}
|
|
@@ -165,7 +165,6 @@ exports.LangSmithResourceNameConflictError = LangSmithResourceNameConflictError;
|
|
|
165
165
|
* - Resource values exceeding server-defined limits (CPU, memory, storage)
|
|
166
166
|
* - Invalid resource units
|
|
167
167
|
* - Invalid name formats
|
|
168
|
-
* - Pool validation failures (e.g., template has volumes)
|
|
169
168
|
*/
|
|
170
169
|
class LangSmithValidationError extends LangSmithSandboxError {
|
|
171
170
|
constructor(message, field, details, errorType) {
|
|
@@ -224,7 +223,7 @@ class LangSmithResourceCreationError extends LangSmithSandboxError {
|
|
|
224
223
|
constructor(message, resourceType, errorType) {
|
|
225
224
|
super(message);
|
|
226
225
|
/**
|
|
227
|
-
* Type of resource that failed (e.g., "sandbox", "
|
|
226
|
+
* Type of resource that failed (e.g., "sandbox", "snapshot").
|
|
228
227
|
*/
|
|
229
228
|
Object.defineProperty(this, "resourceType", {
|
|
230
229
|
enumerable: true,
|
|
@@ -75,7 +75,6 @@ export declare class LangSmithResourceNameConflictError extends LangSmithSandbox
|
|
|
75
75
|
* - Resource values exceeding server-defined limits (CPU, memory, storage)
|
|
76
76
|
* - Invalid resource units
|
|
77
77
|
* - Invalid name formats
|
|
78
|
-
* - Pool validation failures (e.g., template has volumes)
|
|
79
78
|
*/
|
|
80
79
|
export declare class LangSmithValidationError extends LangSmithSandboxError {
|
|
81
80
|
field?: string;
|
|
@@ -97,7 +96,7 @@ export declare class LangSmithQuotaExceededError extends LangSmithSandboxError {
|
|
|
97
96
|
*/
|
|
98
97
|
export declare class LangSmithResourceCreationError extends LangSmithSandboxError {
|
|
99
98
|
/**
|
|
100
|
-
* Type of resource that failed (e.g., "sandbox", "
|
|
99
|
+
* Type of resource that failed (e.g., "sandbox", "snapshot").
|
|
101
100
|
*/
|
|
102
101
|
resourceType?: string;
|
|
103
102
|
/**
|
|
@@ -153,7 +153,6 @@ export class LangSmithResourceNameConflictError extends LangSmithSandboxError {
|
|
|
153
153
|
* - Resource values exceeding server-defined limits (CPU, memory, storage)
|
|
154
154
|
* - Invalid resource units
|
|
155
155
|
* - Invalid name formats
|
|
156
|
-
* - Pool validation failures (e.g., template has volumes)
|
|
157
156
|
*/
|
|
158
157
|
export class LangSmithValidationError extends LangSmithSandboxError {
|
|
159
158
|
constructor(message, field, details, errorType) {
|
|
@@ -210,7 +209,7 @@ export class LangSmithResourceCreationError extends LangSmithSandboxError {
|
|
|
210
209
|
constructor(message, resourceType, errorType) {
|
|
211
210
|
super(message);
|
|
212
211
|
/**
|
|
213
|
-
* Type of resource that failed (e.g., "sandbox", "
|
|
212
|
+
* Type of resource that failed (e.g., "sandbox", "snapshot").
|
|
214
213
|
*/
|
|
215
214
|
Object.defineProperty(this, "resourceType", {
|
|
216
215
|
enumerable: true,
|