langsmith 0.10.3 → 0.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/client.cjs +19 -4
  2. package/dist/client.d.ts +2 -1
  3. package/dist/client.js +19 -4
  4. package/dist/evaluation/_runner.cjs +47 -5
  5. package/dist/evaluation/_runner.d.ts +12 -0
  6. package/dist/evaluation/_runner.js +47 -5
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/sandbox/access_delegation.cjs +24 -0
  11. package/dist/sandbox/access_delegation.d.ts +15 -0
  12. package/dist/sandbox/access_delegation.js +21 -0
  13. package/dist/sandbox/client.cjs +70 -5
  14. package/dist/sandbox/client.d.ts +18 -0
  15. package/dist/sandbox/client.js +70 -5
  16. package/dist/sandbox/command_handle.cjs +34 -0
  17. package/dist/sandbox/command_handle.d.ts +10 -0
  18. package/dist/sandbox/command_handle.js +34 -0
  19. package/dist/sandbox/helpers.cjs +129 -0
  20. package/dist/sandbox/helpers.d.ts +29 -0
  21. package/dist/sandbox/helpers.js +123 -0
  22. package/dist/sandbox/index.cjs +7 -1
  23. package/dist/sandbox/index.d.ts +5 -1
  24. package/dist/sandbox/index.js +2 -0
  25. package/dist/sandbox/mounts.cjs +21 -3
  26. package/dist/sandbox/mounts.d.ts +7 -1
  27. package/dist/sandbox/mounts.js +21 -3
  28. package/dist/sandbox/proxy_config.cjs +41 -10
  29. package/dist/sandbox/proxy_config.d.ts +33 -6
  30. package/dist/sandbox/proxy_config.js +40 -10
  31. package/dist/sandbox/sandbox.cjs +160 -4
  32. package/dist/sandbox/sandbox.d.ts +71 -1
  33. package/dist/sandbox/sandbox.js +161 -5
  34. package/dist/sandbox/service_url.cjs +125 -0
  35. package/dist/sandbox/service_url.d.ts +50 -0
  36. package/dist/sandbox/service_url.js +120 -0
  37. package/dist/sandbox/types.d.ts +251 -8
  38. package/dist/sandbox/ws_execute.cjs +31 -1
  39. package/dist/sandbox/ws_execute.d.ts +9 -0
  40. package/dist/sandbox/ws_execute.js +31 -1
  41. package/dist/utils/error.cjs +17 -22
  42. package/dist/utils/error.js +17 -22
  43. package/dist/wrappers/openai_agents.cjs +37 -28
  44. package/dist/wrappers/openai_agents.d.ts +0 -1
  45. package/dist/wrappers/openai_agents.js +37 -28
  46. package/package.json +2 -2
@@ -136,10 +136,20 @@ class WSStreamControl {
136
136
  writable: true,
137
137
  value: false
138
138
  });
139
+ Object.defineProperty(this, "_closeStdinPending", {
140
+ enumerable: true,
141
+ configurable: true,
142
+ writable: true,
143
+ value: false
144
+ });
139
145
  }
140
146
  /** Bind to the active WebSocket. Called inside the generator. */
141
147
  _bind(ws) {
142
148
  this._ws = ws;
149
+ if (this._closeStdinPending) {
150
+ this._closeStdinPending = false;
151
+ this.sendCloseStdin();
152
+ }
143
153
  }
144
154
  /** Mark as closed. Called when the generator exits. */
145
155
  _unbind() {
@@ -163,6 +173,22 @@ class WSStreamControl {
163
173
  this._ws.send(JSON.stringify({ type: "input", data }));
164
174
  }
165
175
  }
176
+ /**
177
+ * Half-close the command's stdin so it reads EOF.
178
+ *
179
+ * A reconnected stream binds its socket only once iteration starts, so a
180
+ * close arriving before that is queued and sent on bind — dropping it would
181
+ * leave the command waiting for an EOF that never comes.
182
+ */
183
+ sendCloseStdin() {
184
+ if (this._closed)
185
+ return;
186
+ if (this._ws && this._ws.readyState === 1) {
187
+ this._ws.send(JSON.stringify({ type: "close_stdin" }));
188
+ return;
189
+ }
190
+ this._closeStdinPending = true;
191
+ }
166
192
  }
167
193
  exports.WSStreamControl = WSStreamControl;
168
194
  // =============================================================================
