@uselemma/tracing 7.6.0 → 7.7.1

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/langchain.js CHANGED
@@ -5,6 +5,48 @@ exports.langChain = langChain;
5
5
  exports.langGraph = langGraph;
6
6
  const client_1 = require("./client");
7
7
  const tool_result_1 = require("./tool-result");
8
+ const KNOWN_PROVIDERS = [
9
+ "openai",
10
+ "anthropic",
11
+ "azure",
12
+ "azure_openai",
13
+ "google",
14
+ "google_genai",
15
+ "google_vertexai",
16
+ "vertexai",
17
+ "bedrock",
18
+ "amazon_bedrock",
19
+ "cohere",
20
+ "mistral",
21
+ "mistralai",
22
+ "groq",
23
+ "fireworks",
24
+ "together",
25
+ "ollama",
26
+ "huggingface",
27
+ "huggingface_hub",
28
+ "deepseek",
29
+ "xai",
30
+ "perplexity",
31
+ ];
32
+ const CLASS_PROVIDER_HINTS = [
33
+ [/openai/i, "openai"],
34
+ [/anthropic|claude/i, "anthropic"],
35
+ [/azure/i, "azure"],
36
+ [/vertex/i, "google"],
37
+ [/google|gemini/i, "google"],
38
+ [/bedrock|amazon/i, "bedrock"],
39
+ [/cohere/i, "cohere"],
40
+ [/mistral/i, "mistral"],
41
+ [/groq/i, "groq"],
42
+ [/fireworks/i, "fireworks"],
43
+ [/together/i, "together"],
44
+ [/ollama/i, "ollama"],
45
+ [/hugging ?face|hf\b/i, "huggingface"],
46
+ [/deepseek/i, "deepseek"],
47
+ [/xai|grok/i, "xai"],
48
+ [/perplexity/i, "perplexity"],
49
+ ];
8
50
  function serializedName(serialized, fallback) {
9
51
  if (typeof serialized?.name === "string" && serialized.name) {
10
52
  return serialized.name;
@@ -15,17 +57,281 @@ function serializedName(serialized, fallback) {
15
57
  }
16
58
  return fallback;
17
59
  }
18
- function modelName(serialized) {
60
+ function modelName(serialized, extraParams) {
19
61
  const kwargs = serialized?.kwargs;
20
- const value = kwargs?.model ??
21
- kwargs?.modelName ??
22
- kwargs?.model_name ??
23
- kwargs?.model_id ??
24
- serialized?.model ??
25
- serialized?.modelName ??
26
- serialized?.model_name ??
27
- serialized?.model_id;
28
- return typeof value === "string" ? value : undefined;
62
+ const sources = [kwargs, serialized, extraParams];
63
+ for (const source of sources) {
64
+ if (!source)
65
+ continue;
66
+ for (const key of [
67
+ "model",
68
+ "modelName",
69
+ "model_name",
70
+ "model_id",
71
+ "modelId",
72
+ ]) {
73
+ const value = source[key];
74
+ if (typeof value === "string" && value)
75
+ return value;
76
+ }
77
+ }
78
+ return undefined;
79
+ }
80
+ function lookupString(sources, keys) {
81
+ for (const source of sources) {
82
+ if (!source)
83
+ continue;
84
+ for (const key of keys) {
85
+ const value = source[key];
86
+ if (typeof value === "string" && value)
87
+ return value;
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+ function tagValue(tags, keys) {
93
+ if (!tags?.length)
94
+ return undefined;
95
+ for (const key of keys) {
96
+ const prefix = `${key}:`;
97
+ for (const tag of tags) {
98
+ if (typeof tag !== "string")
99
+ continue;
100
+ if (tag.startsWith(prefix)) {
101
+ const value = tag.slice(prefix.length).trim();
102
+ if (value)
103
+ return value;
104
+ }
105
+ if (tag.startsWith(`${key}=`)) {
106
+ const value = tag.slice(key.length + 1).trim();
107
+ if (value)
108
+ return value;
109
+ }
110
+ }
111
+ }
112
+ return undefined;
113
+ }
114
+ function messageContent(message) {
115
+ if (!message || typeof message !== "object")
116
+ return message;
117
+ const record = message;
118
+ if ("content" in record)
119
+ return record.content;
120
+ return message;
121
+ }
122
+ function messageRole(message) {
123
+ if (!message || typeof message !== "object")
124
+ return undefined;
125
+ const record = message;
126
+ if (typeof record.role === "string" && record.role)
127
+ return record.role;
128
+ const type = (typeof record.type === "string" && record.type) ||
129
+ (typeof record._type === "string" && record._type) ||
130
+ (typeof record.getType === "function"
131
+ ? record.getType()
132
+ : undefined);
133
+ if (!type) {
134
+ const id = record.id;
135
+ if (Array.isArray(id) && id.length > 0) {
136
+ return roleFromClassName(String(id[id.length - 1]));
137
+ }
138
+ const name = typeof record.name === "string"
139
+ ? record.name
140
+ : typeof record.constructor === "function" &&
141
+ typeof record.constructor.name === "string"
142
+ ? record.constructor.name
143
+ : undefined;
144
+ return name ? roleFromClassName(name) : undefined;
145
+ }
146
+ switch (type) {
147
+ case "human":
148
+ case "user":
149
+ return "user";
150
+ case "ai":
151
+ case "assistant":
152
+ return "assistant";
153
+ case "system":
154
+ return "system";
155
+ case "tool":
156
+ return "tool";
157
+ case "function":
158
+ return "function";
159
+ case "developer":
160
+ return "developer";
161
+ default:
162
+ return roleFromClassName(type) ?? type;
163
+ }
164
+ }
165
+ function roleFromClassName(name) {
166
+ const lower = name.toLowerCase();
167
+ if (lower.includes("human") || lower === "user")
168
+ return "user";
169
+ if (lower.includes("ai") || lower.includes("assistant"))
170
+ return "assistant";
171
+ if (lower.includes("system"))
172
+ return "system";
173
+ if (lower.includes("tool"))
174
+ return "tool";
175
+ if (lower.includes("function"))
176
+ return "function";
177
+ if (lower.includes("chat") && lower.includes("message"))
178
+ return undefined;
179
+ return undefined;
180
+ }
181
+ function toolCallsFromMessage(record) {
182
+ if (Array.isArray(record.tool_calls) && record.tool_calls.length) {
183
+ return record.tool_calls;
184
+ }
185
+ if (Array.isArray(record.toolCalls) && record.toolCalls.length) {
186
+ return record.toolCalls;
187
+ }
188
+ const additional = record.additional_kwargs;
189
+ if (additional && typeof additional === "object") {
190
+ const calls = additional.tool_calls;
191
+ if (Array.isArray(calls) && calls.length)
192
+ return calls;
193
+ }
194
+ const kwargs = record.kwargs;
195
+ if (kwargs && typeof kwargs === "object") {
196
+ const calls = kwargs.tool_calls;
197
+ if (Array.isArray(calls) && calls.length)
198
+ return calls;
199
+ }
200
+ return undefined;
201
+ }
202
+ /** Normalize LangChain message classes / dicts to `{ role, content, ... }`. */
203
+ function normalizeMessage(message) {
204
+ if (typeof message === "string") {
205
+ return { role: "user", content: message };
206
+ }
207
+ if (!message || typeof message !== "object") {
208
+ return { role: "user", content: message };
209
+ }
210
+ const record = message;
211
+ // Serialized LangChain messages often nest fields under `kwargs`.
212
+ const kwargs = record.kwargs && typeof record.kwargs === "object"
213
+ ? record.kwargs
214
+ : undefined;
215
+ const content = "content" in record
216
+ ? record.content
217
+ : kwargs && "content" in kwargs
218
+ ? kwargs.content
219
+ : messageContent(message);
220
+ const role = messageRole(message) ?? messageRole(kwargs) ?? "user";
221
+ const normalized = {
222
+ role,
223
+ content,
224
+ };
225
+ const toolCalls = toolCallsFromMessage(record) ?? (kwargs ? toolCallsFromMessage(kwargs) : undefined);
226
+ if (toolCalls)
227
+ normalized.tool_calls = toolCalls;
228
+ const toolCallId = (typeof record.tool_call_id === "string" && record.tool_call_id) ||
229
+ (typeof record.toolCallId === "string" && record.toolCallId) ||
230
+ (kwargs && typeof kwargs.tool_call_id === "string"
231
+ ? kwargs.tool_call_id
232
+ : undefined);
233
+ if (toolCallId)
234
+ normalized.tool_call_id = toolCallId;
235
+ const name = (typeof record.name === "string" && record.name) ||
236
+ (kwargs && typeof kwargs.name === "string" ? kwargs.name : undefined);
237
+ if (name && (role === "tool" || role === "function")) {
238
+ normalized.name = name;
239
+ }
240
+ return normalized;
241
+ }
242
+ function normalizeMessages(messages) {
243
+ return messages.map(normalizeMessage);
244
+ }
245
+ function asMessageList(input) {
246
+ if (Array.isArray(input))
247
+ return input;
248
+ if (input && typeof input === "object") {
249
+ const record = input;
250
+ if (Array.isArray(record.messages))
251
+ return record.messages;
252
+ if (Array.isArray(record.input))
253
+ return record.input;
254
+ }
255
+ return undefined;
256
+ }
257
+ /** Prefer the current user turn for the Lemma root input. */
258
+ function rootTraceInput(input) {
259
+ if (typeof input === "string")
260
+ return input;
261
+ const messages = asMessageList(input);
262
+ if (messages && messages.length > 0) {
263
+ for (let i = messages.length - 1; i >= 0; i--) {
264
+ const normalized = normalizeMessage(messages[i]);
265
+ if (normalized.role === "user")
266
+ return normalized.content;
267
+ }
268
+ return normalizeMessage(messages[messages.length - 1]).content;
269
+ }
270
+ if (input && typeof input === "object" && !Array.isArray(input)) {
271
+ const record = input;
272
+ for (const key of [
273
+ "input",
274
+ "question",
275
+ "query",
276
+ "prompt",
277
+ "text",
278
+ "user_input",
279
+ "userInput",
280
+ ]) {
281
+ const value = record[key];
282
+ if (typeof value === "string" && value)
283
+ return value;
284
+ }
285
+ }
286
+ return input;
287
+ }
288
+ function rootTraceOutput(output) {
289
+ if (output == null)
290
+ return output;
291
+ if (typeof output === "string")
292
+ return output;
293
+ if (output && typeof output === "object" && !Array.isArray(output)) {
294
+ const record = output;
295
+ // Preserve structured assistant payloads (e.g. tool_calls) already normalized.
296
+ if (record.role === "assistant" &&
297
+ (record.tool_calls != null || record.toolCalls != null)) {
298
+ return output;
299
+ }
300
+ }
301
+ const messages = asMessageList(output);
302
+ if (messages && messages.length > 0) {
303
+ for (let i = messages.length - 1; i >= 0; i--) {
304
+ const normalized = normalizeMessage(messages[i]);
305
+ if (normalized.role === "assistant") {
306
+ return structuredAssistantOutput(normalized);
307
+ }
308
+ }
309
+ return structuredAssistantOutput(normalizeMessage(messages[messages.length - 1]));
310
+ }
311
+ if (output && typeof output === "object" && !Array.isArray(output)) {
312
+ const record = output;
313
+ for (const key of ["output", "answer", "result", "text", "content"]) {
314
+ const value = record[key];
315
+ if (typeof value === "string" && value)
316
+ return value;
317
+ if (value && typeof value === "object") {
318
+ const nested = value;
319
+ if (typeof nested.content === "string")
320
+ return nested.content;
321
+ }
322
+ }
323
+ }
324
+ return output;
325
+ }
326
+ function structuredAssistantOutput(message) {
327
+ if (message.tool_calls) {
328
+ return {
329
+ role: "assistant",
330
+ content: message.content,
331
+ tool_calls: message.tool_calls,
332
+ };
333
+ }
334
+ return message.content;
29
335
  }
30
336
  function firstText(value) {
31
337
  if (typeof value === "string")
@@ -43,21 +349,178 @@ function firstText(value) {
43
349
  }
44
350
  return undefined;
45
351
  }
46
- function llmOutput(result) {
352
+ function generationMessage(item) {
353
+ if (!item || typeof item !== "object")
354
+ return undefined;
355
+ const record = item;
356
+ if (record.message != null)
357
+ return record.message;
358
+ // Prefer real chat messages; plain `{ text }` generation items are handled
359
+ // via firstText so we don't re-wrap them as role/content objects incorrectly.
360
+ if (typeof record.role === "string" ||
361
+ typeof record.type === "string" ||
362
+ typeof record._type === "string") {
363
+ return record;
364
+ }
365
+ if ("content" in record && typeof record.text !== "string")
366
+ return record;
367
+ return undefined;
368
+ }
369
+ function llmStructuredOutput(result) {
47
370
  const generations = result.generations;
48
371
  if (!Array.isArray(generations))
49
372
  return result;
373
+ const messages = [];
374
+ for (const group of generations) {
375
+ if (!Array.isArray(group))
376
+ continue;
377
+ for (const item of group) {
378
+ const message = generationMessage(item);
379
+ if (message != null) {
380
+ messages.push(normalizeMessage(message));
381
+ continue;
382
+ }
383
+ const text = firstText(item);
384
+ if (text != null) {
385
+ messages.push({ role: "assistant", content: text });
386
+ }
387
+ }
388
+ }
389
+ if (messages.length === 1) {
390
+ return structuredAssistantOutput(messages[0]);
391
+ }
392
+ if (messages.length > 1)
393
+ return messages;
50
394
  const text = generations.flat().map(firstText).filter(Boolean).join("");
51
395
  return text || generations;
52
396
  }
397
+ function llmOutputMessages(result) {
398
+ const generations = result.generations;
399
+ if (!Array.isArray(generations))
400
+ return undefined;
401
+ const messages = [];
402
+ for (const group of generations) {
403
+ if (!Array.isArray(group))
404
+ continue;
405
+ for (const item of group) {
406
+ const message = generationMessage(item);
407
+ if (message != null) {
408
+ messages.push(normalizeMessage(message));
409
+ continue;
410
+ }
411
+ const text = firstText(item);
412
+ if (text != null)
413
+ messages.push({ role: "assistant", content: text });
414
+ }
415
+ }
416
+ return messages.length ? messages : undefined;
417
+ }
418
+ function hasToolCalls(output) {
419
+ if (!output || typeof output !== "object")
420
+ return false;
421
+ if (Array.isArray(output))
422
+ return output.some((item) => hasToolCalls(item));
423
+ const record = output;
424
+ if (Array.isArray(record.tool_calls) && record.tool_calls.length > 0) {
425
+ return true;
426
+ }
427
+ if (Array.isArray(record.toolCalls) && record.toolCalls.length > 0) {
428
+ return true;
429
+ }
430
+ return false;
431
+ }
432
+ function providerFromId(id) {
433
+ if (!Array.isArray(id))
434
+ return undefined;
435
+ for (const part of id) {
436
+ if (typeof part !== "string")
437
+ continue;
438
+ const lower = part.toLowerCase().replace(/-/g, "_");
439
+ for (const provider of KNOWN_PROVIDERS) {
440
+ if (lower === provider || lower.includes(provider)) {
441
+ if (provider === "azure_openai")
442
+ return "azure";
443
+ if (provider === "google_genai" || provider === "google_vertexai") {
444
+ return "google";
445
+ }
446
+ if (provider === "amazon_bedrock")
447
+ return "bedrock";
448
+ if (provider === "mistralai")
449
+ return "mistral";
450
+ if (provider === "huggingface_hub")
451
+ return "huggingface";
452
+ return provider;
453
+ }
454
+ }
455
+ // langchain_openai / langchain_anthropic package ids
456
+ const pkg = lower.match(/^langchain[_]?([a-z0-9]+)/);
457
+ if (pkg?.[1] && pkg[1] !== "core" && pkg[1] !== "community") {
458
+ return providerFromClassName(pkg[1]) ?? pkg[1];
459
+ }
460
+ }
461
+ return undefined;
462
+ }
463
+ function providerFromClassName(name) {
464
+ for (const [pattern, provider] of CLASS_PROVIDER_HINTS) {
465
+ if (pattern.test(name))
466
+ return provider;
467
+ }
468
+ return undefined;
469
+ }
470
+ function llmProvider(serialized, extraParams) {
471
+ const kwargs = serialized?.kwargs;
472
+ const sources = [kwargs, serialized, extraParams];
473
+ for (const source of sources) {
474
+ if (!source)
475
+ continue;
476
+ for (const key of [
477
+ "provider",
478
+ "ls_provider",
479
+ "llm_provider",
480
+ "llmProvider",
481
+ ]) {
482
+ const value = source[key];
483
+ if (typeof value === "string" && value && value !== "langchain") {
484
+ return value;
485
+ }
486
+ }
487
+ }
488
+ const fromId = providerFromId(serialized?.id);
489
+ if (fromId)
490
+ return fromId;
491
+ const className = serializedName(serialized, "");
492
+ const fromClass = className ? providerFromClassName(className) : undefined;
493
+ if (fromClass)
494
+ return fromClass;
495
+ const type = (typeof extraParams?._type === "string" && extraParams._type) ||
496
+ (typeof kwargs?._type === "string" && kwargs._type);
497
+ if (type) {
498
+ const fromType = providerFromClassName(type);
499
+ if (fromType)
500
+ return fromType;
501
+ }
502
+ return undefined;
503
+ }
53
504
  function errorMessage(error) {
54
505
  return error instanceof Error ? error.message : String(error);
55
506
  }
507
+ function durationMs(start, end) {
508
+ return Math.max(0, end.getTime() - start.getTime());
509
+ }
510
+ function langchainAttributes(runId, parentRunId, runType) {
511
+ return Object.fromEntries(Object.entries({
512
+ "langchain.run_id": runId,
513
+ "langchain.parent_run_id": parentRunId,
514
+ "langchain.run_type": runType,
515
+ }).filter(([, value]) => value !== undefined && value !== null));
516
+ }
56
517
  class LemmaLangChainCallbackHandler {
57
518
  options;
58
519
  name = "lemma";
59
520
  lemma;
60
521
  runs = new Map();
522
+ traces = new Map();
523
+ pending = new Set();
61
524
  constructor(options = {}) {
62
525
  this.options = options;
63
526
  this.lemma = options.lemma;
@@ -71,205 +534,618 @@ class LemmaLangChainCallbackHandler {
71
534
  });
72
535
  return this.lemma;
73
536
  }
74
- traceName(serialized, fallback) {
75
- return this.options.agentName ?? serializedName(serialized, fallback);
537
+ recordInputs() {
538
+ return this.options.recordInputs !== false;
539
+ }
540
+ recordOutputs() {
541
+ return this.options.recordOutputs !== false;
542
+ }
543
+ resolveThreadId(metadata, tags) {
544
+ const key = this.options.threadIdKey ?? "threadId";
545
+ const keys = [key, "threadId", "thread_id", "conversation_id", "session_id"];
546
+ return (lookupString([metadata, this.options.metadata], keys) ??
547
+ tagValue(tags, keys));
548
+ }
549
+ resolveUserId(metadata, tags) {
550
+ if (this.options.userIdKey) {
551
+ return (lookupString([metadata, this.options.metadata], [this.options.userIdKey]) ?? tagValue(tags, [this.options.userIdKey]));
552
+ }
553
+ const keys = ["userId", "user_id", "resourceId"];
554
+ return (lookupString([metadata, this.options.metadata], keys) ??
555
+ tagValue(tags, keys));
556
+ }
557
+ applyIdentity(stored, metadata, tags) {
558
+ const threadId = this.resolveThreadId(metadata, tags);
559
+ const userId = this.resolveUserId(metadata, tags);
560
+ if (threadId)
561
+ stored.handle.threadId(threadId);
562
+ if (userId)
563
+ stored.handle.userId(userId);
76
564
  }
77
- startTrace(runId, serialized, input, fallbackName, metadata) {
78
- const trace = this.getLemma().trace({
79
- name: this.traceName(serialized, fallbackName),
80
- input: this.options.recordInputs === false ? undefined : input,
565
+ noteBounds(stored, start, end) {
566
+ if (start) {
567
+ stored.earliestStart =
568
+ !stored.earliestStart || start < stored.earliestStart
569
+ ? start
570
+ : stored.earliestStart;
571
+ }
572
+ if (end) {
573
+ stored.latestEnd =
574
+ !stored.latestEnd || end > stored.latestEnd ? end : stored.latestEnd;
575
+ }
576
+ }
577
+ noteRootInput(stored, input) {
578
+ if (!this.recordInputs() || input == null || stored.hasRootInput)
579
+ return;
580
+ stored.rootInput = rootTraceInput(input);
581
+ stored.hasRootInput = true;
582
+ stored.handle.input(stored.rootInput);
583
+ }
584
+ noteRootOutput(stored, output) {
585
+ if (!this.recordOutputs() || output == null || stored.rootError)
586
+ return;
587
+ stored.rootOutput = rootTraceOutput(output);
588
+ }
589
+ noteRootError(stored, error) {
590
+ if (!error || stored.rootError)
591
+ return;
592
+ stored.rootError = error;
593
+ }
594
+ trackPending(promise) {
595
+ this.pending.add(promise);
596
+ void promise.finally(() => this.pending.delete(promise));
597
+ }
598
+ createOwnedTrace(runId, name, input, kind, metadata, tags) {
599
+ const startedAt = new Date();
600
+ const handle = this.getLemma().trace({
601
+ name,
602
+ input: this.recordInputs() ? rootTraceInput(input) : undefined,
81
603
  metadata: {
82
604
  ...this.options.metadata,
83
605
  ...(metadata ?? {}),
84
606
  langchainRunId: runId,
85
607
  },
608
+ threadId: this.resolveThreadId(metadata, tags),
609
+ userId: this.resolveUserId(metadata, tags),
610
+ startedAt,
86
611
  });
87
- this.runs.set(runId, { trace, type: "chain" });
88
- return trace;
612
+ const stored = {
613
+ handle,
614
+ ended: false,
615
+ openedAt: startedAt,
616
+ earliestStart: startedAt,
617
+ hasRootInput: this.recordInputs() && input != null,
618
+ rootInput: this.recordInputs() ? rootTraceInput(input) : undefined,
619
+ };
620
+ this.traces.set(runId, stored);
621
+ const run = {
622
+ owningTraceId: runId,
623
+ rootRunId: runId,
624
+ kind,
625
+ startedAt,
626
+ ownsTrace: true,
627
+ };
628
+ this.runs.set(runId, run);
629
+ return { stored, run };
630
+ }
631
+ storedTrace(owningTraceId) {
632
+ return this.traces.get(owningTraceId);
89
633
  }
90
- parent(runId) {
91
- if (!runId)
634
+ parentRun(parentRunId) {
635
+ if (!parentRunId)
92
636
  return undefined;
93
- return this.runs.get(runId);
637
+ return this.runs.get(parentRunId);
638
+ }
639
+ /**
640
+ * Resolve the parent attachment target.
641
+ * - Known parent → attach under that parent's owning trace.
642
+ * - Missing / unknown parent → create a NEW owned trace for this run
643
+ * (never overwrite another concurrent trace's state).
644
+ */
645
+ resolveAttachment(runId, parentRunId, createRoot) {
646
+ const parent = this.parentRun(parentRunId);
647
+ if (!parent) {
648
+ const created = createRoot();
649
+ return {
650
+ stored: created.stored,
651
+ parentId: undefined,
652
+ ownsTrace: true,
653
+ owningTraceId: runId,
654
+ rootRunId: runId,
655
+ };
656
+ }
657
+ const stored = this.storedTrace(parent.owningTraceId);
658
+ if (!stored || stored.ended) {
659
+ // Parent bookkeeping is gone — start a fresh owned trace rather than
660
+ // leaking into / stealing another concurrent run's state.
661
+ const created = createRoot();
662
+ return {
663
+ stored: created.stored,
664
+ parentId: undefined,
665
+ ownsTrace: true,
666
+ owningTraceId: runId,
667
+ rootRunId: runId,
668
+ };
669
+ }
670
+ // Orphan-safe: only nest under a still-open parent handle; otherwise attach
671
+ // at the root of the same owned trace (parentId undefined).
672
+ return {
673
+ stored,
674
+ parentId: parent.handle?.id,
675
+ ownsTrace: false,
676
+ owningTraceId: parent.owningTraceId,
677
+ rootRunId: parent.rootRunId,
678
+ };
679
+ }
680
+ forgetTraceRuns(owningTraceId) {
681
+ for (const [runId, run] of this.runs) {
682
+ if (run.owningTraceId === owningTraceId)
683
+ this.runs.delete(runId);
684
+ }
685
+ }
686
+ async finalizeTrace(owningTraceId, stored) {
687
+ this.traces.delete(owningTraceId);
688
+ this.forgetTraceRuns(owningTraceId);
689
+ if (stored.ended)
690
+ return;
691
+ stored.ended = true;
692
+ const endedAt = stored.latestEnd ?? new Date();
693
+ const startedAt = stored.earliestStart ?? stored.openedAt ?? endedAt;
694
+ const timing = {
695
+ startedAt,
696
+ endedAt,
697
+ durationMs: durationMs(startedAt, endedAt),
698
+ };
699
+ if (stored.rootError) {
700
+ stored.handle.fail(this.recordOutputs() ? stored.rootError : "error");
701
+ const promise = stored.handle.end(timing);
702
+ this.trackPending(promise);
703
+ await promise;
704
+ return;
705
+ }
706
+ if (!this.recordOutputs() || stored.rootOutput === undefined) {
707
+ const promise = stored.handle.end(timing);
708
+ this.trackPending(promise);
709
+ await promise;
710
+ return;
711
+ }
712
+ const promise = stored.handle.end({
713
+ output: stored.rootOutput,
714
+ ...timing,
715
+ });
716
+ this.trackPending(promise);
717
+ await promise;
718
+ }
719
+ maybeFinalizeOwner(run, endedAt) {
720
+ if (!run.ownsTrace)
721
+ return;
722
+ const stored = this.traces.get(run.owningTraceId);
723
+ if (!stored)
724
+ return;
725
+ this.noteBounds(stored, run.startedAt, endedAt);
726
+ const promise = this.finalizeTrace(run.owningTraceId, stored);
727
+ this.trackPending(promise);
728
+ return promise;
729
+ }
730
+ traceName(serialized, fallback) {
731
+ return this.options.agentName ?? serializedName(serialized, fallback);
94
732
  }
95
- handleChainStart(serialized, inputs, runId, parentRunId, _tags, metadata, _runType, name) {
96
- const parent = this.parent(parentRunId);
733
+ handleChainStart(serialized, inputs, runId, parentRunId, tags, metadata, runType, name) {
734
+ const startedAt = new Date();
735
+ const chainName = name ?? serializedName(serialized, "langchain-chain");
736
+ const parent = this.parentRun(parentRunId);
97
737
  if (!parent) {
98
- this.startTrace(runId, { ...serialized, name: name ?? serialized?.name }, inputs, "langchain-run", metadata);
738
+ this.createOwnedTrace(runId, this.traceName({ ...serialized, name: name ?? serialized?.name }, "langchain-run"), inputs, "chain", metadata, tags);
739
+ return;
740
+ }
741
+ const stored = this.storedTrace(parent.owningTraceId);
742
+ if (!stored || stored.ended) {
743
+ this.createOwnedTrace(runId, this.traceName({ ...serialized, name: name ?? serialized?.name }, "langchain-run"), inputs, "chain", metadata, tags);
99
744
  return;
100
745
  }
101
- const handle = (parent.handle ?? parent.trace)?.startSpan({
102
- name: name ?? serializedName(serialized, "langchain-chain"),
103
- input: this.options.recordInputs === false ? undefined : inputs,
746
+ this.applyIdentity(stored, metadata, tags);
747
+ this.noteBounds(stored, startedAt, undefined);
748
+ // Nested chains (incl. LangGraph nodes) become child spans; do not steal
749
+ // root input from intermediate node state after the root already set it.
750
+ const handle = stored.handle.startSpan({
751
+ name: chainName,
752
+ parentId: parent.handle?.id,
753
+ input: this.recordInputs() ? inputs : undefined,
104
754
  metadata: this.options.metadata,
105
- attributes: {
106
- "langchain.run_id": runId,
107
- "langchain.parent_run_id": parentRunId,
108
- "langchain.run_type": "chain",
109
- },
755
+ attributes: langchainAttributes(runId, parentRunId, runType || "chain"),
756
+ startedAt,
757
+ });
758
+ this.runs.set(runId, {
759
+ owningTraceId: parent.owningTraceId,
760
+ rootRunId: parent.rootRunId,
761
+ handle,
762
+ kind: "chain",
763
+ startedAt,
764
+ parentRunId,
765
+ ownsTrace: false,
110
766
  });
111
- this.runs.set(runId, { handle, parentRunId, type: "chain" });
112
767
  }
113
768
  async handleChainEnd(outputs, runId) {
114
769
  const run = this.runs.get(runId);
115
770
  if (!run)
116
771
  return;
117
- if (run.trace) {
118
- await run.trace.end(this.options.recordOutputs === false ? undefined : { output: outputs });
119
- this.runs.delete(runId);
120
- return;
772
+ const endedAt = new Date();
773
+ const stored = this.storedTrace(run.owningTraceId);
774
+ if (run.handle) {
775
+ run.handle.end({
776
+ output: this.recordOutputs() ? outputs : undefined,
777
+ endedAt,
778
+ durationMs: durationMs(run.startedAt, endedAt),
779
+ });
780
+ }
781
+ if (stored) {
782
+ this.noteBounds(stored, run.startedAt, endedAt);
783
+ if (run.ownsTrace) {
784
+ this.noteRootOutput(stored, outputs);
785
+ }
121
786
  }
122
- run.handle?.end({
123
- output: this.options.recordOutputs === false ? undefined : outputs,
124
- });
125
787
  this.runs.delete(runId);
788
+ if (run.ownsTrace && stored) {
789
+ await this.finalizeTrace(run.owningTraceId, stored);
790
+ }
126
791
  }
127
792
  async handleChainError(error, runId) {
128
793
  const run = this.runs.get(runId);
129
794
  if (!run)
130
795
  return;
131
- if (run.trace) {
132
- run.trace.fail(error);
133
- await run.trace.end();
134
- this.runs.delete(runId);
135
- return;
796
+ const endedAt = new Date();
797
+ const message = errorMessage(error);
798
+ const stored = this.storedTrace(run.owningTraceId);
799
+ if (run.handle) {
800
+ run.handle.end({
801
+ status: "ERROR",
802
+ error: this.recordOutputs() ? message : undefined,
803
+ endedAt,
804
+ durationMs: durationMs(run.startedAt, endedAt),
805
+ });
806
+ }
807
+ if (stored) {
808
+ this.noteBounds(stored, run.startedAt, endedAt);
809
+ if (run.ownsTrace) {
810
+ this.noteRootError(stored, message);
811
+ }
136
812
  }
137
- run.handle?.end({ status: "ERROR", error: errorMessage(error) });
138
813
  this.runs.delete(runId);
814
+ if (run.ownsTrace && stored) {
815
+ await this.finalizeTrace(run.owningTraceId, stored);
816
+ }
139
817
  }
140
- handleLLMStart(serialized, prompts, runId, parentRunId, extraParams) {
141
- const parent = this.parent(parentRunId);
142
- const trace = parent?.trace ??
143
- parent?.handle ??
144
- this.startTrace(runId, serialized, prompts, "langchain-llm");
145
- const handle = trace.startGeneration({
818
+ handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata) {
819
+ const startedAt = new Date();
820
+ const attachment = this.resolveAttachment(runId, parentRunId, () => this.createOwnedTrace(runId, this.traceName(serialized, "langchain-llm"), prompts, "llm", metadata, tags));
821
+ // createOwnedTrace already registered the run when ownsTrace; update it.
822
+ if (attachment.ownsTrace) {
823
+ this.noteRootInput(attachment.stored, prompts);
824
+ }
825
+ this.applyIdentity(attachment.stored, metadata, tags);
826
+ this.noteBounds(attachment.stored, startedAt, undefined);
827
+ const provider = llmProvider(serialized, extraParams);
828
+ const model = modelName(serialized, extraParams);
829
+ const handle = attachment.stored.handle.startGeneration({
146
830
  name: serializedName(serialized, "langchain-llm"),
147
- input: this.options.recordInputs === false ? undefined : prompts,
831
+ parentId: attachment.parentId,
832
+ input: this.recordInputs() ? prompts : undefined,
148
833
  metadata: this.options.metadata,
149
- model: modelName(serialized),
150
- llmProvider: "langchain",
151
- llmInputMessages: this.options.recordInputs === false
152
- ? undefined
153
- : prompts.map((content) => ({ role: "user", content })),
834
+ model,
835
+ llmProvider: provider,
836
+ llmInputMessages: this.recordInputs()
837
+ ? prompts.map((content) => ({ role: "user", content }))
838
+ : undefined,
154
839
  llmInvocationParameters: extraParams,
155
- attributes: {
156
- "langchain.run_id": runId,
157
- "langchain.parent_run_id": parentRunId,
158
- "langchain.run_type": "llm",
159
- },
840
+ attributes: langchainAttributes(runId, parentRunId, "llm"),
841
+ startedAt,
842
+ });
843
+ this.runs.set(runId, {
844
+ owningTraceId: attachment.owningTraceId,
845
+ rootRunId: attachment.rootRunId,
846
+ handle,
847
+ kind: "llm",
848
+ startedAt,
849
+ parentRunId,
850
+ ownsTrace: attachment.ownsTrace,
160
851
  });
161
- this.runs.set(runId, { handle, parentRunId, type: "llm" });
162
852
  }
163
- handleChatModelStart(serialized, messages, runId, parentRunId, extraParams) {
853
+ handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata) {
854
+ const startedAt = new Date();
164
855
  const flatMessages = messages.flat();
165
- const parent = this.parent(parentRunId);
166
- const trace = parent?.trace ??
167
- parent?.handle ??
168
- this.startTrace(runId, serialized, flatMessages, "langchain-chat-model");
169
- const handle = trace.startGeneration({
856
+ const normalized = this.recordInputs()
857
+ ? normalizeMessages(flatMessages)
858
+ : undefined;
859
+ const attachment = this.resolveAttachment(runId, parentRunId, () => this.createOwnedTrace(runId, this.traceName(serialized, "langchain-chat-model"), flatMessages, "llm", metadata, tags));
860
+ if (attachment.ownsTrace) {
861
+ this.noteRootInput(attachment.stored, flatMessages);
862
+ }
863
+ this.applyIdentity(attachment.stored, metadata, tags);
864
+ this.noteBounds(attachment.stored, startedAt, undefined);
865
+ const provider = llmProvider(serialized, extraParams);
866
+ const model = modelName(serialized, extraParams);
867
+ const handle = attachment.stored.handle.startGeneration({
170
868
  name: serializedName(serialized, "langchain-chat-model"),
171
- input: this.options.recordInputs === false ? undefined : flatMessages,
869
+ parentId: attachment.parentId,
870
+ input: this.recordInputs() ? normalized : undefined,
172
871
  metadata: this.options.metadata,
173
- model: modelName(serialized),
174
- llmProvider: "langchain",
175
- llmInputMessages: this.options.recordInputs === false ? undefined : flatMessages,
872
+ model,
873
+ llmProvider: provider,
874
+ llmInputMessages: normalized,
176
875
  llmInvocationParameters: extraParams,
177
- attributes: {
178
- "langchain.run_id": runId,
179
- "langchain.parent_run_id": parentRunId,
180
- "langchain.run_type": "llm",
181
- },
876
+ attributes: langchainAttributes(runId, parentRunId, "llm"),
877
+ startedAt,
878
+ });
879
+ this.runs.set(runId, {
880
+ owningTraceId: attachment.owningTraceId,
881
+ rootRunId: attachment.rootRunId,
882
+ handle,
883
+ kind: "llm",
884
+ startedAt,
885
+ parentRunId,
886
+ ownsTrace: attachment.ownsTrace,
182
887
  });
183
- this.runs.set(runId, { handle, parentRunId, type: "llm" });
184
888
  }
185
- handleLLMEnd(output, runId) {
889
+ deferredOwnerFor(owningTraceId) {
890
+ for (const run of this.runs.values()) {
891
+ if (run.ownsTrace &&
892
+ run.deferFinalize &&
893
+ run.owningTraceId === owningTraceId) {
894
+ return run;
895
+ }
896
+ }
897
+ return undefined;
898
+ }
899
+ async handleLLMEnd(output, runId) {
186
900
  const run = this.runs.get(runId);
187
901
  if (!run?.handle)
188
902
  return;
189
- const outputText = llmOutput(output);
903
+ const endedAt = new Date();
904
+ const structured = llmStructuredOutput(output);
905
+ const outputMessages = llmOutputMessages(output);
906
+ const softError = (0, tool_result_1.toolResultError)(structured);
907
+ const awaitingTools = !softError && hasToolCalls(structured);
190
908
  run.handle.end({
191
- output: this.options.recordOutputs === false ? undefined : outputText,
192
- llmOutputMessages: this.options.recordOutputs === false || outputText === undefined
193
- ? undefined
194
- : [{ role: "assistant", content: outputText }],
909
+ output: this.recordOutputs() && !softError ? structured : undefined,
910
+ error: this.recordOutputs() ? (softError ?? undefined) : undefined,
911
+ status: softError ? "ERROR" : undefined,
912
+ endedAt,
913
+ durationMs: durationMs(run.startedAt, endedAt),
914
+ llmOutputMessages: this.recordOutputs() && !softError ? outputMessages : undefined,
195
915
  });
916
+ const stored = this.storedTrace(run.owningTraceId);
917
+ if (stored) {
918
+ this.noteBounds(stored, run.startedAt, endedAt);
919
+ if (run.ownsTrace) {
920
+ if (softError)
921
+ this.noteRootError(stored, softError);
922
+ else
923
+ this.noteRootOutput(stored, structured);
924
+ }
925
+ else if (!softError) {
926
+ // Prefer chain-level root output; refresh from later generations.
927
+ this.noteRootOutput(stored, structured);
928
+ }
929
+ }
930
+ if (awaitingTools) {
931
+ // Keep the run stub so tool/follow-up generation callbacks can nest
932
+ // under this generation (whether or not this run owns the root trace).
933
+ if (run.ownsTrace)
934
+ run.deferFinalize = true;
935
+ return;
936
+ }
196
937
  this.runs.delete(runId);
938
+ if (run.ownsTrace) {
939
+ await this.maybeFinalizeOwner(run, endedAt);
940
+ return;
941
+ }
942
+ // Final answer generation under a deferred owned LLM — close that root.
943
+ const deferred = this.deferredOwnerFor(run.owningTraceId);
944
+ if (deferred) {
945
+ this.runs.delete(deferred.rootRunId);
946
+ await this.maybeFinalizeOwner(deferred, endedAt);
947
+ }
948
+ }
949
+ async finalizeDeferredOwner(owningTraceId, endedAt, rootError) {
950
+ const deferred = this.deferredOwnerFor(owningTraceId);
951
+ if (!deferred)
952
+ return;
953
+ const stored = this.storedTrace(deferred.owningTraceId);
954
+ if (stored && rootError)
955
+ this.noteRootError(stored, rootError);
956
+ this.runs.delete(deferred.rootRunId);
957
+ await this.maybeFinalizeOwner(deferred, endedAt);
197
958
  }
198
- handleLLMError(error, runId) {
959
+ async handleLLMError(error, runId) {
199
960
  const run = this.runs.get(runId);
200
- run?.handle?.end({ status: "ERROR", error: errorMessage(error) });
961
+ if (!run)
962
+ return;
963
+ const endedAt = new Date();
964
+ const message = errorMessage(error);
965
+ run.handle?.end({
966
+ status: "ERROR",
967
+ error: this.recordOutputs() ? message : undefined,
968
+ endedAt,
969
+ durationMs: durationMs(run.startedAt, endedAt),
970
+ });
971
+ const stored = this.storedTrace(run.owningTraceId);
972
+ if (stored) {
973
+ this.noteBounds(stored, run.startedAt, endedAt);
974
+ if (run.ownsTrace)
975
+ this.noteRootError(stored, message);
976
+ }
201
977
  this.runs.delete(runId);
978
+ if (run.ownsTrace) {
979
+ await this.maybeFinalizeOwner(run, endedAt);
980
+ return;
981
+ }
982
+ await this.finalizeDeferredOwner(run.owningTraceId, endedAt, message);
202
983
  }
203
- handleToolStart(serialized, input, runId, parentRunId) {
204
- const parent = this.parent(parentRunId);
205
- const trace = parent?.trace ??
206
- parent?.handle ??
207
- this.startTrace(runId, serialized, input, "langchain-tool");
984
+ handleToolStart(serialized, input, runId, parentRunId, tags, metadata) {
985
+ const startedAt = new Date();
986
+ const attachment = this.resolveAttachment(runId, parentRunId, () => this.createOwnedTrace(runId, this.traceName(serialized, "langchain-tool"), input, "tool", metadata, tags));
987
+ if (attachment.ownsTrace) {
988
+ this.noteRootInput(attachment.stored, input);
989
+ }
990
+ this.applyIdentity(attachment.stored, metadata, tags);
991
+ this.noteBounds(attachment.stored, startedAt, undefined);
208
992
  const name = serializedName(serialized, "langchain-tool");
209
- const handle = trace.startTool({
993
+ const handle = attachment.stored.handle.startTool({
210
994
  name,
995
+ parentId: attachment.parentId,
211
996
  toolName: name,
212
- input: this.options.recordInputs === false ? undefined : input,
997
+ input: this.recordInputs() ? input : undefined,
213
998
  metadata: this.options.metadata,
214
- attributes: {
215
- "langchain.run_id": runId,
216
- "langchain.parent_run_id": parentRunId,
217
- "langchain.run_type": "tool",
218
- },
999
+ attributes: langchainAttributes(runId, parentRunId, "tool"),
1000
+ startedAt,
1001
+ });
1002
+ this.runs.set(runId, {
1003
+ owningTraceId: attachment.owningTraceId,
1004
+ rootRunId: attachment.rootRunId,
1005
+ handle,
1006
+ kind: "tool",
1007
+ startedAt,
1008
+ parentRunId,
1009
+ ownsTrace: attachment.ownsTrace,
219
1010
  });
220
- this.runs.set(runId, { handle, parentRunId, type: "tool" });
221
1011
  }
222
- handleToolEnd(output, runId) {
1012
+ async handleToolEnd(output, runId) {
223
1013
  const run = this.runs.get(runId);
224
1014
  if (!run)
225
1015
  return;
1016
+ const endedAt = new Date();
226
1017
  const softError = (0, tool_result_1.toolResultError)(output);
227
1018
  if (softError) {
228
1019
  run.handle?.end({
229
1020
  status: "ERROR",
230
- error: this.options.recordOutputs === false ? undefined : softError,
1021
+ error: this.recordOutputs() ? softError : undefined,
1022
+ endedAt,
1023
+ durationMs: durationMs(run.startedAt, endedAt),
231
1024
  });
232
- this.runs.delete(runId);
233
- return;
234
1025
  }
235
- run.handle?.end({
236
- output: this.options.recordOutputs === false ? undefined : output,
237
- });
1026
+ else {
1027
+ run.handle?.end({
1028
+ output: this.recordOutputs() ? output : undefined,
1029
+ endedAt,
1030
+ durationMs: durationMs(run.startedAt, endedAt),
1031
+ });
1032
+ }
1033
+ const stored = this.storedTrace(run.owningTraceId);
1034
+ if (stored) {
1035
+ this.noteBounds(stored, run.startedAt, endedAt);
1036
+ if (run.ownsTrace) {
1037
+ if (softError)
1038
+ this.noteRootError(stored, softError);
1039
+ else
1040
+ this.noteRootOutput(stored, output);
1041
+ }
1042
+ }
238
1043
  this.runs.delete(runId);
1044
+ await this.maybeFinalizeOwner(run, endedAt);
239
1045
  }
240
- handleToolError(error, runId) {
1046
+ async handleToolError(error, runId) {
241
1047
  const run = this.runs.get(runId);
242
- run?.handle?.end({ status: "ERROR", error: errorMessage(error) });
1048
+ if (!run)
1049
+ return;
1050
+ const endedAt = new Date();
1051
+ const message = errorMessage(error);
1052
+ run.handle?.end({
1053
+ status: "ERROR",
1054
+ error: this.recordOutputs() ? message : undefined,
1055
+ endedAt,
1056
+ durationMs: durationMs(run.startedAt, endedAt),
1057
+ });
1058
+ const stored = this.storedTrace(run.owningTraceId);
1059
+ if (stored) {
1060
+ this.noteBounds(stored, run.startedAt, endedAt);
1061
+ if (run.ownsTrace)
1062
+ this.noteRootError(stored, message);
1063
+ }
243
1064
  this.runs.delete(runId);
1065
+ if (run.ownsTrace) {
1066
+ await this.maybeFinalizeOwner(run, endedAt);
1067
+ return;
1068
+ }
1069
+ await this.finalizeDeferredOwner(run.owningTraceId, endedAt, message);
244
1070
  }
245
- handleRetrieverStart(serialized, query, runId, parentRunId) {
246
- const parent = this.parent(parentRunId);
247
- const trace = parent?.trace ??
248
- parent?.handle ??
249
- this.startTrace(runId, serialized, query, "langchain-retriever");
250
- const handle = trace.startSpan({
1071
+ handleRetrieverStart(serialized, query, runId, parentRunId, tags, metadata) {
1072
+ const startedAt = new Date();
1073
+ const attachment = this.resolveAttachment(runId, parentRunId, () => this.createOwnedTrace(runId, this.traceName(serialized, "langchain-retriever"), query, "retriever", metadata, tags));
1074
+ if (attachment.ownsTrace) {
1075
+ this.noteRootInput(attachment.stored, query);
1076
+ }
1077
+ this.applyIdentity(attachment.stored, metadata, tags);
1078
+ this.noteBounds(attachment.stored, startedAt, undefined);
1079
+ const handle = attachment.stored.handle.startSpan({
251
1080
  name: serializedName(serialized, "langchain-retriever"),
252
- input: this.options.recordInputs === false ? undefined : query,
1081
+ parentId: attachment.parentId,
1082
+ input: this.recordInputs() ? query : undefined,
253
1083
  metadata: this.options.metadata,
254
- attributes: {
255
- "langchain.run_id": runId,
256
- "langchain.parent_run_id": parentRunId,
257
- "langchain.run_type": "retriever",
258
- },
1084
+ attributes: langchainAttributes(runId, parentRunId, "retriever"),
1085
+ startedAt,
1086
+ });
1087
+ this.runs.set(runId, {
1088
+ owningTraceId: attachment.owningTraceId,
1089
+ rootRunId: attachment.rootRunId,
1090
+ handle,
1091
+ kind: "retriever",
1092
+ startedAt,
1093
+ parentRunId,
1094
+ ownsTrace: attachment.ownsTrace,
259
1095
  });
260
- this.runs.set(runId, { handle, parentRunId, type: "retriever" });
261
1096
  }
262
- handleRetrieverEnd(documents, runId) {
1097
+ async handleRetrieverEnd(documents, runId) {
263
1098
  const run = this.runs.get(runId);
264
- run?.handle?.end({
265
- output: this.options.recordOutputs === false ? undefined : documents,
1099
+ if (!run)
1100
+ return;
1101
+ const endedAt = new Date();
1102
+ run.handle?.end({
1103
+ output: this.recordOutputs() ? documents : undefined,
1104
+ endedAt,
1105
+ durationMs: durationMs(run.startedAt, endedAt),
266
1106
  });
1107
+ const stored = this.storedTrace(run.owningTraceId);
1108
+ if (stored) {
1109
+ this.noteBounds(stored, run.startedAt, endedAt);
1110
+ if (run.ownsTrace)
1111
+ this.noteRootOutput(stored, documents);
1112
+ }
267
1113
  this.runs.delete(runId);
1114
+ await this.maybeFinalizeOwner(run, endedAt);
268
1115
  }
269
- handleRetrieverError(error, runId) {
1116
+ async handleRetrieverError(error, runId) {
270
1117
  const run = this.runs.get(runId);
271
- run?.handle?.end({ status: "ERROR", error: errorMessage(error) });
1118
+ if (!run)
1119
+ return;
1120
+ const endedAt = new Date();
1121
+ const message = errorMessage(error);
1122
+ run.handle?.end({
1123
+ status: "ERROR",
1124
+ error: this.recordOutputs() ? message : undefined,
1125
+ endedAt,
1126
+ durationMs: durationMs(run.startedAt, endedAt),
1127
+ });
1128
+ const stored = this.storedTrace(run.owningTraceId);
1129
+ if (stored) {
1130
+ this.noteBounds(stored, run.startedAt, endedAt);
1131
+ if (run.ownsTrace)
1132
+ this.noteRootError(stored, message);
1133
+ }
272
1134
  this.runs.delete(runId);
1135
+ await this.maybeFinalizeOwner(run, endedAt);
1136
+ }
1137
+ /** Await outstanding terminal deliveries. */
1138
+ async flush() {
1139
+ await Promise.all([
1140
+ ...Array.from(this.traces.entries(), ([id, stored]) => this.finalizeTrace(id, stored)),
1141
+ ...Array.from(this.pending),
1142
+ ]);
1143
+ }
1144
+ /** Finalize open traces and reset integration state. */
1145
+ async shutdown() {
1146
+ await this.flush();
1147
+ this.runs.clear();
1148
+ this.traces.clear();
273
1149
  }
274
1150
  }
275
1151
  exports.LemmaLangChainCallbackHandler = LemmaLangChainCallbackHandler;
@@ -277,6 +1153,11 @@ exports.LangChainCallbackHandler = LemmaLangChainCallbackHandler;
277
1153
  function langChain(options = {}) {
278
1154
  return new LemmaLangChainCallbackHandler(options);
279
1155
  }
1156
+ /**
1157
+ * LangGraph adapter: LangGraph emits LangChain callback events, so this is the
1158
+ * same handler with a LangGraph default trace name. Nesting, identity, and
1159
+ * finalization semantics match `langChain()`.
1160
+ */
280
1161
  function langGraph(options = {}) {
281
1162
  return langChain({ agentName: "langgraph-agent", ...options });
282
1163
  }