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.
Files changed (41) hide show
  1. package/dist/client.cjs +33 -4
  2. package/dist/client.d.ts +10 -6
  3. package/dist/client.js +33 -4
  4. package/dist/experimental/vercel/_agent_type.cjs +30 -0
  5. package/dist/experimental/vercel/_agent_type.d.ts +5 -0
  6. package/dist/experimental/vercel/_agent_type.js +26 -0
  7. package/dist/experimental/vercel/telemetry.cjs +2 -7
  8. package/dist/experimental/vercel/telemetry.js +2 -7
  9. package/dist/experimental/vercel/wrap.cjs +5 -11
  10. package/dist/experimental/vercel/wrap.js +6 -12
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/sandbox/client.cjs +11 -5
  15. package/dist/sandbox/client.d.ts +4 -4
  16. package/dist/sandbox/client.js +11 -5
  17. package/dist/sandbox/index.cjs +2 -1
  18. package/dist/sandbox/index.d.ts +2 -2
  19. package/dist/sandbox/index.js +1 -1
  20. package/dist/sandbox/mounts.cjs +25 -2
  21. package/dist/sandbox/mounts.d.ts +12 -1
  22. package/dist/sandbox/mounts.js +24 -2
  23. package/dist/sandbox/proxy_config.cjs +28 -4
  24. package/dist/sandbox/proxy_config.d.ts +4 -2
  25. package/dist/sandbox/proxy_config.js +28 -4
  26. package/dist/sandbox/types.d.ts +36 -1
  27. package/dist/utils/_git.cjs +28 -1
  28. package/dist/utils/_git.js +28 -1
  29. package/dist/utils/ls_agent_type.cjs +31 -0
  30. package/dist/utils/ls_agent_type.d.ts +9 -0
  31. package/dist/utils/ls_agent_type.js +25 -0
  32. package/dist/utils/profiles.cjs +125 -1
  33. package/dist/utils/profiles.d.ts +6 -0
  34. package/dist/utils/profiles.js +124 -1
  35. package/dist/wrappers/anthropic.cjs +39 -6
  36. package/dist/wrappers/anthropic.js +39 -6
  37. package/dist/wrappers/openai.cjs +4 -0
  38. package/dist/wrappers/openai.js +4 -0
  39. package/dist/wrappers/openai_agents.cjs +15 -12
  40. package/dist/wrappers/openai_agents.js +15 -12
  41. package/package.json +12 -2
@@ -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" && mount.type !== "gcs" && mount.type !== "git") {
144
- throw new Error("mountConfig only supports s3, gcs, and git mounts");
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;
@@ -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[];
@@ -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" && mount.type !== "gcs" && mount.type !== "git") {
137
- throw new Error("mountConfig only supports s3, gcs, and git mounts");
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
- return {
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
- return {
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
- return {
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
- return {
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
  }
@@ -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;
@@ -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,
@@ -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
+ }
@@ -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 response = await fetchImplementation(`${normalizeConfigUrl(refreshApiUrl)}/oauth/token`, {
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",
@@ -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;