langsmith 0.8.9 → 0.8.10

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 CHANGED
@@ -893,7 +893,8 @@ class Client {
893
893
  config.hideInputs ?? config.anonymizer ?? defaultConfig.hideInputs;
894
894
  this.hideOutputs =
895
895
  config.hideOutputs ?? config.anonymizer ?? defaultConfig.hideOutputs;
896
- this.hideMetadata = config.hideMetadata ?? defaultConfig.hideMetadata;
896
+ this.hideMetadata =
897
+ config.hideMetadata ?? config.anonymizer ?? defaultConfig.hideMetadata;
897
898
  this.anonymizer = config.anonymizer;
898
899
  this.omitTracedRuntimeInfo = config.omitTracedRuntimeInfo ?? false;
899
900
  this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
@@ -1555,6 +1556,9 @@ class Client {
1555
1556
  const batchSizeBytes = batch.reduce((sum, item) => sum + (item.size ?? 0), 0);
1556
1557
  try {
1557
1558
  if (this.langSmithToOTELTranslator !== undefined) {
1559
+ for (const item of batch) {
1560
+ item.item = await this._maskRunMetadata(item.item);
1561
+ }
1558
1562
  this._sendBatchToOTELTranslator(batch);
1559
1563
  }
1560
1564
  else {
@@ -1635,6 +1639,25 @@ class Client {
1635
1639
  this.langSmithToOTELTranslator.exportBatch(operations, otelContextMap);
1636
1640
  }
1637
1641
  }
1642
+ async _maskRunMetadata(run) {
1643
+ if (run.extra?.metadata == null) {
1644
+ return run;
1645
+ }
1646
+ return {
1647
+ ...run,
1648
+ extra: {
1649
+ ...run.extra,
1650
+ metadata: await this.processMetadata(run.extra.metadata),
1651
+ },
1652
+ };
1653
+ }
1654
+ async _mergeRuntimeEnvAndMaskMetadata(run) {
1655
+ const merged = mergeRuntimeEnvIntoRun(run, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo);
1656
+ if (this.omitTracedRuntimeInfo) {
1657
+ return merged;
1658
+ }
1659
+ return this._maskRunMetadata(merged);
1660
+ }
1638
1661
  async processRunOperation(item) {
1639
1662
  clearTimeout(this.autoBatchTimeout);
1640
1663
  this.autoBatchTimeout = undefined;
@@ -1791,7 +1814,7 @@ class Client {
1791
1814
  }).catch(console.error);
1792
1815
  return;
1793
1816
  }
1794
- const mergedRunCreateParam = mergeRuntimeEnvIntoRun(runCreate, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo);
1817
+ const mergedRunCreateParam = await this._mergeRuntimeEnvAndMaskMetadata(runCreate);
1795
1818
  if (options?.apiKey !== undefined) {
1796
1819
  headers["x-api-key"] = options.apiKey;
1797
1820
  }
@@ -2980,7 +3003,7 @@ class Client {
2980
3003
  _hostUrl: this.getHostUrl(),
2981
3004
  }));
2982
3005
  }
