langsmith 0.10.3 → 0.10.5
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 +19 -4
- package/dist/client.d.ts +2 -1
- package/dist/client.js +19 -4
- package/dist/evaluation/_runner.cjs +47 -5
- package/dist/evaluation/_runner.d.ts +12 -0
- package/dist/evaluation/_runner.js +47 -5
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- 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/helpers.cjs +129 -0
- package/dist/sandbox/helpers.d.ts +29 -0
- package/dist/sandbox/helpers.js +123 -0
- package/dist/sandbox/index.cjs +7 -1
- package/dist/sandbox/index.d.ts +5 -1
- package/dist/sandbox/index.js +2 -0
- 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/ws_execute.cjs +31 -1
- package/dist/sandbox/ws_execute.d.ts +9 -0
- package/dist/sandbox/ws_execute.js +31 -1
- package/dist/utils/error.cjs +17 -22
- package/dist/utils/error.js +17 -22
- package/dist/wrappers/openai_agents.cjs +37 -28
- package/dist/wrappers/openai_agents.d.ts +0 -1
- package/dist/wrappers/openai_agents.js +37 -28
- package/package.json +2 -2
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
|
+
}
|
package/dist/sandbox/index.cjs
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @packageDocumentation
|
|
30
30
|
*/
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectTimeoutError = exports.LangSmithSandboxRetryableConnectionError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = exports.s3Mount = exports.mountConfig = exports.gitMount = exports.gcsMount = exports.contextHubMount = exports.workspaceSecret = exports.proxyConfig = exports.opaqueSecret = exports.gcpAuth = exports.awsAuth = exports.CommandHandle = exports.Sandbox = exports.SandboxClient = void 0;
|
|
32
|
+
exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectTimeoutError = exports.LangSmithSandboxRetryableConnectionError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = exports.s3Mount = exports.mountConfig = exports.gitMount = exports.gcsMount = exports.contextHubMount = exports.SERVICE_TOKEN_HEADER = exports.ServiceLoginUrl = exports.ServiceUrl = exports.validateAccessDelegation = exports.workspaceSecret = exports.proxyConfig = exports.opaqueSecret = exports.gcpAuth = exports.awsAuth = exports.CommandHandle = exports.Sandbox = exports.SandboxClient = void 0;
|
|
33
33
|
// Main classes
|
|
34
34
|
var client_js_1 = require("./client.cjs");
|
|
35
35
|
Object.defineProperty(exports, "SandboxClient", { enumerable: true, get: function () { return client_js_1.SandboxClient; } });
|
|
@@ -43,6 +43,12 @@ Object.defineProperty(exports, "gcpAuth", { enumerable: true, get: function () {
|
|
|
43
43
|
Object.defineProperty(exports, "opaqueSecret", { enumerable: true, get: function () { return proxy_config_js_1.opaqueSecret; } });
|
|
44
44
|
Object.defineProperty(exports, "proxyConfig", { enumerable: true, get: function () { return proxy_config_js_1.proxyConfig; } });
|
|
45
45
|
Object.defineProperty(exports, "workspaceSecret", { enumerable: true, get: function () { return proxy_config_js_1.workspaceSecret; } });
|
|
46
|
+
var access_delegation_js_1 = require("./access_delegation.cjs");
|
|
47
|
+
Object.defineProperty(exports, "validateAccessDelegation", { enumerable: true, get: function () { return access_delegation_js_1.validateAccessDelegation; } });
|
|
48
|
+
var service_url_js_1 = require("./service_url.cjs");
|
|
49
|
+
Object.defineProperty(exports, "ServiceUrl", { enumerable: true, get: function () { return service_url_js_1.ServiceUrl; } });
|
|
50
|
+
Object.defineProperty(exports, "ServiceLoginUrl", { enumerable: true, get: function () { return service_url_js_1.ServiceLoginUrl; } });
|
|
51
|
+
Object.defineProperty(exports, "SERVICE_TOKEN_HEADER", { enumerable: true, get: function () { return service_url_js_1.SERVICE_TOKEN_HEADER; } });
|
|
46
52
|
var mounts_js_1 = require("./mounts.cjs");
|
|
47
53
|
Object.defineProperty(exports, "contextHubMount", { enumerable: true, get: function () { return mounts_js_1.contextHubMount; } });
|
|
48
54
|
Object.defineProperty(exports, "gcsMount", { enumerable: true, get: function () { return mounts_js_1.gcsMount; } });
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export { SandboxClient } from "./client.js";
|
|
|
31
31
|
export { Sandbox } from "./sandbox.js";
|
|
32
32
|
export { CommandHandle } from "./command_handle.js";
|
|
33
33
|
export { awsAuth, gcpAuth, opaqueSecret, proxyConfig, workspaceSecret, } from "./proxy_config.js";
|
|
34
|
+
export { validateAccessDelegation } from "./access_delegation.js";
|
|
35
|
+
export { ServiceUrl, ServiceLoginUrl, SERVICE_TOKEN_HEADER, } from "./service_url.js";
|
|
34
36
|
export { contextHubMount, gcsMount, gitMount, mountConfig, s3Mount, } from "./mounts.js";
|
|
35
|
-
export type {
|
|
37
|
+
export type { AccessDelegation, AccessDelegationMode, } from "./access_delegation.js";
|
|
38
|
+
export type { ServiceAccess, ServiceUrlData } from "./service_url.js";
|
|
39
|
+
export type { ExecutionResult, FileChunk, FileInfo, FileStat, GlobOptions, GlobResult, GrepMatch, GrepOptions, GrepResult, ReadRangeOptions, RunConfig, OutputChunk, WsMessage, WsRunOptions, ResourceStatus, Snapshot, SandboxData, SandboxClientConfig, RunOptions, CreateSandboxOptions, SandboxAccessControl, SandboxAwsAuthRule, SandboxAwsMountAuthConfig, SandboxGcpAuthRule, SandboxGcpMountAuthConfig, SandboxMountAuth, SandboxMountAuthConfig, SandboxMountConfig, SandboxProxyConfig, SandboxProxyRule, SandboxProxySecret, SandboxMount, MountCacheConfig, ContextHubMountConfig, ContextHubMountSpec, GCSMountConfig, GCSMountSpec, GitMountConfig, GitMountRefSpec, GitMountSpec, S3MountConfig, S3MountSpec, CreateSnapshotOptions, CreateDockerfileSnapshotOptions, CaptureSnapshotOptions, DownloadContentDisposition, DownloadURL, GenerateDownloadURLOptions, ListSnapshotsOptions, WaitForSnapshotOptions, StartSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, } from "./types.js";
|
|
36
40
|
export { LangSmithSandboxError, LangSmithSandboxAPIError, LangSmithSandboxAuthenticationError, LangSmithSandboxConnectionError, LangSmithSandboxRetryableConnectionError, LangSmithSandboxConnectTimeoutError, LangSmithSandboxServerReloadError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithResourceInUseError, LangSmithResourceAlreadyExistsError, LangSmithResourceNameConflictError, LangSmithValidationError, LangSmithQuotaExceededError, LangSmithResourceCreationError, LangSmithSandboxCreationError, LangSmithSandboxNotReadyError, LangSmithSandboxOperationError, LangSmithCommandTimeoutError, LangSmithDataplaneNotConfiguredError, } from "./errors.js";
|
package/dist/sandbox/index.js
CHANGED
|
@@ -32,6 +32,8 @@ export { SandboxClient } from "./client.js";
|
|
|
32
32
|
export { Sandbox } from "./sandbox.js";
|
|
33
33
|
export { CommandHandle } from "./command_handle.js";
|
|
34
34
|
export { awsAuth, gcpAuth, opaqueSecret, proxyConfig, workspaceSecret, } from "./proxy_config.js";
|
|
35
|
+
export { validateAccessDelegation } from "./access_delegation.js";
|
|
36
|
+
export { ServiceUrl, ServiceLoginUrl, SERVICE_TOKEN_HEADER, } from "./service_url.js";
|
|
35
37
|
export { contextHubMount, gcsMount, gitMount, mountConfig, s3Mount, } from "./mounts.js";
|
|
36
38
|
// Errors
|
|
37
39
|
export {
|
package/dist/sandbox/mounts.cjs
CHANGED
|
@@ -6,6 +6,7 @@ exports.gcsMount = gcsMount;
|
|
|
6
6
|
exports.contextHubMount = contextHubMount;
|
|
7
7
|
exports.mountConfig = mountConfig;
|
|
8
8
|
exports.validateMountConfigProxyConfig = validateMountConfigProxyConfig;
|
|
9
|
+
const proxy_config_js_1 = require("./proxy_config.cjs");
|
|
9
10
|
function requireNonEmptyString(value, field) {
|
|
10
11
|
if (typeof value !== "string" || value.trim() === "") {
|
|
11
12
|
throw new Error(`${field} must be a non-empty string`);
|
|
@@ -217,6 +218,11 @@ function normalizeMountAuth(auth) {
|
|
|
217
218
|
if (aws === undefined) {
|
|
218
219
|
throw new Error("aws mount auth must include an aws block");
|
|
219
220
|
}
|
|
221
|
+
const roleArn = (0, proxy_config_js_1.getAwsRoleArn)(aws);
|
|
222
|
+
if (roleArn !== undefined) {
|
|
223
|
+
byProvider.aws = { role_arn: roleArn };
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
220
226
|
byProvider.aws = {
|
|
221
227
|
access_key_id: copyMountSecret(aws.access_key_id, "accessKeyId"),
|
|
222
228
|
secret_access_key: copyMountSecret(aws.secret_access_key, "secretAccessKey"),
|
|
@@ -234,11 +240,21 @@ function normalizeMountAuth(auth) {
|
|
|
234
240
|
}
|
|
235
241
|
return byProvider;
|
|
236
242
|
}
|
|
237
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Build mount-scoped auth, or use an enabled AWS rule from proxyConfig for S3.
|
|
245
|
+
* Pass the same proxyConfig to sandbox creation; its general IAM permissions
|
|
246
|
+
* remain unchanged. GCS mounts still require explicit GCP auth in this config.
|
|
247
|
+
*/
|
|
248
|
+
function mountConfig({ auth = [], mounts, proxyConfig, }) {
|
|
238
249
|
const normalizedMounts = normalizeMounts(mounts);
|
|
239
250
|
const authByProvider = normalizeMountAuth(auth);
|
|
240
251
|
const mountProviders = new Set(normalizedMounts.map((mount) => mount.type));
|
|
241
|
-
|
|
252
|
+
const proxyProviders = new Set((proxyConfig?.rules ?? [])
|
|
253
|
+
.filter((rule) => rule?.enabled === true)
|
|
254
|
+
.map((rule) => rule.type));
|
|
255
|
+
if (mountProviders.has("s3") &&
|
|
256
|
+
authByProvider.aws === undefined &&
|
|
257
|
+
!proxyProviders.has("aws")) {
|
|
242
258
|
throw new Error("s3 mounts require aws auth in mountConfig");
|
|
243
259
|
}
|
|
244
260
|
if (mountProviders.has("gcs") && authByProvider.gcp === undefined) {
|
|
@@ -250,10 +266,12 @@ function mountConfig({ auth = [], mounts, }) {
|
|
|
250
266
|
if (authByProvider.gcp !== undefined && !mountProviders.has("gcs")) {
|
|
251
267
|
throw new Error("gcp auth requires at least one gcs mount in mountConfig");
|
|
252
268
|
}
|
|
253
|
-
|
|
269
|
+
const config = {
|
|
254
270
|
auth: authByProvider,
|
|
255
271
|
mounts: normalizedMounts,
|
|
256
272
|
};
|
|
273
|
+
validateMountConfigProxyConfig(config, proxyConfig);
|
|
274
|
+
return config;
|
|
257
275
|
}
|
|
258
276
|
function validateMountConfigProxyConfig(mountConfig, proxyConfig) {
|
|
259
277
|
if (proxyConfig === undefined) {
|
package/dist/sandbox/mounts.d.ts
CHANGED
|
@@ -36,8 +36,14 @@ export declare function contextHubMount({ id, mountPath, repo, initialPullOnly,
|
|
|
36
36
|
repo: string;
|
|
37
37
|
initialPullOnly?: boolean;
|
|
38
38
|
}): ContextHubMountSpec;
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Build mount-scoped auth, or use an enabled AWS rule from proxyConfig for S3.
|
|
41
|
+
* Pass the same proxyConfig to sandbox creation; its general IAM permissions
|
|
42
|
+
* remain unchanged. GCS mounts still require explicit GCP auth in this config.
|
|
43
|
+
*/
|
|
44
|
+
export declare function mountConfig({ auth, mounts, proxyConfig, }: {
|
|
40
45
|
auth?: SandboxMountAuth[];
|
|
41
46
|
mounts: SandboxMount[];
|
|
47
|
+
proxyConfig?: SandboxProxyConfig;
|
|
42
48
|
}): SandboxMountConfig;
|
|
43
49
|
export declare function validateMountConfigProxyConfig(mountConfig: SandboxMountConfig, proxyConfig: SandboxProxyConfig | undefined): void;
|
package/dist/sandbox/mounts.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getAwsRoleArn } from "./proxy_config.js";
|
|
1
2
|
function requireNonEmptyString(value, field) {
|
|
2
3
|
if (typeof value !== "string" || value.trim() === "") {
|
|
3
4
|
throw new Error(`${field} must be a non-empty string`);
|
|
@@ -209,6 +210,11 @@ function normalizeMountAuth(auth) {
|
|
|
209
210
|
if (aws === undefined) {
|
|
210
211
|
throw new Error("aws mount auth must include an aws block");
|
|
211
212
|
}
|
|
213
|
+
const roleArn = getAwsRoleArn(aws);
|
|
214
|
+
if (roleArn !== undefined) {
|
|
215
|
+
byProvider.aws = { role_arn: roleArn };
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
212
218
|
byProvider.aws = {
|
|
213
219
|
access_key_id: copyMountSecret(aws.access_key_id, "accessKeyId"),
|
|
214
220
|
secret_access_key: copyMountSecret(aws.secret_access_key, "secretAccessKey"),
|
|
@@ -226,11 +232,21 @@ function normalizeMountAuth(auth) {
|
|
|
226
232
|
}
|
|
227
233
|
return byProvider;
|
|
228
234
|
}
|
|
229
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Build mount-scoped auth, or use an enabled AWS rule from proxyConfig for S3.
|
|
237
|
+
* Pass the same proxyConfig to sandbox creation; its general IAM permissions
|
|
238
|
+
* remain unchanged. GCS mounts still require explicit GCP auth in this config.
|
|
239
|
+
*/
|
|
240
|
+
export function mountConfig({ auth = [], mounts, proxyConfig, }) {
|
|
230
241
|
const normalizedMounts = normalizeMounts(mounts);
|
|
231
242
|
const authByProvider = normalizeMountAuth(auth);
|
|
232
243
|
const mountProviders = new Set(normalizedMounts.map((mount) => mount.type));
|
|
233
|
-
|
|
244
|
+
const proxyProviders = new Set((proxyConfig?.rules ?? [])
|
|
245
|
+
.filter((rule) => rule?.enabled === true)
|
|
246
|
+
.map((rule) => rule.type));
|
|
247
|
+
if (mountProviders.has("s3") &&
|
|
248
|
+
authByProvider.aws === undefined &&
|
|
249
|
+
!proxyProviders.has("aws")) {
|
|
234
250
|
throw new Error("s3 mounts require aws auth in mountConfig");
|
|
235
251
|
}
|
|
236
252
|
if (mountProviders.has("gcs") && authByProvider.gcp === undefined) {
|
|
@@ -242,10 +258,12 @@ export function mountConfig({ auth = [], mounts, }) {
|
|
|
242
258
|
if (authByProvider.gcp !== undefined && !mountProviders.has("gcs")) {
|
|
243
259
|
throw new Error("gcp auth requires at least one gcs mount in mountConfig");
|
|
244
260
|
}
|
|
245
|
-
|
|
261
|
+
const config = {
|
|
246
262
|
auth: authByProvider,
|
|
247
263
|
mounts: normalizedMounts,
|
|
248
264
|
};
|
|
265
|
+
validateMountConfigProxyConfig(config, proxyConfig);
|
|
266
|
+
return config;
|
|
249
267
|
}
|
|
250
268
|
export function validateMountConfigProxyConfig(mountConfig, proxyConfig) {
|
|
251
269
|
if (proxyConfig === undefined) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAwsRoleArn = getAwsRoleArn;
|
|
3
4
|
exports.workspaceSecret = workspaceSecret;
|
|
4
5
|
exports.opaqueSecret = opaqueSecret;
|
|
5
6
|
exports.proxyConfig = proxyConfig;
|
|
@@ -49,6 +50,10 @@ function requireProxyRules(rules) {
|
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
function validateProxyProviderRule(rule) {
|
|
53
|
+
if (rule.type === "aws") {
|
|
54
|
+
getAwsRoleArn(rule.aws);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
52
57
|
if (rule.type !== "gcp") {
|
|
53
58
|
return;
|
|
54
59
|
}
|
|
@@ -58,6 +63,21 @@ function validateProxyProviderRule(rule) {
|
|
|
58
63
|
}
|
|
59
64
|
requireNonEmptyStringArray(gcp.scopes, "scopes");
|
|
60
65
|
}
|
|
66
|
+
/** Validate role-only descriptors without tightening legacy static inputs. */
|
|
67
|
+
function getAwsRoleArn(aws) {
|
|
68
|
+
if (aws === null || typeof aws !== "object" || Array.isArray(aws)) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const config = aws;
|
|
72
|
+
if (!("role_arn" in config) || config.role_arn === "") {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const roleArn = requireNonEmptyString(config.role_arn, "roleArn");
|
|
76
|
+
if (Object.keys(config).some((key) => key !== "role_arn")) {
|
|
77
|
+
throw new Error("AWS role auth must contain only role_arn");
|
|
78
|
+
}
|
|
79
|
+
return roleArn;
|
|
80
|
+
}
|
|
61
81
|
/** Reference a LangSmith workspace secret in a sandbox proxy configuration. */
|
|
62
82
|
function workspaceSecret(name) {
|
|
63
83
|
const normalized = requireNonEmptyString(name, "name");
|
|
@@ -82,28 +102,39 @@ function opaqueSecret(value) {
|
|
|
82
102
|
};
|
|
83
103
|
}
|
|
84
104
|
/** Build a sandbox proxy config from one or more proxy rules. */
|
|
85
|
-
function proxyConfig({ rules,
|
|
105
|
+
function proxyConfig({ rules, accessControl, } = {}) {
|
|
86
106
|
const config = {
|
|
87
107
|
rules: requireProxyRules(rules),
|
|
88
108
|
};
|
|
89
|
-
if (noProxy !== undefined) {
|
|
90
|
-
config.no_proxy = requireNonEmptyStringArray(noProxy, "noProxy");
|
|
91
|
-
}
|
|
92
109
|
if (accessControl !== undefined) {
|
|
93
110
|
config.access_control = { ...accessControl };
|
|
94
111
|
}
|
|
95
112
|
return config;
|
|
96
113
|
}
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
function awsAuth({ accessKeyId, secretAccessKey, roleArn, name = "aws", enabled = true, envVars, }) {
|
|
115
|
+
const candidate = {};
|
|
116
|
+
if (roleArn !== undefined)
|
|
117
|
+
candidate.role_arn = roleArn;
|
|
118
|
+
if (accessKeyId !== undefined)
|
|
119
|
+
candidate.access_key_id = accessKeyId;
|
|
120
|
+
if (secretAccessKey !== undefined)
|
|
121
|
+
candidate.secret_access_key = secretAccessKey;
|
|
122
|
+
const normalizedRole = getAwsRoleArn(candidate);
|
|
123
|
+
let aws;
|
|
124
|
+
if (normalizedRole !== undefined) {
|
|
125
|
+
aws = { role_arn: normalizedRole };
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
if (accessKeyId === undefined || secretAccessKey === undefined) {
|
|
129
|
+
throw new Error("AWS auth requires roleArn or both static credentials");
|
|
130
|
+
}
|
|
131
|
+
aws = { access_key_id: accessKeyId, secret_access_key: secretAccessKey };
|
|
132
|
+
}
|
|
99
133
|
const rule = {
|
|
100
134
|
name: requireNonEmptyString(name, "name"),
|
|
101
135
|
type: "aws",
|
|
102
136
|
enabled,
|
|
103
|
-
aws
|
|
104
|
-
access_key_id: accessKeyId,
|
|
105
|
-
secret_access_key: secretAccessKey,
|
|
106
|
-
},
|
|
137
|
+
aws,
|
|
107
138
|
};
|
|
108
139
|
if (envVars !== undefined) {
|
|
109
140
|
rule.env_vars = requireEnvVars(envVars);
|
|
@@ -1,22 +1,48 @@
|
|
|
1
1
|
import type { SandboxAccessControl, SandboxAwsAuthRule, SandboxGcpAuthRule, SandboxProxyConfig, SandboxProxyRule, SandboxProxySecret } from "./types.js";
|
|
2
|
+
/** Validate role-only descriptors without tightening legacy static inputs. */
|
|
3
|
+
export declare function getAwsRoleArn(aws: unknown): string | undefined;
|
|
2
4
|
/** Reference a LangSmith workspace secret in a sandbox proxy configuration. */
|
|
3
5
|
export declare function workspaceSecret(name: string): SandboxProxySecret;
|
|
4
6
|
/** Provide a write-only secret value for a sandbox proxy configuration. */
|
|
5
7
|
export declare function opaqueSecret(value: string): SandboxProxySecret;
|
|
6
8
|
/** Build a sandbox proxy config from one or more proxy rules. */
|
|
7
|
-
export declare function proxyConfig({ rules,
|
|
9
|
+
export declare function proxyConfig({ rules, accessControl, }?: {
|
|
8
10
|
rules?: SandboxProxyRule[];
|
|
11
|
+
/** @deprecated Ignored. The sandbox runtime has no proxy bypass list. */
|
|
9
12
|
noProxy?: string[];
|
|
10
13
|
accessControl?: SandboxAccessControl;
|
|
11
14
|
}): SandboxProxyConfig;
|
|
12
|
-
|
|
13
|
-
export declare function awsAuth({ accessKeyId, secretAccessKey, name, enabled, envVars, }: {
|
|
14
|
-
accessKeyId: SandboxProxySecret;
|
|
15
|
-
secretAccessKey: SandboxProxySecret;
|
|
15
|
+
interface AwsAuthCommonOptions {
|
|
16
16
|
name?: string;
|
|
17
17
|
enabled?: boolean;
|
|
18
18
|
envVars?: Record<string, string>;
|
|
19
|
-
}
|
|
19
|
+
}
|
|
20
|
+
type AwsStaticAuthConfig = Extract<SandboxAwsAuthRule["aws"], {
|
|
21
|
+
access_key_id: SandboxProxySecret;
|
|
22
|
+
}>;
|
|
23
|
+
type AwsRoleAuthConfig = Extract<SandboxAwsAuthRule["aws"], {
|
|
24
|
+
role_arn: string;
|
|
25
|
+
}>;
|
|
26
|
+
type AwsStaticAuthOptions = AwsAuthCommonOptions & {
|
|
27
|
+
accessKeyId: SandboxProxySecret;
|
|
28
|
+
secretAccessKey: SandboxProxySecret;
|
|
29
|
+
roleArn?: "";
|
|
30
|
+
};
|
|
31
|
+
type AwsRoleAuthOptions = AwsAuthCommonOptions & {
|
|
32
|
+
roleArn: string;
|
|
33
|
+
accessKeyId?: never;
|
|
34
|
+
secretAccessKey?: never;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Sign supported AWS HTTPS requests using static keys or an IAM role.
|
|
38
|
+
* Role auth requires backend support and is configured at sandbox creation.
|
|
39
|
+
* LangSmith supplies the workspace External ID and renews credentials.
|
|
40
|
+
* A role in proxyConfig uses its effective IAM permissions; the same helper
|
|
41
|
+
* in mountConfig.auth uses the backend's mount-scoped S3 permissions.
|
|
42
|
+
*/
|
|
43
|
+
export declare function awsAuth(options: AwsStaticAuthOptions): SandboxAwsAuthRule<AwsStaticAuthConfig>;
|
|
44
|
+
export declare function awsAuth(options: AwsRoleAuthOptions): SandboxAwsAuthRule<AwsRoleAuthConfig>;
|
|
45
|
+
export declare function awsAuth(options: AwsStaticAuthOptions | AwsRoleAuthOptions): SandboxAwsAuthRule;
|
|
20
46
|
/** Build a sandbox proxy rule that injects GCP OAuth bearer auth. */
|
|
21
47
|
export declare function gcpAuth({ serviceAccountJson, scopes, name, enabled, envVars, }: {
|
|
22
48
|
serviceAccountJson: SandboxProxySecret;
|
|
@@ -25,3 +51,4 @@ export declare function gcpAuth({ serviceAccountJson, scopes, name, enabled, env
|
|
|
25
51
|
enabled?: boolean;
|
|
26
52
|
envVars?: Record<string, string>;
|
|
27
53
|
}): SandboxGcpAuthRule;
|
|
54
|
+
export {};
|
|
@@ -42,6 +42,10 @@ function requireProxyRules(rules) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
function validateProxyProviderRule(rule) {
|
|
45
|
+
if (rule.type === "aws") {
|
|
46
|
+
getAwsRoleArn(rule.aws);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
45
49
|
if (rule.type !== "gcp") {
|
|
46
50
|
return;
|
|
47
51
|
}
|
|
@@ -51,6 +55,21 @@ function validateProxyProviderRule(rule) {
|
|
|
51
55
|
}
|
|
52
56
|
requireNonEmptyStringArray(gcp.scopes, "scopes");
|
|
53
57
|
}
|
|
58
|
+
/** Validate role-only descriptors without tightening legacy static inputs. */
|
|
59
|
+
export function getAwsRoleArn(aws) {
|
|
60
|
+
if (aws === null || typeof aws !== "object" || Array.isArray(aws)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const config = aws;
|
|
64
|
+
if (!("role_arn" in config) || config.role_arn === "") {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const roleArn = requireNonEmptyString(config.role_arn, "roleArn");
|
|
68
|
+
if (Object.keys(config).some((key) => key !== "role_arn")) {
|
|
69
|
+
throw new Error("AWS role auth must contain only role_arn");
|
|
70
|
+
}
|
|
71
|
+
return roleArn;
|
|
72
|
+
}
|
|
54
73
|
/** Reference a LangSmith workspace secret in a sandbox proxy configuration. */
|
|
55
74
|
export function workspaceSecret(name) {
|
|
56
75
|
const normalized = requireNonEmptyString(name, "name");
|
|
@@ -75,28 +94,39 @@ export function opaqueSecret(value) {
|
|
|
75
94
|
};
|
|
76
95
|
}
|
|
77
96
|
/** Build a sandbox proxy config from one or more proxy rules. */
|
|
78
|
-
export function proxyConfig({ rules,
|
|
97
|
+
export function proxyConfig({ rules, accessControl, } = {}) {
|
|
79
98
|
const config = {
|
|
80
99
|
rules: requireProxyRules(rules),
|
|
81
100
|
};
|
|
82
|
-
if (noProxy !== undefined) {
|
|
83
|
-
config.no_proxy = requireNonEmptyStringArray(noProxy, "noProxy");
|
|
84
|
-
}
|
|
85
101
|
if (accessControl !== undefined) {
|
|
86
102
|
config.access_control = { ...accessControl };
|
|
87
103
|
}
|
|
88
104
|
return config;
|
|
89
105
|
}
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
export function awsAuth({ accessKeyId, secretAccessKey, roleArn, name = "aws", enabled = true, envVars, }) {
|
|
107
|
+
const candidate = {};
|
|
108
|
+
if (roleArn !== undefined)
|
|
109
|
+
candidate.role_arn = roleArn;
|
|
110
|
+
if (accessKeyId !== undefined)
|
|
111
|
+
candidate.access_key_id = accessKeyId;
|
|
112
|
+
if (secretAccessKey !== undefined)
|
|
113
|
+
candidate.secret_access_key = secretAccessKey;
|
|
114
|
+
const normalizedRole = getAwsRoleArn(candidate);
|
|
115
|
+
let aws;
|
|
116
|
+
if (normalizedRole !== undefined) {
|
|
117
|
+
aws = { role_arn: normalizedRole };
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
if (accessKeyId === undefined || secretAccessKey === undefined) {
|
|
121
|
+
throw new Error("AWS auth requires roleArn or both static credentials");
|
|
122
|
+
}
|
|
123
|
+
aws = { access_key_id: accessKeyId, secret_access_key: secretAccessKey };
|
|
124
|
+
}
|
|
92
125
|
const rule = {
|
|
93
126
|
name: requireNonEmptyString(name, "name"),
|
|
94
127
|
type: "aws",
|
|
95
128
|
enabled,
|
|
96
|
-
aws
|
|
97
|
-
access_key_id: accessKeyId,
|
|
98
|
-
secret_access_key: secretAccessKey,
|
|
99
|
-
},
|
|
129
|
+
aws,
|
|
100
130
|
};
|
|
101
131
|
if (envVars !== undefined) {
|
|
102
132
|
rule.env_vars = requireEnvVars(envVars);
|