langsmith 0.10.4 → 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 +10 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +10 -0
- 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/package.json +1 -1
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);
|
package/dist/sandbox/sandbox.cjs
CHANGED
|
@@ -140,6 +140,26 @@ class Sandbox {
|
|
|
140
140
|
writable: true,
|
|
141
141
|
value: void 0
|
|
142
142
|
});
|
|
143
|
+
/**
|
|
144
|
+
* User, working directory and environment this sandbox's commands run
|
|
145
|
+
* with. Absent on sandboxes created before the server recorded it.
|
|
146
|
+
*/
|
|
147
|
+
Object.defineProperty(this, "run_config", {
|
|
148
|
+
enumerable: true,
|
|
149
|
+
configurable: true,
|
|
150
|
+
writable: true,
|
|
151
|
+
value: void 0
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* LangSmith access granted to code inside the sandbox, or undefined when it
|
|
155
|
+
* has none.
|
|
156
|
+
*/
|
|
157
|
+
Object.defineProperty(this, "access_delegation", {
|
|
158
|
+
enumerable: true,
|
|
159
|
+
configurable: true,
|
|
160
|
+
writable: true,
|
|
161
|
+
value: void 0
|
|
162
|
+
});
|
|
143
163
|
Object.defineProperty(this, "_client", {
|
|
144
164
|
enumerable: true,
|
|
145
165
|
configurable: true,
|
|
@@ -160,6 +180,8 @@ class Sandbox {
|
|
|
160
180
|
this.vCpus = data.vcpus;
|
|
161
181
|
this.mem_bytes = data.mem_bytes;
|
|
162
182
|
this.fs_capacity_bytes = data.fs_capacity_bytes;
|
|
183
|
+
this.run_config = data.run_config;
|
|
184
|
+
this.access_delegation = data.access_delegation;
|
|
163
185
|
this._client = client;
|
|
164
186
|
}
|
|
165
187
|
/**
|
|
@@ -180,8 +202,10 @@ class Sandbox {
|
|
|
180
202
|
return this.dataplane_url;
|
|
181
203
|
}
|
|
182
204
|
async run(command, options = {}) {
|
|
183
|
-
const { wait = true, onStdout, onStderr, idleTimeout, killOnDisconnect, ttlSeconds, pty, ...restOptions } = options;
|
|
205
|
+
const { wait = true, onStdout, onStderr, idleTimeout, killOnDisconnect, ttlSeconds, pty, closeInput, ...restOptions } = options;
|
|
184
206
|
const hasCallbacks = onStdout !== undefined || onStderr !== undefined;
|
|
207
|
+
(0, helpers_js_1.assertRunConfigNotCombined)(restOptions);
|
|
208
|
+
const closeStdin = (0, helpers_js_1.resolveCloseInput)(closeInput, pty ?? false);
|
|
185
209
|
if (!wait || hasCallbacks) {
|
|
186
210
|
// WebSocket required for streaming / non-blocking
|
|
187
211
|
const handle = await this._runWs(command, {
|
|
@@ -190,6 +214,7 @@ class Sandbox {
|
|
|
190
214
|
killOnDisconnect,
|
|
191
215
|
ttlSeconds,
|
|
192
216
|
pty,
|
|
217
|
+
closeInput: closeStdin,
|
|
193
218
|
onStdout,
|
|
194
219
|
onStderr,
|
|
195
220
|
});
|
|
@@ -208,6 +233,7 @@ class Sandbox {
|
|
|
208
233
|
killOnDisconnect,
|
|
209
234
|
ttlSeconds,
|
|
210
235
|
pty,
|
|
236
|
+
closeInput: closeStdin,
|
|
211
237
|
});
|
|
212
238
|
return await handle.result;
|
|
213
239
|
}
|
|
@@ -225,7 +251,7 @@ class Sandbox {
|
|
|
225
251
|
* @internal
|
|
226
252
|
*/
|
|
227
253
|
async _runWs(command, options = {}) {
|
|
228
|
-
const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
|
|
254
|
+
const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, runConfig, closeInput: closeStdin = false, } = options;
|
|
229
255
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
230
256
|
const clientHeaders = this._client.getDefaultHeaders();
|
|
231
257
|
// A client-supplied command_id makes execute idempotent: the daemon does
|
|
@@ -247,6 +273,8 @@ class Sandbox {
|
|
|
247
273
|
killOnDisconnect,
|
|
248
274
|
ttlSeconds,
|
|
249
275
|
pty,
|
|
276
|
+
runConfig,
|
|
277
|
+
closeStdin,
|
|
250
278
|
openTimeout: (0, ws_execute_js_1.openTimeoutFor)(deadline),
|
|
251
279
|
...(Object.keys(clientHeaders).length > 0
|
|
252
280
|
? { headers: clientHeaders }
|
|
@@ -255,6 +283,8 @@ class Sandbox {
|
|
|
255
283
|
const handle = new command_handle_js_1.CommandHandle(stream, control, this, {
|
|
256
284
|
onStdout,
|
|
257
285
|
onStderr,
|
|
286
|
+
stdinClosed: closeStdin,
|
|
287
|
+
pty: pty ?? false,
|
|
258
288
|
});
|
|
259
289
|
try {
|
|
260
290
|
await handle._ensureStarted();
|
|
@@ -297,7 +327,7 @@ class Sandbox {
|
|
|
297
327
|
* @internal
|
|
298
328
|
*/
|
|
299
329
|
async _runHttp(command, options = {}) {
|
|
300
|
-
const { timeout = 60, env, cwd, shell = "/bin/bash" } = options;
|
|
330
|
+
const { timeout = 60, env, cwd, runConfig, shell = "/bin/bash" } = options;
|
|
301
331
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
302
332
|
const url = `${dataplaneUrl}/execute`;
|
|
303
333
|
const payload = {
|
|
@@ -308,6 +338,9 @@ class Sandbox {
|
|
|
308
338
|
if (env !== undefined) {
|
|
309
339
|
payload.env = env;
|
|
310
340
|
}
|
|
341
|
+
if (runConfig !== undefined) {
|
|
342
|
+
payload.run_config = runConfig;
|
|
343
|
+
}
|
|
311
344
|
if (cwd !== undefined) {
|
|
312
345
|
payload.cwd = cwd;
|
|
313
346
|
}
|
|
@@ -339,7 +372,7 @@ class Sandbox {
|
|
|
339
372
|
* @returns A new CommandHandle.
|
|
340
373
|
*/
|
|
341
374
|
async reconnect(commandId, options = {}) {
|
|
342
|
-
const { stdoutOffset = 0, stderrOffset = 0 } = options;
|
|
375
|
+
const { stdoutOffset = 0, stderrOffset = 0, stdinClosed = false, pty = false, } = options;
|
|
343
376
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
344
377
|
const clientHeaders = this._client.getDefaultHeaders();
|
|
345
378
|
const [stream, control] = await (0, ws_execute_js_1.reconnectWsStream)(dataplaneUrl, this._client.getApiKey(), commandId, {
|
|
@@ -353,6 +386,8 @@ class Sandbox {
|
|
|
353
386
|
commandId,
|
|
354
387
|
stdoutOffset,
|
|
355
388
|
stderrOffset,
|
|
389
|
+
stdinClosed,
|
|
390
|
+
pty,
|
|
356
391
|
});
|
|
357
392
|
}
|
|
358
393
|
/**
|
|
@@ -413,6 +448,127 @@ class Sandbox {
|
|
|
413
448
|
const buffer = await response.arrayBuffer();
|
|
414
449
|
return new Uint8Array(buffer);
|
|
415
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Report a file's size and validators without transferring it.
|
|
453
|
+
*
|
|
454
|
+
* @param path - File path to stat.
|
|
455
|
+
* @param timeout - Request timeout in seconds.
|
|
456
|
+
* @returns The size and the `ETag` to pass to {@link readRange}.
|
|
457
|
+
*/
|
|
458
|
+
async stat(path, timeout = 60) {
|
|
459
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
460
|
+
const url = `${dataplaneUrl}/download?path=${encodeURIComponent(path)}`;
|
|
461
|
+
const response = await this._client._fetch(url, {
|
|
462
|
+
method: "HEAD",
|
|
463
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
464
|
+
});
|
|
465
|
+
if (!response.ok) {
|
|
466
|
+
await (0, helpers_js_1.handleFileHttpError)(response, path, this.name);
|
|
467
|
+
}
|
|
468
|
+
return (0, helpers_js_1.fileStatFromResponse)(response);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Read part of a file, for chunked reads and resumed downloads.
|
|
472
|
+
*
|
|
473
|
+
* @param path - File path to read.
|
|
474
|
+
* @param options - The byte range, plus optional `ifRange`/`ifNoneMatch`
|
|
475
|
+
* validators. A 200 answer to a ranged request means the file changed and
|
|
476
|
+
* the server sent it whole -- restart rather than append.
|
|
477
|
+
* @returns The bytes and where they sit in the file.
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* ```typescript
|
|
481
|
+
* const head = await sandbox.readRange("/big.bin", { start: 0, end: 1023 });
|
|
482
|
+
* const next = await sandbox.readRange("/big.bin", {
|
|
483
|
+
* start: head.end ?? 1024,
|
|
484
|
+
* ifRange: head.etag,
|
|
485
|
+
* });
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
async readRange(path, options) {
|
|
489
|
+
const { ifRange, ifNoneMatch, timeout = 60, headers } = options;
|
|
490
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
491
|
+
const url = `${dataplaneUrl}/download?path=${encodeURIComponent(path)}`;
|
|
492
|
+
const requestHeaders = {
|
|
493
|
+
...(headers ?? {}),
|
|
494
|
+
Range: (0, helpers_js_1.buildRangeHeader)(options),
|
|
495
|
+
};
|
|
496
|
+
if (ifRange)
|
|
497
|
+
requestHeaders["If-Range"] = ifRange;
|
|
498
|
+
if (ifNoneMatch)
|
|
499
|
+
requestHeaders["If-None-Match"] = ifNoneMatch;
|
|
500
|
+
const response = await this._client._fetch(url, {
|
|
501
|
+
method: "GET",
|
|
502
|
+
headers: requestHeaders,
|
|
503
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
504
|
+
});
|
|
505
|
+
if (!response.ok && response.status !== 304) {
|
|
506
|
+
await (0, helpers_js_1.handleFileHttpError)(response, path, this.name);
|
|
507
|
+
}
|
|
508
|
+
return (0, helpers_js_1.fileChunkFromResponse)(response);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Find files and directories matching a pattern.
|
|
512
|
+
*
|
|
513
|
+
* @param pattern - Match against each entry's path relative to `path`.
|
|
514
|
+
* Supports `**` for any number of segments plus `*`, `?` and `[...]`
|
|
515
|
+
* within one segment.
|
|
516
|
+
* @param path - Absolute path of the directory to search under.
|
|
517
|
+
* @param options - Result cap and request options.
|
|
518
|
+
* @returns The matches; check `truncated` before treating them as complete.
|
|
519
|
+
*/
|
|
520
|
+
async glob(pattern, path, options = {}) {
|
|
521
|
+
const body = { pattern, path };
|
|
522
|
+
if (options.limit !== undefined)
|
|
523
|
+
body.limit = options.limit;
|
|
524
|
+
return (await this._fileSearch("glob", body, options));
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* List a directory's immediate entries, without recursing.
|
|
528
|
+
*
|
|
529
|
+
* A convenience over {@link glob} with the pattern `*`.
|
|
530
|
+
*
|
|
531
|
+
* @param path - Absolute path of the directory to list.
|
|
532
|
+
* @param options - Result cap and request options.
|
|
533
|
+
* @returns The directory's files and subdirectories.
|
|
534
|
+
*/
|
|
535
|
+
async ls(path, options = {}) {
|
|
536
|
+
return this.glob("*", path, options);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Search file contents for a literal string.
|
|
540
|
+
*
|
|
541
|
+
* @param pattern - Literal text to search for. Not a regular expression.
|
|
542
|
+
* @param path - Absolute path of the directory to search under.
|
|
543
|
+
* @param options - Optional `glob` file filter, result cap, request options.
|
|
544
|
+
* @returns The matching lines; check `truncated` for completeness.
|
|
545
|
+
*/
|
|
546
|
+
async grep(pattern, path, options = {}) {
|
|
547
|
+
const body = { pattern, path };
|
|
548
|
+
if (options.glob !== undefined)
|
|
549
|
+
body.glob = options.glob;
|
|
550
|
+
if (options.limit !== undefined)
|
|
551
|
+
body.limit = options.limit;
|
|
552
|
+
return (await this._fileSearch("grep", body, options));
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* POST one of the read-only filesystem search endpoints.
|
|
556
|
+
* @internal
|
|
557
|
+
*/
|
|
558
|
+
async _fileSearch(operation, body, options) {
|
|
559
|
+
const { timeout = 60, headers } = options;
|
|
560
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
561
|
+
const response = await this._client._fetch(`${dataplaneUrl}/${operation}`, {
|
|
562
|
+
method: "POST",
|
|
563
|
+
headers: { "Content-Type": "application/json", ...(headers ?? {}) },
|
|
564
|
+
body: JSON.stringify(body),
|
|
565
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
566
|
+
});
|
|
567
|
+
if (!response.ok) {
|
|
568
|
+
await (0, helpers_js_1.handleFileHttpError)(response, String(body.path), this.name);
|
|
569
|
+
}
|
|
570
|
+
return response.json();
|
|
571
|
+
}
|
|
416
572
|
/**
|
|
417
573
|
* Create a link that downloads one file from this sandbox.
|
|
418
574
|
*
|