@agent-inspect/langchain 6.7.5 → 6.9.0

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/index.mjs CHANGED
@@ -317,6 +317,247 @@ function streamMetadataFromState(state, options) {
317
317
  }
318
318
  return out;
319
319
  }
320
+
321
+ // packages/langchain/src/tool-identity.ts
322
+ function serializedLabel(s) {
323
+ if (typeof s.name === "string" && s.name.trim()) return s.name.trim();
324
+ if (Array.isArray(s.id) && s.id.length > 0) {
325
+ const last = s.id[s.id.length - 1];
326
+ if (typeof last === "string" && last.trim()) return last.trim();
327
+ }
328
+ return typeof s.type === "string" && s.type.trim() ? s.type.trim() : void 0;
329
+ }
330
+ function nonEmpty(value) {
331
+ if (typeof value !== "string") return void 0;
332
+ const trimmed = value.trim();
333
+ return trimmed.length > 0 ? trimmed : void 0;
334
+ }
335
+ function resolveToolIdentity(tool, runName, metadata, toolCallId) {
336
+ const frameworkRunName = nonEmpty(runName);
337
+ const metaToolName = nonEmpty(metadata?.toolName);
338
+ const metaTool = nonEmpty(metadata?.tool);
339
+ const classLabel = serializedLabel(tool);
340
+ const callId = nonEmpty(toolCallId) ?? nonEmpty(metadata?.toolCallId) ?? nonEmpty(metadata?.tool_call_id);
341
+ const displayName = frameworkRunName ?? metaToolName ?? metaTool ?? classLabel ?? "unknown-tool";
342
+ return {
343
+ displayName,
344
+ toolName: displayName,
345
+ ...classLabel && classLabel !== displayName ? { toolClass: classLabel } : {},
346
+ ...callId ? { toolCallId: callId } : {},
347
+ ...frameworkRunName ? { frameworkRunName } : {}
348
+ };
349
+ }
350
+ function applyToolIdentityAttributes(attrs, identity) {
351
+ attrs.tool = identity.displayName;
352
+ attrs.toolName = identity.toolName;
353
+ if (identity.toolClass) attrs.toolClass = identity.toolClass;
354
+ if (identity.toolCallId) attrs.toolCallId = identity.toolCallId;
355
+ if (identity.frameworkRunName) attrs.frameworkRunName = identity.frameworkRunName;
356
+ }
357
+
358
+ // packages/langchain/src/persist-intent.ts
359
+ function resolvePersistIntent(input) {
360
+ const hasTraceDir = typeof input.traceDir === "string" && input.traceDir.trim().length > 0;
361
+ const explicit = input.persist;
362
+ if (explicit === false) {
363
+ return { persist: false, contradictory: hasTraceDir };
364
+ }
365
+ if (explicit === true) {
366
+ return { persist: true, contradictory: false };
367
+ }
368
+ return { persist: hasTraceDir, contradictory: false };
369
+ }
370
+
371
+ // packages/langchain/src/invocation-state.ts
372
+ function createInvocationState(envelopeRunId) {
373
+ return {
374
+ envelopeRunId,
375
+ activeRuns: /* @__PURE__ */ new Map(),
376
+ endedRuns: /* @__PURE__ */ new Set(),
377
+ knownRelationships: /* @__PURE__ */ new Map(),
378
+ pendingRelationships: [],
379
+ completionGeneration: 0,
380
+ envelopeStarted: false,
381
+ finalized: false
382
+ };
383
+ }
384
+ function isObservedCallbackRun(state, parentLcRunId) {
385
+ return state.activeRuns.has(parentLcRunId) || state.endedRuns.has(parentLcRunId);
386
+ }
387
+ function beginCallbackRun(state, input) {
388
+ state.completionGeneration += 1;
389
+ const run = {
390
+ lcRunId: input.lcRunId,
391
+ ...input.parentLcRunId !== void 0 ? { parentLcRunId: input.parentLcRunId } : {},
392
+ startedAt: input.startedAt,
393
+ ...input.kind !== void 0 ? { kind: input.kind } : {},
394
+ ...input.stepId !== void 0 ? { stepId: input.stepId } : {}
395
+ };
396
+ state.activeRuns.set(input.lcRunId, run);
397
+ if (input.parentLcRunId !== void 0) {
398
+ if (isObservedCallbackRun(state, input.parentLcRunId)) {
399
+ state.knownRelationships.set(input.lcRunId, input.parentLcRunId);
400
+ } else {
401
+ state.pendingRelationships.push({
402
+ childLcRunId: input.lcRunId,
403
+ parentLcRunId: input.parentLcRunId,
404
+ reason: "unobserved-parent"
405
+ });
406
+ }
407
+ }
408
+ return run;
409
+ }
410
+ function endCallbackRun(state, lcRunId) {
411
+ const active = state.activeRuns.get(lcRunId);
412
+ if (active === void 0) {
413
+ if (state.endedRuns.has(lcRunId) || state.finalized) {
414
+ return { ended: false, late: true };
415
+ }
416
+ return { ended: false, late: true };
417
+ }
418
+ state.completionGeneration += 1;
419
+ state.activeRuns.delete(lcRunId);
420
+ state.endedRuns.add(lcRunId);
421
+ return { ended: true, late: false };
422
+ }
423
+ function markEnvelopeStarted(state, startTime) {
424
+ if (state.envelopeStarted) return false;
425
+ state.envelopeStarted = true;
426
+ state.runStartTime = startTime;
427
+ return true;
428
+ }
429
+ function markFinalized(state) {
430
+ if (state.finalized) return false;
431
+ if (!state.envelopeStarted) return false;
432
+ state.finalized = true;
433
+ return true;
434
+ }
435
+ function noteTerminalError(state, message) {
436
+ state.terminalError = { message };
437
+ }
438
+ function canScheduleFinalize(state) {
439
+ return state.envelopeStarted && !state.finalized && state.activeRuns.size === 0;
440
+ }
441
+ function bumpCompletionGeneration(state) {
442
+ state.completionGeneration += 1;
443
+ return state.completionGeneration;
444
+ }
445
+ function resetInvocationState(state, nextEnvelopeRunId) {
446
+ state.activeRuns.clear();
447
+ state.endedRuns.clear();
448
+ state.knownRelationships.clear();
449
+ state.pendingRelationships.length = 0;
450
+ state.terminalError = void 0;
451
+ state.completionGeneration += 1;
452
+ state.envelopeStarted = false;
453
+ state.finalized = false;
454
+ state.runStartTime = void 0;
455
+ if (nextEnvelopeRunId !== void 0) {
456
+ state.envelopeRunId = nextEnvelopeRunId;
457
+ }
458
+ }
459
+
460
+ // packages/langchain/src/parent-reconciliation.ts
461
+ var WELL_KNOWN_SEMANTIC_PARENTS = /* @__PURE__ */ new Set([
462
+ "LangGraph",
463
+ "__start__",
464
+ "__end__"
465
+ ]);
466
+ function isSemanticParentLabel(parentLcRunId) {
467
+ if (WELL_KNOWN_SEMANTIC_PARENTS.has(parentLcRunId)) return true;
468
+ return parentLcRunId.startsWith("__") && parentLcRunId.endsWith("__");
469
+ }
470
+ var LANGGRAPH_PARENT_KEYS = [
471
+ "handoffFrom",
472
+ "taskId",
473
+ "nodeId",
474
+ "nodeName",
475
+ "checkpointNamespace"
476
+ ];
477
+ function langGraphRecord(attributes) {
478
+ const lg = attributes?.langGraph;
479
+ if (typeof lg !== "object" || lg === null || Array.isArray(lg)) return void 0;
480
+ return lg;
481
+ }
482
+ function resolveParentRelationship(input, lookup) {
483
+ const parentLcRunId = input.parentLcRunId;
484
+ if (parentLcRunId) {
485
+ const exact = lookup.exactStepByLcRunId(parentLcRunId);
486
+ if (exact) {
487
+ return {
488
+ parentStepId: exact,
489
+ confidence: "explicit",
490
+ parentMapping: "exact"
491
+ };
492
+ }
493
+ }
494
+ const lg = langGraphRecord(input.attributes);
495
+ if (lg) {
496
+ for (const key of LANGGRAPH_PARENT_KEYS) {
497
+ if (key !== "handoffFrom" && key !== "checkpointNamespace") continue;
498
+ const raw = lg[key];
499
+ if (typeof raw !== "string" || !raw.trim()) continue;
500
+ const stepId = lookup.uniqueStepByLangGraphKey(key === "handoffFrom" ? "taskId" : key, raw);
501
+ const matched = stepId ?? (key === "handoffFrom" ? lookup.uniqueStepByLangGraphKey("nodeName", raw) ?? lookup.uniqueStepByLangGraphKey("nodeId", raw) : void 0);
502
+ if (matched) {
503
+ return {
504
+ parentStepId: matched,
505
+ confidence: "correlated",
506
+ parentMapping: "langgraph-metadata",
507
+ correlatedVia: key
508
+ };
509
+ }
510
+ }
511
+ }
512
+ if (parentLcRunId && isSemanticParentLabel(parentLcRunId)) {
513
+ const semantic = lookup.uniqueStepBySemanticLabel(parentLcRunId);
514
+ if (semantic) {
515
+ return {
516
+ parentStepId: semantic,
517
+ confidence: "correlated",
518
+ parentMapping: "semantic-name",
519
+ semanticParentLabel: parentLcRunId
520
+ };
521
+ }
522
+ return {
523
+ confidence: "unresolved",
524
+ parentMapping: "unresolved",
525
+ semanticParentLabel: parentLcRunId,
526
+ unresolvedParentRunId: parentLcRunId
527
+ };
528
+ }
529
+ if (parentLcRunId) {
530
+ return {
531
+ confidence: "unresolved",
532
+ parentMapping: "unresolved",
533
+ unresolvedParentRunId: parentLcRunId
534
+ };
535
+ }
536
+ return {
537
+ confidence: "explicit",
538
+ parentMapping: "exact"
539
+ };
540
+ }
541
+ function applyParentResolutionMetadata(metadata, resolution) {
542
+ const hasParentSignal = Boolean(resolution.parentStepId) || Boolean(resolution.unresolvedParentRunId) || Boolean(resolution.semanticParentLabel) || Boolean(resolution.correlatedVia);
543
+ if (!hasParentSignal) return;
544
+ metadata.parentMapping = resolution.parentMapping;
545
+ metadata.parentConfidence = resolution.confidence;
546
+ if (resolution.semanticParentLabel) {
547
+ metadata.semanticParentLabel = resolution.semanticParentLabel;
548
+ }
549
+ if (resolution.unresolvedParentRunId) {
550
+ metadata.unresolvedParentRunId = resolution.unresolvedParentRunId;
551
+ }
552
+ if (resolution.correlatedVia) {
553
+ metadata.parentCorrelatedVia = resolution.correlatedVia;
554
+ }
555
+ if (resolution.parentMapping === "synthetic-group") {
556
+ metadata.synthetic = true;
557
+ }
558
+ }
559
+
560
+ // packages/langchain/src/trace-persistence.ts
320
561
  function kindToStepType(kind) {
321
562
  switch (kind) {
322
563
  case "LLM":
@@ -346,27 +587,30 @@ var LangChainTracePersistence = class {
346
587
  #standalone;
347
588
  #silent;
348
589
  #safety;
349
- #runStarted = false;
350
- #runCompleted = false;
351
- #runStartTime;
352
- #rootLcRunId;
353
- /** Live LangChain callback runIds for standalone envelope finalization. */
354
- #activeLcRunIds = /* @__PURE__ */ new Set();
355
- #sawError = false;
356
- #lastErrorMessage;
357
- #finalizationToken = 0;
590
+ #lifecycle;
358
591
  #lcToStepId = /* @__PURE__ */ new Map();
592
+ /** `${field}\0${value}` → stepId, or null when ambiguous. */
593
+ #langGraphIndex = /* @__PURE__ */ new Map();
594
+ /** Semantic / display label → stepId, or null when ambiguous. */
595
+ #semanticLabelIndex = /* @__PURE__ */ new Map();
596
+ /** Semantic label → synthetic step id (created once ≥2 siblings share the label). */
597
+ #syntheticByLabel = /* @__PURE__ */ new Map();
598
+ /** Count of unresolved semantic-parent children seen per label (this invocation). */
599
+ #semanticParentCounts = /* @__PURE__ */ new Map();
600
+ #lateEventCount = 0;
359
601
  constructor(options = {}) {
360
602
  const inContext = hasActiveContext();
361
603
  this.#standalone = !inContext;
362
604
  this.#silent = options.silent ?? false;
363
605
  this.#traceDir = inContext ? getTraceDirFromContext() ?? resolveTraceDir({ dir: options.traceDir }) : resolveTraceDir({ dir: options.traceDir });
364
- this.#runId = (inContext ? getCurrentRunId() : void 0) ?? options.runId ?? createRunId();
606
+ const contextRunId = inContext ? getCurrentRunId() : void 0;
607
+ this.#runId = contextRunId ?? options.runId ?? createRunId();
365
608
  this.#runName = options.runName ?? "langchain-agent";
366
609
  this.#safety = resolveTraceSafetyOptions({
367
610
  redact: options.redact ? { rules: options.redact } : true,
368
611
  maxPreviewLength: options.maxPreviewChars
369
612
  });
613
+ this.#lifecycle = createInvocationState(this.#runId);
370
614
  }
371
615
  get runId() {
372
616
  return this.#runId;
@@ -374,49 +618,217 @@ var LangChainTracePersistence = class {
374
618
  get traceDir() {
375
619
  return this.#traceDir;
376
620
  }
621
+ /** @internal Test / diagnostics access to per-invocation lifecycle. */
622
+ get lifecycle() {
623
+ return this.#lifecycle;
624
+ }
625
+ /** Count of end/start events ignored after finalize (diagnostics). */
626
+ get lateEventCount() {
627
+ return this.#lateEventCount;
628
+ }
629
+ /** Bounded adapter diagnostics for CLI/MCP summaries (no filesystem paths). */
630
+ getDiagnostics() {
631
+ return {
632
+ lateEventCount: this.#lateEventCount,
633
+ activeRunCount: this.#lifecycle.activeRuns.size,
634
+ endedRunCount: this.#lifecycle.endedRuns.size,
635
+ pendingRelationshipCount: this.#lifecycle.pendingRelationships.length,
636
+ knownRelationshipCount: this.#lifecycle.knownRelationships.size,
637
+ syntheticGroupCount: this.#syntheticByLabel.size,
638
+ envelopeStarted: this.#lifecycle.envelopeStarted,
639
+ finalized: this.#lifecycle.finalized,
640
+ completionGeneration: this.#lifecycle.completionGeneration,
641
+ hasTerminalError: Boolean(this.#lifecycle.terminalError)
642
+ };
643
+ }
644
+ /**
645
+ * Start a fresh envelope after a prior invocation finalized (callback reuse).
646
+ * Allocates a new run id unless still nested in an inspectRun context.
647
+ */
648
+ beginNewInvocation() {
649
+ if (hasActiveContext()) {
650
+ const ctxId = getCurrentRunId();
651
+ if (ctxId) {
652
+ this.#runId = ctxId;
653
+ resetInvocationState(this.#lifecycle, this.#runId);
654
+ this.#clearStepIndexes();
655
+ this.#lateEventCount = 0;
656
+ return;
657
+ }
658
+ }
659
+ this.#runId = createRunId();
660
+ resetInvocationState(this.#lifecycle, this.#runId);
661
+ this.#clearStepIndexes();
662
+ this.#lateEventCount = 0;
663
+ }
377
664
  reset() {
378
- this.#finalizationToken += 1;
379
- this.#runStarted = false;
380
- this.#runCompleted = false;
381
- this.#runStartTime = void 0;
382
- this.#rootLcRunId = void 0;
383
- this.#activeLcRunIds.clear();
384
- this.#sawError = false;
385
- this.#lastErrorMessage = void 0;
665
+ resetInvocationState(this.#lifecycle);
666
+ this.#clearStepIndexes();
667
+ this.#lateEventCount = 0;
668
+ }
669
+ #clearStepIndexes() {
386
670
  this.#lcToStepId.clear();
671
+ this.#langGraphIndex.clear();
672
+ this.#semanticLabelIndex.clear();
673
+ this.#syntheticByLabel.clear();
674
+ this.#semanticParentCounts.clear();
675
+ }
676
+ #registerUnique(index, key, stepId) {
677
+ if (!index.has(key)) {
678
+ index.set(key, stepId);
679
+ return;
680
+ }
681
+ if (index.get(key) !== stepId) {
682
+ index.set(key, null);
683
+ }
684
+ }
685
+ #langGraphIndexKey(field, value) {
686
+ return `${field}\0${value}`;
687
+ }
688
+ #registerStepIndexes(stepId, name, attributes) {
689
+ const labels = /* @__PURE__ */ new Set([name]);
690
+ const stripped = name.replace(/^(chain|tool|llm|retriever|agent):/, "");
691
+ if (stripped) labels.add(stripped);
692
+ for (const label of labels) {
693
+ this.#registerUnique(this.#semanticLabelIndex, label, stepId);
694
+ }
695
+ const lg = attributes.langGraph;
696
+ if (typeof lg === "object" && lg !== null && !Array.isArray(lg)) {
697
+ const record = lg;
698
+ for (const field of [
699
+ "taskId",
700
+ "nodeId",
701
+ "nodeName",
702
+ "checkpointNamespace"
703
+ ]) {
704
+ const raw = record[field];
705
+ if (typeof raw === "string" && raw.trim()) {
706
+ this.#registerUnique(
707
+ this.#langGraphIndex,
708
+ this.#langGraphIndexKey(field, raw),
709
+ stepId
710
+ );
711
+ }
712
+ }
713
+ }
714
+ }
715
+ #resolveParent(parentLcRunId, attributes) {
716
+ return resolveParentRelationship(
717
+ { parentLcRunId, attributes },
718
+ {
719
+ exactStepByLcRunId: (lcRunId) => this.#lcToStepId.get(lcRunId),
720
+ uniqueStepByLangGraphKey: (key, value) => {
721
+ const hit = this.#langGraphIndex.get(this.#langGraphIndexKey(key, value));
722
+ return hit === null || hit === void 0 ? void 0 : hit;
723
+ },
724
+ uniqueStepBySemanticLabel: (label) => {
725
+ const hit = this.#semanticLabelIndex.get(label);
726
+ return hit === null || hit === void 0 ? void 0 : hit;
727
+ }
728
+ }
729
+ );
730
+ }
731
+ /**
732
+ * When ≥2 steps share the same unresolved semantic parent label, emit one
733
+ * synthetic grouping node and attach this (and later) siblings under it.
734
+ * The first sibling remains unresolved (append-only JSONL cannot rewrite it).
735
+ */
736
+ async #maybeAttachSyntheticGroup(resolution, startTime) {
737
+ const label = resolution.semanticParentLabel;
738
+ if (resolution.parentMapping !== "unresolved" || !label || resolution.parentStepId) {
739
+ return resolution;
740
+ }
741
+ const existing = this.#syntheticByLabel.get(label);
742
+ if (existing) {
743
+ return {
744
+ parentStepId: existing,
745
+ confidence: "synthetic",
746
+ parentMapping: "synthetic-group",
747
+ semanticParentLabel: label,
748
+ unresolvedParentRunId: label
749
+ };
750
+ }
751
+ const nextCount = (this.#semanticParentCounts.get(label) ?? 0) + 1;
752
+ this.#semanticParentCounts.set(label, nextCount);
753
+ if (nextCount < 2) {
754
+ return resolution;
755
+ }
756
+ const syntheticStepId = createStepId();
757
+ this.#syntheticByLabel.set(label, syntheticStepId);
758
+ const metadata = {
759
+ adapter: "langchain",
760
+ confidence: "synthetic",
761
+ synthetic: true,
762
+ parentMapping: "synthetic-group",
763
+ parentConfidence: "synthetic",
764
+ semanticParentLabel: label,
765
+ unresolvedParentRunId: label
766
+ };
767
+ const event = {
768
+ schemaVersion: "0.1",
769
+ event: "step_started",
770
+ timestamp: startTime,
771
+ runId: this.#runId,
772
+ stepId: syntheticStepId,
773
+ name: `synthetic:${label}`,
774
+ type: "logic",
775
+ startTime,
776
+ metadata
777
+ };
778
+ await this.#write(event);
779
+ return {
780
+ parentStepId: syntheticStepId,
781
+ confidence: "synthetic",
782
+ parentMapping: "synthetic-group",
783
+ semanticParentLabel: label,
784
+ unresolvedParentRunId: label
785
+ };
387
786
  }
388
- noteRoot(lcRunId, parentRunId) {
389
- if (!parentRunId && !this.#rootLcRunId) {
390
- this.#rootLcRunId = lcRunId;
787
+ /** Rotate when a prior standalone invocation already finalized. */
788
+ #prepareForStart() {
789
+ if (this.#standalone && this.#lifecycle.finalized) {
790
+ this.beginNewInvocation();
391
791
  }
392
792
  }
793
+ /**
794
+ * @deprecated Root-ID heuristics are no longer used for envelope completion.
795
+ * Retained as a no-op for call-site compatibility during the v6.8 train.
796
+ */
797
+ noteRoot(_lcRunId, _parentRunId) {
798
+ }
393
799
  resolveParentId(lcParentRunId) {
394
800
  if (!lcParentRunId) return void 0;
395
801
  return this.#lcToStepId.get(lcParentRunId);
396
802
  }
397
803
  async onStepStart(params) {
398
804
  try {
399
- this.#finalizationToken += 1;
400
- this.noteRoot(params.lcRunId, params.lcParentRunId);
401
- this.#activeLcRunIds.add(params.lcRunId);
402
- if (this.#standalone && !this.#runStarted) {
403
- await this.#ensureRunStarted(params.startTime, params.attributes);
404
- }
805
+ this.#prepareForStart();
405
806
  const stepId = createStepId();
406
807
  this.#lcToStepId.set(params.lcRunId, stepId);
407
- const parentId = this.resolveParentId(params.lcParentRunId);
408
- const metadata = toStepMetadata(params.attributes);
409
- if (params.lcParentRunId && !parentId) {
410
- metadata.parentMapping = "unresolved";
411
- metadata.unresolvedParentRunId = params.lcParentRunId;
808
+ this.#registerStepIndexes(stepId, params.name, params.attributes);
809
+ beginCallbackRun(this.#lifecycle, {
810
+ lcRunId: params.lcRunId,
811
+ parentLcRunId: params.lcParentRunId,
812
+ startedAt: params.startTime,
813
+ kind: params.kind,
814
+ stepId
815
+ });
816
+ if (this.#standalone && !this.#lifecycle.envelopeStarted) {
817
+ await this.#ensureRunStarted(params.startTime, params.attributes);
412
818
  }
819
+ const resolution = await this.#maybeAttachSyntheticGroup(
820
+ this.#resolveParent(params.lcParentRunId, params.attributes),
821
+ params.startTime
822
+ );
823
+ const metadata = toStepMetadata(params.attributes);
824
+ applyParentResolutionMetadata(metadata, resolution);
413
825
  const event = {
414
826
  schemaVersion: "0.1",
415
827
  event: "step_started",
416
828
  timestamp: params.startTime,
417
829
  runId: this.#runId,
418
830
  stepId,
419
- ...parentId ? { parentId } : {},
831
+ ...resolution.parentStepId ? { parentId: resolution.parentStepId } : {},
420
832
  name: params.name,
421
833
  type: kindToStepType(params.kind),
422
834
  startTime: params.startTime,
@@ -429,35 +841,58 @@ var LangChainTracePersistence = class {
429
841
  }
430
842
  async onStepEnd(params) {
431
843
  try {
844
+ if (this.#lifecycle.finalized && !this.#lifecycle.activeRuns.has(params.lcRunId) && !this.#lcToStepId.has(params.lcRunId)) {
845
+ this.#lateEventCount += 1;
846
+ return;
847
+ }
432
848
  let stepId = this.#lcToStepId.get(params.lcRunId);
433
849
  if (!stepId && params.completionAttributes) {
850
+ if (this.#lifecycle.finalized) {
851
+ this.#lateEventCount += 1;
852
+ return;
853
+ }
434
854
  stepId = createStepId();
435
855
  this.#lcToStepId.set(params.lcRunId, stepId);
436
- const parentId = this.resolveParentId(params.lcParentRunId);
437
- const metadata = toStepMetadata(params.completionAttributes);
438
- if (params.lcParentRunId && !parentId) {
439
- metadata.parentMapping = "unresolved";
440
- metadata.unresolvedParentRunId = params.lcParentRunId;
441
- }
856
+ const synthName = String(params.completionAttributes.name ?? "llm:llm");
857
+ this.#registerStepIndexes(stepId, synthName, params.completionAttributes);
858
+ beginCallbackRun(this.#lifecycle, {
859
+ lcRunId: params.lcRunId,
860
+ parentLcRunId: params.lcParentRunId,
861
+ startedAt: params.endTime - (params.durationMs ?? 0),
862
+ kind: params.completionAttributes.kind ?? "LLM",
863
+ stepId
864
+ });
442
865
  const startTime = params.endTime - (params.durationMs ?? 0);
866
+ const resolution = await this.#maybeAttachSyntheticGroup(
867
+ this.#resolveParent(params.lcParentRunId, params.completionAttributes),
868
+ startTime
869
+ );
870
+ const metadata = toStepMetadata(params.completionAttributes);
871
+ applyParentResolutionMetadata(metadata, resolution);
443
872
  const started = {
444
873
  schemaVersion: "0.1",
445
874
  event: "step_started",
446
875
  timestamp: startTime,
447
876
  runId: this.#runId,
448
877
  stepId,
449
- ...parentId ? { parentId } : {},
450
- name: String(params.completionAttributes.name ?? "llm:llm"),
878
+ ...resolution.parentStepId ? { parentId: resolution.parentStepId } : {},
879
+ name: synthName,
451
880
  type: kindToStepType(
452
881
  params.completionAttributes.kind ?? "LLM"
453
882
  ),
454
883
  startTime,
455
884
  metadata
456
885
  };
886
+ if (this.#standalone && !this.#lifecycle.envelopeStarted) {
887
+ await this.#ensureRunStarted(startTime, params.completionAttributes);
888
+ }
457
889
  await this.#write(started);
458
890
  }
459
891
  if (!stepId) return;
460
- const durationMs = typeof params.durationMs === "number" && Number.isFinite(params.durationMs) ? Math.max(0, Math.floor(params.durationMs)) : Math.max(0, params.endTime - (this.#runStartTime ?? params.endTime));
892
+ const durationMs = typeof params.durationMs === "number" && Number.isFinite(params.durationMs) ? Math.max(0, Math.floor(params.durationMs)) : Math.max(
893
+ 0,
894
+ params.endTime - (this.#lifecycle.runStartTime ?? params.endTime)
895
+ );
461
896
  const event = {
462
897
  schemaVersion: "0.1",
463
898
  event: "step_completed",
@@ -471,10 +906,12 @@ var LangChainTracePersistence = class {
471
906
  };
472
907
  await this.#write(event);
473
908
  if (params.status === "error") {
474
- this.#sawError = true;
475
- if (params.errorMessage) this.#lastErrorMessage = params.errorMessage;
909
+ noteTerminalError(
910
+ this.#lifecycle,
911
+ params.errorMessage ?? "adapter step error"
912
+ );
476
913
  }
477
- this.#activeLcRunIds.delete(params.lcRunId);
914
+ endCallbackRun(this.#lifecycle, params.lcRunId);
478
915
  await this.#scheduleStandaloneFinalization(params.endTime);
479
916
  } catch (err) {
480
917
  this.#warn(err);
@@ -483,27 +920,33 @@ var LangChainTracePersistence = class {
483
920
  /** Point-in-time adapter events (e.g. agent action) — writes start + completed pair. */
484
921
  async onInstantStep(params) {
485
922
  try {
486
- this.#finalizationToken += 1;
487
- this.noteRoot(params.lcRunId, params.lcParentRunId);
488
- this.#activeLcRunIds.add(params.lcRunId);
489
- if (this.#standalone && !this.#runStarted) {
490
- await this.#ensureRunStarted(params.timestamp, params.attributes);
491
- }
923
+ this.#prepareForStart();
492
924
  const stepId = createStepId();
493
925
  this.#lcToStepId.set(params.lcRunId, stepId);
494
- const parentId = this.resolveParentId(params.lcParentRunId);
495
- const metadata = toStepMetadata(params.attributes);
496
- if (params.lcParentRunId && !parentId) {
497
- metadata.parentMapping = "unresolved";
498
- metadata.unresolvedParentRunId = params.lcParentRunId;
926
+ this.#registerStepIndexes(stepId, params.name, params.attributes);
927
+ beginCallbackRun(this.#lifecycle, {
928
+ lcRunId: params.lcRunId,
929
+ parentLcRunId: params.lcParentRunId,
930
+ startedAt: params.timestamp,
931
+ kind: params.kind,
932
+ stepId
933
+ });
934
+ if (this.#standalone && !this.#lifecycle.envelopeStarted) {
935
+ await this.#ensureRunStarted(params.timestamp, params.attributes);
499
936
  }
937
+ const resolution = await this.#maybeAttachSyntheticGroup(
938
+ this.#resolveParent(params.lcParentRunId, params.attributes),
939
+ params.timestamp
940
+ );
941
+ const metadata = toStepMetadata(params.attributes);
942
+ applyParentResolutionMetadata(metadata, resolution);
500
943
  const started = {
501
944
  schemaVersion: "0.1",
502
945
  event: "step_started",
503
946
  timestamp: params.timestamp,
504
947
  runId: this.#runId,
505
948
  stepId,
506
- ...parentId ? { parentId } : {},
949
+ ...resolution.parentStepId ? { parentId: resolution.parentStepId } : {},
507
950
  name: params.name,
508
951
  type: kindToStepType(params.kind),
509
952
  startTime: params.timestamp,
@@ -523,15 +966,71 @@ var LangChainTracePersistence = class {
523
966
  };
524
967
  await this.#write(completed);
525
968
  if (params.status === "error") {
526
- this.#sawError = true;
527
- if (params.errorMessage) this.#lastErrorMessage = params.errorMessage;
969
+ noteTerminalError(
970
+ this.#lifecycle,
971
+ params.errorMessage ?? "adapter step error"
972
+ );
528
973
  }
529
- this.#activeLcRunIds.delete(params.lcRunId);
974
+ endCallbackRun(this.#lifecycle, params.lcRunId);
530
975
  await this.#scheduleStandaloneFinalization(params.timestamp);
531
976
  } catch (err) {
532
977
  this.#warn(err);
533
978
  }
534
979
  }
980
+ /**
981
+ * Drain deferred microtask finalization. Idempotent; never throws to callers.
982
+ * @experimental
983
+ */
984
+ async flush() {
985
+ try {
986
+ await Promise.resolve();
987
+ await Promise.resolve();
988
+ } catch (err) {
989
+ this.#warn(err);
990
+ }
991
+ }
992
+ /**
993
+ * Force-complete the standalone envelope when safe/started.
994
+ * Works even if active callback runs remain (unusual framework shapes / serverless).
995
+ * Idempotent; never throws to callers.
996
+ * @experimental
997
+ */
998
+ async finalize(options = {}) {
999
+ try {
1000
+ if (!this.#standalone) return false;
1001
+ if (this.#lifecycle.finalized) return false;
1002
+ if (!this.#lifecycle.envelopeStarted) return false;
1003
+ bumpCompletionGeneration(this.#lifecycle);
1004
+ const status = options.status ?? (this.#lifecycle.terminalError ? "error" : "success");
1005
+ if (status === "error") {
1006
+ noteTerminalError(
1007
+ this.#lifecycle,
1008
+ options.errorMessage ?? this.#lifecycle.terminalError?.message ?? "adapter finalize error"
1009
+ );
1010
+ }
1011
+ await this.#ensureRunCompleted(
1012
+ options.endTime ?? Date.now(),
1013
+ status,
1014
+ status === "error" ? options.errorMessage ?? this.#lifecycle.terminalError?.message : void 0
1015
+ );
1016
+ return this.#lifecycle.finalized;
1017
+ } catch (err) {
1018
+ this.#warn(err);
1019
+ return false;
1020
+ }
1021
+ }
1022
+ /**
1023
+ * Flush + finalize. Idempotent; never throws to callers.
1024
+ * @experimental
1025
+ */
1026
+ async close() {
1027
+ try {
1028
+ await this.flush();
1029
+ await this.finalize();
1030
+ } catch (err) {
1031
+ this.#warn(err);
1032
+ }
1033
+ }
535
1034
  /**
536
1035
  * When the last active LangChain callback ends, yield one microtask so a
537
1036
  * same-turn sibling start can cancel finalization, then write run_completed
@@ -539,24 +1038,20 @@ var LangChainTracePersistence = class {
539
1038
  * block the envelope.
540
1039
  */
541
1040
  async #scheduleStandaloneFinalization(endTime) {
542
- if (!this.#standalone || this.#runCompleted || !this.#runStarted) return;
543
- if (this.#activeLcRunIds.size > 0) return;
544
- const token = ++this.#finalizationToken;
1041
+ if (!this.#standalone || !canScheduleFinalize(this.#lifecycle)) return;
1042
+ const generation = this.#lifecycle.completionGeneration;
545
1043
  await Promise.resolve();
546
- if (token !== this.#finalizationToken) return;
547
- if (!this.#standalone || this.#runCompleted || !this.#runStarted) return;
548
- if (this.#activeLcRunIds.size > 0) return;
549
- const status = this.#sawError ? "error" : "success";
1044
+ if (this.#lifecycle.completionGeneration !== generation) return;
1045
+ if (!this.#standalone || !canScheduleFinalize(this.#lifecycle)) return;
1046
+ const status = this.#lifecycle.terminalError ? "error" : "success";
550
1047
  await this.#ensureRunCompleted(
551
1048
  endTime,
552
1049
  status,
553
- status === "error" ? this.#lastErrorMessage : void 0
1050
+ status === "error" ? this.#lifecycle.terminalError?.message : void 0
554
1051
  );
555
1052
  }
556
1053
  async #ensureRunStarted(startTime, attrs) {
557
- if (this.#runStarted) return;
558
- this.#runStarted = true;
559
- this.#runStartTime = startTime;
1054
+ if (!markEnvelopeStarted(this.#lifecycle, startTime)) return;
560
1055
  await initializeTraceFile(this.#runId, this.#traceDir);
561
1056
  const metadata = {
562
1057
  adapter: "langchain",
@@ -576,9 +1071,21 @@ var LangChainTracePersistence = class {
576
1071
  await this.#write(event);
577
1072
  }
578
1073
  async #ensureRunCompleted(endTime, stepStatus, errorMessage) {
579
- if (this.#runCompleted || !this.#runStarted) return;
580
- this.#runCompleted = true;
581
- const startTime = this.#runStartTime ?? endTime;
1074
+ if (!markFinalized(this.#lifecycle)) return;
1075
+ for (const [, syntheticStepId] of this.#syntheticByLabel) {
1076
+ const completed = {
1077
+ schemaVersion: "0.1",
1078
+ event: "step_completed",
1079
+ timestamp: endTime,
1080
+ runId: this.#runId,
1081
+ stepId: syntheticStepId,
1082
+ status: "success",
1083
+ endTime,
1084
+ durationMs: Math.max(0, endTime - (this.#lifecycle.runStartTime ?? endTime))
1085
+ };
1086
+ await this.#write(completed);
1087
+ }
1088
+ const startTime = this.#lifecycle.runStartTime ?? endTime;
582
1089
  const durationMs = Math.max(0, endTime - startTime);
583
1090
  const runStatus = stepStatus === "error" ? "error" : "success";
584
1091
  const event = {
@@ -608,20 +1115,11 @@ var LangChainTracePersistence = class {
608
1115
  function isRecord2(v) {
609
1116
  return typeof v === "object" && v !== null && !Array.isArray(v);
610
1117
  }
611
- function serializedLabel(s) {
1118
+ function serializedLabel2(s) {
612
1119
  if (typeof s.name === "string" && s.name.trim()) return s.name;
613
1120
  if (Array.isArray(s.id) && s.id.length > 0) return s.id[s.id.length - 1];
614
1121
  return s.type;
615
1122
  }
616
- function resolveToolDisplayName(tool, runName, metadata) {
617
- const fromRun = typeof runName === "string" ? runName.trim() : "";
618
- if (fromRun) return fromRun;
619
- const metaName = metadata?.toolName;
620
- if (typeof metaName === "string" && metaName.trim()) return metaName.trim();
621
- const metaTool = metadata?.tool;
622
- if (typeof metaTool === "string" && metaTool.trim()) return metaTool.trim();
623
- return serializedLabel(tool) ?? "tool";
624
- }
625
1123
  function errorShape(err) {
626
1124
  if (err instanceof Error) {
627
1125
  return { errorName: err.name, errorMessage: err.message };
@@ -630,6 +1128,11 @@ function errorShape(err) {
630
1128
  }
631
1129
  var AgentInspectCallback = class extends BaseCallbackHandler {
632
1130
  name = "agent-inspect";
1131
+ /**
1132
+ * Ensure LangGraph/LangChain awaits async handler work (persistence) before
1133
+ * invoke/stream settles. Required for deterministic standalone envelopes.
1134
+ */
1135
+ awaitHandlers = true;
633
1136
  #opts;
634
1137
  #redactor;
635
1138
  #persistence;
@@ -640,16 +1143,22 @@ var AgentInspectCallback = class extends BaseCallbackHandler {
640
1143
  #rootRunId;
641
1144
  constructor(options = {}) {
642
1145
  super({});
1146
+ const intent = resolvePersistIntent(options);
643
1147
  this.#opts = {
644
1148
  capture: options.capture ?? "metadata-only",
645
1149
  silent: options.silent ?? false,
646
1150
  maxPreviewChars: options.maxPreviewChars ?? 200,
647
- persist: options.persist ?? false,
648
1151
  runName: options.runName ?? "langchain-agent",
649
- ...options
1152
+ ...options,
1153
+ persist: intent.persist
650
1154
  };
651
1155
  this.#redactor = new Redactor({ rules: this.#opts.redact });
652
- if (this.#opts.persist) {
1156
+ if (intent.contradictory && !this.#opts.silent) {
1157
+ console.error(
1158
+ "[agent-inspect:langchain] persist:false with traceDir set \u2014 traces stay in-memory; remove traceDir or set persist:true"
1159
+ );
1160
+ }
1161
+ if (intent.persist) {
653
1162
  this.#persistence = new LangChainTracePersistence({
654
1163
  runName: this.#opts.runName,
655
1164
  traceDir: this.#opts.traceDir,
@@ -675,6 +1184,91 @@ var AgentInspectCallback = class extends BaseCallbackHandler {
675
1184
  this.#rootRunId = void 0;
676
1185
  this.#persistence?.reset();
677
1186
  }
1187
+ /**
1188
+ * Bounded adapter diagnostics for the current invocation (counts only).
1189
+ * Safe for CLI/MCP summaries — no absolute paths or payloads.
1190
+ *
1191
+ * @experimental
1192
+ */
1193
+ getDiagnostics() {
1194
+ const base = this.#persistence?.getDiagnostics() ?? {
1195
+ lateEventCount: 0,
1196
+ activeRunCount: 0,
1197
+ endedRunCount: 0,
1198
+ pendingRelationshipCount: 0,
1199
+ knownRelationshipCount: 0,
1200
+ syntheticGroupCount: 0,
1201
+ envelopeStarted: false,
1202
+ finalized: false,
1203
+ completionGeneration: 0,
1204
+ hasTerminalError: false
1205
+ };
1206
+ return {
1207
+ ...base,
1208
+ inMemoryEventCount: this.#events.length,
1209
+ deferredPersistStartCount: this.#deferredPersistStart.size
1210
+ };
1211
+ }
1212
+ /**
1213
+ * Drain deferred completion work (microtask finalization).
1214
+ * Idempotent. Failures are isolated and never thrown to user code.
1215
+ *
1216
+ * @experimental Additive finalization API for serverless / unusual callback shapes.
1217
+ */
1218
+ async flush() {
1219
+ try {
1220
+ await this.#persistence?.flush();
1221
+ } catch (err) {
1222
+ if (!this.#opts.silent) {
1223
+ console.error("[agent-inspect:langchain]", err);
1224
+ }
1225
+ }
1226
+ }
1227
+ /**
1228
+ * Complete the standalone envelope when started. Fallback when automatic
1229
+ * completion cannot run (e.g. process shutdown). Idempotent.
1230
+ * Failures are isolated and never thrown to user code.
1231
+ *
1232
+ * @experimental Additive finalization API for serverless / unusual callback shapes.
1233
+ */
1234
+ async finalize(options) {
1235
+ try {
1236
+ await this.#persistence?.finalize(options);
1237
+ } catch (err) {
1238
+ if (!this.#opts.silent) {
1239
+ console.error("[agent-inspect:langchain]", err);
1240
+ }
1241
+ }
1242
+ }
1243
+ /**
1244
+ * `flush()` then `finalize()`. Idempotent. Failures are isolated.
1245
+ *
1246
+ * @experimental Additive finalization API for serverless / unusual callback shapes.
1247
+ */
1248
+ async close() {
1249
+ try {
1250
+ await this.flush();
1251
+ await this.finalize();
1252
+ } catch (err) {
1253
+ if (!this.#opts.silent) {
1254
+ console.error("[agent-inspect:langchain]", err);
1255
+ }
1256
+ }
1257
+ }
1258
+ /**
1259
+ * When persist mode has already finalized an envelope, rotate to a new
1260
+ * invocation before the next start so reused handlers do not mix runs.
1261
+ */
1262
+ #prepareCallbackInvocation() {
1263
+ const persistence = this.#persistence;
1264
+ if (!persistence?.lifecycle.finalized) return;
1265
+ this.#events = [];
1266
+ this.#starts.clear();
1267
+ this.#streamState.clear();
1268
+ this.#deferredPersistStart.clear();
1269
+ this.#rootRunId = void 0;
1270
+ persistence.beginNewInvocation();
1271
+ }
678
1272
  #streamPreviewLimit() {
679
1273
  if (this.#opts.capture !== "preview") return 0;
680
1274
  return this.#opts.maxStreamPreviewChars ?? this.#opts.maxPreviewChars ?? 200;
@@ -836,9 +1430,10 @@ var AgentInspectCallback = class extends BaseCallbackHandler {
836
1430
  });
837
1431
  }
838
1432
  async handleChainStart(chain, inputs, runId, runType, tags, metadata, runName, parentRunId, _extra) {
1433
+ this.#prepareCallbackInvocation();
839
1434
  this.#ensureRoot(runId, parentRunId);
840
1435
  this.#rememberStart(runId, "CHAIN");
841
- const label = serializedLabel(chain) ?? "chain";
1436
+ const label = serializedLabel2(chain) ?? "chain";
842
1437
  const previews = {};
843
1438
  if (this.#opts.capture === "preview") previews.inputPreview = inputs;
844
1439
  const attrs = {
@@ -917,6 +1512,7 @@ var AgentInspectCallback = class extends BaseCallbackHandler {
917
1512
  await this.#persistStepEnd(runId, parentRunId, "error", ts, durationMs, errorMessage);
918
1513
  }
919
1514
  async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata, runName) {
1515
+ this.#prepareCallbackInvocation();
920
1516
  this.#ensureRoot(runId, parentRunId);
921
1517
  this.#rememberStart(runId, "LLM");
922
1518
  const model = extractModelName(llm);
@@ -949,6 +1545,7 @@ var AgentInspectCallback = class extends BaseCallbackHandler {
949
1545
  await this.#persistLlmStepStart(runId, parentRunId, stepName, "LLM", ts, attrs);
950
1546
  }
951
1547
  async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata, runName) {
1548
+ this.#prepareCallbackInvocation();
952
1549
  this.#ensureRoot(runId, parentRunId);
953
1550
  this.#rememberStart(runId, "LLM");
954
1551
  const model = extractModelName(llm);
@@ -1067,21 +1664,22 @@ var AgentInspectCallback = class extends BaseCallbackHandler {
1067
1664
  });
1068
1665
  this.#clearStreamState(runId);
1069
1666
  }
1070
- async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName, _toolCallId) {
1667
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName, toolCallId) {
1668
+ this.#prepareCallbackInvocation();
1071
1669
  this.#ensureRoot(runId, parentRunId);
1072
1670
  this.#rememberStart(runId, "TOOL");
1073
- const toolName = resolveToolDisplayName(tool, runName, metadata);
1671
+ const identity = resolveToolIdentity(tool, runName, metadata, toolCallId);
1074
1672
  const previews = {};
1075
1673
  if (this.#opts.capture === "preview") previews.inputPreview = input;
1076
1674
  const attrs = {
1077
- ...this.#baseAttrs(runId, parentRunId, tags, runName),
1078
- tool: toolName
1675
+ ...this.#baseAttrs(runId, parentRunId, tags, runName)
1079
1676
  };
1677
+ applyToolIdentityAttributes(attrs, identity);
1080
1678
  this.#mergeMetadata(attrs, metadata);
1081
1679
  this.#applyPreview(attrs, previews);
1082
1680
  this.#rememberStartMetadata(runId, attrs);
1083
1681
  const ts = Date.now();
1084
- const stepName = `tool:${toolName}`;
1682
+ const stepName = `tool:${identity.displayName}`;
1085
1683
  this.#pushEvent({
1086
1684
  eventId: `${runId}:TOOL:start`,
1087
1685
  runId: this.#traceRunId(runId),
@@ -1151,9 +1749,10 @@ var AgentInspectCallback = class extends BaseCallbackHandler {
1151
1749
  await this.#persistStepEnd(runId, parentRunId, "error", ts, durationMs, errorMessage);
1152
1750
  }
1153
1751
  async handleRetrieverStart(retriever, _query, runId, parentRunId, tags, metadata, name) {
1752
+ this.#prepareCallbackInvocation();
1154
1753
  this.#ensureRoot(runId, parentRunId);
1155
1754
  this.#rememberStart(runId, "RETRIEVER");
1156
- const rname = name ?? serializedLabel(retriever) ?? "retriever";
1755
+ const rname = name ?? serializedLabel2(retriever) ?? "retriever";
1157
1756
  const attrs = {
1158
1757
  ...this.#baseAttrs(runId, parentRunId, tags, void 0),
1159
1758
  retriever: rname
@@ -1234,6 +1833,7 @@ var AgentInspectCallback = class extends BaseCallbackHandler {
1234
1833
  await this.#persistStepEnd(runId, parentRunId, "error", ts, durationMs, errorMessage);
1235
1834
  }
1236
1835
  async handleAgentAction(action, runId, parentRunId, tags) {
1836
+ this.#prepareCallbackInvocation();
1237
1837
  this.#ensureRoot(runId, parentRunId);
1238
1838
  const attrs = {
1239
1839
  ...this.#baseAttrs(runId, parentRunId, tags, void 0),