@@ -330,7 +356,7 @@ async function* readWsMessages(ws) {
330
356
  * data arrives in addition to yielding the messages.
331
357
  */
332
358
  async function runWsStream(dataplaneUrl, apiKey, command, options = {}) {
333
- const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout = 300, killOnDisconnect = false, ttlSeconds = 600, pty, headers: extraHeaders, openTimeout = exports.WS_OPEN_TIMEOUT, } = options;
359
+ const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout = 300, killOnDisconnect = false, ttlSeconds = 600, pty, runConfig, closeStdin, headers: extraHeaders, openTimeout = exports.WS_OPEN_TIMEOUT, } = options;
334
360
  const wsUrl = buildWsUrl(dataplaneUrl);
335
361
  const headers = buildAuthHeaders(apiKey, extraHeaders);
336
362
  const control = new WSStreamControl();
@@ -353,6 +379,10 @@ async function runWsStream(dataplaneUrl, apiKey, command, options = {}) {
353
379
  payload.env = env;
354
380
  if (cwd)
355
381
  payload.cwd = cwd;
382
+ if (runConfig)
383
+ payload.run_config = runConfig;
384
+ if (closeStdin)
385
+ payload.close_stdin = true;
356
386
  if (commandId)
357
387
  payload.command_id = commandId;
358
388
  if (pty)
@@ -35,6 +35,7 @@ export declare class WSStreamControl {
35
35
  private _ws;
36
36
  private _closed;
37
37
  private _killed;
38
+ private _closeStdinPending;
38
39
  /** Bind to the active WebSocket. Called inside the generator. */
39
40
  _bind(ws: WsWebSocket): void;
40
41
  /** Mark as closed. Called when the generator exits. */
@@ -45,6 +46,14 @@ export declare class WSStreamControl {
45
46
  sendKill(): void;
46
47
  /** Send stdin data to the running command. */
47
48
  sendInput(data: string): void;
49
+ /**
50
+ * Half-close the command's stdin so it reads EOF.
51
+ *
52
+ * A reconnected stream binds its socket only once iteration starts, so a
53
+ * close arriving before that is queued and sent on bind — dropping it would
54
+ * leave the command waiting for an EOF that never comes.
55
+ */
56
+ sendCloseStdin(): void;
48
57
  }
49
58
  /**
50
59
  * Raise the appropriate exception from a server error message.
@@ -124,10 +124,20 @@ export class WSStreamControl {
124
124
  writable: true,
125
125
  value: false
126
126
  });
127
+ Object.defineProperty(this, "_closeStdinPending", {
128
+ enumerable: true,
129
+ configurable: true,
130
+ writable: true,
131
+ value: false
132
+ });
127
133
  }
128
134
  /** Bind to the active WebSocket. Called inside the generator. */
129
135
  _bind(ws) {
130
136
  this._ws = ws;
137
+ if (this._closeStdinPending) {
138
+ this._closeStdinPending = false;
139
+ this.sendCloseStdin();
140
+ }
131
141
  }
132
142
  /** Mark as closed. Called when the generator exits. */
133
143
  _unbind() {
@@ -151,6 +161,22 @@ export class WSStreamControl {
151
161
  this._ws.send(JSON.stringify({ type: "input", data }));
152
162
  }
153
163
  }
164
+ /**
165
+ * Half-close the command's stdin so it reads EOF.
166
+ *
167
+ * A reconnected stream binds its socket only once iteration starts, so a
168
+ * close arriving before that is queued and sent on bind — dropping it would
169
+ * leave the command waiting for an EOF that never comes.
170
+ */
171
+ sendCloseStdin() {
172
+ if (this._closed)
173
+ return;
174
+ if (this._ws && this._ws.readyState === 1) {
175
+ this._ws.send(JSON.stringify({ type: "close_stdin" }));
176
+ return;
177
+ }
178
+ this._closeStdinPending = true;
179
+ }
154
180
  }
155
181
  // =============================================================================
156
182
  // Error Handling
@@ -317,7 +343,7 @@ async function* readWsMessages(ws) {
317
343
  * data arrives in addition to yielding the messages.
318
344
  */
319
345
  export async function runWsStream(dataplaneUrl, apiKey, command, options = {}) {
320
- const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout = 300, killOnDisconnect = false, ttlSeconds = 600, pty, headers: extraHeaders, openTimeout = WS_OPEN_TIMEOUT, } = options;
346
+ const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout = 300, killOnDisconnect = false, ttlSeconds = 600, pty, runConfig, closeStdin, headers: extraHeaders, openTimeout = WS_OPEN_TIMEOUT, } = options;
321
347
  const wsUrl = buildWsUrl(dataplaneUrl);
322
348
  const headers = buildAuthHeaders(apiKey, extraHeaders);
323
349
  const control = new WSStreamControl();
@@ -340,6 +366,10 @@ export async function runWsStream(dataplaneUrl, apiKey, command, options = {}) {
340
366
  payload.env = env;
341
367
  if (cwd)
342
368
  payload.cwd = cwd;
369
+ if (runConfig)
370
+ payload.run_config = runConfig;
371
+ if (closeStdin)
372
+ payload.close_stdin = true;
343
373
  if (commandId)
344
374
  payload.command_id = commandId;
345
375
  if (pty)
@@ -140,32 +140,27 @@ async function raiseForStatus(response, context, consumeOnSuccess) {
140
140
  }
141
141
  return;
142
142
  }
143
+ try {
144
+ errorBody = await response.text();
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ }
147
+ catch (_e) {
148
+ errorBody = "";
149
+ }
143
150
  if (response.status === 403) {
151
+ let errorData;
144
152
  try {
145
- const errorData = await response.json();
146
- const errorCode = errorData?.error;
147
- if (errorCode === "org_scoped_key_requires_workspace") {
148
- errorBody =
149
- "This API key is org-scoped and requires workspace specification. " +
150
- "Please provide 'workspaceId' parameter, " +
151
- "or set LANGSMITH_WORKSPACE_ID environment variable.";
152
- }
153
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
- }
155
- catch (_e) {
156
- const errorWithStatus = new Error(`${response.status} ${response.statusText}`);
157
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
- errorWithStatus.status = response?.status;
159
- throw errorWithStatus;
153
+ errorData = JSON.parse(errorBody);
160
154
  }
161
- }
162
- if (errorBody === undefined) {
163
- try {
164
- errorBody = await response.text();
165
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
+ catch {
156
+ // Non-JSON body (e.g. a proxy in front of a self-hosted instance):
157
+ // fall through and surface the raw body like every other status.
166
158
  }
167
- catch (_e) {
168
- errorBody = "";
159
+ if (errorData?.error === "org_scoped_key_requires_workspace") {
160
+ errorBody =
161
+ "This API key is org-scoped and requires workspace specification. " +
162
+ "Please provide 'workspaceId' parameter, " +
163
+ "or set LANGSMITH_WORKSPACE_ID environment variable.";
169
164
  }
170
165
  }
171
166
  const fullMessage = `Failed to ${context}. Received status [${response.status}]: ${response.statusText}. Message: ${errorBody}`;
@@ -129,32 +129,27 @@ export async function raiseForStatus(response, context, consumeOnSuccess) {
129
129
  }
130
130
  return;
131
131
  }
132
+ try {
133
+ errorBody = await response.text();
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
+ }
136
+ catch (_e) {
137
+ errorBody = "";
138
+ }
132
139
  if (response.status === 403) {
140
+ let errorData;
133
141
  try {
134
- const errorData = await response.json();
135
- const errorCode = errorData?.error;
136
- if (errorCode === "org_scoped_key_requires_workspace") {
137
- errorBody =
138
- "This API key is org-scoped and requires workspace specification. " +
139
- "Please provide 'workspaceId' parameter, " +
140
- "or set LANGSMITH_WORKSPACE_ID environment variable.";
141
- }
142
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
- }
144
- catch (_e) {
145
- const errorWithStatus = new Error(`${response.status} ${response.statusText}`);
146
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
- errorWithStatus.status = response?.status;
148
- throw errorWithStatus;
142
+ errorData = JSON.parse(errorBody);
149
143
  }
150
- }
151
- if (errorBody === undefined) {
152
- try {
153
- errorBody = await response.text();
154
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
+ catch {
145
+ // Non-JSON body (e.g. a proxy in front of a self-hosted instance):
146
+ // fall through and surface the raw body like every other status.
155
147
  }
156
- catch (_e) {
157
- errorBody = "";
148
+ if (errorData?.error === "org_scoped_key_requires_workspace") {
149
+ errorBody =
150
+ "This API key is org-scoped and requires workspace specification. " +
151
+ "Please provide 'workspaceId' parameter, " +
152
+ "or set LANGSMITH_WORKSPACE_ID environment variable.";
158
153
  }
159
154
  }
160
155
  const fullMessage = `Failed to ${context}. Received status [${response.status}]: ${response.statusText}. Message: ${errorBody}`;
@@ -369,6 +369,33 @@ function createResponsesUsageMetadata(usage) {
369
369
  }
370
370
  return result;
371
371
  }
372
+ /**
373
+ * Return an ls_agent_type for the span, or undefined to leave it alone.
374
+ *
375
+ * If the run already has middleware/subagent/compaction, keep it. Otherwise
376
+ * guardrails become middleware and agents under any tool become subagents.
377
+ *
378
+ * Openai-agents structural counterpart to resolveVercelLsAgentType.
379
+ */
380
+ function _resolveOpenAIAgentsLsAgentType(spanData, parentRun, existingTag) {
381
+ if (typeof existingTag === "string" &&
382
+ ls_agent_type_js_1.NON_ROOT_LS_AGENT_TYPES.has(existingTag)) {
383
+ return undefined;
384
+ }
385
+ if (spanData.type === "guardrail")
386
+ return "middleware";
387
+ // Walk the full parent chain, not just the direct parent: asTool inserts
388
+ // a chain run between the tool and the inner agent span.
389
+ if (spanData.type === "agent") {
390
+ let cursor = parentRun;
391
+ while (cursor !== undefined) {
392
+ if (cursor.run_type === "tool")
393
+ return "subagent";
394
+ cursor = cursor.parent_run;
395
+ }
396
+ }
397
+ return undefined;
398
+ }
372
399
  /**
373
400
  * Tracing processor for the [OpenAI Agents SDK](https://openai.github.io/openai-agents-js/).
374
401
  *
@@ -463,12 +490,6 @@ class OpenAIAgentsTracingProcessor {
463
490
  writable: true,
464
491
  value: new Map()
465
492
  });
466
- Object.defineProperty(this, "_spanDataTypes", {
467
- enumerable: true,
468
- configurable: true,
469
- writable: true,
470
- value: new Map()
471
- });
472
493
  Object.defineProperty(this, "_unpostedTraces", {
473
494
  enumerable: true,
474
495
  configurable: true,
@@ -684,28 +705,17 @@ class OpenAIAgentsTracingProcessor {
684
705
  console.error("Error creating span run:", e);
685
706
  return;
686
707
  }
687
- // Add ls_agent_type metadata for agent spans that are children of
688
- // function spans (i.e., agents used as tools).
689
- // Handoff agents are not considered subagents.
690
- if (spanData.type === "agent") {
691
- const parentSpanType = parentId
692
- ? this._spanDataTypes.get(parentId)
693
- : undefined;
694
- if (parentSpanType === "function") {
695
- if (!childRun.extra) {
696
- childRun.extra = {};
697
- }
698
- if (!childRun.extra.metadata) {
699
- childRun.extra.metadata = {};
700
- }
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
- }
705
- }
708
+ // Handoffs replace the caller rather than run as a tool, so a handoff
709
+ // agent has no tool ancestor and correctly stays untagged here.
710
+ if (!childRun.extra)
711
+ childRun.extra = {};
712
+ if (!childRun.extra.metadata)
713
+ childRun.extra.metadata = {};
714
+ const meta = childRun.extra.metadata;
715
+ const structuralTag = _resolveOpenAIAgentsLsAgentType(spanData, parentRun, meta.ls_agent_type);
716
+ if (structuralTag !== undefined) {
717
+ meta.ls_agent_type = structuralTag;
706
718
  }
707
- // Track span data type for parent lookups
708
- this._spanDataTypes.set(span.spanId, spanData.type);
709
719
  this._runs.set(span.spanId, childRun);
710
720
  // Enter AsyncLocalStorage context synchronously so nested traceable()
711
721
  // calls inside the span's body nest under this run. Remember the
@@ -741,7 +751,6 @@ class OpenAIAgentsTracingProcessor {
741
751
  enterRunTreeContext(previousStore);
742
752
  }
743
753
  const run = this._runs.get(span.spanId);
744
- this._spanDataTypes.delete(span.spanId);
745
754
  if (!run) {
746
755
  return;
747
756
  }
@@ -69,7 +69,6 @@ export declare class OpenAIAgentsTracingProcessor implements TracingProcessor {
69
69
  private _firstResponseInputs;
70
70
  private _lastResponseOutputs;
71
71
  private _runs;
72
- private _spanDataTypes;
73
72
  private _unpostedTraces;
74
73
  private _unpostedSpans;
75
74
  private _previousStoreByTrace;
@@ -366,6 +366,33 @@ function createResponsesUsageMetadata(usage) {
366
366
  }
367
367
  return result;
368
368
  }
369
+ /**
370
+ * Return an ls_agent_type for the span, or undefined to leave it alone.
371
+ *
372
+ * If the run already has middleware/subagent/compaction, keep it. Otherwise
373
+ * guardrails become middleware and agents under any tool become subagents.
374
+ *
375
+ * Openai-agents structural counterpart to resolveVercelLsAgentType.
376
+ */
377
+ function _resolveOpenAIAgentsLsAgentType(spanData, parentRun, existingTag) {
378
+ if (typeof existingTag === "string" &&
379
+ NON_ROOT_LS_AGENT_TYPES.has(existingTag)) {
380
+ return undefined;
381
+ }
382
+ if (spanData.type === "guardrail")
383
+ return "middleware";
384
+ // Walk the full parent chain, not just the direct parent: asTool inserts
385
+ // a chain run between the tool and the inner agent span.
386
+ if (spanData.type === "agent") {
387
+ let cursor = parentRun;
388
+ while (cursor !== undefined) {
389
+ if (cursor.run_type === "tool")
390
+ return "subagent";
391
+ cursor = cursor.parent_run;
392
+ }
393
+ }
394
+ return undefined;
395
+ }
369
396
  /**
370
397
  * Tracing processor for the [OpenAI Agents SDK](https://openai.github.io/openai-agents-js/).
371
398
  *
@@ -460,12 +487,6 @@ export class OpenAIAgentsTracingProcessor {
460
487
  writable: true,
461
488
  value: new Map()
462
489
  });
463
- Object.defineProperty(this, "_spanDataTypes", {
464
- enumerable: true,
465
- configurable: true,
466
- writable: true,
467
- value: new Map()
468
- });
469
490
  Object.defineProperty(this, "_unpostedTraces", {
470
491
  enumerable: true,
471
492
  configurable: true,
@@ -681,28 +702,17 @@ export class OpenAIAgentsTracingProcessor {
681
702
  console.error("Error creating span run:", e);
682
703
  return;
683
704
  }
684
- // Add ls_agent_type metadata for agent spans that are children of
685
- // function spans (i.e., agents used as tools).
686
- // Handoff agents are not considered subagents.
687
- if (spanData.type === "agent") {
688
- const parentSpanType = parentId
689
- ? this._spanDataTypes.get(parentId)
690
- : undefined;
691
- if (parentSpanType === "function") {
692
- if (!childRun.extra) {
693
- childRun.extra = {};
694
- }
695
- if (!childRun.extra.metadata) {
696
- childRun.extra.metadata = {};
697
- }
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
- }
702
- }
705
+ // Handoffs replace the caller rather than run as a tool, so a handoff
706
+ // agent has no tool ancestor and correctly stays untagged here.
707
+ if (!childRun.extra)
708
+ childRun.extra = {};
709
+ if (!childRun.extra.metadata)
710
+ childRun.extra.metadata = {};
711
+ const meta = childRun.extra.metadata;
712
+ const structuralTag = _resolveOpenAIAgentsLsAgentType(spanData, parentRun, meta.ls_agent_type);
713
+ if (structuralTag !== undefined) {
714
+ meta.ls_agent_type = structuralTag;
703
715
  }
704
- // Track span data type for parent lookups
705
- this._spanDataTypes.set(span.spanId, spanData.type);
706
716
  this._runs.set(span.spanId, childRun);
707
717
  // Enter AsyncLocalStorage context synchronously so nested traceable()
708
718
  // calls inside the span's body nest under this run. Remember the
@@ -738,7 +748,6 @@ export class OpenAIAgentsTracingProcessor {
738
748
  enterRunTreeContext(previousStore);
739
749
  }
740
750
  const run = this._runs.get(span.spanId);
741
- this._spanDataTypes.delete(span.spanId);
742
751
  if (!run) {
743
752
  return;
744
753
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
5
5
  "packageManager": "pnpm@10.33.0",
6
6
  "files": [
@@ -488,7 +488,7 @@
488
488
  "brace-expansion@>=3.0.0 <5.0.7": "5.0.7",
489
489
  "fast-uri": "3.1.6",
490
490
  "postcss": "8.5.23",
491
- "js-yaml": "4.3.1",
491
+ "js-yaml": "4.3.2",
492
492
  "hono": "4.13.5",
493
493
  "ip-address": "10.3.1",
494
494
  "undici@>=7.0.0 <8.0.0": "7.29.0",