@librechat/agents 3.6.8 → 3.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +46 -39
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +2 -2
  4. package/dist/cjs/agents/projection.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +28 -18
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +87 -16
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/openai/index.cjs +10 -0
  10. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +14 -0
  12. package/dist/cjs/messages/alternation.cjs +125 -20
  13. package/dist/cjs/messages/alternation.cjs.map +1 -1
  14. package/dist/cjs/messages/core.cjs +195 -18
  15. package/dist/cjs/messages/core.cjs.map +1 -1
  16. package/dist/cjs/messages/format.cjs +488 -118
  17. package/dist/cjs/messages/format.cjs.map +1 -1
  18. package/dist/cjs/messages/handoffCue.cjs +3 -2
  19. package/dist/cjs/messages/handoffCue.cjs.map +1 -1
  20. package/dist/cjs/messages/index.cjs +1 -0
  21. package/dist/cjs/messages/injected.cjs +5 -2
  22. package/dist/cjs/messages/injected.cjs.map +1 -1
  23. package/dist/cjs/messages/provenance.cjs +411 -0
  24. package/dist/cjs/messages/provenance.cjs.map +1 -0
  25. package/dist/cjs/messages/prune.cjs +18 -13
  26. package/dist/cjs/messages/prune.cjs.map +1 -1
  27. package/dist/cjs/messages/toolResultTypes.cjs +663 -0
  28. package/dist/cjs/messages/toolResultTypes.cjs.map +1 -0
  29. package/dist/cjs/run.cjs +7 -4
  30. package/dist/cjs/run.cjs.map +1 -1
  31. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +21 -4
  32. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  33. package/dist/cjs/tools/CallerCapabilities.cjs +48 -0
  34. package/dist/cjs/tools/CallerCapabilities.cjs.map +1 -0
  35. package/dist/cjs/tools/ProgrammaticCallerPolicy.cjs +34 -0
  36. package/dist/cjs/tools/ProgrammaticCallerPolicy.cjs.map +1 -0
  37. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +21 -4
  38. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolNode.cjs +25 -24
  40. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  41. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +12 -4
  42. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  43. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +13 -4
  44. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  45. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +47 -11
  46. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
  47. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
  48. package/dist/cjs/tools/search/search.cjs +19 -9
  49. package/dist/cjs/tools/search/search.cjs.map +1 -1
  50. package/dist/cjs/tools/search/tool.cjs +2 -1
  51. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  52. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
  53. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  54. package/dist/cjs/utils/toolContent.cjs +2 -2
  55. package/dist/cjs/utils/toolContent.cjs.map +1 -1
  56. package/dist/esm/agents/AgentContext.mjs +46 -39
  57. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  58. package/dist/esm/agents/projection.mjs +2 -2
  59. package/dist/esm/agents/projection.mjs.map +1 -1
  60. package/dist/esm/graphs/Graph.mjs +30 -20
  61. package/dist/esm/graphs/Graph.mjs.map +1 -1
  62. package/dist/esm/graphs/MultiAgentGraph.mjs +87 -16
  63. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  64. package/dist/esm/llm/openai/index.mjs +10 -0
  65. package/dist/esm/llm/openai/index.mjs.map +1 -1
  66. package/dist/esm/main.mjs +4 -3
  67. package/dist/esm/messages/alternation.mjs +125 -20
  68. package/dist/esm/messages/alternation.mjs.map +1 -1
  69. package/dist/esm/messages/core.mjs +196 -19
  70. package/dist/esm/messages/core.mjs.map +1 -1
  71. package/dist/esm/messages/format.mjs +488 -119
  72. package/dist/esm/messages/format.mjs.map +1 -1
  73. package/dist/esm/messages/handoffCue.mjs +3 -2
  74. package/dist/esm/messages/handoffCue.mjs.map +1 -1
  75. package/dist/esm/messages/index.mjs +1 -0
  76. package/dist/esm/messages/injected.mjs +5 -2
  77. package/dist/esm/messages/injected.mjs.map +1 -1
  78. package/dist/esm/messages/provenance.mjs +401 -0
  79. package/dist/esm/messages/provenance.mjs.map +1 -0
  80. package/dist/esm/messages/prune.mjs +18 -13
  81. package/dist/esm/messages/prune.mjs.map +1 -1
  82. package/dist/esm/messages/toolResultTypes.mjs +657 -0
  83. package/dist/esm/messages/toolResultTypes.mjs.map +1 -0
  84. package/dist/esm/run.mjs +7 -4
  85. package/dist/esm/run.mjs.map +1 -1
  86. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +21 -4
  87. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  88. package/dist/esm/tools/CallerCapabilities.mjs +45 -0
  89. package/dist/esm/tools/CallerCapabilities.mjs.map +1 -0
  90. package/dist/esm/tools/ProgrammaticCallerPolicy.mjs +32 -0
  91. package/dist/esm/tools/ProgrammaticCallerPolicy.mjs.map +1 -0
  92. package/dist/esm/tools/ProgrammaticToolCalling.mjs +21 -4
  93. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  94. package/dist/esm/tools/ToolNode.mjs +25 -24
  95. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  96. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +14 -6
  97. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  98. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +15 -6
  99. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  100. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +47 -12
  101. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  102. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  103. package/dist/esm/tools/search/search.mjs +19 -9
  104. package/dist/esm/tools/search/search.mjs.map +1 -1
  105. package/dist/esm/tools/search/tool.mjs +2 -1
  106. package/dist/esm/tools/search/tool.mjs.map +1 -1
  107. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
  108. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  109. package/dist/esm/utils/toolContent.mjs +2 -2
  110. package/dist/esm/utils/toolContent.mjs.map +1 -1
  111. package/dist/types/agents/AgentContext.d.ts +8 -13
  112. package/dist/types/agents/projection.d.ts +3 -1
  113. package/dist/types/graphs/Graph.d.ts +1 -1
  114. package/dist/types/llm/openai/index.d.ts +20 -0
  115. package/dist/types/messages/format.d.ts +5 -0
  116. package/dist/types/messages/index.d.ts +1 -0
  117. package/dist/types/messages/provenance.d.ts +85 -0
  118. package/dist/types/messages/toolResultTypes.d.ts +33 -0
  119. package/dist/types/tools/CallerCapabilities.d.ts +12 -0
  120. package/dist/types/tools/ProgrammaticCallerPolicy.d.ts +20 -0
  121. package/dist/types/tools/ToolNode.d.ts +4 -7
  122. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +4 -0
  123. package/dist/types/tools/ptcTimeout.d.ts +8 -0
  124. package/dist/types/tools/search/types.d.ts +15 -0
  125. package/dist/types/types/graph.d.ts +3 -1
  126. package/dist/types/types/tools.d.ts +13 -2
  127. package/package.json +1 -1
  128. package/src/agents/AgentContext.ts +109 -67
  129. package/src/agents/projection.ts +9 -1
  130. package/src/graphs/Graph.ts +56 -23
  131. package/src/graphs/MultiAgentGraph.ts +175 -33
  132. package/src/llm/openai/index.ts +43 -0
  133. package/src/messages/alternation.ts +217 -34
  134. package/src/messages/core.ts +358 -38
  135. package/src/messages/format.ts +958 -137
  136. package/src/messages/handoffCue.ts +7 -4
  137. package/src/messages/index.ts +1 -0
  138. package/src/messages/injected.ts +9 -6
  139. package/src/messages/provenance.ts +897 -0
  140. package/src/messages/prune.ts +28 -10
  141. package/src/messages/toolResultTypes.ts +1174 -0
  142. package/src/run.ts +9 -4
  143. package/src/tools/BashProgrammaticToolCalling.ts +46 -12
  144. package/src/tools/CallerCapabilities.ts +78 -0
  145. package/src/tools/ProgrammaticCallerPolicy.ts +97 -0
  146. package/src/tools/ProgrammaticToolCalling.ts +41 -7
  147. package/src/tools/ToolNode.ts +70 -52
  148. package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +24 -26
  149. package/src/tools/local/LocalProgrammaticToolCalling.ts +28 -23
  150. package/src/tools/local/resolveLocalExecutionTools.ts +132 -13
  151. package/src/tools/ptcTimeout.ts +6 -0
  152. package/src/tools/search/search.ts +25 -8
  153. package/src/tools/search/tool.ts +2 -0
  154. package/src/tools/search/types.ts +16 -0
  155. package/src/tools/subagent/SubagentExecutor.ts +8 -4
  156. package/src/types/graph.ts +3 -0
  157. package/src/types/tools.ts +14 -3
  158. package/src/utils/toolContent.ts +2 -2
