langsmith 0.5.25 → 0.5.26

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 (59) hide show
  1. package/dist/client.cjs +6 -2
  2. package/dist/client.js +6 -2
  3. package/dist/experimental/anthropic/context.cjs +419 -49
  4. package/dist/experimental/anthropic/context.js +420 -50
  5. package/dist/experimental/anthropic/index.cjs +78 -10
  6. package/dist/experimental/anthropic/index.js +80 -12
  7. package/dist/experimental/anthropic/messages.cjs +53 -0
  8. package/dist/experimental/anthropic/messages.d.ts +6 -0
  9. package/dist/experimental/anthropic/messages.js +52 -0
  10. package/dist/experimental/anthropic/transcripts.cjs +144 -0
  11. package/dist/experimental/anthropic/transcripts.d.ts +22 -0
  12. package/dist/experimental/anthropic/transcripts.js +141 -0
  13. package/dist/experimental/anthropic/types.d.ts +1 -0
  14. package/dist/experimental/anthropic/usage.cjs +19 -20
  15. package/dist/experimental/anthropic/usage.d.ts +2 -1
  16. package/dist/experimental/anthropic/usage.js +18 -20
  17. package/dist/experimental/opencode/index.cjs +36 -0
  18. package/dist/experimental/opencode/index.d.ts +3 -0
  19. package/dist/experimental/opencode/index.js +32 -0
  20. package/dist/experimental/opencode/tracer.cjs +389 -0
  21. package/dist/experimental/opencode/tracer.d.ts +30 -0
  22. package/dist/experimental/opencode/tracer.js +385 -0
  23. package/dist/experimental/otel/setup.cjs +1 -1
  24. package/dist/experimental/otel/setup.js +1 -1
  25. package/dist/experimental/sandbox/sandbox.cjs +6 -2
  26. package/dist/experimental/sandbox/sandbox.d.ts +5 -1
  27. package/dist/experimental/sandbox/sandbox.js +6 -2
  28. package/dist/experimental/sandbox/types.d.ts +3 -1
  29. package/dist/experimental/vercel/index.cjs +1 -1
  30. package/dist/experimental/vercel/index.js +1 -1
  31. package/dist/experimental/vercel/middleware.cjs +2 -1
  32. package/dist/experimental/vercel/middleware.js +2 -1
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/singletons/traceable.cjs +1 -3
  37. package/dist/singletons/traceable.js +1 -3
  38. package/dist/traceable.cjs +1 -3
  39. package/dist/traceable.js +1 -3
  40. package/dist/utils/_git.cjs +2 -2
  41. package/dist/utils/_git.js +2 -2
  42. package/dist/utils/env.cjs +2 -2
  43. package/dist/utils/env.js +2 -2
  44. package/dist/utils/error.cjs +2 -2
  45. package/dist/utils/error.js +2 -2
  46. package/dist/utils/jestlike/reporter.cjs +1 -1
  47. package/dist/utils/jestlike/reporter.js +1 -1
  48. package/dist/utils/jestlike/vendor/chain.cjs +2 -3
  49. package/dist/utils/jestlike/vendor/chain.js +2 -3
  50. package/dist/vitest/utils/esm.mjs +4 -4
  51. package/dist/wrappers/gemini.cjs +1 -1
  52. package/dist/wrappers/gemini.js +1 -1
  53. package/dist/wrappers/openai.cjs +2 -6
  54. package/dist/wrappers/openai.js +2 -6
  55. package/experimental/opencode.cjs +1 -0
  56. package/experimental/opencode.d.cts +1 -0
  57. package/experimental/opencode.d.ts +1 -0
  58. package/experimental/opencode.js +1 -0
  59. package/package.json +24 -18
@@ -1,11 +1,27 @@
1
- import { convertFromAnthropicMessage, isTaskTool, isToolBlock, } from "./messages.js";
1
+ import { convertFromAnthropicMessage, isTaskTool, isToolBlock, mergeMessagesById, } from "./messages.js";
2
2
  import { getCurrentRunTree } from "../../traceable.js";
