langsmith 0.10.4 → 0.10.6
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/address.cjs +119 -0
- package/dist/address.d.ts +13 -0
- package/dist/address.js +107 -0
- package/dist/client.cjs +68 -22
- package/dist/client.d.ts +13 -0
- package/dist/client.js +68 -22
- package/dist/evaluation/_runner.cjs +47 -5
- package/dist/evaluation/_runner.d.ts +12 -0
- package/dist/evaluation/_runner.js +47 -5
- package/dist/experimental/otel/constants.cjs +5 -1
- package/dist/experimental/otel/constants.d.ts +3 -0
- package/dist/experimental/otel/constants.js +3 -0
- package/dist/experimental/otel/translator.cjs +14 -0
- package/dist/experimental/otel/translator.js +14 -0
- package/dist/index.cjs +4 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/run_trees.cjs +154 -24
- package/dist/run_trees.d.ts +13 -1
- package/dist/run_trees.js +154 -24
- package/dist/sandbox/access_delegation.cjs +24 -0
- package/dist/sandbox/access_delegation.d.ts +15 -0
- package/dist/sandbox/access_delegation.js +21 -0
- package/dist/sandbox/client.cjs +70 -5
- package/dist/sandbox/client.d.ts +18 -0
- package/dist/sandbox/client.js +70 -5
- package/dist/sandbox/command_handle.cjs +34 -0
- package/dist/sandbox/command_handle.d.ts +10 -0
- package/dist/sandbox/command_handle.js +34 -0
- package/dist/sandbox/errors.cjs +12 -1
- package/dist/sandbox/errors.d.ts +7 -0
- package/dist/sandbox/errors.js +10 -0
- package/dist/sandbox/helpers.cjs +129 -0
- package/dist/sandbox/helpers.d.ts +29 -0
- package/dist/sandbox/helpers.js +123 -0
- package/dist/sandbox/index.cjs +12 -1
- package/dist/sandbox/index.d.ts +8 -2
- package/dist/sandbox/index.js +4 -1
- package/dist/sandbox/mounts.cjs +21 -3
- package/dist/sandbox/mounts.d.ts +7 -1
- package/dist/sandbox/mounts.js +21 -3
- package/dist/sandbox/proxy_config.cjs +41 -10
- package/dist/sandbox/proxy_config.d.ts +33 -6
- package/dist/sandbox/proxy_config.js +40 -10
- package/dist/sandbox/sandbox.cjs +160 -4
- package/dist/sandbox/sandbox.d.ts +71 -1
- package/dist/sandbox/sandbox.js +161 -5
- package/dist/sandbox/service_url.cjs +125 -0
- package/dist/sandbox/service_url.d.ts +50 -0
- package/dist/sandbox/service_url.js +120 -0
- package/dist/sandbox/types.d.ts +251 -8
- package/dist/sandbox/verify.cjs +300 -0
- package/dist/sandbox/verify.d.ts +122 -0
- package/dist/sandbox/verify.js +296 -0
- package/dist/sandbox/ws_execute.cjs +31 -1
- package/dist/sandbox/ws_execute.d.ts +9 -0
- package/dist/sandbox/ws_execute.js +31 -1
- package/dist/schemas.d.ts +5 -0
- package/dist/traceable.cjs +7 -1
- package/dist/traceable.js +7 -1
- package/dist/utils/agent_addressing.cjs +92 -0
- package/dist/utils/agent_addressing.d.ts +30 -0
- package/dist/utils/agent_addressing.js +83 -0
- package/dist/utils/env.cjs +2 -0
- package/dist/utils/env.js +2 -0
- package/dist/utils/error.cjs +17 -22
- package/dist/utils/error.js +17 -22
- package/package.json +5 -5
package/dist/sandbox/client.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Main SandboxClient class for interacting with the sandbox server API.
|
|
3
3
|
*/
|
|
4
|
+
import { validateAccessDelegation } from "./access_delegation.js";
|
|
5
|
+
import { ServiceLoginUrl, ServiceUrl, } from "./service_url.js";
|
|
4
6
|
import { getLangSmithEnvironmentVariable } from "../utils/env.js";
|
|
5
7
|
import { _getFetchImplementation } from "../singletons/fetch.js";
|
|
6
8
|
import { AsyncCaller } from "../utils/async_caller.js";
|
|
@@ -404,6 +406,53 @@ export class SandboxClient {
|
|
|
404
406
|
const suffix = segments.length ? `/${segments.join("/")}` : "";
|
|
405
407
|
return `${this._baseUrl}/boxes/${encodeURIComponent(name)}${suffix}`;
|
|
406
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Generate an authenticated URL for an HTTP service running in a sandbox.
|
|
411
|
+
*
|
|
412
|
+
* Without `access`, mints a short-lived token and returns a {@link ServiceUrl}
|
|
413
|
+
* that refreshes it as it nears expiry. With `access` set to `"restricted"`
|
|
414
|
+
* or `"workspace"` the URL is gated by LangSmith login instead and a
|
|
415
|
+
* {@link ServiceLoginUrl} is returned: no token, no expiry, browser only.
|
|
416
|
+
*
|
|
417
|
+
* A login grant is durable, so token mode is refused with 409 while one is
|
|
418
|
+
* in place, and `expiresInSeconds` does not apply to the login modes.
|
|
419
|
+
*/
|
|
420
|
+
async serviceUrl(name, options) {
|
|
421
|
+
const { port, expiresInSeconds, access, signal } = options;
|
|
422
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
423
|
+
throw new LangSmithValidationError(`port must be between 1 and 65535 (got ${port})`, "port");
|
|
424
|
+
}
|
|
425
|
+
if (access !== undefined &&
|
|
426
|
+
access !== "restricted" &&
|
|
427
|
+
access !== "workspace") {
|
|
428
|
+
throw new LangSmithValidationError(`access must be "restricted" or "workspace" (got ${String(access)})`, "access");
|
|
429
|
+
}
|
|
430
|
+
const loginMode = access !== undefined;
|
|
431
|
+
if (expiresInSeconds !== undefined && loginMode) {
|
|
432
|
+
throw new LangSmithValidationError(`expiresInSeconds does not apply to access "${access}": a LangSmith ` +
|
|
433
|
+
"login URL carries no token and does not expire", "expiresInSeconds");
|
|
434
|
+
}
|
|
435
|
+
const url = this._boxUrl(name, "service-url");
|
|
436
|
+
const payload = { port };
|
|
437
|
+
if (!loginMode && expiresInSeconds !== undefined) {
|
|
438
|
+
payload.expires_in_seconds = expiresInSeconds;
|
|
439
|
+
}
|
|
440
|
+
if (access !== undefined) {
|
|
441
|
+
payload.access = access;
|
|
442
|
+
}
|
|
443
|
+
const response = await this._postJson(url, payload, { signal });
|
|
444
|
+
const data = await response.json();
|
|
445
|
+
if (loginMode) {
|
|
446
|
+
return new ServiceLoginUrl(data);
|
|
447
|
+
}
|
|
448
|
+
// Refresh with the token parameters only. Carrying options.signal over
|
|
449
|
+
// would tie every later refresh to the first request's lifetime, so a
|
|
450
|
+
// caller-supplied timeout signal would leave the URL unable to refresh.
|
|
451
|
+
return new ServiceUrl(data, () => this.serviceUrl(name, {
|
|
452
|
+
port,
|
|
453
|
+
expiresInSeconds,
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
407
456
|
async _postJson(url, body, options) {
|
|
408
457
|
const response = await this._fetch(url, {
|
|
409
458
|
method: "POST",
|
|
@@ -421,7 +470,7 @@ export class SandboxClient {
|
|
|
421
470
|
const resolvedOptions = typeof snapshotIdOrOptions === "object" && snapshotIdOrOptions !== null
|
|
422
471
|
? snapshotIdOrOptions
|
|
423
472
|
: options;
|
|
424
|
-
const { snapshotName, name, timeout = 30, waitForReady = true, idleTtlSeconds, deleteAfterStopSeconds, vCpus, memBytes, fsCapacityBytes, mountConfig, proxyConfig, } = resolvedOptions;
|
|
473
|
+
const { snapshotName, name, timeout = 30, waitForReady = true, idleTtlSeconds, deleteAfterStopSeconds, vCpus, memBytes, fsCapacityBytes, mountConfig, proxyConfig, runConfig, accessDelegation, } = resolvedOptions;
|
|
425
474
|
if (snapshotId && snapshotName) {
|
|
426
475
|
throw new LangSmithValidationError("At most one of snapshotId or options.snapshotName may be set", "snapshotId");
|
|
427
476
|
}
|
|
@@ -465,6 +514,12 @@ export class SandboxClient {
|
|
|
465
514
|
if (proxyConfig !== undefined) {
|
|
466
515
|
payload.proxy_config = proxyConfig;
|
|
467
516
|
}
|
|
517
|
+
if (runConfig !== undefined) {
|
|
518
|
+
payload.run_config = runConfig;
|
|
519
|
+
}
|
|
520
|
+
if (accessDelegation !== undefined) {
|
|
521
|
+
payload.access_delegation = validateAccessDelegation(accessDelegation);
|
|
522
|
+
}
|
|
468
523
|
const httpTimeout = waitForReady ? (timeout + 30) * 1000 : 30 * 1000;
|
|
469
524
|
const response = await this._fetch(url, {
|
|
470
525
|
method: "POST",
|
|
@@ -520,13 +575,14 @@ export class SandboxClient {
|
|
|
520
575
|
const options = typeof newNameOrOptions === "string"
|
|
521
576
|
? { newName: newNameOrOptions }
|
|
522
577
|
: newNameOrOptions;
|
|
523
|
-
const { newName, idleTtlSeconds, deleteAfterStopSeconds, proxyConfig } = options;
|
|
578
|
+
const { newName, idleTtlSeconds, deleteAfterStopSeconds, proxyConfig, runConfig, } = options;
|
|
524
579
|
validateTtl(idleTtlSeconds, "idleTtlSeconds");
|
|
525
580
|
validateTtl(deleteAfterStopSeconds, "deleteAfterStopSeconds");
|
|
526
581
|
if (newName === undefined &&
|
|
527
582
|
idleTtlSeconds === undefined &&
|
|
528
583
|
deleteAfterStopSeconds === undefined &&
|
|
529
|
-
proxyConfig === undefined
|
|
584
|
+
proxyConfig === undefined &&
|
|
585
|
+
runConfig === undefined) {
|
|
530
586
|
return this.getSandbox(name);
|
|
531
587
|
}
|
|
532
588
|
const url = this._boxUrl(name);
|
|
@@ -543,6 +599,9 @@ export class SandboxClient {
|
|
|
543
599
|
if (proxyConfig !== undefined) {
|
|
544
600
|
payload.proxy_config = proxyConfig;
|
|
545
601
|
}
|
|
602
|
+
if (runConfig !== undefined) {
|
|
603
|
+
payload.run_config = runConfig;
|
|
604
|
+
}
|
|
546
605
|
const response = await this._fetch(url, {
|
|
547
606
|
method: "PATCH",
|
|
548
607
|
headers: { "Content-Type": "application/json" },
|
|
@@ -727,7 +786,7 @@ export class SandboxClient {
|
|
|
727
786
|
* @returns Snapshot in "ready" status.
|
|
728
787
|
*/
|
|
729
788
|
async createSnapshot(name, dockerImage, fsCapacityBytes, options = {}) {
|
|
730
|
-
const { registryId, timeout = 60, signal } = options;
|
|
789
|
+
const { registryId, runConfig, timeout = 60, signal } = options;
|
|
731
790
|
const url = `${this._baseUrl}/snapshots`;
|
|
732
791
|
const payload = {
|
|
733
792
|
name,
|
|
@@ -737,6 +796,9 @@ export class SandboxClient {
|
|
|
737
796
|
if (registryId !== undefined) {
|
|
738
797
|
payload.registry_id = registryId;
|
|
739
798
|
}
|
|
799
|
+
if (runConfig !== undefined) {
|
|
800
|
+
payload.run_config = runConfig;
|
|
801
|
+
}
|
|
740
802
|
const response = await this._postJson(url, payload, { signal });
|
|
741
803
|
const snapshot = (await response.json());
|
|
742
804
|
return this.waitForSnapshot(snapshot.id, { timeout, signal });
|
|
@@ -829,7 +891,7 @@ export class SandboxClient {
|
|
|
829
891
|
* @returns Snapshot in "ready" status.
|
|
830
892
|
*/
|
|
831
893
|
async captureSnapshot(sandboxName, name, options = {}) {
|
|
832
|
-
const { dockerImage, fsCapacityBytes, timeout = 60, signal } = options;
|
|
894
|
+
const { dockerImage, fsCapacityBytes, runConfig, timeout = 60, signal, } = options;
|
|
833
895
|
const url = this._boxUrl(sandboxName, "snapshot");
|
|
834
896
|
const payload = { name };
|
|
835
897
|
if (dockerImage !== undefined) {
|
|
@@ -838,6 +900,9 @@ export class SandboxClient {
|
|
|
838
900
|
if (fsCapacityBytes !== undefined) {
|
|
839
901
|
payload.fs_capacity_bytes = fsCapacityBytes;
|
|
840
902
|
}
|
|
903
|
+
if (runConfig !== undefined) {
|
|
904
|
+
payload.run_config = runConfig;
|
|
905
|
+
}
|
|
841
906
|
const response = await this._postJson(url, payload, { signal });
|
|
842
907
|
const snapshot = (await response.json());
|
|
843
908
|
return this.waitForSnapshot(snapshot.id, { timeout, signal });
|
|
@@ -141,6 +141,18 @@ class CommandHandle {
|
|
|
141
141
|
writable: true,
|
|
142
142
|
value: void 0
|
|
143
143
|
});
|
|
144
|
+
Object.defineProperty(this, "_stdinClosed", {
|
|
145
|
+
enumerable: true,
|
|
146
|
+
configurable: true,
|
|
147
|
+
writable: true,
|
|
148
|
+
value: void 0
|
|
149
|
+
});
|
|
150
|
+
Object.defineProperty(this, "_pty", {
|
|
151
|
+
enumerable: true,
|
|
152
|
+
configurable: true,
|
|
153
|
+
writable: true,
|
|
154
|
+
value: void 0
|
|
155
|
+
});
|
|
144
156
|
this._stream = messageStream;
|
|
145
157
|
this._control = control;
|
|
146
158
|
this._sandbox = sandbox;
|
|
@@ -148,6 +160,8 @@ class CommandHandle {
|
|
|
148
160
|
this._lastStderrOffset = options?.stderrOffset ?? 0;
|
|
149
161
|
this._onStdout = options?.onStdout;
|
|
150
162
|
this._onStderr = options?.onStderr;
|
|
163
|
+
this._stdinClosed = options?.stdinClosed ?? false;
|
|
164
|
+
this._pty = options?.pty ?? false;
|
|
151
165
|
// New executions (no commandId): _ensureStarted reads "started".
|
|
152
166
|
// Reconnections (commandId set): skip since reconnect streams
|
|
153
167
|
// don't send a "started" message.
|
|
@@ -319,10 +333,28 @@ class CommandHandle {
|
|
|
319
333
|
* Write data to the command's stdin.
|
|
320
334
|
*/
|
|
321
335
|
sendInput(data) {
|
|
336
|
+
if (this._stdinClosed) {
|
|
337
|
+
throw new errors_js_1.LangSmithSandboxOperationError("stdin is closed for this command. Non-PTY commands close stdin by " +
|
|
338
|
+
"default so a command that reads it sees EOF instead of hanging; " +
|
|
339
|
+
"pass closeInput: false to run() to stream input into it.", "sendInput");
|
|
340
|
+
}
|
|
322
341
|
if (this._control) {
|
|
323
342
|
this._control.sendInput(data);
|
|
324
343
|
}
|
|
325
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Half-close stdin so the command reads EOF.
|
|
347
|
+
*
|
|
348
|
+
* Idempotent, and a no-op under a PTY, where input and output share one
|
|
349
|
+
* terminal file descriptor and there is no write end to close -- send an
|
|
350
|
+
* EOT byte (0x04) with {@link sendInput} instead.
|
|
351
|
+
*/
|
|
352
|
+
closeInput() {
|
|
353
|
+
if (this._pty || this._stdinClosed)
|
|
354
|
+
return;
|
|
355
|
+
this._stdinClosed = true;
|
|
356
|
+
this._control?.sendCloseStdin();
|
|
357
|
+
}
|
|
326
358
|
/** Last known stdout byte offset (for manual reconnection). */
|
|
327
359
|
get lastStdoutOffset() {
|
|
328
360
|
return this._lastStdoutOffset;
|
|
@@ -344,6 +376,8 @@ class CommandHandle {
|
|
|
344
376
|
return this._sandbox.reconnect(this._commandId, {
|
|
345
377
|
stdoutOffset: this._lastStdoutOffset,
|
|
346
378
|
stderrOffset: this._lastStderrOffset,
|
|
379
|
+
stdinClosed: this._stdinClosed,
|
|
380
|
+
pty: this._pty,
|
|
347
381
|
});
|
|
348
382
|
}
|
|
349
383
|
}
|
|
@@ -48,6 +48,8 @@ export declare class CommandHandle {
|
|
|
48
48
|
private _reconnectAttempts;
|
|
49
49
|
private _onStdout?;
|
|
50
50
|
private _onStderr?;
|
|
51
|
+
private _stdinClosed;
|
|
52
|
+
private _pty;
|
|
51
53
|
/**
|
|
52
54
|
* Read the 'started' message to populate commandId and pid.
|
|
53
55
|
*
|
|
@@ -87,6 +89,14 @@ export declare class CommandHandle {
|
|
|
87
89
|
* Write data to the command's stdin.
|
|
88
90
|
*/
|
|
89
91
|
sendInput(data: string): void;
|
|
92
|
+
/**
|
|
93
|
+
* Half-close stdin so the command reads EOF.
|
|
94
|
+
*
|
|
95
|
+
* Idempotent, and a no-op under a PTY, where input and output share one
|
|
96
|
+
* terminal file descriptor and there is no write end to close -- send an
|
|
97
|
+
* EOT byte (0x04) with {@link sendInput} instead.
|
|
98
|
+
*/
|
|
99
|
+
closeInput(): void;
|
|
90
100
|
/** Last known stdout byte offset (for manual reconnection). */
|
|
91
101
|
get lastStdoutOffset(): number;
|
|
92
102
|
/** Last known stderr byte offset (for manual reconnection). */
|
|
@@ -138,6 +138,18 @@ export class CommandHandle {
|
|
|
138
138
|
writable: true,
|
|
139
139
|
value: void 0
|
|
140
140
|
});
|
|
141
|
+
Object.defineProperty(this, "_stdinClosed", {
|
|
142
|
+
enumerable: true,
|
|
143
|
+
configurable: true,
|
|
144
|
+
writable: true,
|
|
145
|
+
value: void 0
|
|
146
|
+
});
|
|
147
|
+
Object.defineProperty(this, "_pty", {
|
|
148
|
+
enumerable: true,
|
|
149
|
+
configurable: true,
|
|
150
|
+
writable: true,
|
|
151
|
+
value: void 0
|
|
152
|
+
});
|
|
141
153
|
this._stream = messageStream;
|
|
142
154
|
this._control = control;
|
|
143
155
|
this._sandbox = sandbox;
|
|
@@ -145,6 +157,8 @@ export class CommandHandle {
|
|
|
145
157
|
this._lastStderrOffset = options?.stderrOffset ?? 0;
|
|
146
158
|
this._onStdout = options?.onStdout;
|
|
147
159
|
this._onStderr = options?.onStderr;
|
|
160
|
+
this._stdinClosed = options?.stdinClosed ?? false;
|
|
161
|
+
this._pty = options?.pty ?? false;
|
|
148
162
|
// New executions (no commandId): _ensureStarted reads "started".
|
|
149
163
|
// Reconnections (commandId set): skip since reconnect streams
|
|
150
164
|
// don't send a "started" message.
|
|
@@ -316,10 +330,28 @@ export class CommandHandle {
|
|
|
316
330
|
* Write data to the command's stdin.
|
|
317
331
|
*/
|
|
318
332
|
sendInput(data) {
|
|
333
|
+
if (this._stdinClosed) {
|
|
334
|
+
throw new LangSmithSandboxOperationError("stdin is closed for this command. Non-PTY commands close stdin by " +
|
|
335
|
+
"default so a command that reads it sees EOF instead of hanging; " +
|
|
336
|
+
"pass closeInput: false to run() to stream input into it.", "sendInput");
|
|
337
|
+
}
|
|
319
338
|
if (this._control) {
|
|
320
339
|
this._control.sendInput(data);
|
|
321
340
|
}
|
|
322
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Half-close stdin so the command reads EOF.
|
|
344
|
+
*
|
|
345
|
+
* Idempotent, and a no-op under a PTY, where input and output share one
|
|
346
|
+
* terminal file descriptor and there is no write end to close -- send an
|
|
347
|
+
* EOT byte (0x04) with {@link sendInput} instead.
|
|
348
|
+
*/
|
|
349
|
+
closeInput() {
|
|
350
|
+
if (this._pty || this._stdinClosed)
|
|
351
|
+
return;
|
|
352
|
+
this._stdinClosed = true;
|
|
353
|
+
this._control?.sendCloseStdin();
|
|
354
|
+
}
|
|
323
355
|
/** Last known stdout byte offset (for manual reconnection). */
|
|
324
356
|
get lastStdoutOffset() {
|
|
325
357
|
return this._lastStdoutOffset;
|
|
@@ -341,6 +373,8 @@ export class CommandHandle {
|
|
|
341
373
|
return this._sandbox.reconnect(this._commandId, {
|
|
342
374
|
stdoutOffset: this._lastStdoutOffset,
|
|
343
375
|
stderrOffset: this._lastStderrOffset,
|
|
376
|
+
stdinClosed: this._stdinClosed,
|
|
377
|
+
pty: this._pty,
|
|
344
378
|
});
|
|
345
379
|
}
|
|
346
380
|
}
|
package/dist/sandbox/errors.cjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* properties for specific handling when needed.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectTimeoutError = exports.LangSmithStreamEndedBeforeStartedError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxRetryableConnectionError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = void 0;
|
|
10
|
+
exports.LangSmithSandboxTokenVerificationError = exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectTimeoutError = exports.LangSmithStreamEndedBeforeStartedError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxRetryableConnectionError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = void 0;
|
|
11
11
|
/**
|
|
12
12
|
* Base exception for sandbox client errors.
|
|
13
13
|
*/
|
|
@@ -417,3 +417,14 @@ class LangSmithSandboxServerReloadError extends LangSmithSandboxConnectionError
|
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
exports.LangSmithSandboxServerReloadError = LangSmithSandboxServerReloadError;
|
|
420
|
+
/**
|
|
421
|
+
* Raised when a sandbox user token or proxy callback signature fails
|
|
422
|
+
* verification.
|
|
423
|
+
*/
|
|
424
|
+
class LangSmithSandboxTokenVerificationError extends LangSmithSandboxError {
|
|
425
|
+
constructor(message) {
|
|
426
|
+
super(message);
|
|
427
|
+
this.name = "LangSmithSandboxTokenVerificationError";
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
exports.LangSmithSandboxTokenVerificationError = LangSmithSandboxTokenVerificationError;
|
package/dist/sandbox/errors.d.ts
CHANGED
|
@@ -192,3 +192,10 @@ export declare class LangSmithSandboxConnectTimeoutError extends LangSmithSandbo
|
|
|
192
192
|
export declare class LangSmithSandboxServerReloadError extends LangSmithSandboxConnectionError {
|
|
193
193
|
constructor(message: string);
|
|
194
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Raised when a sandbox user token or proxy callback signature fails
|
|
197
|
+
* verification.
|
|
198
|
+
*/
|
|
199
|
+
export declare class LangSmithSandboxTokenVerificationError extends LangSmithSandboxError {
|
|
200
|
+
constructor(message: string);
|
|
201
|
+
}
|
package/dist/sandbox/errors.js
CHANGED
|
@@ -393,3 +393,13 @@ export class LangSmithSandboxServerReloadError extends LangSmithSandboxConnectio
|
|
|
393
393
|
this.name = "LangSmithSandboxServerReloadError";
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Raised when a sandbox user token or proxy callback signature fails
|
|
398
|
+
* verification.
|
|
399
|
+
*/
|
|
400
|
+
export class LangSmithSandboxTokenVerificationError extends LangSmithSandboxError {
|
|
401
|
+
constructor(message) {
|
|
402
|
+
super(message);
|
|
403
|
+
this.name = "LangSmithSandboxTokenVerificationError";
|
|
404
|
+
}
|
|
405
|
+
}
|
package/dist/sandbox/helpers.cjs
CHANGED
|
@@ -14,6 +14,12 @@ exports.handleSandboxCreationError = handleSandboxCreationError;
|
|
|
14
14
|
exports.throwIfNotReady = throwIfNotReady;
|
|
15
15
|
exports.handleClientHttpError = handleClientHttpError;
|
|
16
16
|
exports.handleSandboxHttpError = handleSandboxHttpError;
|
|
17
|
+
exports.assertRunConfigNotCombined = assertRunConfigNotCombined;
|
|
18
|
+
exports.resolveCloseInput = resolveCloseInput;
|
|
19
|
+
exports.buildRangeHeader = buildRangeHeader;
|
|
20
|
+
exports.fileStatFromResponse = fileStatFromResponse;
|
|
21
|
+
exports.fileChunkFromResponse = fileChunkFromResponse;
|
|
22
|
+
exports.handleFileHttpError = handleFileHttpError;
|
|
17
23
|
const errors_js_1 = require("./errors.cjs");
|
|
18
24
|
// =============================================================================
|
|
19
25
|
// Input validation
|
|
@@ -268,3 +274,126 @@ async function handleSandboxHttpError(response) {
|
|
|
268
274
|
}
|
|
269
275
|
throw new errors_js_1.LangSmithSandboxError(message);
|
|
270
276
|
}
|
|
277
|
+
// =============================================================================
|
|
278
|
+
// Run configuration
|
|
279
|
+
// =============================================================================
|
|
280
|
+
/**
|
|
281
|
+
* Reject a per-command `runConfig` alongside the deprecated `env`/`cwd`.
|
|
282
|
+
*
|
|
283
|
+
* The server answers 400 for a request carrying both spellings rather than
|
|
284
|
+
* letting one silently win, so refuse it here where the message can name the
|
|
285
|
+
* replacement.
|
|
286
|
+
*/
|
|
287
|
+
function assertRunConfigNotCombined(options) {
|
|
288
|
+
if (options.runConfig !== undefined &&
|
|
289
|
+
(options.env !== undefined || options.cwd !== undefined)) {
|
|
290
|
+
throw new errors_js_1.LangSmithValidationError("Cannot combine runConfig with the deprecated env/cwd options. " +
|
|
291
|
+
"Use runConfig.env_vars and runConfig.work_dir instead.");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Whether to half-close stdin at spawn.
|
|
296
|
+
*
|
|
297
|
+
* Defaults on for a non-PTY command: a command that reads stdin otherwise
|
|
298
|
+
* blocks on a pipe nobody writes to until the timeout kills it. A PTY has no
|
|
299
|
+
* separate write end to close, so the server ignores the flag there.
|
|
300
|
+
*/
|
|
301
|
+
function resolveCloseInput(closeInput, pty) {
|
|
302
|
+
if (pty)
|
|
303
|
+
return false;
|
|
304
|
+
return closeInput ?? true;
|
|
305
|
+
}
|
|
306
|
+
// =============================================================================
|
|
307
|
+
// Ranged downloads
|
|
308
|
+
// =============================================================================
|
|
309
|
+
/** Render a byte range as an RFC 9110 `Range` header value. */
|
|
310
|
+
function buildRangeHeader(options) {
|
|
311
|
+
const { start, end, suffixBytes } = options;
|
|
312
|
+
if (suffixBytes !== undefined) {
|
|
313
|
+
if (start !== undefined || end !== undefined) {
|
|
314
|
+
throw new errors_js_1.LangSmithValidationError("Cannot combine suffixBytes with start/end.");
|
|
315
|
+
}
|
|
316
|
+
if (suffixBytes <= 0) {
|
|
317
|
+
throw new errors_js_1.LangSmithValidationError("suffixBytes must be positive.");
|
|
318
|
+
}
|
|
319
|
+
return `bytes=-${suffixBytes}`;
|
|
320
|
+
}
|
|
321
|
+
if (start === undefined) {
|
|
322
|
+
throw new errors_js_1.LangSmithValidationError("Provide start (with optional end), or suffixBytes.");
|
|
323
|
+
}
|
|
324
|
+
if (start < 0) {
|
|
325
|
+
throw new errors_js_1.LangSmithValidationError("start must not be negative.");
|
|
326
|
+
}
|
|
327
|
+
if (end === undefined) {
|
|
328
|
+
return `bytes=${start}-`;
|
|
329
|
+
}
|
|
330
|
+
if (end < start) {
|
|
331
|
+
throw new errors_js_1.LangSmithValidationError("end must not precede start.");
|
|
332
|
+
}
|
|
333
|
+
return `bytes=${start}-${end}`;
|
|
334
|
+
}
|
|
335
|
+
/** Read the first byte offset and total size out of `Content-Range`. */
|
|
336
|
+
function parseContentRange(value) {
|
|
337
|
+
if (!value || !value.startsWith("bytes "))
|
|
338
|
+
return { start: 0 };
|
|
339
|
+
const spec = value.slice("bytes ".length).trim();
|
|
340
|
+
const [rangePart, totalPart] = spec.split("/");
|
|
341
|
+
const first = rangePart.split("-")[0].trim();
|
|
342
|
+
const start = /^\d+$/.test(first) ? Number(first) : 0;
|
|
343
|
+
const total = /^\d+$/.test((totalPart ?? "").trim())
|
|
344
|
+
? Number(totalPart)
|
|
345
|
+
: undefined;
|
|
346
|
+
return { start, total };
|
|
347
|
+
}
|
|
348
|
+
/** Build a FileStat from a HEAD response's headers. */
|
|
349
|
+
function fileStatFromResponse(response) {
|
|
350
|
+
const length = response.headers.get("content-length");
|
|
351
|
+
return {
|
|
352
|
+
size_bytes: length && /^\d+$/.test(length) ? Number(length) : 0,
|
|
353
|
+
etag: response.headers.get("etag") ?? undefined,
|
|
354
|
+
last_modified: response.headers.get("last-modified") ?? undefined,
|
|
355
|
+
content_type: response.headers.get("content-type") ?? undefined,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** Build a FileChunk from a ranged download response. */
|
|
359
|
+
async function fileChunkFromResponse(response) {
|
|
360
|
+
const etag = response.headers.get("etag") ?? undefined;
|
|
361
|
+
const lastModified = response.headers.get("last-modified") ?? undefined;
|
|
362
|
+
if (response.status === 304) {
|
|
363
|
+
return {
|
|
364
|
+
content: new Uint8Array(0),
|
|
365
|
+
etag,
|
|
366
|
+
start: 0,
|
|
367
|
+
partial: false,
|
|
368
|
+
unchanged: true,
|
|
369
|
+
last_modified: lastModified,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const content = new Uint8Array(await response.arrayBuffer());
|
|
373
|
+
const partial = response.status === 206;
|
|
374
|
+
// A stale If-Range answers 200 with the whole file; the caller has to
|
|
375
|
+
// restart rather than append, so report it from byte zero.
|
|
376
|
+
const { start, total } = partial
|
|
377
|
+
? parseContentRange(response.headers.get("content-range"))
|
|
378
|
+
: { start: 0, total: content.length };
|
|
379
|
+
return {
|
|
380
|
+
content,
|
|
381
|
+
etag,
|
|
382
|
+
total_bytes: total,
|
|
383
|
+
start,
|
|
384
|
+
partial,
|
|
385
|
+
unchanged: false,
|
|
386
|
+
last_modified: lastModified,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/** Map a file-operation HTTP error, including the non-JSON 416. */
|
|
390
|
+
async function handleFileHttpError(response, path, sandboxName) {
|
|
391
|
+
if (response.status === 404) {
|
|
392
|
+
throw new errors_js_1.LangSmithResourceNotFoundError(`File '${path}' not found in sandbox '${sandboxName}'`, "file");
|
|
393
|
+
}
|
|
394
|
+
if (response.status === 416) {
|
|
395
|
+
throw new errors_js_1.LangSmithSandboxOperationError(`Requested range for '${path}' starts past the end of the file ` +
|
|
396
|
+
`(${response.headers.get("content-range") ?? "unknown size"}).`, "read");
|
|
397
|
+
}
|
|
398
|
+
return handleSandboxHttpError(response);
|
|
399
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* These functions are used to parse error responses and raise appropriate
|
|
5
5
|
* exceptions. They contain no I/O operations.
|
|
6
6
|
*/
|
|
7
|
+
import type { FileChunk, FileStat, ReadRangeOptions, RunConfig } from "./types.js";
|
|
7
8
|
/**
|
|
8
9
|
* Validate TTL values for sandbox create/update (minute resolution).
|
|
9
10
|
*
|
|
@@ -76,4 +77,32 @@ export declare function handleClientHttpError(response: Response): Promise<never
|
|
|
76
77
|
* - 403 -> LangSmithSandboxOperationError (permission denied)
|
|
77
78
|
*/
|
|
78
79
|
export declare function handleSandboxHttpError(response: Response): Promise<never>;
|
|
80
|
+
/**
|
|
81
|
+
* Reject a per-command `runConfig` alongside the deprecated `env`/`cwd`.
|
|
82
|
+
*
|
|
83
|
+
* The server answers 400 for a request carrying both spellings rather than
|
|
84
|
+
* letting one silently win, so refuse it here where the message can name the
|
|
85
|
+
* replacement.
|
|
86
|
+
*/
|
|
87
|
+
export declare function assertRunConfigNotCombined(options: {
|
|
88
|
+
runConfig?: RunConfig;
|
|
89
|
+
env?: Record<string, string>;
|
|
90
|
+
cwd?: string;
|
|
91
|
+
}): void;
|
|
92
|
+
/**
|
|
93
|
+
* Whether to half-close stdin at spawn.
|
|
94
|
+
*
|
|
95
|
+
* Defaults on for a non-PTY command: a command that reads stdin otherwise
|
|
96
|
+
* blocks on a pipe nobody writes to until the timeout kills it. A PTY has no
|
|
97
|
+
* separate write end to close, so the server ignores the flag there.
|
|
98
|
+
*/
|
|
99
|
+
export declare function resolveCloseInput(closeInput: boolean | undefined, pty: boolean): boolean;
|
|
100
|
+
/** Render a byte range as an RFC 9110 `Range` header value. */
|
|
101
|
+
export declare function buildRangeHeader(options: ReadRangeOptions): string;
|
|
102
|
+
/** Build a FileStat from a HEAD response's headers. */
|
|
103
|
+
export declare function fileStatFromResponse(response: Response): FileStat;
|
|
104
|
+
/** Build a FileChunk from a ranged download response. */
|
|
105
|
+
export declare function fileChunkFromResponse(response: Response): Promise<FileChunk>;
|
|
106
|
+
/** Map a file-operation HTTP error, including the non-JSON 416. */
|
|
107
|
+
export declare function handleFileHttpError(response: Response, path: string, sandboxName: string): Promise<never>;
|
|
79
108
|
export {};
|
package/dist/sandbox/helpers.js
CHANGED
|
@@ -258,3 +258,126 @@ export async function handleSandboxHttpError(response) {
|
|
|
258
258
|
}
|
|
259
259
|
throw new LangSmithSandboxError(message);
|
|
260
260
|
}
|
|
261
|
+
// =============================================================================
|
|
262
|
+
// Run configuration
|
|
263
|
+
// =============================================================================
|
|
264
|
+
/**
|
|
265
|
+
* Reject a per-command `runConfig` alongside the deprecated `env`/`cwd`.
|
|
266
|
+
*
|
|
267
|
+
* The server answers 400 for a request carrying both spellings rather than
|
|
268
|
+
* letting one silently win, so refuse it here where the message can name the
|
|
269
|
+
* replacement.
|
|
270
|
+
*/
|
|
271
|
+
export function assertRunConfigNotCombined(options) {
|
|
272
|
+
if (options.runConfig !== undefined &&
|
|
273
|
+
(options.env !== undefined || options.cwd !== undefined)) {
|
|
274
|
+
throw new LangSmithValidationError("Cannot combine runConfig with the deprecated env/cwd options. " +
|
|
275
|
+
"Use runConfig.env_vars and runConfig.work_dir instead.");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Whether to half-close stdin at spawn.
|
|
280
|
+
*
|
|
281
|
+
* Defaults on for a non-PTY command: a command that reads stdin otherwise
|
|
282
|
+
* blocks on a pipe nobody writes to until the timeout kills it. A PTY has no
|
|
283
|
+
* separate write end to close, so the server ignores the flag there.
|
|
284
|
+
*/
|
|
285
|
+
export function resolveCloseInput(closeInput, pty) {
|
|
286
|
+
if (pty)
|
|
287
|
+
return false;
|
|
288
|
+
return closeInput ?? true;
|
|
289
|
+
}
|
|
290
|
+
// =============================================================================
|
|
291
|
+
// Ranged downloads
|
|
292
|
+
// =============================================================================
|
|
293
|
+
/** Render a byte range as an RFC 9110 `Range` header value. */
|
|
294
|
+
export function buildRangeHeader(options) {
|
|
295
|
+
const { start, end, suffixBytes } = options;
|
|
296
|
+
if (suffixBytes !== undefined) {
|
|
297
|
+
if (start !== undefined || end !== undefined) {
|
|
298
|
+
throw new LangSmithValidationError("Cannot combine suffixBytes with start/end.");
|
|
299
|
+
}
|
|
300
|
+
if (suffixBytes <= 0) {
|
|
301
|
+
throw new LangSmithValidationError("suffixBytes must be positive.");
|
|
302
|
+
}
|
|
303
|
+
return `bytes=-${suffixBytes}`;
|
|
304
|
+
}
|
|
305
|
+
if (start === undefined) {
|
|
306
|
+
throw new LangSmithValidationError("Provide start (with optional end), or suffixBytes.");
|
|
307
|
+
}
|
|
308
|
+
if (start < 0) {
|
|
309
|
+
throw new LangSmithValidationError("start must not be negative.");
|
|
310
|
+
}
|
|
311
|
+
if (end === undefined) {
|
|
312
|
+
return `bytes=${start}-`;
|
|
313
|
+
}
|
|
314
|
+
if (end < start) {
|
|
315
|
+
throw new LangSmithValidationError("end must not precede start.");
|
|
316
|
+
}
|
|
317
|
+
return `bytes=${start}-${end}`;
|
|
318
|
+
}
|
|
319
|
+
/** Read the first byte offset and total size out of `Content-Range`. */
|
|
320
|
+
function parseContentRange(value) {
|
|
321
|
+
if (!value || !value.startsWith("bytes "))
|
|
322
|
+
return { start: 0 };
|
|
323
|
+
const spec = value.slice("bytes ".length).trim();
|
|
324
|
+
const [rangePart, totalPart] = spec.split("/");
|
|
325
|
+
const first = rangePart.split("-")[0].trim();
|
|
326
|
+
const start = /^\d+$/.test(first) ? Number(first) : 0;
|
|
327
|
+
const total = /^\d+$/.test((totalPart ?? "").trim())
|
|
328
|
+
? Number(totalPart)
|
|
329
|
+
: undefined;
|
|
330
|
+
return { start, total };
|
|
331
|
+
}
|
|
332
|
+
/** Build a FileStat from a HEAD response's headers. */
|
|
333
|
+
export function fileStatFromResponse(response) {
|
|
334
|
+
const length = response.headers.get("content-length");
|
|
335
|
+
return {
|
|
336
|
+
size_bytes: length && /^\d+$/.test(length) ? Number(length) : 0,
|
|
337
|
+
etag: response.headers.get("etag") ?? undefined,
|
|
338
|
+
last_modified: response.headers.get("last-modified") ?? undefined,
|
|
339
|
+
content_type: response.headers.get("content-type") ?? undefined,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/** Build a FileChunk from a ranged download response. */
|
|
343
|
+
export async function fileChunkFromResponse(response) {
|
|
344
|
+
const etag = response.headers.get("etag") ?? undefined;
|
|
345
|
+
const lastModified = response.headers.get("last-modified") ?? undefined;
|
|
346
|
+
if (response.status === 304) {
|
|
347
|
+
return {
|
|
348
|
+
content: new Uint8Array(0),
|
|
349
|
+
etag,
|
|
350
|
+
start: 0,
|
|
351
|
+
partial: false,
|
|
352
|
+
unchanged: true,
|
|
353
|
+
last_modified: lastModified,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const content = new Uint8Array(await response.arrayBuffer());
|
|
357
|
+
const partial = response.status === 206;
|
|
358
|
+
// A stale If-Range answers 200 with the whole file; the caller has to
|
|
359
|
+
// restart rather than append, so report it from byte zero.
|
|
360
|
+
const { start, total } = partial
|
|
361
|
+
? parseContentRange(response.headers.get("content-range"))
|
|
362
|
+
: { start: 0, total: content.length };
|
|
363
|
+
return {
|
|
364
|
+
content,
|
|
365
|
+
etag,
|
|
366
|
+
total_bytes: total,
|
|
367
|
+
start,
|
|
368
|
+
partial,
|
|
369
|
+
unchanged: false,
|
|
370
|
+
last_modified: lastModified,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/** Map a file-operation HTTP error, including the non-JSON 416. */
|
|
374
|
+
export async function handleFileHttpError(response, path, sandboxName) {
|
|
375
|
+
if (response.status === 404) {
|
|
376
|
+
throw new LangSmithResourceNotFoundError(`File '${path}' not found in sandbox '${sandboxName}'`, "file");
|
|
377
|
+
}
|
|
378
|
+
if (response.status === 416) {
|
|
379
|
+
throw new LangSmithSandboxOperationError(`Requested range for '${path}' starts past the end of the file ` +
|
|
380
|
+
`(${response.headers.get("content-range") ?? "unknown size"}).`, "read");
|
|
381
|
+
}
|
|
382
|
+
return handleSandboxHttpError(response);
|
|
383
|
+
}
|