@@ -9,10 +9,12 @@ import {
9
9
  } from '@langchain/core/messages';
10
10
  import type { ContentBlock as LangChainContentBlock } from '@langchain/core/messages';
11
11
  import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
12
+ import type { ProviderMessageProvenancePart } from './provenance';
12
13
  import type * as t from '@/types';
13
14
  import {
14
15
  cloneToolMessageWithContent,
15
16
  compactToolContent,
17
+ getToolContentCharLength,
16
18
  getBoundedCacheControlledTextToolContent,
17
19
  getBoundedSingleTextToolContent,
18
20
  getComputerCallOutputScreenshot,
@@ -20,6 +22,13 @@ import {
20
22
  isComputerCallOutputMessage,
21
23
  serializeToolContentBounded,
22
24
  } from '@/utils/toolContent';
25
+ import {
26
+ getProviderMessageProvenance,
27
+ getProviderSourceMessageIds,
28
+ hasBijectiveProviderContentPartMapping,
29
+ setProviderMessageProvenance,
30
+ stampSyntheticProviderMessage,
31
+ } from './provenance';
23
32
  import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
24
33
  import { stripAnthropicCacheControl } from './cache';
25
34
  import { ContentTypes, Providers } from '@/common';
@@ -496,26 +505,177 @@ function appendContentBlocks(
496
505
  }
497
506
  }