2983
- async createProject({ projectName, description = null, metadata = null, upsert = false, projectExtra = null, referenceDatasetId = null, numExamples = null, numRepetitions = null, evaluatorKeys = null, }) {
3006
+ async createProject({ projectName, description = null, metadata = null, upsert = false, projectExtra = null, referenceDatasetId = null, numExamples = null, numRepetitions = null, evaluatorKeys = null, tagValueIds = null, }) {
2984
3007
  const upsert_ = upsert ? `?upsert=true` : "";
2985
3008
  const endpoint = `${this.apiUrl}/sessions${upsert_}`;
2986
3009
  const extra = projectExtra || {};
@@ -3004,6 +3027,9 @@ class Client {
3004
3027
  if (evaluatorKeys != null && evaluatorKeys.length > 0) {
3005
3028
  body["evaluator_keys"] = evaluatorKeys;
3006
3029
  }
3030
+ if (tagValueIds !== null) {
3031
+ body["tag_value_ids"] = tagValueIds;
3032
+ }
3007
3033
  const serializedBody = JSON.stringify(body);
3008
3034
  const response = await this.caller.call(async () => {
3009
3035
  const res = await this._fetch(endpoint, {
@@ -3247,7 +3273,7 @@ class Client {
3247
3273
  const result = await response.json();
3248
3274
  return result;
3249
3275
  }
3250
- async createDataset(name, { description, dataType, inputsSchema, outputsSchema, metadata, } = {}) {
3276
+ async createDataset(name, { description, dataType, inputsSchema, outputsSchema, metadata, tagValueIds, } = {}) {
3251
3277
  const body = {
3252
3278
  name,
3253
3279
  description,
@@ -3262,6 +3288,9 @@ class Client {
3262
3288
  if (outputsSchema) {
3263
3289
  body.outputs_schema_definition = outputsSchema;
3264
3290
  }
3291
+ if (tagValueIds !== undefined) {
3292
+ body.tag_value_ids = tagValueIds;
3293
+ }
3265
3294
  const serializedBody = JSON.stringify(body);
3266
3295
  const response = await this.caller.call(async () => {
3267
3296
  const res = await this._fetch(`${this.apiUrl}/datasets`, {
package/dist/client.d.ts CHANGED
@@ -20,11 +20,11 @@ export interface ClientConfig {
20
20
  timeout_ms?: number;
21
21
  webUrl?: string;
22
22
  /**
23
- * A function applied for masking serialized run inputs and outputs,
24
- * before sending to the API. Can be called with raw inputs, raw
25
- * outputs, or a nested `{ error: string }` object for errors.
23
+ * A function applied for masking serialized run inputs, outputs and
24
+ * metadata, before sending to the API. Can be called with raw inputs, raw
25
+ * outputs, raw metadata, or a nested `{ error: string }` object for errors.
26
26
  *
27
- * If a `hideInputs` or `hideOutputs` function is present,
27
+ * If a `hideInputs`, `hideOutputs` or `hideMetadata` function is present,
28
28
  * the client will call it instead of the anonymizer as appropriate.
29
29
  */
30
30
  anonymizer?: (values: KVMap) => KVMap | Promise<KVMap>;
@@ -413,6 +413,7 @@ export type CreateProjectParams = {
413
413
  numExamples?: number | null;
414
414
  numRepetitions?: number | null;
415
415
  evaluatorKeys?: string[] | null;
416
+ tagValueIds?: string[] | null;
416
417
  };
417
418
  type AutoBatchQueueItem = {
418
419
  action: "create" | "update";
@@ -670,6 +671,8 @@ export declare class Client implements LangSmithTracingClientInterface {
670
671
  private static _writeTraceToFallbackDir;
671
672
  private _processBatch;
672
673
  private _sendBatchToOTELTranslator;
674
+ private _maskRunMetadata;
675
+ private _mergeRuntimeEnvAndMaskMetadata;
673
676
  private processRunOperation;
674
677
  protected _getServerInfo(): Promise<any>;
675
678
  private _checkStainlessVersion;
@@ -872,7 +875,7 @@ export declare class Client implements LangSmithTracingClientInterface {
872
875
  listSharedExamples(shareToken: string, options?: {
873
876
  exampleIds?: string[];
874
877
  }): Promise<Example[]>;
875
- createProject({ projectName, description, metadata, upsert, projectExtra, referenceDatasetId, numExamples, numRepetitions, evaluatorKeys, }: CreateProjectParams): Promise<TracerSession>;
878
+ createProject({ projectName, description, metadata, upsert, projectExtra, referenceDatasetId, numExamples, numRepetitions, evaluatorKeys, tagValueIds, }: CreateProjectParams): Promise<TracerSession>;
876
879
  updateProject(projectId: string, { name, description, metadata, projectExtra, endTime, }: {
877
880
  name?: string | null;
878
881
  description?: string | null;
@@ -914,12 +917,13 @@ export declare class Client implements LangSmithTracingClientInterface {
914
917
  projectName?: string;
915
918
  }): Promise<void>;
916
919
  uploadCsv({ csvFile, fileName, inputKeys, outputKeys, description, dataType, name, }: UploadCSVParams): Promise<Dataset>;
917
- createDataset(name: string, { description, dataType, inputsSchema, outputsSchema, metadata, }?: {
920
+ createDataset(name: string, { description, dataType, inputsSchema, outputsSchema, metadata, tagValueIds, }?: {
918
921
  description?: string;
919
922
  dataType?: DataType;
920
923
  inputsSchema?: KVMap;
921
924
  outputsSchema?: KVMap;
922
925
  metadata?: RecordStringAny;
926
+ tagValueIds?: string[];
923
927
  }): Promise<Dataset>;
924
928
  readDataset({ datasetId, datasetName, }: {
925
929
  datasetId?: string;
package/dist/client.js CHANGED
@@ -854,7 +854,8 @@ export class Client {
854
854
  config.hideInputs ?? config.anonymizer ?? defaultConfig.hideInputs;
855
855
  this.hideOutputs =
856
856
  config.hideOutputs ?? config.anonymizer ?? defaultConfig.hideOutputs;
857
- this.hideMetadata = config.hideMetadata ?? defaultConfig.hideMetadata;
857
+ this.hideMetadata =
858
+ config.hideMetadata ?? config.anonymizer ?? defaultConfig.hideMetadata;
858
859
  this.anonymizer = config.anonymizer;
859
860
  this.omitTracedRuntimeInfo = config.omitTracedRuntimeInfo ?? false;
860
861
  this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
@@ -1516,6 +1517,9 @@ export class Client {
1516
1517
  const batchSizeBytes = batch.reduce((sum, item) => sum + (item.size ?? 0), 0);
1517
1518
  try {
1518
1519
  if (this.langSmithToOTELTranslator !== undefined) {
1520
+ for (const item of batch) {
1521
+ item.item = await this._maskRunMetadata(item.item);
1522
+ }
1519
1523
  this._sendBatchToOTELTranslator(batch);
1520
1524
  }
1521
1525
  else {
@@ -1596,6 +1600,25 @@ export class Client {
1596
1600
  this.langSmithToOTELTranslator.exportBatch(operations, otelContextMap);
1597
1601
  }
1598
1602
  }
1603
+ async _maskRunMetadata(run) {
1604
+ if (run.extra?.metadata == null) {
1605
+ return run;
1606
+ }
1607
+ return {
1608
+ ...run,
1609
+ extra: {
1610
+ ...run.extra,
1611
+ metadata: await this.processMetadata(run.extra.metadata),
1612
+ },
1613
+ };
1614
+ }
1615
+ async _mergeRuntimeEnvAndMaskMetadata(run) {
1616
+ const merged = mergeRuntimeEnvIntoRun(run, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo);
1617
+ if (this.omitTracedRuntimeInfo) {
1618
+ return merged;
1619
+ }
1620
+ return this._maskRunMetadata(merged);
1621
+ }
1599
1622
  async processRunOperation(item) {
1600
1623
  clearTimeout(this.autoBatchTimeout);
1601
1624
  this.autoBatchTimeout = undefined;
@@ -1752,7 +1775,7 @@ export class Client {
1752
1775
  }).catch(console.error);
1753
1776
  return;
1754
1777
  }
1755
- const mergedRunCreateParam = mergeRuntimeEnvIntoRun(runCreate, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo);
1778
+ const mergedRunCreateParam = await this._mergeRuntimeEnvAndMaskMetadata(runCreate);
1756
1779
  if (options?.apiKey !== undefined) {
1757
1780
  headers["x-api-key"] = options.apiKey;
1758
1781
  }
@@ -2941,7 +2964,7 @@ export class Client {
2941
2964
  _hostUrl: this.getHostUrl(),
2942
2965
  }));
2943
2966
  }
2944
- async createProject({ projectName, description = null, metadata = null, upsert = false, projectExtra = null, referenceDatasetId = null, numExamples = null, numRepetitions = null, evaluatorKeys = null, }) {
2967
+ async createProject({ projectName, description = null, metadata = null, upsert = false, projectExtra = null, referenceDatasetId = null, numExamples = null, numRepetitions = null, evaluatorKeys = null, tagValueIds = null, }) {
2945
2968
  const upsert_ = upsert ? `?upsert=true` : "";
2946
2969
  const endpoint = `${this.apiUrl}/sessions${upsert_}`;
2947
2970
  const extra = projectExtra || {};
@@ -2965,6 +2988,9 @@ export class Client {
2965
2988
  if (evaluatorKeys != null && evaluatorKeys.length > 0) {
2966
2989
  body["evaluator_keys"] = evaluatorKeys;
2967
2990
  }
2991
+ if (tagValueIds !== null) {
2992
+ body["tag_value_ids"] = tagValueIds;
2993
+ }
2968
2994
  const serializedBody = JSON.stringify(body);
2969
2995
  const response = await this.caller.call(async () => {
2970
2996
  const res = await this._fetch(endpoint, {
@@ -3208,7 +3234,7 @@ export class Client {
3208
3234
  const result = await response.json();
3209
3235
  return result;
3210
3236
  }
3211
- async createDataset(name, { description, dataType, inputsSchema, outputsSchema, metadata, } = {}) {
3237
+ async createDataset(name, { description, dataType, inputsSchema, outputsSchema, metadata, tagValueIds, } = {}) {
3212
3238
  const body = {
3213
3239
  name,
3214
3240
  description,
@@ -3223,6 +3249,9 @@ export class Client {
3223
3249
  if (outputsSchema) {
3224
3250
  body.outputs_schema_definition = outputsSchema;
3225
3251
  }
3252
+ if (tagValueIds !== undefined) {
3253
+ body.tag_value_ids = tagValueIds;
3254
+ }
3226
3255
  const serializedBody = JSON.stringify(body);
3227
3256
  const response = await this.caller.call(async () => {
3228
3257
  const res = await this._fetch(`${this.apiUrl}/datasets`, {
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveVercelLsAgentType = resolveVercelLsAgentType;
4
+ exports.vercelLsAgentTypeMetadata = vercelLsAgentTypeMetadata;
5
+ const run_trees_js_1 = require("../../run_trees.cjs");
6
+ const traceable_js_1 = require("../../singletons/traceable.cjs");
7
+ const ls_agent_type_js_1 = require("../../utils/ls_agent_type.cjs");
8
+ // Vercel builds its traceable's `metadata` config eagerly, before traceable
9
+ // itself runs — this resolver stamps `ls_agent_type` at that moment.
10
+ // Traceable's own propagation would carry a parent's middleware/subagent/
11
+ // compaction/"root" tag down on its own. This resolver is needed
12
+ // for correct default tagging of the root `ls_agent_type` == root
13
+ // and tagging of subagents.
14
+ function resolveVercelLsAgentType(parentRunTree) {
15
+ const parent = parentRunTree ?? (0, traceable_js_1.getCurrentRunTree)(true);
16
+ if (!(0, run_trees_js_1.isRunTree)(parent))
17
+ return (0, ls_agent_type_js_1.resolveDefaultLsAgentType)(parent);
18
+ const parentTag = parent.extra?.metadata?.ls_agent_type;
19
+ if (typeof parentTag === "string" && ls_agent_type_js_1.NON_ROOT_LS_AGENT_TYPES.has(parentTag)) {
20
+ return parentTag;
21
+ }
22
+ if (parent.run_type === "tool")
23
+ return "subagent";
24
+ if (parentTag === "root")
25
+ return "root";
26
+ return undefined;
27
+ }
28
+ function vercelLsAgentTypeMetadata(parentRunTree) {
29
+ return (0, ls_agent_type_js_1.lsAgentTypeMetadata)(resolveVercelLsAgentType(parentRunTree));
30
+ }
@@ -0,0 +1,5 @@
1
+ import { LsAgentType } from "../../utils/ls_agent_type.js";
2
+ export declare function resolveVercelLsAgentType(parentRunTree?: unknown): LsAgentType | undefined;
3
+ export declare function vercelLsAgentTypeMetadata(parentRunTree?: unknown): {
4
+ ls_agent_type?: LsAgentType;
5
+ };
@@ -0,0 +1,26 @@
1
+ import { isRunTree } from "../../run_trees.js";
2
+ import { getCurrentRunTree } from "../../singletons/traceable.js";
3
+ import { NON_ROOT_LS_AGENT_TYPES, lsAgentTypeMetadata, resolveDefaultLsAgentType, } from "../../utils/ls_agent_type.js";
4
+ // Vercel builds its traceable's `metadata` config eagerly, before traceable
5
+ // itself runs — this resolver stamps `ls_agent_type` at that moment.
6
+ // Traceable's own propagation would carry a parent's middleware/subagent/
7
+ // compaction/"root" tag down on its own. This resolver is needed
8
+ // for correct default tagging of the root `ls_agent_type` == root
9
+ // and tagging of subagents.
10
+ export function resolveVercelLsAgentType(parentRunTree) {
11
+ const parent = parentRunTree ?? getCurrentRunTree(true);
12
+ if (!isRunTree(parent))
13
+ return resolveDefaultLsAgentType(parent);
14
+ const parentTag = parent.extra?.metadata?.ls_agent_type;
15
+ if (typeof parentTag === "string" && NON_ROOT_LS_AGENT_TYPES.has(parentTag)) {
16
+ return parentTag;
17
+ }
18
+ if (parent.run_type === "tool")
19
+ return "subagent";
20
+ if (parentTag === "root")
21
+ return "root";
22
+ return undefined;
23
+ }
24
+ export function vercelLsAgentTypeMetadata(parentRunTree) {
25
+ return lsAgentTypeMetadata(resolveVercelLsAgentType(parentRunTree));
26
+ }
@@ -7,6 +7,7 @@ const env_js_1 = require("../../env.cjs");
7
7
  const utils_js_1 = require("./utils.cjs");
8
8
  const utils_js_2 = require("./utils.cjs");
9
9
  const types_js_1 = require("../../utils/types.cjs");
10
+ const _agent_type_js_1 = require("./_agent_type.cjs");
10
11
  function _formatMessages(messages) {
11
12
  if (!Array.isArray(messages))
12
13
  return messages;
@@ -52,12 +53,6 @@ event, traceRawHttp) {
52
53
  }
53
54
  return (0, utils_js_1.convertMessageToTracedFormat)(output);
54
55
  }
55
- function _getLsAgentType(parentRunTree) {
56
- if ((0, run_trees_js_1.isRunTree)(parentRunTree) && parentRunTree.run_type === "tool") {
57
- return "subagent";
58
- }
59
- return "root";
60
- }
61
56
  function _hasNonzeroUsageMetadata(runTree) {
62
57
  const usageMetadata = runTree.extra?.metadata?.usage_metadata;
63
58
  if (!(0, types_js_1.isRecord)(usageMetadata))
@@ -200,7 +195,7 @@ function LangSmithTelemetry(config) {
200
195
  metadata: {
201
196
  ...customMetadata,
202
197
  ai_sdk_method: event.operationId,
203
- ls_agent_type: _getLsAgentType(parentRunTree),
198
+ ...(0, _agent_type_js_1.vercelLsAgentTypeMetadata)(parentRunTree),
204
199
  ls_model_name: event.modelId,
205
200
  ls_integration: "vercel-ai-sdk-telemetry",
206
201
  },
@@ -4,6 +4,7 @@ import { isEnvTracingEnabled } from "../../env.js";
4
4
  import { convertMessageToTracedFormat } from "./utils.js";
5
5
  import { setUsageMetadataOnRunTree } from "./utils.js";
6
6
  import { isPrimitive, isRecord } from "../../utils/types.js";
7
+ import { vercelLsAgentTypeMetadata } from "./_agent_type.js";
7
8
  function _formatMessages(messages) {
8
9
  if (!Array.isArray(messages))
9
10
  return messages;
@@ -49,12 +50,6 @@ event, traceRawHttp) {
49
50
  }
50
51
  return convertMessageToTracedFormat(output);
51
52
  }
52
- function _getLsAgentType(parentRunTree) {
53
- if (isRunTree(parentRunTree) && parentRunTree.run_type === "tool") {
54
- return "subagent";
55
- }
56
- return "root";
57
- }
58
53
  function _hasNonzeroUsageMetadata(runTree) {
59
54
  const usageMetadata = runTree.extra?.metadata?.usage_metadata;
60
55
  if (!isRecord(usageMetadata))
@@ -197,7 +192,7 @@ export function LangSmithTelemetry(config) {
197
192
  metadata: {
198
193
  ...customMetadata,
199
194
  ai_sdk_method: event.operationId,
200
- ls_agent_type: _getLsAgentType(parentRunTree),
195
+ ...vercelLsAgentTypeMetadata(parentRunTree),
201
196
  ls_model_name: event.modelId,
202
197
  ls_integration: "vercel-ai-sdk-telemetry",
203
198
  },
@@ -5,6 +5,7 @@ const middleware_js_1 = require("./middleware.cjs");
5
5
  const utils_js_1 = require("./utils.cjs");
6
6
  Object.defineProperty(exports, "convertMessageToTracedFormat", { enumerable: true, get: function () { return utils_js_1.convertMessageToTracedFormat; } });
7
7
  const traceable_js_1 = require("../../traceable.cjs");
8
+ const _agent_type_js_1 = require("./_agent_type.cjs");
8
9
  const _getModelDisplayName = (model) => {
9
10
  if (typeof model === "string") {
10
11
  return model;
@@ -118,19 +119,12 @@ const _resolveConfigs = (baseLsConfig, runtimeLsConfig) => {
118
119
  resolvedToolConfig,
119
120
  };
120
121
  };
121
- const _getLsAgentType = () => {
122
- const parentRun = (0, traceable_js_1.getCurrentRunTree)(true);
123
- if (parentRun != null && parentRun.run_type === "tool") {
124
- return "subagent";
125
- }
126
- return "root";
127
- };
128
122
  const _getGenerateTextWrapperConfig = ({ model, runName, aiSdkMethodName, resolvedLsConfig, hasExplicitOutput, hasExplicitExperimentalOutput, traceResponseMetadata, }) => {
129
123
  return {
130
124
  name: runName ?? _getModelDisplayName(model),
131
125
  ...resolvedLsConfig,
132
126
  metadata: {
133
- ls_agent_type: _getLsAgentType(),
127
+ ...(0, _agent_type_js_1.vercelLsAgentTypeMetadata)(),
134
128
  ai_sdk_method: aiSdkMethodName ?? "ai.generateText",
135
129
  ...resolvedLsConfig?.metadata,
136
130
  },
@@ -206,7 +200,7 @@ const _getStreamTextWrapperConfig = ({ model, runName, aiSdkMethodName, resolved
206
200
  name: runName ?? _getModelDisplayName(model),
207
201
  ...resolvedLsConfig,
208
202
  metadata: {
209
- ls_agent_type: _getLsAgentType(),
203
+ ...(0, _agent_type_js_1.vercelLsAgentTypeMetadata)(),
210
204
  ai_sdk_method: aiSdkMethodName ?? "ai.streamText",
211
205
  ...resolvedLsConfig?.metadata,
212
206
  },
@@ -409,7 +403,7 @@ const wrapAISDK = (ai, baseLsConfig) => {
409
403
  name: _getModelDisplayName(params.model),
410
404
  ...resolvedLsConfig,
411
405
  metadata: {
412
- ls_agent_type: _getLsAgentType(),
406
+ ...(0, _agent_type_js_1.vercelLsAgentTypeMetadata)(),
413
407
  ai_sdk_method: "ai.generateObject",
414
408
  ...resolvedLsConfig?.metadata,
415
409
  },
@@ -531,7 +525,7 @@ const wrapAISDK = (ai, baseLsConfig) => {
531
525
  name: _getModelDisplayName(params.model),
532
526
  ...resolvedLsConfig,
533
527
  metadata: {
534
- ls_agent_type: _getLsAgentType(),
528
+ ...(0, _agent_type_js_1.vercelLsAgentTypeMetadata)(),
535
529
  ai_sdk_method: "ai.streamObject",
536
530
  ...resolvedLsConfig?.metadata,
537
531
  },
@@ -1,6 +1,7 @@
1
1
  import { LangSmithMiddleware, } from "./middleware.js";
2
2
  import { convertMessageToTracedFormat } from "./utils.js";
3
- import { isTraceableFunction, traceable, getCurrentRunTree, } from "../../traceable.js";
3
+ import { isTraceableFunction, traceable } from "../../traceable.js";
4
+ import { vercelLsAgentTypeMetadata } from "./_agent_type.js";
4
5
  const _getModelDisplayName = (model) => {
5
6
  if (typeof model === "string") {
6
7
  return model;
@@ -114,19 +115,12 @@ const _resolveConfigs = (baseLsConfig, runtimeLsConfig) => {
114
115
  resolvedToolConfig,
115
116
  };
116
117
  };
117
- const _getLsAgentType = () => {
118
- const parentRun = getCurrentRunTree(true);
119
- if (parentRun != null && parentRun.run_type === "tool") {
120
- return "subagent";
121
- }
122
- return "root";
123
- };
124
118
  const _getGenerateTextWrapperConfig = ({ model, runName, aiSdkMethodName, resolvedLsConfig, hasExplicitOutput, hasExplicitExperimentalOutput, traceResponseMetadata, }) => {
125
119
  return {
126
120
  name: runName ?? _getModelDisplayName(model),
127
121
  ...resolvedLsConfig,
128
122
  metadata: {
129
- ls_agent_type: _getLsAgentType(),
123
+ ...vercelLsAgentTypeMetadata(),
130
124
  ai_sdk_method: aiSdkMethodName ?? "ai.generateText",
131
125
  ...resolvedLsConfig?.metadata,
132
126
  },
@@ -202,7 +196,7 @@ const _getStreamTextWrapperConfig = ({ model, runName, aiSdkMethodName, resolved
202
196
  name: runName ?? _getModelDisplayName(model),
203
197
  ...resolvedLsConfig,
204
198
  metadata: {
205
- ls_agent_type: _getLsAgentType(),
199
+ ...vercelLsAgentTypeMetadata(),
206
200
  ai_sdk_method: aiSdkMethodName ?? "ai.streamText",
207
201
  ...resolvedLsConfig?.metadata,
208
202
  },
@@ -404,7 +398,7 @@ const wrapAISDK = (ai, baseLsConfig) => {
404
398
  name: _getModelDisplayName(params.model),
405
399
  ...resolvedLsConfig,
406
400
  metadata: {
407
- ls_agent_type: _getLsAgentType(),
401
+ ...vercelLsAgentTypeMetadata(),
408
402
  ai_sdk_method: "ai.generateObject",
409
403
  ...resolvedLsConfig?.metadata,
410
404
  },
@@ -526,7 +520,7 @@ const wrapAISDK = (ai, baseLsConfig) => {
526
520
  name: _getModelDisplayName(params.model),
527
521
  ...resolvedLsConfig,
528
522
  metadata: {
529
- ls_agent_type: _getLsAgentType(),
523
+ ...vercelLsAgentTypeMetadata(),
530
524
  ai_sdk_method: "ai.streamObject",
531
525
  ...resolvedLsConfig?.metadata,
532
526
  },
package/dist/index.cjs CHANGED
@@ -35,6 +35,6 @@ Object.defineProperty(exports, "UnprocessableEntityError", { enumerable: true, g
35
35
  Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return error_js_1.RateLimitError; } });
36
36
  Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return error_js_1.InternalServerError; } });
37
37
  // Update using pnpm bump-version
38
- exports.__version__ = "0.8.9";
38
+ exports.__version__ = "0.8.10";
39
39
  // Metadata key to hide a traced run from LangSmith's Messages View.
40
40
  exports.LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
package/dist/index.d.ts CHANGED
@@ -7,5 +7,5 @@ export { computeRunIdForSecondaryReplica, uuid7, uuid7FromTime, } from "./uuid.j
7
7
  export { isTracingEnabled } from "./utils/guard.js";
8
8
  export { Cache, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
9
9
  export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
10
- export declare const __version__ = "0.8.9";
10
+ export declare const __version__ = "0.8.10";
11
11
  export declare const LS_MESSAGE_VIEW_EXCLUDE: "ls_message_view_exclude";
package/dist/index.js CHANGED
@@ -7,6 +7,6 @@ export { isTracingEnabled } from "./utils/guard.js";
7
7
  export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
8
8
  export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
9
9
  // Update using pnpm bump-version
10
- export const __version__ = "0.8.9";
10
+ export const __version__ = "0.8.10";
11
11
  // Metadata key to hide a traced run from LangSmith's Messages View.
12
12
  export const LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
@@ -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;
@@ -5,6 +5,8 @@ export const DEFAULT_API_URL = "https://api.smith.langchain.com";
5
5
  const OAUTH_CLIENT_ID = "langsmith-cli";
6
6
  const TOKEN_REFRESH_LEEWAY_MS = 60_000;
7
7
  const TOKEN_REFRESH_TIMEOUT_MS = 10_000;
8
+ const OAUTH_DISCOVERY_TIMEOUT_MS = 5_000;
9
+ const WELL_KNOWN_OAUTH_PATH = "/.well-known/oauth-authorization-server";
8
10
  function isBrowserLikeRuntime() {
9
11
  const env = getEnv();
10
12
  return env === "browser" || env === "webworker";
@@ -87,6 +89,126 @@ function normalizeConfigUrl(apiUrl) {
87
89
  ? normalized.slice(0, -apiV1Suffix.length)
88
90
  : normalized;
89
91
  }
92
+ /**
93
+ * Metadata base URLs to probe, most specific first: the configured mount point,
94
+ * then the self-hosted and SaaS locations, so both a bare origin and an
95
+ * explicit `/api` resolve.
96
+ */
97
+ function oauthDiscoveryCandidates(apiUrl) {
98
+ const given = normalizeConfigUrl(apiUrl);
99
+ const origin = given.endsWith("/api")
100
+ ? given.slice(0, -"/api".length)
101
+ : given;
102
+ const candidates = [];
103
+ for (const candidate of [given, `${origin}/api`, origin]) {
104
+ if (candidate && candidate !== "/api" && !candidates.includes(candidate)) {
105
+ candidates.push(candidate);
106
+ }
107
+ }
108
+ return candidates;
109
+ }
110
+ /**
111
+ * Check the document describes the deployment we probed. RFC 8414 requires the
112
+ * issuer to match the URL the well-known path was built from, and every
113
+ * endpoint must share the issuer's origin. Refresh tokens are posted to these
114
+ * endpoints, so an unvalidated document could redirect credentials elsewhere.
115
+ */
116
+ function isTrustedOAuthMetadata(doc, base) {
117
+ const { issuer } = doc;
118
+ if (typeof issuer !== "string" ||
119
+ issuer.replace(/\/+$/, "") !== base.replace(/\/+$/, "")) {
120
+ return false;
121
+ }
122
+ let issuerUrl;
123
+ try {
124
+ issuerUrl = new URL(issuer);
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ for (const endpoint of [
130
+ doc.device_authorization_endpoint,
131
+ doc.token_endpoint,
132
+ ]) {
133
+ if (typeof endpoint !== "string" || !endpoint) {
134
+ return false;
135
+ }
136
+ try {
137
+ const url = new URL(endpoint);
138
+ if (url.protocol !== issuerUrl.protocol || url.host !== issuerUrl.host) {
139
+ return false;
140
+ }
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ return true;
147
+ }
148
+ /**
149
+ * Metadata URLs for an issuer, RFC 8414 form first. RFC 8414 inserts the
150
+ * well-known segment between the origin and the issuer path, so
151
+ * `https://host/api` is described at
152
+ * `https://host/.well-known/oauth-authorization-server/api`. Deployments
153
+ * commonly also serve the appended form, and for a path-less issuer the two are
154
+ * identical.
155
+ */
156
+ function oauthMetadataUrls(base) {
157
+ const appended = `${base}${WELL_KNOWN_OAUTH_PATH}`;
158
+ let inserted;
159
+ try {
160
+ const url = new URL(base);
161
+ inserted = `${url.origin}${WELL_KNOWN_OAUTH_PATH}${url.pathname === "/" ? "" : url.pathname}`;
162
+ }
163
+ catch {
164
+ return [appended];
165
+ }
166
+ return inserted === appended ? [inserted] : [inserted, appended];
167
+ }
168
+ async function fetchOAuthMetadata(url, base, fetchImplementation) {
169
+ let response;
170
+ try {
171
+ response = await fetchImplementation(url, {
172
+ method: "GET",
173
+ headers: { Accept: "application/json" },
174
+ signal: AbortSignal.timeout(OAUTH_DISCOVERY_TIMEOUT_MS),
175
+ });
176
+ }
177
+ catch {
178
+ return undefined;
179
+ }
180
+ if (!response.ok) {
181
+ return undefined;
182
+ }
183
+ let doc;
184
+ try {
185
+ // Self-hosted answers unknown paths with an HTML 200 from the SPA.
186
+ doc = (await response.json());
187
+ }
188
+ catch {
189
+ return undefined;
190
+ }
191
+ if (!doc || typeof doc !== "object" || !isTrustedOAuthMetadata(doc, base)) {
192
+ return undefined;
193
+ }
194
+ return doc;
195
+ }
196
+ /**
197
+ * Resolve the token endpoint, preferring the deployment's metadata document.
198
+ * Falls back to `<mount>/oauth/token`, keeping a trailing `/api` because
199
+ * self-hosted mounts the authorization server under it.
200
+ */
201
+ export async function resolveTokenEndpoint(apiUrl, fetchImplementation) {
202
+ for (const base of oauthDiscoveryCandidates(apiUrl)) {
203
+ for (const url of oauthMetadataUrls(base)) {
204
+ const doc = await fetchOAuthMetadata(url, base, fetchImplementation);
205
+ if (doc) {
206
+ return doc.token_endpoint;
207
+ }
208
+ }
209
+ }
210
+ return `${normalizeConfigUrl(apiUrl)}/oauth/token`;
211
+ }
90
212
  function applyTokenResponse(profile, token) {
91
213
  profile.oauth ??= {};
92
214
  if (token.access_token) {
@@ -224,7 +346,8 @@ export class ProfileAuth {
224
346
  client_id: OAUTH_CLIENT_ID,
225
347
  refresh_token: this.state.profile.oauth?.refresh_token ?? refreshToken,
226
348
  });
227
- const response = await fetchImplementation(`${normalizeConfigUrl(refreshApiUrl)}/oauth/token`, {
349
+ const tokenEndpoint = await resolveTokenEndpoint(refreshApiUrl, fetchImplementation);
350
+ const response = await fetchImplementation(tokenEndpoint, {
228
351
  method: "POST",
229
352
  headers: {
230
353
  "Content-Type": "application/x-www-form-urlencoded",
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.wrapOpenAI = void 0;
4
4
  const traceable_js_1 = require("../traceable.cjs");
5
+ const traceable_js_2 = require("../singletons/traceable.cjs");
6
+ const ls_agent_type_js_1 = require("../utils/ls_agent_type.cjs");
5
7
  const TRACED_INVOCATION_KEYS = [
6
8
  "frequency_penalty",
7
9
  "n",
@@ -261,6 +263,7 @@ const getChatModelInvocationParamsFn = (provider, prepopulatedInvocationParams,
261
263
  ...prepopulatedInvocationParams,
262
264
  ...ls_invocation_params,
263
265
  },
266
+ ...(0, ls_agent_type_js_1.defaultLsAgentTypeMetadata)(requestMetadata, (0, traceable_js_2.getCurrentRunTree)(true)),
264
267
  };
265
268
  };
266
269
  };
@@ -416,6 +419,7 @@ const wrapOpenAI = (openai, options) => {
416
419
  ...prepopulatedInvocationParams,
417
420
  ...ls_invocation_params,
418
421
  },
422
+ ...(0, ls_agent_type_js_1.defaultLsAgentTypeMetadata)({}, (0, traceable_js_2.getCurrentRunTree)(true)),
419
423
  };
420
424
  },
421
425
  ...cleanedOptions,
@@ -1,4 +1,6 @@
1
1
  import { isTraceableFunction, traceable, } from "../traceable.js";
2
+ import { getCurrentRunTree } from "../singletons/traceable.js";
3
+ import { defaultLsAgentTypeMetadata } from "../utils/ls_agent_type.js";
2
4
  const TRACED_INVOCATION_KEYS = [
3
5
  "frequency_penalty",
4
6
  "n",
@@ -258,6 +260,7 @@ const getChatModelInvocationParamsFn = (provider, prepopulatedInvocationParams,
258
260
  ...prepopulatedInvocationParams,
259
261
  ...ls_invocation_params,
260
262
  },
263
+ ...defaultLsAgentTypeMetadata(requestMetadata, getCurrentRunTree(true)),
261
264
  };
262
265
  };
263
266
  };
@@ -413,6 +416,7 @@ export const wrapOpenAI = (openai, options) => {
413
416
  ...prepopulatedInvocationParams,
414
417
  ...ls_invocation_params,
415
418
  },
419
+ ...defaultLsAgentTypeMetadata({}, getCurrentRunTree(true)),
416
420
  };
417
421
  },
418
422
  ...cleanedOptions,
@@ -10,6 +10,7 @@ const node_async_hooks_1 = require("node:async_hooks");
10
10
  const run_trees_js_1 = require("../run_trees.cjs");
11
11
  const client_js_1 = require("../client.cjs");
12
12
  const traceable_js_1 = require("../singletons/traceable.cjs");
13
+ const ls_agent_type_js_1 = require("../utils/ls_agent_type.cjs");
13
14
  traceable_js_1.AsyncLocalStorageProviderSingleton.initializeGlobalInstance(new node_async_hooks_1.AsyncLocalStorage());
14
15
  /**
15
16
  * Set the current AsyncLocalStorage store to the given RunTree without a
@@ -521,14 +522,15 @@ class OpenAIAgentsTracingProcessor {
521
522
  else {
522
523
  runName = "Agent workflow";
523
524
  }
524
- // Build metadata
525
- const runExtra = {
526
- metadata: {
527
- ...this._metadata,
528
- ls_integration: "openai-agents-sdk",
529
- ls_agent_type: "root",
530
- },
525
+ // Merge three sources: processor defaults, trace.metadata (user's per-run
526
+ // ls_agent_type), and force-set ls_integration.
527
+ const mergedMetadata = {
528
+ ...this._metadata,
529
+ ...(trace.metadata ?? {}),
530
+ ls_integration: "openai-agents-sdk",
531
531
  };
532
+ Object.assign(mergedMetadata, (0, ls_agent_type_js_1.defaultLsAgentTypeMetadata)(mergedMetadata, currentRunTree));
533
+ const runExtra = { metadata: mergedMetadata };
532
534
  const traceDict = trace.toJSON() ?? {};
533
535
  const groupId = trace.groupId ??
534
536
  traceDict.groupId ??
@@ -589,9 +591,10 @@ class OpenAIAgentsTracingProcessor {
589
591
  }
590
592
  this._runs.delete(trace.traceId);
591
593
  const traceDict = trace.toJSON() ?? {};
594
+ // trace.metadata may have new keys since onTraceStart; pull them in.
592
595
  const metadata = {
593
- ...traceDict.metadata,
594
596
  ...this._metadata,
597
+ ...traceDict.metadata,
595
598
  };
596
599
  try {
597
600
  // Update run with final inputs/outputs
@@ -695,10 +698,10 @@ class OpenAIAgentsTracingProcessor {
695
698
  if (!childRun.extra.metadata) {
696
699
  childRun.extra.metadata = {};
697
700
  }
698
- childRun.extra.metadata = {
699
- ...childRun.extra.metadata,
700
- ls_agent_type: "subagent",
701
- };
701
+ const meta = childRun.extra.metadata;
702
+ if (!ls_agent_type_js_1.NON_ROOT_LS_AGENT_TYPES.has(meta.ls_agent_type)) {
703
+ meta.ls_agent_type = "subagent";
704
+ }
702
705
  }
703
706
  }
704
707
  // Track span data type for parent lookups
@@ -7,6 +7,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
7
7
  import { RunTree } from "../run_trees.js";
8
8
  import { Client } from "../client.js";
9
9
  import { AsyncLocalStorageProviderSingleton, getCurrentRunTree, } from "../singletons/traceable.js";
10
+ import { NON_ROOT_LS_AGENT_TYPES, defaultLsAgentTypeMetadata, } from "../utils/ls_agent_type.js";
10
11
  AsyncLocalStorageProviderSingleton.initializeGlobalInstance(new AsyncLocalStorage());
11
12
  /**
12
13
  * Set the current AsyncLocalStorage store to the given RunTree without a
@@ -518,14 +519,15 @@ export class OpenAIAgentsTracingProcessor {
518
519
  else {
519
520
  runName = "Agent workflow";
520
521
  }
521
- // Build metadata
522
- const runExtra = {
523
- metadata: {
524
- ...this._metadata,
525
- ls_integration: "openai-agents-sdk",
526
- ls_agent_type: "root",
527
- },
522
+ // Merge three sources: processor defaults, trace.metadata (user's per-run
523
+ // ls_agent_type), and force-set ls_integration.
524
+ const mergedMetadata = {
525
+ ...this._metadata,
526
+ ...(trace.metadata ?? {}),
527
+ ls_integration: "openai-agents-sdk",
528
528
  };
529
+ Object.assign(mergedMetadata, defaultLsAgentTypeMetadata(mergedMetadata, currentRunTree));
530
+ const runExtra = { metadata: mergedMetadata };
529
531
  const traceDict = trace.toJSON() ?? {};
530
532
  const groupId = trace.groupId ??
531
533
  traceDict.groupId ??
@@ -586,9 +588,10 @@ export class OpenAIAgentsTracingProcessor {
586
588
  }
587
589
  this._runs.delete(trace.traceId);
588
590
  const traceDict = trace.toJSON() ?? {};
591
+ // trace.metadata may have new keys since onTraceStart; pull them in.
589
592
  const metadata = {
590
- ...traceDict.metadata,
591
593
  ...this._metadata,
594
+ ...traceDict.metadata,
592
595
  };
593
596
  try {
594
597
  // Update run with final inputs/outputs
@@ -692,10 +695,10 @@ export class OpenAIAgentsTracingProcessor {
692
695
  if (!childRun.extra.metadata) {
693
696
  childRun.extra.metadata = {};
694
697
  }
695
- childRun.extra.metadata = {
696
- ...childRun.extra.metadata,
697
- ls_agent_type: "subagent",
698
- };
698
+ const meta = childRun.extra.metadata;
699
+ if (!NON_ROOT_LS_AGENT_TYPES.has(meta.ls_agent_type)) {
700
+ meta.ls_agent_type = "subagent";
701
+ }
699
702
  }
700
703
  }
701
704
  // Track span data type for parent lookups
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
5
5
  "packageManager": "pnpm@10.33.0",
6
6
  "files": [