langsmith 0.8.9 → 0.8.11
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 +33 -4
- package/dist/client.d.ts +10 -6
- package/dist/client.js +33 -4
- package/dist/experimental/vercel/_agent_type.cjs +30 -0
- package/dist/experimental/vercel/_agent_type.d.ts +5 -0
- package/dist/experimental/vercel/_agent_type.js +26 -0
- package/dist/experimental/vercel/telemetry.cjs +2 -7
- package/dist/experimental/vercel/telemetry.js +2 -7
- package/dist/experimental/vercel/wrap.cjs +5 -11
- package/dist/experimental/vercel/wrap.js +6 -12
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sandbox/client.cjs +11 -5
- package/dist/sandbox/client.d.ts +4 -4
- package/dist/sandbox/client.js +11 -5
- package/dist/sandbox/index.cjs +2 -1
- package/dist/sandbox/index.d.ts +2 -2
- package/dist/sandbox/index.js +1 -1
- package/dist/sandbox/mounts.cjs +25 -2
- package/dist/sandbox/mounts.d.ts +12 -1
- package/dist/sandbox/mounts.js +24 -2
- package/dist/sandbox/proxy_config.cjs +28 -4
- package/dist/sandbox/proxy_config.d.ts +4 -2
- package/dist/sandbox/proxy_config.js +28 -4
- package/dist/sandbox/types.d.ts +36 -1
- package/dist/utils/_git.cjs +28 -1
- package/dist/utils/_git.js +28 -1
- package/dist/utils/ls_agent_type.cjs +31 -0
- package/dist/utils/ls_agent_type.d.ts +9 -0
- package/dist/utils/ls_agent_type.js +25 -0
- package/dist/utils/profiles.cjs +125 -1
- package/dist/utils/profiles.d.ts +6 -0
- package/dist/utils/profiles.js +124 -1
- package/dist/wrappers/anthropic.cjs +39 -6
- package/dist/wrappers/anthropic.js +39 -6
- package/dist/wrappers/openai.cjs +4 -0
- package/dist/wrappers/openai.js +4 -0
- package/dist/wrappers/openai_agents.cjs +15 -12
- package/dist/wrappers/openai_agents.js +15 -12
- package/package.json +12 -2
package/dist/sandbox/mounts.cjs
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.s3Mount = s3Mount;
|
|
4
4
|
exports.gitMount = gitMount;
|
|
5
5
|
exports.gcsMount = gcsMount;
|
|
6
|
+
exports.contextHubMount = contextHubMount;
|
|
6
7
|
exports.mountConfig = mountConfig;
|
|
7
8
|
exports.validateMountConfigProxyConfig = validateMountConfigProxyConfig;
|
|
8
9
|
function requireNonEmptyString(value, field) {
|
|
@@ -132,6 +133,25 @@ function gcsMount({ id, mountPath, bucket, prefix, readOnly, cache, }) {
|
|
|
132
133
|
}
|
|
133
134
|
return mount;
|
|
134
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Build a read-only Context Hub mount. The sync is one-way: files written
|
|
138
|
+
* under the mount path inside the sandbox are never pushed back to the repo,
|
|
139
|
+
* and the next sync overwrites them.
|
|
140
|
+
*/
|
|
141
|
+
function contextHubMount({ id, mountPath, repo, initialPullOnly, }) {
|
|
142
|
+
const mount = {
|
|
143
|
+
id: requireNonEmptyString(id, "id"),
|
|
144
|
+
type: "contexthub",
|
|
145
|
+
mount_path: requireNonEmptyString(mountPath, "mountPath"),
|
|
146
|
+
contexthub: {
|
|
147
|
+
repo: requireNonEmptyString(repo, "repo"),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
if (initialPullOnly !== undefined) {
|
|
151
|
+
mount.contexthub.initial_pull_only = initialPullOnly;
|
|
152
|
+
}
|
|
153
|
+
return mount;
|
|
154
|
+
}
|
|
135
155
|
function normalizeMounts(mounts) {
|
|
136
156
|
if (!Array.isArray(mounts) || mounts.length === 0) {
|
|
137
157
|
throw new Error("mounts must be a non-empty array of mount objects");
|
|
@@ -140,8 +160,11 @@ function normalizeMounts(mounts) {
|
|
|
140
160
|
if (mount === null || typeof mount !== "object" || Array.isArray(mount)) {
|
|
141
161
|
throw new Error("mounts must be a non-empty array of mount objects");
|
|
142
162
|
}
|
|
143
|
-
if (mount.type !== "s3" &&
|
|
144
|
-
|
|
163
|
+
if (mount.type !== "s3" &&
|
|
164
|
+
mount.type !== "gcs" &&
|
|
165
|
+
mount.type !== "git" &&
|
|
166
|
+
mount.type !== "contexthub") {
|
|
167
|
+
throw new Error("mountConfig only supports s3, gcs, git, and contexthub mounts");
|
|
145
168
|
}
|
|
146
169
|
rejectProviderCredentialsInMount(mount);
|
|
147
170
|
return mount;
|
package/dist/sandbox/mounts.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GCSMountSpec, GitMountRefSpec, GitMountSpec, MountCacheConfig, S3MountSpec, SandboxMount, SandboxMountAuth, SandboxMountConfig, SandboxProxyConfig } from "./types.js";
|
|
1
|
+
import type { ContextHubMountSpec, GCSMountSpec, GitMountRefSpec, GitMountSpec, MountCacheConfig, S3MountSpec, SandboxMount, SandboxMountAuth, SandboxMountConfig, SandboxProxyConfig } from "./types.js";
|
|
2
2
|
export declare function s3Mount({ id, mountPath, bucket, region, prefix, endpointUrl, pathStyle, readOnly, cache, }: {
|
|
3
3
|
id: string;
|
|
4
4
|
mountPath: string;
|
|
@@ -25,6 +25,17 @@ export declare function gcsMount({ id, mountPath, bucket, prefix, readOnly, cach
|
|
|
25
25
|
readOnly?: boolean;
|
|
26
26
|
cache?: MountCacheConfig;
|
|
27
27
|
}): GCSMountSpec;
|
|
28
|
+
/**
|
|
29
|
+
* Build a read-only Context Hub mount. The sync is one-way: files written
|
|
30
|
+
* under the mount path inside the sandbox are never pushed back to the repo,
|
|
31
|
+
* and the next sync overwrites them.
|
|
32
|
+
*/
|
|
33
|
+
export declare function contextHubMount({ id, mountPath, repo, initialPullOnly, }: {
|
|
34
|
+
id: string;
|
|
35
|
+
mountPath: string;
|
|
36
|
+
repo: string;
|
|
37
|
+
initialPullOnly?: boolean;
|
|
38
|
+
}): ContextHubMountSpec;
|
|
28
39
|
export declare function mountConfig({ auth, mounts, }: {
|
|
29
40
|
auth?: SandboxMountAuth[];
|
|
30
41
|
mounts: SandboxMount[];
|
package/dist/sandbox/mounts.js
CHANGED
|
@@ -125,6 +125,25 @@ export function gcsMount({ id, mountPath, bucket, prefix, readOnly, cache, }) {
|
|
|
125
125
|
}
|
|
126
126
|
return mount;
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Build a read-only Context Hub mount. The sync is one-way: files written
|
|
130
|
+
* under the mount path inside the sandbox are never pushed back to the repo,
|
|
131
|
+
* and the next sync overwrites them.
|
|
132
|
+
*/
|
|
133
|
+
export function contextHubMount({ id, mountPath, repo, initialPullOnly, }) {
|
|
134
|
+
const mount = {
|
|
135
|
+
id: requireNonEmptyString(id, "id"),
|
|
136
|
+
type: "contexthub",
|
|
137
|
+
mount_path: requireNonEmptyString(mountPath, "mountPath"),
|
|
138
|
+
contexthub: {
|
|
139
|
+
repo: requireNonEmptyString(repo, "repo"),
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
if (initialPullOnly !== undefined) {
|
|
143
|
+
mount.contexthub.initial_pull_only = initialPullOnly;
|
|
144
|
+
}
|
|
145
|
+
return mount;
|
|
146
|
+
}
|
|
128
147
|
function normalizeMounts(mounts) {
|
|
129
148
|
if (!Array.isArray(mounts) || mounts.length === 0) {
|
|
130
149
|
throw new Error("mounts must be a non-empty array of mount objects");
|
|
@@ -133,8 +152,11 @@ function normalizeMounts(mounts) {
|
|
|
133
152
|
if (mount === null || typeof mount !== "object" || Array.isArray(mount)) {
|
|
134
153
|
throw new Error("mounts must be a non-empty array of mount objects");
|
|
135
154
|
}
|
|
136
|
-
if (mount.type !== "s3" &&
|
|
137
|
-
|
|
155
|
+
if (mount.type !== "s3" &&
|
|
156
|
+
mount.type !== "gcs" &&
|
|
157
|
+
mount.type !== "git" &&
|
|
158
|
+
mount.type !== "contexthub") {
|
|
159
|
+
throw new Error("mountConfig only supports s3, gcs, git, and contexthub mounts");
|
|
138
160
|
}
|
|
139
161
|
rejectProviderCredentialsInMount(mount);
|
|
140
162
|
return mount;
|
|
@@ -17,6 +17,22 @@ function requireNonEmptyStringArray(values, field) {
|
|
|
17
17
|
}
|
|
18
18
|
return values.map((value) => requireNonEmptyString(value, field));
|
|
19
19
|
}
|
|
20
|
+
function requireEnvVars(envVars) {
|
|
21
|
+
if (envVars === null ||
|
|
22
|
+
typeof envVars !== "object" ||
|
|
23
|
+
Array.isArray(envVars)) {
|
|
24
|
+
throw new Error("envVars must be a non-empty object of names to values");
|
|
25
|
+
}
|
|
26
|
+
const entries = Object.entries(envVars);
|
|
27
|
+
if (entries.length === 0) {
|
|
28
|
+
throw new Error("envVars must be a non-empty object of names to values");
|
|
29
|
+
}
|
|
30
|
+
return Object.fromEntries(entries.map(([name, value]) => {
|
|
31
|
+
// Validated on the trimmed form, stored verbatim: whitespace can be significant.
|
|
32
|
+
requireNonEmptyString(value, `envVars[${name}]`);
|
|
33
|
+
return [requireNonEmptyString(name, "envVars name"), value];
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
20
36
|
function requireProxyRules(rules) {
|
|
21
37
|
if (rules === undefined) {
|
|
22
38
|
return [];
|
|
@@ -79,8 +95,8 @@ function proxyConfig({ rules, noProxy, accessControl, } = {}) {
|
|
|
79
95
|
return config;
|
|
80
96
|
}
|
|
81
97
|
/** Build a sandbox proxy rule that signs AWS HTTPS requests with SigV4. */
|
|
82
|
-
function awsAuth({ accessKeyId, secretAccessKey, name = "aws", enabled = true, }) {
|
|
83
|
-
|
|
98
|
+
function awsAuth({ accessKeyId, secretAccessKey, name = "aws", enabled = true, envVars, }) {
|
|
99
|
+
const rule = {
|
|
84
100
|
name: requireNonEmptyString(name, "name"),
|
|
85
101
|
type: "aws",
|
|
86
102
|
enabled,
|
|
@@ -89,19 +105,27 @@ function awsAuth({ accessKeyId, secretAccessKey, name = "aws", enabled = true, }
|
|
|
89
105
|
secret_access_key: secretAccessKey,
|
|
90
106
|
},
|
|
91
107
|
};
|
|
108
|
+
if (envVars !== undefined) {
|
|
109
|
+
rule.env_vars = requireEnvVars(envVars);
|
|
110
|
+
}
|
|
111
|
+
return rule;
|
|
92
112
|
}
|
|
93
113
|
/** Build a sandbox proxy rule that injects GCP OAuth bearer auth. */
|
|
94
|
-
function gcpAuth({ serviceAccountJson, scopes, name = "gcp", enabled = true, }) {
|
|
114
|
+
function gcpAuth({ serviceAccountJson, scopes, name = "gcp", enabled = true, envVars, }) {
|
|
95
115
|
const gcp = {
|
|
96
116
|
service_account_json: serviceAccountJson,
|
|
97
117
|
};
|
|
98
118
|
if (scopes !== undefined) {
|
|
99
119
|
gcp.scopes = requireNonEmptyStringArray(scopes, "scopes");
|
|
100
120
|
}
|
|
101
|
-
|
|
121
|
+
const rule = {
|
|
102
122
|
name: requireNonEmptyString(name, "name"),
|
|
103
123
|
type: "gcp",
|
|
104
124
|
enabled,
|
|
105
125
|
gcp,
|
|
106
126
|
};
|
|
127
|
+
if (envVars !== undefined) {
|
|
128
|
+
rule.env_vars = requireEnvVars(envVars);
|
|
129
|
+
}
|
|
130
|
+
return rule;
|
|
107
131
|
}
|
|
@@ -10,16 +10,18 @@ export declare function proxyConfig({ rules, noProxy, accessControl, }?: {
|
|
|
10
10
|
accessControl?: SandboxAccessControl;
|
|
11
11
|
}): SandboxProxyConfig;
|
|
12
12
|
/** Build a sandbox proxy rule that signs AWS HTTPS requests with SigV4. */
|
|
13
|
-
export declare function awsAuth({ accessKeyId, secretAccessKey, name, enabled, }: {
|
|
13
|
+
export declare function awsAuth({ accessKeyId, secretAccessKey, name, enabled, envVars, }: {
|
|
14
14
|
accessKeyId: SandboxProxySecret;
|
|
15
15
|
secretAccessKey: SandboxProxySecret;
|
|
16
16
|
name?: string;
|
|
17
17
|
enabled?: boolean;
|
|
18
|
+
envVars?: Record<string, string>;
|
|
18
19
|
}): SandboxAwsAuthRule;
|
|
19
20
|
/** Build a sandbox proxy rule that injects GCP OAuth bearer auth. */
|
|
20
|
-
export declare function gcpAuth({ serviceAccountJson, scopes, name, enabled, }: {
|
|
21
|
+
export declare function gcpAuth({ serviceAccountJson, scopes, name, enabled, envVars, }: {
|
|
21
22
|
serviceAccountJson: SandboxProxySecret;
|
|
22
23
|
scopes?: string[];
|
|
23
24
|
name?: string;
|
|
24
25
|
enabled?: boolean;
|
|
26
|
+
envVars?: Record<string, string>;
|
|
25
27
|
}): SandboxGcpAuthRule;
|
|
@@ -10,6 +10,22 @@ function requireNonEmptyStringArray(values, field) {
|
|
|
10
10
|
}
|
|
11
11
|
return values.map((value) => requireNonEmptyString(value, field));
|
|
12
12
|
}
|
|
13
|
+
function requireEnvVars(envVars) {
|
|
14
|
+
if (envVars === null ||
|
|
15
|
+
typeof envVars !== "object" ||
|
|
16
|
+
Array.isArray(envVars)) {
|
|
17
|
+
throw new Error("envVars must be a non-empty object of names to values");
|
|
18
|
+
}
|
|
19
|
+
const entries = Object.entries(envVars);
|
|
20
|
+
if (entries.length === 0) {
|
|
21
|
+
throw new Error("envVars must be a non-empty object of names to values");
|
|
22
|
+
}
|
|
23
|
+
return Object.fromEntries(entries.map(([name, value]) => {
|
|
24
|
+
// Validated on the trimmed form, stored verbatim: whitespace can be significant.
|
|
25
|
+
requireNonEmptyString(value, `envVars[${name}]`);
|
|
26
|
+
return [requireNonEmptyString(name, "envVars name"), value];
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
13
29
|
function requireProxyRules(rules) {
|
|
14
30
|
if (rules === undefined) {
|
|
15
31
|
return [];
|
|
@@ -72,8 +88,8 @@ export function proxyConfig({ rules, noProxy, accessControl, } = {}) {
|
|
|
72
88
|
return config;
|
|
73
89
|
}
|
|
74
90
|
/** Build a sandbox proxy rule that signs AWS HTTPS requests with SigV4. */
|
|
75
|
-
export function awsAuth({ accessKeyId, secretAccessKey, name = "aws", enabled = true, }) {
|
|
76
|
-
|
|
91
|
+
export function awsAuth({ accessKeyId, secretAccessKey, name = "aws", enabled = true, envVars, }) {
|
|
92
|
+
const rule = {
|
|
77
93
|
name: requireNonEmptyString(name, "name"),
|
|
78
94
|
type: "aws",
|
|
79
95
|
enabled,
|
|
@@ -82,19 +98,27 @@ export function awsAuth({ accessKeyId, secretAccessKey, name = "aws", enabled =
|
|
|
82
98
|
secret_access_key: secretAccessKey,
|
|
83
99
|
},
|
|
84
100
|
};
|
|
101
|
+
if (envVars !== undefined) {
|
|
102
|
+
rule.env_vars = requireEnvVars(envVars);
|
|
103
|
+
}
|
|
104
|
+
return rule;
|
|
85
105
|
}
|
|
86
106
|
/** Build a sandbox proxy rule that injects GCP OAuth bearer auth. */
|
|
87
|
-
export function gcpAuth({ serviceAccountJson, scopes, name = "gcp", enabled = true, }) {
|
|
107
|
+
export function gcpAuth({ serviceAccountJson, scopes, name = "gcp", enabled = true, envVars, }) {
|
|
88
108
|
const gcp = {
|
|
89
109
|
service_account_json: serviceAccountJson,
|
|
90
110
|
};
|
|
91
111
|
if (scopes !== undefined) {
|
|
92
112
|
gcp.scopes = requireNonEmptyStringArray(scopes, "scopes");
|
|
93
113
|
}
|
|
94
|
-
|
|
114
|
+
const rule = {
|
|
95
115
|
name: requireNonEmptyString(name, "name"),
|
|
96
116
|
type: "gcp",
|
|
97
117
|
enabled,
|
|
98
118
|
gcp,
|
|
99
119
|
};
|
|
120
|
+
if (envVars !== undefined) {
|
|
121
|
+
rule.env_vars = requireEnvVars(envVars);
|
|
122
|
+
}
|
|
123
|
+
return rule;
|
|
100
124
|
}
|
package/dist/sandbox/types.d.ts
CHANGED
|
@@ -270,6 +270,13 @@ export interface SandboxAwsAuthRule {
|
|
|
270
270
|
type: "aws";
|
|
271
271
|
/** Whether the rule is enabled. */
|
|
272
272
|
enabled?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* Plaintext environment variables set for every command in the sandbox while
|
|
275
|
+
* the rule is enabled, for tools that refuse to run unless a credential
|
|
276
|
+
* variable is present even though the proxy injects the real credential on
|
|
277
|
+
* the wire.
|
|
278
|
+
*/
|
|
279
|
+
env_vars?: Record<string, string>;
|
|
273
280
|
/** AWS credentials used by the proxy signer. */
|
|
274
281
|
aws: {
|
|
275
282
|
access_key_id: SandboxProxySecret;
|
|
@@ -284,6 +291,13 @@ export interface SandboxGcpAuthRule {
|
|
|
284
291
|
type: "gcp";
|
|
285
292
|
/** Whether the rule is enabled. */
|
|
286
293
|
enabled?: boolean;
|
|
294
|
+
/**
|
|
295
|
+
* Plaintext environment variables set for every command in the sandbox while
|
|
296
|
+
* the rule is enabled, for tools that refuse to run unless a credential
|
|
297
|
+
* variable is present even though the proxy injects the real credential on
|
|
298
|
+
* the wire.
|
|
299
|
+
*/
|
|
300
|
+
env_vars?: Record<string, string>;
|
|
287
301
|
/** GCP service-account credential and OAuth scopes. */
|
|
288
302
|
gcp: {
|
|
289
303
|
service_account_json: SandboxProxySecret;
|
|
@@ -386,8 +400,29 @@ export interface GitMountSpec {
|
|
|
386
400
|
/** Git mount configuration. */
|
|
387
401
|
git: GitMountConfig;
|
|
388
402
|
}
|
|
403
|
+
/** Context Hub configuration for a sandbox mount. */
|
|
404
|
+
export interface ContextHubMountConfig {
|
|
405
|
+
/**
|
|
406
|
+
* Context Hub repository to sync, as `owner/repo` (e.g. `-/my-agent`, where
|
|
407
|
+
* `-` is the current workspace).
|
|
408
|
+
*/
|
|
409
|
+
repo: string;
|
|
410
|
+
/** Sync once at startup instead of polling for updates. */
|
|
411
|
+
initial_pull_only?: boolean;
|
|
412
|
+
}
|
|
413
|
+
/** Read-only Context Hub-backed sandbox mount specification. */
|
|
414
|
+
export interface ContextHubMountSpec {
|
|
415
|
+
/** Stable mount identifier. */
|
|
416
|
+
id: string;
|
|
417
|
+
/** Mount type. */
|
|
418
|
+
type: "contexthub";
|
|
419
|
+
/** Absolute path inside the sandbox where the repo tree appears. */
|
|
420
|
+
mount_path: string;
|
|
421
|
+
/** Context Hub mount configuration. */
|
|
422
|
+
contexthub: ContextHubMountConfig;
|
|
423
|
+
}
|
|
389
424
|
/** Sandbox mount specification. */
|
|
390
|
-
export type SandboxMount = S3MountSpec | GCSMountSpec | GitMountSpec;
|
|
425
|
+
export type SandboxMount = S3MountSpec | GCSMountSpec | GitMountSpec | ContextHubMountSpec;
|
|
391
426
|
/** AWS credentials used by the backend to authenticate S3 mounts. */
|
|
392
427
|
export interface SandboxAwsMountAuthConfig {
|
|
393
428
|
access_key_id: SandboxProxySecret;
|
package/dist/utils/_git.cjs
CHANGED
|
@@ -17,6 +17,33 @@ const execGit = (command, exec) => {
|
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
|
+
function sanitizeRemoteUrl(remoteUrl) {
|
|
21
|
+
if (remoteUrl == null) {
|
|
22
|
+
return remoteUrl;
|
|
23
|
+
}
|
|
24
|
+
const schemeSeparator = remoteUrl.indexOf("://");
|
|
25
|
+
if (schemeSeparator === -1) {
|
|
26
|
+
return remoteUrl;
|
|
27
|
+
}
|
|
28
|
+
const scheme = remoteUrl.slice(0, schemeSeparator).toLowerCase();
|
|
29
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
30
|
+
return remoteUrl;
|
|
31
|
+
}
|
|
32
|
+
const authorityStart = schemeSeparator + "://".length;
|
|
33
|
+
let authorityEnd = remoteUrl.length;
|
|
34
|
+
for (const delimiter of ["/", "?", "#"]) {
|
|
35
|
+
const delimiterIndex = remoteUrl.indexOf(delimiter, authorityStart);
|
|
36
|
+
if (delimiterIndex !== -1) {
|
|
37
|
+
authorityEnd = Math.min(authorityEnd, delimiterIndex);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const authority = remoteUrl.slice(authorityStart, authorityEnd);
|
|
41
|
+
const userinfoEnd = authority.lastIndexOf("@");
|
|
42
|
+
if (userinfoEnd === -1) {
|
|
43
|
+
return remoteUrl;
|
|
44
|
+
}
|
|
45
|
+
return `${remoteUrl.slice(0, authorityStart)}${authority.slice(userinfoEnd + 1)}${remoteUrl.slice(authorityEnd)}`;
|
|
46
|
+
}
|
|
20
47
|
const getGitInfo = async (remote = "origin") => {
|
|
21
48
|
let exec;
|
|
22
49
|
try {
|
|
@@ -42,7 +69,7 @@ const getGitInfo = async (remote = "origin") => {
|
|
|
42
69
|
execGit(["log", "-1", "--format=%ae"], exec),
|
|
43
70
|
]);
|
|
44
71
|
return {
|
|
45
|
-
remoteUrl,
|
|
72
|
+
remoteUrl: sanitizeRemoteUrl(remoteUrl),
|
|
46
73
|
commit,
|
|
47
74
|
commitTime,
|
|
48
75
|
branch,
|
package/dist/utils/_git.js
CHANGED
|
@@ -14,6 +14,33 @@ const execGit = (command, exec) => {
|
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
|
+
function sanitizeRemoteUrl(remoteUrl) {
|
|
18
|
+
if (remoteUrl == null) {
|
|
19
|
+
return remoteUrl;
|
|
20
|
+
}
|
|
21
|
+
const schemeSeparator = remoteUrl.indexOf("://");
|
|
22
|
+
if (schemeSeparator === -1) {
|
|
23
|
+
return remoteUrl;
|
|
24
|
+
}
|
|
25
|
+
const scheme = remoteUrl.slice(0, schemeSeparator).toLowerCase();
|
|
26
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
27
|
+
return remoteUrl;
|
|
28
|
+
}
|
|
29
|
+
const authorityStart = schemeSeparator + "://".length;
|
|
30
|
+
let authorityEnd = remoteUrl.length;
|
|
31
|
+
for (const delimiter of ["/", "?", "#"]) {
|
|
32
|
+
const delimiterIndex = remoteUrl.indexOf(delimiter, authorityStart);
|
|
33
|
+
if (delimiterIndex !== -1) {
|
|
34
|
+
authorityEnd = Math.min(authorityEnd, delimiterIndex);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const authority = remoteUrl.slice(authorityStart, authorityEnd);
|
|
38
|
+
const userinfoEnd = authority.lastIndexOf("@");
|
|
39
|
+
if (userinfoEnd === -1) {
|
|
40
|
+
return remoteUrl;
|
|
41
|
+
}
|
|
42
|
+
return `${remoteUrl.slice(0, authorityStart)}${authority.slice(userinfoEnd + 1)}${remoteUrl.slice(authorityEnd)}`;
|
|
43
|
+
}
|
|
17
44
|
export const getGitInfo = async (remote = "origin") => {
|
|
18
45
|
let exec;
|
|
19
46
|
try {
|
|
@@ -39,7 +66,7 @@ export const getGitInfo = async (remote = "origin") => {
|
|
|
39
66
|
execGit(["log", "-1", "--format=%ae"], exec),
|
|
40
67
|
]);
|
|
41
68
|
return {
|
|
42
|
-
remoteUrl,
|
|
69
|
+
remoteUrl: sanitizeRemoteUrl(remoteUrl),
|
|
43
70
|
commit,
|
|
44
71
|
commitTime,
|
|
45
72
|
branch,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Shared primitives for the `ls_agent_type` metadata tag.
|
|
3
|
+
// Consumed by wrappers/integrations that stamp or inspect `ls_agent_type` on
|
|
4
|
+
// traced runs (wrap_openai, OpenAI Agents SDK, Vercel, future wrappers).
|
|
5
|
+
// Keep this module SDK-agnostic: the caller passes in the parent run tree.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.NON_ROOT_LS_AGENT_TYPES = void 0;
|
|
8
|
+
exports.resolveDefaultLsAgentType = resolveDefaultLsAgentType;
|
|
9
|
+
exports.lsAgentTypeMetadata = lsAgentTypeMetadata;
|
|
10
|
+
exports.defaultLsAgentTypeMetadata = defaultLsAgentTypeMetadata;
|
|
11
|
+
const run_trees_js_1 = require("../run_trees.cjs");
|
|
12
|
+
// Non-root tags: preserved against default/structural stamping.
|
|
13
|
+
exports.NON_ROOT_LS_AGENT_TYPES = new Set([
|
|
14
|
+
"middleware",
|
|
15
|
+
"subagent",
|
|
16
|
+
"compaction",
|
|
17
|
+
]);
|
|
18
|
+
// `"root"` at trace root; `undefined` when nested (rely on propagation).
|
|
19
|
+
function resolveDefaultLsAgentType(parentRunTree) {
|
|
20
|
+
return (0, run_trees_js_1.isRunTree)(parentRunTree) ? undefined : "root";
|
|
21
|
+
}
|
|
22
|
+
// Spread helper: `{ ls_agent_type: tag }` when tag is set, `{}` when undefined.
|
|
23
|
+
function lsAgentTypeMetadata(tag) {
|
|
24
|
+
return tag !== undefined ? { ls_agent_type: tag } : {};
|
|
25
|
+
}
|
|
26
|
+
// Preserves user-supplied ls_agent_type; stamps default only when key is absent.
|
|
27
|
+
function defaultLsAgentTypeMetadata(existingMetadata, parentRunTree) {
|
|
28
|
+
if ("ls_agent_type" in existingMetadata)
|
|
29
|
+
return {};
|
|
30
|
+
return lsAgentTypeMetadata(resolveDefaultLsAgentType(parentRunTree));
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type LsAgentType = "root" | "middleware" | "subagent" | "compaction";
|
|
2
|
+
export declare const NON_ROOT_LS_AGENT_TYPES: ReadonlySet<string>;
|
|
3
|
+
export declare function resolveDefaultLsAgentType(parentRunTree?: unknown): LsAgentType | undefined;
|
|
4
|
+
export declare function lsAgentTypeMetadata(tag: LsAgentType | undefined): {
|
|
5
|
+
ls_agent_type?: LsAgentType;
|
|
6
|
+
};
|
|
7
|
+
export declare function defaultLsAgentTypeMetadata(existingMetadata: Record<string, unknown>, parentRunTree?: unknown): {
|
|
8
|
+
ls_agent_type?: LsAgentType;
|
|
9
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Shared primitives for the `ls_agent_type` metadata tag.
|
|
2
|
+
// Consumed by wrappers/integrations that stamp or inspect `ls_agent_type` on
|
|
3
|
+
// traced runs (wrap_openai, OpenAI Agents SDK, Vercel, future wrappers).
|
|
4
|
+
// Keep this module SDK-agnostic: the caller passes in the parent run tree.
|
|
5
|
+
import { isRunTree } from "../run_trees.js";
|
|
6
|
+
// Non-root tags: preserved against default/structural stamping.
|
|
7
|
+
export const NON_ROOT_LS_AGENT_TYPES = new Set([
|
|
8
|
+
"middleware",
|
|
9
|
+
"subagent",
|
|
10
|
+
"compaction",
|
|
11
|
+
]);
|
|
12
|
+
// `"root"` at trace root; `undefined` when nested (rely on propagation).
|
|
13
|
+
export function resolveDefaultLsAgentType(parentRunTree) {
|
|
14
|
+
return isRunTree(parentRunTree) ? undefined : "root";
|
|
15
|
+
}
|
|
16
|
+
// Spread helper: `{ ls_agent_type: tag }` when tag is set, `{}` when undefined.
|
|
17
|
+
export function lsAgentTypeMetadata(tag) {
|
|
18
|
+
return tag !== undefined ? { ls_agent_type: tag } : {};
|
|
19
|
+
}
|
|
20
|
+
// Preserves user-supplied ls_agent_type; stamps default only when key is absent.
|
|
21
|
+
export function defaultLsAgentTypeMetadata(existingMetadata, parentRunTree) {
|
|
22
|
+
if ("ls_agent_type" in existingMetadata)
|
|
23
|
+
return {};
|
|
24
|
+
return lsAgentTypeMetadata(resolveDefaultLsAgentType(parentRunTree));
|
|
25
|
+
}
|
package/dist/utils/profiles.cjs
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ProfileAuth = exports.DEFAULT_API_URL = void 0;
|
|
37
37
|
exports.hasValue = hasValue;
|
|
38
|
+
exports.resolveTokenEndpoint = resolveTokenEndpoint;
|
|
38
39
|
exports.loadProfileClientConfig = loadProfileClientConfig;
|
|
39
40
|
const env_js_1 = require("./env.cjs");
|
|
40
41
|
const fsUtils = __importStar(require("./fs.cjs"));
|
|
@@ -43,6 +44,8 @@ exports.DEFAULT_API_URL = "https://api.smith.langchain.com";
|
|
|
43
44
|
const OAUTH_CLIENT_ID = "langsmith-cli";
|
|
44
45
|
const TOKEN_REFRESH_LEEWAY_MS = 60_000;
|
|
45
46
|
const TOKEN_REFRESH_TIMEOUT_MS = 10_000;
|
|
47
|
+
const OAUTH_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
48
|
+
const WELL_KNOWN_OAUTH_PATH = "/.well-known/oauth-authorization-server";
|
|
46
49
|
function isBrowserLikeRuntime() {
|
|
47
50
|
const env = (0, env_js_1.getEnv)();
|
|
48
51
|
return env === "browser" || env === "webworker";
|
|
@@ -125,6 +128,126 @@ function normalizeConfigUrl(apiUrl) {
|
|
|
125
128
|
? normalized.slice(0, -apiV1Suffix.length)
|
|
126
129
|
: normalized;
|
|
127
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Metadata base URLs to probe, most specific first: the configured mount point,
|
|
133
|
+
* then the self-hosted and SaaS locations, so both a bare origin and an
|
|
134
|
+
* explicit `/api` resolve.
|
|
135
|
+
*/
|
|
136
|
+
function oauthDiscoveryCandidates(apiUrl) {
|
|
137
|
+
const given = normalizeConfigUrl(apiUrl);
|
|
138
|
+
const origin = given.endsWith("/api")
|
|
139
|
+
? given.slice(0, -"/api".length)
|
|
140
|
+
: given;
|
|
141
|
+
const candidates = [];
|
|
142
|
+
for (const candidate of [given, `${origin}/api`, origin]) {
|
|
143
|
+
if (candidate && candidate !== "/api" && !candidates.includes(candidate)) {
|
|
144
|
+
candidates.push(candidate);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return candidates;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Check the document describes the deployment we probed. RFC 8414 requires the
|
|
151
|
+
* issuer to match the URL the well-known path was built from, and every
|
|
152
|
+
* endpoint must share the issuer's origin. Refresh tokens are posted to these
|
|
153
|
+
* endpoints, so an unvalidated document could redirect credentials elsewhere.
|
|
154
|
+
*/
|
|
155
|
+
function isTrustedOAuthMetadata(doc, base) {
|
|
156
|
+
const { issuer } = doc;
|
|
157
|
+
if (typeof issuer !== "string" ||
|
|
158
|
+
issuer.replace(/\/+$/, "") !== base.replace(/\/+$/, "")) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
let issuerUrl;
|
|
162
|
+
try {
|
|
163
|
+
issuerUrl = new URL(issuer);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
for (const endpoint of [
|
|
169
|
+
doc.device_authorization_endpoint,
|
|
170
|
+
doc.token_endpoint,
|
|
171
|
+
]) {
|
|
172
|
+
if (typeof endpoint !== "string" || !endpoint) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const url = new URL(endpoint);
|
|
177
|
+
if (url.protocol !== issuerUrl.protocol || url.host !== issuerUrl.host) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Metadata URLs for an issuer, RFC 8414 form first. RFC 8414 inserts the
|
|
189
|
+
* well-known segment between the origin and the issuer path, so
|
|
190
|
+
* `https://host/api` is described at
|
|
191
|
+
* `https://host/.well-known/oauth-authorization-server/api`. Deployments
|
|
192
|
+
* commonly also serve the appended form, and for a path-less issuer the two are
|
|
193
|
+
* identical.
|
|
194
|
+
*/
|
|
195
|
+
function oauthMetadataUrls(base) {
|
|
196
|
+
const appended = `${base}${WELL_KNOWN_OAUTH_PATH}`;
|
|
197
|
+
let inserted;
|
|
198
|
+
try {
|
|
199
|
+
const url = new URL(base);
|
|
200
|
+
inserted = `${url.origin}${WELL_KNOWN_OAUTH_PATH}${url.pathname === "/" ? "" : url.pathname}`;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return [appended];
|
|
204
|
+
}
|
|
205
|
+
return inserted === appended ? [inserted] : [inserted, appended];
|
|
206
|
+
}
|
|
207
|
+
async function fetchOAuthMetadata(url, base, fetchImplementation) {
|
|
208
|
+
let response;
|
|
209
|
+
try {
|
|
210
|
+
response = await fetchImplementation(url, {
|
|
211
|
+
method: "GET",
|
|
212
|
+
headers: { Accept: "application/json" },
|
|
213
|
+
signal: AbortSignal.timeout(OAUTH_DISCOVERY_TIMEOUT_MS),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
if (!response.ok) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
let doc;
|
|
223
|
+
try {
|
|
224
|
+
// Self-hosted answers unknown paths with an HTML 200 from the SPA.
|
|
225
|
+
doc = (await response.json());
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
if (!doc || typeof doc !== "object" || !isTrustedOAuthMetadata(doc, base)) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
return doc;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Resolve the token endpoint, preferring the deployment's metadata document.
|
|
237
|
+
* Falls back to `<mount>/oauth/token`, keeping a trailing `/api` because
|
|
238
|
+
* self-hosted mounts the authorization server under it.
|
|
239
|
+
*/
|
|
240
|
+
async function resolveTokenEndpoint(apiUrl, fetchImplementation) {
|
|
241
|
+
for (const base of oauthDiscoveryCandidates(apiUrl)) {
|
|
242
|
+
for (const url of oauthMetadataUrls(base)) {
|
|
243
|
+
const doc = await fetchOAuthMetadata(url, base, fetchImplementation);
|
|
244
|
+
if (doc) {
|
|
245
|
+
return doc.token_endpoint;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return `${normalizeConfigUrl(apiUrl)}/oauth/token`;
|
|
250
|
+
}
|
|
128
251
|
function applyTokenResponse(profile, token) {
|
|
129
252
|
profile.oauth ??= {};
|
|
130
253
|
if (token.access_token) {
|
|
@@ -262,7 +385,8 @@ class ProfileAuth {
|
|
|
262
385
|
client_id: OAUTH_CLIENT_ID,
|
|
263
386
|
refresh_token: this.state.profile.oauth?.refresh_token ?? refreshToken,
|
|
264
387
|
});
|
|
265
|
-
const
|
|
388
|
+
const tokenEndpoint = await resolveTokenEndpoint(refreshApiUrl, fetchImplementation);
|
|
389
|
+
const response = await fetchImplementation(tokenEndpoint, {
|
|
266
390
|
method: "POST",
|
|
267
391
|
headers: {
|
|
268
392
|
"Content-Type": "application/x-www-form-urlencoded",
|
package/dist/utils/profiles.d.ts
CHANGED
|
@@ -33,6 +33,12 @@ export type ProfileClientConfig = {
|
|
|
33
33
|
profileAuth?: ProfileAuth;
|
|
34
34
|
};
|
|
35
35
|
export declare function hasValue(value?: string | null): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the token endpoint, preferring the deployment's metadata document.
|
|
38
|
+
* Falls back to `<mount>/oauth/token`, keeping a trailing `/api` because
|
|
39
|
+
* self-hosted mounts the authorization server under it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveTokenEndpoint(apiUrl: string, fetchImplementation: typeof fetch): Promise<string>;
|
|
36
42
|
export declare function loadProfileClientConfig(): ProfileClientConfig;
|
|
37
43
|
export declare class ProfileAuth {
|
|
38
44
|
private state;
|