498
507
 
499
- /**
500
- * Appends one artifact/tool-content segment without retaining an unbounded
501
- * intermediate block array. Both operands are compacted before they are
502
- * combined, and the combined result is compacted again under the aggregate
503
- * cap.
504
- */
505
- function appendBoundedContent(
506
- current: BaseMessage['content'] | undefined,
507
- next: unknown,
508
+ interface BoundedContentAccumulator {
509
+ readonly blocks: t.MessageContentComplex[];
510
+ hasContent: boolean;
511
+ remainingChars: number;
512
+ }
513
+
514
+ function createBoundedContentAccumulator(
508
515
  maxChars: number
509
- ): BaseMessage['content'] {
510
- const boundedNext = compactToolContent(next, maxChars).content;
511
- if (current == null) {
512
- return boundedNext;
516
+ ): BoundedContentAccumulator {
517
+ return {
518
+ blocks: [],
519
+ hasContent: false,
520
+ remainingChars: Number.isFinite(maxChars)
521
+ ? Math.max(0, Math.floor(maxChars))
522
+ : Number.MAX_SAFE_INTEGER,
523
+ };
524
+ }
525
+
526
+ /** Appends a segment only when at least one of its provider-visible bytes fits.
527
+ * Each segment is compacted against the remaining budget before combination,
528
+ * so a later segment cannot be credited merely because recompression replaced
529
+ * earlier bytes with a truncation notice. */
530
+ function appendBoundedContentSegment(
531
+ accumulator: BoundedContentAccumulator,
532
+ next: unknown
533
+ ): { contributed: boolean; complete: boolean } {
534
+ const separatorChars = accumulator.hasContent ? 1 : 0;
535
+ const availableChars = accumulator.remainingChars - separatorChars;
536
+ const originalChars = getToolContentCharLength(next);
537
+ if (originalChars <= 0) {
538
+ return { contributed: false, complete: true };
539
+ }
540
+ if (availableChars <= 0) {
541
+ return { contributed: false, complete: false };
542
+ }
543
+ const compactedNext = compactToolContent(next, availableChars);
544
+ const boundedNext = compactedNext.content;
545
+ const appendedChars = Math.min(
546
+ availableChars,
547
+ getToolContentCharLength(boundedNext)
548
+ );
549
+ if (appendedChars <= 0) {
550
+ return { contributed: false, complete: false };
551
+ }
552
+ appendContentBlocks(accumulator.blocks, boundedNext);
553
+ accumulator.hasContent = true;
554
+ accumulator.remainingChars -= separatorChars + appendedChars;
555
+ return {
556
+ contributed: true,
557
+ complete: !compactedNext.changed && originalChars <= availableChars,
558
+ };
559
+ }
560
+
561
+ function appendBoundedContentContribution(
562
+ accumulator: BoundedContentAccumulator,
563
+ next: unknown
564
+ ): {
565
+ contributed: boolean;
566
+ complete: boolean;
567
+ retainedContentPartIndices?: ReadonlySet<number>;
568
+ } {
569
+ if (!Array.isArray(next)) {
570
+ return appendBoundedContentSegment(accumulator, next);
571
+ }
572
+ const retainedContentPartIndices = new Set<number>();
573
+ let complete = true;
574
+ for (let index = 0; index < next.length; index++) {
575
+ const projection = appendBoundedContentSegment(accumulator, [next[index]]);
576
+ if (!projection.complete) {
577
+ complete = false;
578
+ }
579
+ if (!projection.contributed) {
580
+ const separatorChars = accumulator.hasContent ? 1 : 0;
581
+ if (accumulator.remainingChars <= separatorChars) {
582
+ if (index < next.length - 1) {
583
+ complete = false;
584
+ }
585
+ break;
586
+ }
587
+ continue;
588
+ }
589
+ retainedContentPartIndices.add(index);
513
590
  }
591
+ return {
592
+ contributed: retainedContentPartIndices.size > 0,
593
+ complete,
594
+ retainedContentPartIndices,
595
+ };
596
+ }
514
597
 
515
- const combined: t.MessageContentComplex[] = [];
516
- appendContentBlocks(combined, current);
517
- appendContentBlocks(combined, boundedNext);
518
- return compactToolContent(toLangChainContent(combined), maxChars).content;
598
+ function retainProjectedContentPartProvenance(
599
+ message: BaseMessage,
600
+ parts: readonly ProviderMessageProvenancePart[],
601
+ retainedContentPartIndices: ReadonlySet<number> | undefined,
602
+ complete: boolean
603
+ ): ProviderMessageProvenancePart[] {
604
+ if (
605
+ complete &&
606
+ (retainedContentPartIndices == null ||
607
+ !Array.isArray(message.content) ||
608
+ retainedContentPartIndices.size === message.content.length)
609
+ ) {
610
+ return [...parts];
611
+ }
612
+ const mapsOneToOne = hasBijectiveProviderContentPartMapping(
613
+ parts,
614
+ message.content.length
615
+ );
616
+ const distinctSourceMessageIds = new Set<string>();
617
+ for (const part of parts) {
618
+ if (part.sourceMessageId != null) {
619
+ distinctSourceMessageIds.add(part.sourceMessageId);
620
+ }
621
+ }
622
+ if (!Array.isArray(message.content)) {
623
+ const soleSourceMessageId =
624
+ distinctSourceMessageIds.size === 1
625
+ ? distinctSourceMessageIds.values().next().value
626
+ : undefined;
627
+ return [
628
+ {
629
+ attribution: parts[0]?.attribution ?? 'tool',
630
+ ...(soleSourceMessageId != null && {
631
+ sourceMessageId: soleSourceMessageId,
632
+ }),
633
+ },
634
+ ];
635
+ }
636
+ const retained: ProviderMessageProvenancePart[] = [];
637
+ const appendUnindexedAttribution = (
638
+ part: ProviderMessageProvenancePart
639
+ ): void => {
640
+ const sourceMessageId =
641
+ distinctSourceMessageIds.size <= 1 ? part.sourceMessageId : undefined;
642
+ if (retained.length > 0) {
643
+ const previous = retained[retained.length - 1];
644
+ if (
645
+ previous.attribution === part.attribution &&
646
+ previous.sourceMessageId === sourceMessageId &&
647
+ previous.sourceContentPartIndices == null
648
+ ) {
649
+ return;
650
+ }
651
+ }
652
+ retained.push({
653
+ attribution: part.attribution,
654
+ ...(sourceMessageId != null && { sourceMessageId }),
655
+ });
656
+ };
657
+ for (const part of parts) {
658
+ if (part.sourceContentPartIndices == null) {
659
+ appendUnindexedAttribution(part);
660
+ continue;
661
+ }
662
+ if (!mapsOneToOne || retainedContentPartIndices == null) {
663
+ appendUnindexedAttribution(part);
664
+ continue;
665
+ }
666
+ const sourceContentPartIndices: number[] = [];
667
+ for (const sourceContentPartIndex of part.sourceContentPartIndices) {
668
+ if (retainedContentPartIndices.has(sourceContentPartIndex)) {
669
+ sourceContentPartIndices.push(sourceContentPartIndex);
670
+ }
671
+ }
672
+ if (sourceContentPartIndices.length > 0) {
673
+ retained.push({ ...part, sourceContentPartIndices });
674
+ }
675
+ }
676
+ return retained.length > 0
677
+ ? retained
678
+ : [{ attribution: parts[0]?.attribution ?? 'tool' }];
519
679
  }
520
680
 
521
681
  function cloneAIMessageWithToolCalls(
@@ -2245,9 +2405,11 @@ export function projectComputerCallOutputsToText(
2245
2405
  continue;
2246
2406
  }
2247
2407
  projected ??= [...messages];
2248
- projected[i] = cloneToolMessageWithContent(
2249
- message,
2250
- '[Computer screenshot omitted for this provider]'
2408
+ projected[i] = stampSyntheticProviderMessage(
2409
+ cloneToolMessageWithContent(
2410
+ message,
2411
+ '[Computer screenshot omitted for this provider]'
2412
+ )
2251
2413
  );
2252
2414
  }
2253
2415
  return projected ?? messages;
@@ -2308,21 +2470,130 @@ export function projectAnthropicArtifactContent(
2308
2470
  const baseContent = Array.isArray(msg.content)
2309
2471
  ? msg.content
2310
2472
  : stringifyToolMessageContent(msg.content);
2311
- const content = appendBoundedContent(
2312
- compactToolContent(baseContent, maxChars).content,
2313
- artifactContent,
2314
- maxChars
2473
+ const aggregate = createBoundedContentAccumulator(maxChars);
2474
+ let contentProjection: ReturnType<
2475
+ typeof appendBoundedContentContribution
2476
+ > = { contributed: false, complete: true };
2477
+ /** Preserve the established empty-result text block shape without
2478
+ * crediting it as a provider-visible source contribution. */
2479
+ if (baseContent === '') {
2480
+ aggregate.blocks.push({ type: ContentTypes.TEXT, text: '' });
2481
+ aggregate.hasContent = true;
2482
+ } else {
2483
+ contentProjection = appendBoundedContentContribution(
2484
+ aggregate,
2485
+ baseContent
2486
+ );
2487
+ }
2488
+ const artifactProjection = appendBoundedContentContribution(
2489
+ aggregate,
2490
+ artifactContent
2315
2491
  );
2492
+ const content = toLangChainContent(aggregate.blocks);
2316
2493
  formattedMessages ??= [...messages];
2317
- formattedMessages[j] = cloneToolMessageWithContent(msg, content, {
2494
+ const projectedMessage = cloneToolMessageWithContent(msg, content, {
2318
2495
  ...msg.artifact,
2319
2496
  content: [],
2320
2497
  });
2498
+ const toolProvenanceParts = projectProviderMessageAttribution(
2499
+ msg,
2500
+ 'tool'
2501
+ );
2502
+ const projectedProvenanceParts: ProviderMessageProvenancePart[] = [];
2503
+ if (contentProjection.contributed) {
2504
+ const retainedParts = retainProjectedContentPartProvenance(
2505
+ msg,
2506
+ toolProvenanceParts,
2507
+ contentProjection.retainedContentPartIndices,
2508
+ contentProjection.complete
2509
+ );
2510
+ for (const part of retainedParts) {
2511
+ projectedProvenanceParts.push(part);
2512
+ }
2513
+ }
2514
+ if (artifactProjection.contributed) {
2515
+ const artifactPart = projectUnindexedProviderMessageAttribution(
2516
+ msg,
2517
+ 'tool'
2518
+ );
2519
+ const previousPart =
2520
+ projectedProvenanceParts[projectedProvenanceParts.length - 1];
2521
+ if (
2522
+ projectedProvenanceParts.length === 0 ||
2523
+ previousPart.attribution !== artifactPart.attribution ||
2524
+ previousPart.sourceMessageId !== artifactPart.sourceMessageId ||
2525
+ previousPart.sourceContentPartIndices != null
2526
+ ) {
2527
+ projectedProvenanceParts.push(artifactPart);
2528
+ }
2529
+ }
2530
+ if (projectedProvenanceParts.length > 0) {
2531
+ setProviderMessageProvenance(
2532
+ projectedMessage,
2533
+ projectedProvenanceParts
2534
+ );
2535
+ }
2536
+ formattedMessages[j] = projectedMessage;
2321
2537
  }
2322
2538
  }
2323
2539
  return formattedMessages ?? messages;
2324
2540
  }
2325
2541
 
2542
+ function projectProviderMessageAttribution(
2543
+ message: BaseMessage,
2544
+ attribution: ProviderMessageProvenancePart['attribution']
2545
+ ): ProviderMessageProvenancePart[] {
2546
+ const explicit = getProviderMessageProvenance(message);
2547
+ const sourceMessageIds = getProviderSourceMessageIds(message);
2548
+ const explicitSourceIds = new Set<string>();
2549
+ for (const part of explicit?.parts ?? []) {
2550
+ if (part.sourceMessageId != null) {
2551
+ explicitSourceIds.add(part.sourceMessageId);
2552
+ }
2553
+ }
2554
+ const soleSourceFallback =
2555
+ explicitSourceIds.size === 0 && sourceMessageIds.length === 1
2556
+ ? sourceMessageIds[0]
2557
+ : undefined;
2558
+ const parts: ProviderMessageProvenancePart[] = [];
2559
+ const representedSourceIds = new Set<string>();
2560
+ for (const part of explicit?.parts ?? []) {
2561
+ const sourceMessageId = part.sourceMessageId ?? soleSourceFallback;
2562
+ if (sourceMessageId != null) {
2563
+ representedSourceIds.add(sourceMessageId);
2564
+ }
2565
+ parts.push({
2566
+ attribution,
2567
+ ...(sourceMessageId != null && { sourceMessageId }),
2568
+ ...(part.sourceContentPartIndices != null && {
2569
+ sourceContentPartIndices: part.sourceContentPartIndices,
2570
+ }),
2571
+ });
2572
+ }
2573
+ for (const sourceMessageId of sourceMessageIds) {
2574
+ if (!representedSourceIds.has(sourceMessageId)) {
2575
+ parts.push({ attribution, sourceMessageId });
2576
+ }
2577
+ }
2578
+ return parts.length > 0 ? parts : [{ attribution }];
2579
+ }
2580
+
2581
+ /** Artifacts are attached to a tool message rather than one of its content
2582
+ * positions. Retain the source row only when it is unambiguous, but never
2583
+ * claim a raw content-part index for artifact-only bytes. */
2584
+ function projectUnindexedProviderMessageAttribution(
2585
+ message: BaseMessage,
2586
+ attribution: ProviderMessageProvenancePart['attribution']
2587
+ ): ProviderMessageProvenancePart {
2588
+ const sourceMessageIds = getProviderSourceMessageIds(message);
2589
+ return {
2590
+ attribution,
2591
+ ...(sourceMessageIds.length === 1 && {
2592
+ sourceMessageId: sourceMessageIds[0],
2593
+ }),
2594
+ };
2595
+ }
2596
+
2326
2597
  /**
2327
2598
  * Mutating compatibility wrapper retained for existing package consumers.
2328
2599
  * New provider-call paths should use `projectAnthropicArtifactContent`.
@@ -2339,6 +2610,10 @@ export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
2339
2610
  projected[i] !== messages[i]
2340
2611
  ) {
2341
2612
  messages[i].content = projected[i].content;
2613
+ const provenance = getProviderMessageProvenance(projected[i]);
2614
+ if (provenance != null) {
2615
+ setProviderMessageProvenance(messages[i], provenance.parts);
2616
+ }
2342
2617
  }
2343
2618
  }
2344
2619
  }
@@ -2363,8 +2638,9 @@ export function projectArtifactPayload(
2363
2638
  if (latestAIParentIndex === -1) return messages;
2364
2639
 
2365
2640
  // Single pass: collect relevant tool messages with artifacts and aggregate
2366
- let aggregatedContent: BaseMessage['content'] | undefined;
2641
+ const aggregate = createBoundedContentAccumulator(maxChars);
2367
2642
  let formattedMessages: BaseMessage[] | undefined;
2643
+ const artifactProvenanceParts: ProviderMessageProvenancePart[] = [];
2368
2644
 
2369
2645
  for (let i = latestAIParentIndex + 1; i < messages.length; i++) {
2370
2646
  const msg = messages[i];
@@ -2380,13 +2656,12 @@ export function projectArtifactPayload(
2380
2656
  ) {
2381
2657
  continue;
2382
2658
  }
2383
- aggregatedContent = appendBoundedContent(
2384
- aggregatedContent,
2385
- msg.content,
2386
- maxChars
2659
+ const contentProjection = appendBoundedContentContribution(
2660
+ aggregate,
2661
+ msg.content
2387
2662
  );
2388
2663
  formattedMessages ??= [...messages];
2389
- formattedMessages[i] = cloneToolMessageWithContent(
2664
+ const placeholder = cloneToolMessageWithContent(
2390
2665
  msg,
2391
2666
  'Tool response is included in the next message as a Human message',
2392
2667
  {
@@ -2394,15 +2669,54 @@ export function projectArtifactPayload(
2394
2669
  content: [],
2395
2670
  }
2396
2671
  );
2397
- aggregatedContent = appendBoundedContent(
2398
- aggregatedContent,
2399
- msg.artifact.content,
2400
- maxChars
2672
+ const toolProvenanceParts = projectProviderMessageAttribution(msg, 'tool');
2673
+ setProviderMessageProvenance(
2674
+ placeholder,
2675
+ toolProvenanceParts.map((part) => ({
2676
+ ...part,
2677
+ attribution: 'synthetic',
2678
+ }))
2679
+ );
2680
+ formattedMessages[i] = placeholder;
2681
+ const artifactProjection = appendBoundedContentContribution(
2682
+ aggregate,
2683
+ msg.artifact.content
2401
2684
  );
2685
+ if (contentProjection.contributed) {
2686
+ const retainedParts = retainProjectedContentPartProvenance(
2687
+ msg,
2688
+ toolProvenanceParts,
2689
+ contentProjection.retainedContentPartIndices,
2690
+ contentProjection.complete
2691
+ );
2692
+ for (const part of retainedParts) {
2693
+ artifactProvenanceParts.push(part);
2694
+ }
2695
+ }
2696
+ if (artifactProjection.contributed) {
2697
+ const artifactPart = projectUnindexedProviderMessageAttribution(
2698
+ msg,
2699
+ 'tool'
2700
+ );
2701
+ const previousPart =
2702
+ artifactProvenanceParts[artifactProvenanceParts.length - 1];
2703
+ if (
2704
+ artifactProvenanceParts.length === 0 ||
2705
+ previousPart.attribution !== artifactPart.attribution ||
2706
+ previousPart.sourceMessageId !== artifactPart.sourceMessageId ||
2707
+ previousPart.sourceContentPartIndices != null
2708
+ ) {
2709
+ artifactProvenanceParts.push(artifactPart);
2710
+ }
2711
+ }
2402
2712
  }
2403
2713
 
2404
- if (aggregatedContent != null) {
2405
- formattedMessages?.push(new HumanMessage({ content: aggregatedContent }));
2714
+ if (aggregate.hasContent && artifactProvenanceParts.length > 0) {
2715
+ const artifactPayload = new HumanMessage({
2716
+ content: toLangChainContent(aggregate.blocks),
2717
+ });
2718
+ setProviderMessageProvenance(artifactPayload, artifactProvenanceParts);
2719
+ formattedMessages?.push(artifactPayload);
2406
2720
  }
2407
2721
  return formattedMessages ?? messages;
2408
2722
  }
@@ -2424,9 +2738,15 @@ export function formatArtifactPayload(messages: BaseMessage[]): void {
2424
2738
  projected[i] !== messages[i]
2425
2739
  ) {
2426
2740
  messages[i].content = projected[i].content;
2741
+ const provenance = getProviderMessageProvenance(projected[i]);
2742
+ if (provenance != null) {
2743
+ setProviderMessageProvenance(messages[i], provenance.parts);
2744
+ }
2427
2745
  }
2428
2746
  }
2429
- messages.push(...projected.slice(originalLength));
2747
+ for (let i = originalLength; i < projected.length; i++) {
2748
+ messages.push(projected[i]);
2749
+ }
2430
2750
  }
2431
2751
 
2432
2752
  export function findLastIndex<T>(