3
3
  import { aggregateUsageFromModelUsage, correctUsageFromResults, extractUsageFromMessage, } from "./usage.js";
4
+ import { readTranscript } from "./transcripts.js";
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value != null && !Array.isArray(value);
7
+ }
8
+ function isToolResultError(value) {
9
+ return isRecord(value) && (value.is_error === true || value.isError === true);
10
+ }
11
+ function makeSubagentTranscriptPathKey(path, toolUseId, agentType) {
12
+ return JSON.stringify([path, toolUseId ?? null, agentType ?? null]);
13
+ }
4
14
  /**
5
15
  * @internal
6
16
  */
7
17
  export class StreamManager {
8
18
  constructor() {
19
+ Object.defineProperty(this, "rootRun", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: void 0
24
+ });
9
25
  Object.defineProperty(this, "namespaces", {
10
26
  enumerable: true,
11
27
  configurable: true,
@@ -30,6 +46,48 @@ export class StreamManager {
30
46
  writable: true,
31
47
  value: {}
32
48
  });
49
+ Object.defineProperty(this, "subagents", {
50
+ enumerable: true,
51
+ configurable: true,
52
+ writable: true,
53
+ value: {}
54
+ });
55
+ Object.defineProperty(this, "mainTranscriptPath", {
56
+ enumerable: true,
57
+ configurable: true,
58
+ writable: true,
59
+ value: void 0
60
+ });
61
+ Object.defineProperty(this, "subagentTranscriptPaths", {
62
+ enumerable: true,
63
+ configurable: true,
64
+ writable: true,
65
+ value: []
66
+ });
67
+ Object.defineProperty(this, "pendingAgentTools", {
68
+ enumerable: true,
69
+ configurable: true,
70
+ writable: true,
71
+ value: new Map()
72
+ });
73
+ Object.defineProperty(this, "agentToToolUseId", {
74
+ enumerable: true,
75
+ configurable: true,
76
+ writable: true,
77
+ value: new Map()
78
+ });
79
+ Object.defineProperty(this, "transcriptPathKeys", {
80
+ enumerable: true,
81
+ configurable: true,
82
+ writable: true,
83
+ value: new Set()
84
+ });
85
+ Object.defineProperty(this, "resultModelUsage", {
86
+ enumerable: true,
87
+ configurable: true,
88
+ writable: true,
89
+ value: void 0
90
+ });
33
91
  Object.defineProperty(this, "postRunQueue", {
34
92
  enumerable: true,
35
93
  configurable: true,
@@ -43,10 +101,21 @@ export class StreamManager {
43
101
  value: []
44
102
  });
45
103
  const rootRun = getCurrentRunTree(true);
104
+ this.rootRun = rootRun;
46
105
  this.namespaces = rootRun?.createChild ? { root: rootRun } : {};
47
106
  this.history = { root: [] };
107
+ if (rootRun != null) {
108
+ StreamManager.managersByRootRun.set(rootRun, this);
109
+ }
110
+ StreamManager.liveManagers.add(this);
48
111
  }
49
- addMessage(message) {
112
+ dispose() {
113
+ StreamManager.liveManagers.delete(this);
114
+ if (this.rootRun != null) {
115
+ StreamManager.managersByRootRun.delete(this.rootRun);
116
+ }
117
+ }
118
+ async addMessage(message) {
50
119
  const eventTime = Date.now();
51
120
  // Short-circuit if no root run found
52
121
  // This can happen if tracing is disabled globally
@@ -54,7 +123,7 @@ export class StreamManager {
54
123
  return;
55
124
  if (message.type === "result") {
56
125
  if (message.modelUsage) {
57
- correctUsageFromResults(message.modelUsage, Object.values(this.assistant).filter((runTree) => runTree != null));
126
+ this.resultModelUsage = message.modelUsage;
58
127
  }
59
128
  const usage = message.modelUsage
60
129
  ? aggregateUsageFromModelUsage(message.modelUsage)
@@ -102,7 +171,11 @@ export class StreamManager {
102
171
  this.assistant[messageId].outputs = (() => {
103
172
  const prevMessages = this.assistant[messageId].outputs?.output.messages ?? [];
104
173
  const newMessages = convertFromAnthropicMessage([message]);
105
- return { output: { messages: [...prevMessages, ...newMessages] } };
174
+ return {
175
+ output: {
176
+ messages: mergeMessagesById(prevMessages, newMessages),
177
+ },
178
+ };
106
179
  })();
107
180
  this.assistant[messageId].end_time = eventTime;
108
181
  this.assistant[messageId].extra ??= {};
@@ -118,35 +191,10 @@ export class StreamManager {
118
191
  : [];
119
192
  for (const block of tools) {
120
193
  if (isTaskTool(block)) {
121
- const name = block.input.subagent_type ||
122
- block.input.agent_type ||
123
- (block.input.description
124
- ? block.input.description.split(" ")[0]
125
- : null) ||
126
- "unknown-agent";
127
- this.tools[block.id] ??=
128
- this.createChild("root", {
129
- name,
130
- run_type: "chain",
131
- inputs: block.input,
132
- start_time: eventTime,
133
- extra: {
134
- metadata: {
135
- ls_agent_type: "subagent",
136
- },
137
- },
138
- }) ?? this.tools[block.id];
139
- this.namespaces[block.id] ??= this.tools[block.id];
194
+ this.createAgentToolRun(namespace, block, eventTime);
140
195
  }
141
196
  else {
142
- const name = block.name || "unknown-tool";
143
- this.tools[block.id] ??=
144
- this.createChild(namespace, {
145
- name,
146
- run_type: "tool",
147
- inputs: block.input ? { input: block.input } : {},
148
- start_time: eventTime,
149
- }) ?? this.tools[block.id];
197
+ this.createToolRun(namespace, block, eventTime);
150
198
  }
151
199
  }
152
200
  }
@@ -155,9 +203,7 @@ export class StreamManager {
155
203
  ? message.message.content.filter((block) => "tool_use_id" in block)
156
204
  : [];
157
205
  const getToolOutput = (result) => {
158
- if (typeof result === "object" &&
159
- result != null &&
160
- !Array.isArray(result)) {
206
+ if (isRecord(result)) {
161
207
  return result;
162
208
  }
163
209
  return { content: result };
@@ -166,10 +212,23 @@ export class StreamManager {
166
212
  if (["string", "number", "boolean"].includes(typeof result)) {
167
213
  return String(result);
168
214
  }
215
+ if (Array.isArray(result)) {
216
+ return result.map(getToolError).join("\n");
217
+ }
218
+ if (isRecord(result)) {
219
+ if (typeof result.error === "string")
220
+ return result.error;
221
+ if (typeof result.text === "string")
222
+ return result.text;
223
+ if ("content" in result)
224
+ return getToolError(result.content);
225
+ }
169
226
  return JSON.stringify(result);
170
227
  };
171
228
  for (const block of toolResultBlocks) {
172
- if (this.tools[block.tool_use_id] != null) {
229
+ const tool = this.tools[block.tool_use_id];
230
+ const subagent = this.subagents[block.tool_use_id];
231
+ if (tool != null || subagent != null) {
173
232
  // Previous versions of @anthropic-ai/claude-agent-sdk did provide
174
233
  // tool result in `message.tool_use_result`, but at least since 0.2.50 it disappeared,
175
234
  // so we rely on the last tool result block instead.
@@ -177,35 +236,346 @@ export class StreamManager {
177
236
  ? message.tool_use_result
178
237
  : block.content;
179
238
  const toolOutput = getToolOutput(result);
180
- const toolError = "is_error" in block && block.is_error === true
181
- ? getToolError(result)
182
- : undefined;
183
- void this.tools[block.tool_use_id]?.end(toolOutput, toolError);
239
+ const isError = isToolResultError(block) || isToolResultError(result);
240
+ const toolError = isError ? getToolError(result) : undefined;
241
+ await tool?.end(toolOutput, toolError);
242
+ // Match Python's lifecycle: PostToolUse sets outputs on the
243
+ // subagent chain, but the subagent itself is not ended until after
244
+ // transcript reconciliation. Hidden transcript LLM/tool children can
245
+ // arrive after the Agent/Task tool result, so ending the chain here
246
+ // can make reconciled children appear outside their parent bounds.
247
+ if (subagent != null) {
248
+ subagent.outputs ??= toolOutput;
249
+ subagent.error ??= toolError;
250
+ }
184
251
  }
185
252
  }
186
253
  }
187
254
  this.history[namespace].push(message);
188
255
  }
256
+ addHookEvent(input, toolUseId) {
257
+ if (typeof input !== "object" || input == null)
258
+ return;
259
+ const data = input;
260
+ if (this.mainTranscriptPath == null &&
261
+ typeof data.transcript_path === "string") {
262
+ this.mainTranscriptPath = data.transcript_path;
263
+ }
264
+ if (data.hook_event_name === "PreToolUse" &&
265
+ typeof toolUseId === "string" &&
266
+ (data.tool_name === "Agent" || data.tool_name === "Task")) {
267
+ this.pendingAgentTools.set(toolUseId, typeof data.tool_input === "object" && data.tool_input != null
268
+ ? data.tool_input
269
+ : {});
270
+ return;
271
+ }
272
+ if (data.hook_event_name === "SubagentStart") {
273
+ const agentId = typeof data.agent_id === "string" ? data.agent_id : undefined;
274
+ if (agentId == null)
275
+ return;
276
+ const agentType = typeof data.agent_type === "string" ? data.agent_type : undefined;
277
+ let matchedToolUseId;
278
+ for (const [pendingToolUseId, toolInput] of this.pendingAgentTools) {
279
+ const pendingAgentType = typeof toolInput.subagent_type === "string"
280
+ ? toolInput.subagent_type
281
+ : typeof toolInput.agent_type === "string"
282
+ ? toolInput.agent_type
283
+ : undefined;
284
+ if (agentType == null ||
285
+ pendingAgentType == null ||
286
+ pendingAgentType === agentType) {
287
+ matchedToolUseId = pendingToolUseId;
288
+ break;
289
+ }
290
+ }
291
+ matchedToolUseId ??= this.pendingAgentTools.keys().next().value;
292
+ if (matchedToolUseId != null) {
293
+ this.agentToToolUseId.set(agentId, matchedToolUseId);
294
+ this.pendingAgentTools.delete(matchedToolUseId);
295
+ }
296
+ return;
297
+ }
298
+ if (data.hook_event_name === "SubagentStop") {
299
+ const transcriptPath = typeof data.agent_transcript_path === "string"
300
+ ? data.agent_transcript_path
301
+ : undefined;
302
+ if (!transcriptPath)
303
+ return;
304
+ const agentId = typeof data.agent_id === "string" ? data.agent_id : undefined;
305
+ const agentType = typeof data.agent_type === "string" ? data.agent_type : undefined;
306
+ const mappedToolUseId = agentId != null ? this.agentToToolUseId.get(agentId) : undefined;
307
+ if (agentId != null)
308
+ this.agentToToolUseId.delete(agentId);
309
+ this.addSubagentTranscriptPath(transcriptPath, mappedToolUseId, agentType);
310
+ }
311
+ }
312
+ addSubagentTranscriptPath(path, toolUseId, agentType) {
313
+ const key = makeSubagentTranscriptPathKey(path, toolUseId, agentType);
314
+ if (this.transcriptPathKeys.has(key))
315
+ return;
316
+ this.transcriptPathKeys.add(key);
317
+ this.subagentTranscriptPaths.push({ path, toolUseId, agentType });
318
+ }
319
+ static getActiveToolRun(toolName, input) {
320
+ const currentRun = getCurrentRunTree(true);
321
+ const currentManager = currentRun != null
322
+ ? StreamManager.managersByRootRun.get(currentRun)
323
+ : undefined;
324
+ const currentRunTree = currentManager?.getActiveToolRun(toolName, input);
325
+ if (currentRunTree != null)
326
+ return currentRunTree;
327
+ // Last resort: the SDK invoked an MCP handler from a detached async context
328
+ // that did not inherit the existing LangSmith AsyncLocalStorage. Require
329
+ // both tool name and input to match before scanning live managers to avoid
330
+ // cross-query attribution.
331
+ if (toolName == null || input === undefined)
332
+ return undefined;
333
+ for (const manager of Array.from(StreamManager.liveManagers).reverse()) {
334
+ if (manager === currentManager)
335
+ continue;
336
+ const runTree = manager.getActiveToolRun(toolName, input);
337
+ if (runTree != null)
338
+ return runTree;
339
+ }
340
+ return undefined;
341
+ }
342
+ getActiveToolRun(toolName, input) {
343
+ const toolEntries = Object.values(this.tools).filter((runTree) => runTree != null && runTree.end_time == null && runTree.error == null);
344
+ return toolEntries.find((runTree) => {
345
+ if (toolName != null) {
346
+ const runName = String(runTree.name);
347
+ const nameMatches = runName === toolName ||
348
+ runName.includes(toolName) ||
349
+ toolName.includes(runName);
350
+ if (!nameMatches)
351
+ return false;
352
+ }
353
+ if (input !== undefined) {
354
+ const recorded = runTree.inputs?.input ?? {};
355
+ try {
356
+ return JSON.stringify(recorded) === JSON.stringify(input ?? {});
357
+ }
358
+ catch {
359
+ return false;
360
+ }
361
+ }
362
+ return true;
363
+ });
364
+ }
189
365
  createChild(namespace, args) {
190
- const runTree = this.namespaces[namespace]?.createChild(args);
366
+ const parentRunTree = this.namespaces[namespace];
367
+ if (parentRunTree == null)
368
+ return undefined;
369
+ return this.createChildRun(parentRunTree, args);
370
+ }
371
+ createChildRun(parentRunTree, args) {
372
+ const runTree = parentRunTree.createChild(args);
191
373
  if (runTree == null)
192
374
  return undefined;
193
375
  this.postRunQueue.push(runTree.postRun());
194
376
  this.runTrees.push(runTree);
195
377
  return runTree;
196
378
  }
197
- async finish() {
198
- // Clean up incomplete tools and subagent calls
199
- for (const tool of Object.values(this.tools)) {
200
- if (tool == null)
379
+ getAgentName(block) {
380
+ const subagentType = block.input.subagent_type;
381
+ if (typeof subagentType === "string" && subagentType.length > 0) {
382
+ return subagentType;
383
+ }
384
+ const agentType = block.input.agent_type;
385
+ if (typeof agentType === "string" && agentType.length > 0) {
386
+ return agentType;
387
+ }
388
+ const description = block.input.description;
389
+ if (typeof description === "string" && description.length > 0) {
390
+ return description.split(" ")[0] || "unknown-agent";
391
+ }
392
+ return "unknown-agent";
393
+ }
394
+ createToolRun(namespace, block, startTime) {
395
+ if (typeof block.id !== "string")
396
+ return undefined;
397
+ const name = typeof block.name === "string" ? block.name : "unknown-tool";
398
+ this.tools[block.id] ??=
399
+ this.createChild(namespace, {
400
+ name,
401
+ run_type: "tool",
402
+ inputs: block.input ? { input: block.input } : {},
403
+ start_time: startTime,
404
+ }) ?? this.tools[block.id];
405
+ return this.tools[block.id];
406
+ }
407
+ createAgentToolRun(namespace, block, startTime) {
408
+ if (typeof block.id !== "string")
409
+ return;
410
+ if (typeof block.input !== "object" ||
411
+ block.input == null ||
412
+ Array.isArray(block.input)) {
413
+ return;
414
+ }
415
+ const input = block.input;
416
+ const agentToolRun = this.createToolRun(namespace, block, startTime);
417
+ if (agentToolRun == null)
418
+ return;
419
+ this.subagents[block.id] ??=
420
+ this.createChildRun(agentToolRun, {
421
+ name: this.getAgentName({ input }),
422
+ run_type: "chain",
423
+ inputs: input,
424
+ start_time: startTime,
425
+ extra: {
426
+ metadata: {
427
+ ls_agent_type: "subagent",
428
+ },
429
+ },
430
+ }) ?? this.subagents[block.id];
431
+ this.namespaces[block.id] ??= this.subagents[block.id];
432
+ }
433
+ resolveSubagentNamespace(agentType) {
434
+ const entries = Object.entries(this.namespaces);
435
+ if (agentType == null)
436
+ return undefined;
437
+ return entries.find(([, runTree]) => runTree?.name === agentType)?.[0];
438
+ }
439
+ createSyntheticAssistantRun(namespace, turn) {
440
+ let runTree = this.assistant[turn.messageId];
441
+ if (runTree == null) {
442
+ runTree = this.createChild(namespace, {
443
+ name: "claude.assistant.turn",
444
+ run_type: "llm",
445
+ start_time: turn.timestamp,
446
+ inputs: turn.inputMessages.length > 0 ? { messages: turn.inputMessages } : {},
447
+ outputs: {
448
+ output: { messages: convertFromAnthropicMessage(turn.message) },
449
+ },
450
+ extra: {
451
+ metadata: {
452
+ ls_provider: "anthropic",
453
+ ...(turn.model != null ? { ls_model_name: turn.model } : {}),
454
+ ...(turn.usageMetadata != null
455
+ ? { usage_metadata: turn.usageMetadata }
456
+ : {}),
457
+ },
458
+ },
459
+ });
460
+ if (runTree == null)
461
+ return;
462
+ this.assistant[turn.messageId] = runTree;
463
+ }
464
+ else {
465
+ runTree.outputs = {
466
+ output: { messages: convertFromAnthropicMessage(turn.message) },
467
+ };
468
+ runTree.extra ??= {};
469
+ runTree.extra.metadata ??= {};
470
+ if (turn.model != null)
471
+ runTree.extra.metadata.ls_model_name = turn.model;
472
+ if (turn.usageMetadata != null) {
473
+ runTree.extra.metadata.usage_metadata = turn.usageMetadata;
474
+ }
475
+ }
476
+ runTree.end_time = turn.timestamp;
477
+ const tools = Array.isArray(turn.message.message.content)
478
+ ? turn.message.message.content.filter((block) => isToolBlock(block))
479
+ : [];
480
+ for (const block of tools) {
481
+ if (isTaskTool(block)) {
482
+ this.createAgentToolRun(namespace, block, turn.timestamp);
483
+ }
484
+ else {
485
+ this.createToolRun(namespace, block, turn.timestamp);
486
+ }
487
+ }
488
+ }
489
+ async reconcileTranscripts() {
490
+ const usageByMessageId = {};
491
+ if (this.mainTranscriptPath != null) {
492
+ const transcript = await readTranscript(this.mainTranscriptPath);
493
+ Object.assign(usageByMessageId, transcript.usageByMessageId);
494
+ }
495
+ for (const transcriptPath of this.subagentTranscriptPaths) {
496
+ const namespace = transcriptPath.toolUseId ??
497
+ this.resolveSubagentNamespace(transcriptPath.agentType);
498
+ if (namespace == null || this.namespaces[namespace] == null)
201
499
  continue;
202
- if (tool.outputs == null && tool.error == null) {
203
- void tool.end(undefined, "Run not completed (conversation ended)");
500
+ const transcript = await readTranscript(transcriptPath.path);
501
+ Object.assign(usageByMessageId, transcript.usageByMessageId);
502
+ for (const turn of transcript.turns) {
503
+ this.createSyntheticAssistantRun(namespace, turn);
504
+ }
505
+ for (const toolResult of transcript.toolResults) {
506
+ const tool = this.tools[toolResult.toolUseId];
507
+ if (tool == null)
508
+ continue;
509
+ const output = isRecord(toolResult.content)
510
+ ? toolResult.content
511
+ : { content: toolResult.content };
512
+ const error = toolResult.isError
513
+ ? typeof toolResult.content === "string" ||
514
+ typeof toolResult.content === "number" ||
515
+ typeof toolResult.content === "boolean"
516
+ ? String(toolResult.content)
517
+ : JSON.stringify(toolResult.content)
518
+ : undefined;
519
+ await tool.end(output, error);
520
+ }
521
+ }
522
+ for (const [messageId, usage] of Object.entries(usageByMessageId)) {
523
+ const runTree = this.assistant[messageId];
524
+ if (runTree == null)
525
+ continue;
526
+ runTree.extra ??= {};
527
+ runTree.extra.metadata ??= {};
528
+ runTree.extra.metadata.usage_metadata = usage;
529
+ }
530
+ }
531
+ async finish() {
532
+ try {
533
+ await this.reconcileTranscripts();
534
+ if (this.resultModelUsage != null) {
535
+ correctUsageFromResults(this.resultModelUsage, Object.values(this.assistant).filter((runTree) => runTree != null));
536
+ }
537
+ // Clean up incomplete tools and finalise subagent calls. This mirrors the
538
+ // Python integration: Agent/Task tool runs are ended when their tool result
539
+ // arrives, while subagent chain runs are finalised only after transcript
540
+ // reconciliation so hidden child LLM/tool runs are created first.
541
+ for (const tool of Object.values(this.tools)) {
542
+ if (tool == null)
543
+ continue;
544
+ if (tool.outputs == null && tool.error == null) {
545
+ await tool.end(undefined, "Run not completed (conversation ended)");
546
+ }
204
547
  }
548
+ for (const subagent of Object.values(this.subagents)) {
549
+ if (subagent == null)
550
+ continue;
551
+ if (subagent.end_time == null) {
552
+ if (subagent.outputs == null && subagent.error == null) {
553
+ await subagent.end(undefined, "Run not completed (conversation ended)");
554
+ }
555
+ else {
556
+ await subagent.end();
557
+ }
558
+ }
559
+ }
560
+ // First make sure all the runs are created
561
+ await Promise.allSettled(this.postRunQueue);
562
+ // Then patch the runs
563
+ await Promise.allSettled(this.runTrees.map((runTree) => runTree.patchRun()));
564
+ }
565
+ finally {
566
+ this.dispose();
205
567
  }
206
- // First make sure all the runs are created
207
- await Promise.allSettled(this.postRunQueue);
208
- // Then patch the runs
209
- await Promise.allSettled(this.runTrees.map((runTree) => runTree.patchRun()));
210
568
  }
211
569
  }
570
+ Object.defineProperty(StreamManager, "liveManagers", {
571
+ enumerable: true,
572
+ configurable: true,
573
+ writable: true,
574
+ value: new Set()
575
+ });
576
+ Object.defineProperty(StreamManager, "managersByRootRun", {
577
+ enumerable: true,
578
+ configurable: true,
579
+ writable: true,
580
+ value: new WeakMap()
581
+ });