@librechat/agents 3.3.10 → 3.3.12

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 (211) hide show
  1. package/dist/cjs/graphs/Graph.cjs +248 -27
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/langfuseToolOutputTracing.cjs +228 -16
  4. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  5. package/dist/cjs/llm/bedrock/index.cjs +13 -2
  6. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  7. package/dist/cjs/llm/init.cjs +1 -1
  8. package/dist/cjs/llm/invoke.cjs +160 -43
  9. package/dist/cjs/llm/invoke.cjs.map +1 -1
  10. package/dist/cjs/llm/openai/index.cjs +190 -13
  11. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  12. package/dist/cjs/llm/streamLimits.cjs +723 -0
  13. package/dist/cjs/llm/streamLimits.cjs.map +1 -0
  14. package/dist/cjs/main.cjs +12 -3
  15. package/dist/cjs/messages/core.cjs +592 -27
  16. package/dist/cjs/messages/core.cjs.map +1 -1
  17. package/dist/cjs/run.cjs +7 -3
  18. package/dist/cjs/run.cjs.map +1 -1
  19. package/dist/cjs/session/AgentSession.cjs +4 -1
  20. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  21. package/dist/cjs/stream.cjs +105 -14
  22. package/dist/cjs/stream.cjs.map +1 -1
  23. package/dist/cjs/summarization/node.cjs +157 -37
  24. package/dist/cjs/summarization/node.cjs.map +1 -1
  25. package/dist/cjs/tools/BashExecutor.cjs +3 -2
  26. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  27. package/dist/cjs/tools/CodeExecutor.cjs +4 -3
  28. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  29. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -3
  30. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  31. package/dist/cjs/tools/ToolNode.cjs +77 -4
  32. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  33. package/dist/cjs/tools/ToolSearch.cjs +3 -2
  34. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  35. package/dist/cjs/tools/search/crw-scraper.cjs +7 -1
  36. package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
  37. package/dist/cjs/tools/search/crw-search.cjs +3 -1
  38. package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
  39. package/dist/cjs/tools/search/firecrawl.cjs +7 -1
  40. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  41. package/dist/cjs/tools/search/keenable-scraper.cjs +7 -1
  42. package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
  43. package/dist/cjs/tools/search/keenable-search.cjs +3 -1
  44. package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
  45. package/dist/cjs/tools/search/rerankers.cjs +26 -8
  46. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  47. package/dist/cjs/tools/search/search.cjs +30 -10
  48. package/dist/cjs/tools/search/search.cjs.map +1 -1
  49. package/dist/cjs/tools/search/serper-scraper.cjs +7 -1
  50. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
  51. package/dist/cjs/tools/search/tavily-scraper.cjs +7 -1
  52. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
  53. package/dist/cjs/tools/search/tavily-search.cjs +3 -1
  54. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
  55. package/dist/cjs/tools/search/tool.cjs +17 -3
  56. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  57. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +54 -3
  58. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  59. package/dist/cjs/utils/index.cjs +2 -1
  60. package/dist/cjs/utils/misc.cjs +12 -0
  61. package/dist/cjs/utils/misc.cjs.map +1 -1
  62. package/dist/cjs/utils/proxy.cjs +63 -0
  63. package/dist/cjs/utils/proxy.cjs.map +1 -0
  64. package/dist/esm/graphs/Graph.mjs +247 -26
  65. package/dist/esm/graphs/Graph.mjs.map +1 -1
  66. package/dist/esm/langfuseToolOutputTracing.mjs +228 -16
  67. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  68. package/dist/esm/llm/bedrock/index.mjs +13 -2
  69. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  70. package/dist/esm/llm/init.mjs +1 -1
  71. package/dist/esm/llm/invoke.mjs +160 -43
  72. package/dist/esm/llm/invoke.mjs.map +1 -1
  73. package/dist/esm/llm/openai/index.mjs +192 -15
  74. package/dist/esm/llm/openai/index.mjs.map +1 -1
  75. package/dist/esm/llm/streamLimits.mjs +704 -0
  76. package/dist/esm/llm/streamLimits.mjs.map +1 -0
  77. package/dist/esm/main.mjs +8 -6
  78. package/dist/esm/messages/core.mjs +592 -28
  79. package/dist/esm/messages/core.mjs.map +1 -1
  80. package/dist/esm/run.mjs +7 -3
  81. package/dist/esm/run.mjs.map +1 -1
  82. package/dist/esm/session/AgentSession.mjs +4 -1
  83. package/dist/esm/session/AgentSession.mjs.map +1 -1
  84. package/dist/esm/stream.mjs +105 -14
  85. package/dist/esm/stream.mjs.map +1 -1
  86. package/dist/esm/summarization/node.mjs +157 -37
  87. package/dist/esm/summarization/node.mjs.map +1 -1
  88. package/dist/esm/tools/BashExecutor.mjs +3 -2
  89. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  90. package/dist/esm/tools/CodeExecutor.mjs +4 -3
  91. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  92. package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -3
  93. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  94. package/dist/esm/tools/ToolNode.mjs +77 -4
  95. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  96. package/dist/esm/tools/ToolSearch.mjs +3 -2
  97. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  98. package/dist/esm/tools/search/crw-scraper.mjs +7 -1
  99. package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
  100. package/dist/esm/tools/search/crw-search.mjs +3 -1
  101. package/dist/esm/tools/search/crw-search.mjs.map +1 -1
  102. package/dist/esm/tools/search/firecrawl.mjs +7 -1
  103. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  104. package/dist/esm/tools/search/keenable-scraper.mjs +7 -1
  105. package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
  106. package/dist/esm/tools/search/keenable-search.mjs +3 -1
  107. package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
  108. package/dist/esm/tools/search/rerankers.mjs +26 -8
  109. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  110. package/dist/esm/tools/search/search.mjs +30 -10
  111. package/dist/esm/tools/search/search.mjs.map +1 -1
  112. package/dist/esm/tools/search/serper-scraper.mjs +7 -1
  113. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
  114. package/dist/esm/tools/search/tavily-scraper.mjs +7 -1
  115. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
  116. package/dist/esm/tools/search/tavily-search.mjs +3 -1
  117. package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
  118. package/dist/esm/tools/search/tool.mjs +17 -3
  119. package/dist/esm/tools/search/tool.mjs.map +1 -1
  120. package/dist/esm/tools/subagent/SubagentExecutor.mjs +54 -3
  121. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  122. package/dist/esm/utils/index.mjs +2 -1
  123. package/dist/esm/utils/misc.mjs +12 -1
  124. package/dist/esm/utils/misc.mjs.map +1 -1
  125. package/dist/esm/utils/proxy.mjs +62 -0
  126. package/dist/esm/utils/proxy.mjs.map +1 -0
  127. package/dist/types/graphs/Graph.d.ts +53 -1
  128. package/dist/types/index.d.ts +2 -0
  129. package/dist/types/langfuseToolOutputTracing.d.ts +1 -0
  130. package/dist/types/llm/invoke.d.ts +26 -5
  131. package/dist/types/llm/openai/index.d.ts +3 -0
  132. package/dist/types/llm/streamLimits.d.ts +314 -0
  133. package/dist/types/messages/core.d.ts +11 -6
  134. package/dist/types/run.d.ts +1 -0
  135. package/dist/types/summarization/node.d.ts +27 -2
  136. package/dist/types/tools/BashExecutor.d.ts +2 -2
  137. package/dist/types/tools/CodeExecutor.d.ts +3 -3
  138. package/dist/types/tools/ToolNode.d.ts +11 -1
  139. package/dist/types/tools/search/crw-scraper.d.ts +2 -0
  140. package/dist/types/tools/search/firecrawl.d.ts +2 -0
  141. package/dist/types/tools/search/keenable-scraper.d.ts +2 -0
  142. package/dist/types/tools/search/rerankers.d.ts +9 -5
  143. package/dist/types/tools/search/serper-scraper.d.ts +2 -0
  144. package/dist/types/tools/search/tavily-scraper.d.ts +2 -0
  145. package/dist/types/tools/search/types.d.ts +28 -33
  146. package/dist/types/tools/subagent/SubagentExecutor.d.ts +44 -0
  147. package/dist/types/types/graph.d.ts +7 -1
  148. package/dist/types/types/run.d.ts +46 -1
  149. package/dist/types/types/tools.d.ts +21 -0
  150. package/dist/types/utils/index.d.ts +1 -0
  151. package/dist/types/utils/misc.d.ts +7 -0
  152. package/dist/types/utils/proxy.d.ts +31 -0
  153. package/package.json +2 -1
  154. package/src/__tests__/stream.eagerArgsDivergence.test.ts +158 -0
  155. package/src/__tests__/stream.eagerEventExecution.test.ts +1 -0
  156. package/src/__tests__/stream.streamLimits.test.ts +1982 -0
  157. package/src/graphs/Graph.ts +302 -27
  158. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +234 -0
  159. package/src/index.ts +11 -0
  160. package/src/langfuseToolOutputTracing.ts +410 -14
  161. package/src/llm/bedrock/index.ts +22 -2
  162. package/src/llm/custom-chat-models.smoke.test.ts +747 -0
  163. package/src/llm/invoke.streamLimits.test.ts +142 -0
  164. package/src/llm/invoke.test.ts +187 -1
  165. package/src/llm/invoke.ts +231 -43
  166. package/src/llm/openai/cacheWriteTokens.test.ts +112 -0
  167. package/src/llm/openai/index.ts +348 -31
  168. package/src/llm/openai/llm.spec.ts +107 -6
  169. package/src/llm/streamLimits.test.ts +450 -0
  170. package/src/llm/streamLimits.ts +1158 -0
  171. package/src/messages/core.ts +1290 -42
  172. package/src/messages/formatAgentMessages.test.ts +2623 -0
  173. package/src/run.ts +4 -0
  174. package/src/session/AgentSession.ts +5 -0
  175. package/src/specs/langfuse-tool-output-tracing.test.ts +887 -0
  176. package/src/specs/preemptSeal.test.ts +374 -5
  177. package/src/stream.ts +141 -6
  178. package/src/summarization/__tests__/node.test.ts +269 -0
  179. package/src/summarization/chunkHandler.test.ts +196 -0
  180. package/src/summarization/node.ts +203 -6
  181. package/src/tools/BashExecutor.ts +4 -3
  182. package/src/tools/CodeExecutor.ts +5 -4
  183. package/src/tools/ProgrammaticToolCalling.ts +7 -5
  184. package/src/tools/ToolNode.ts +109 -6
  185. package/src/tools/ToolSearch.ts +4 -3
  186. package/src/tools/__tests__/BashExecutor.test.ts +2 -2
  187. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +2 -4
  188. package/src/tools/__tests__/SubagentExecutor.test.ts +126 -0
  189. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +389 -0
  190. package/src/tools/__tests__/ToolNode.streamLimits.test.ts +69 -0
  191. package/src/tools/search/crw-scraper.ts +6 -0
  192. package/src/tools/search/crw-search.ts +6 -1
  193. package/src/tools/search/firecrawl.ts +6 -0
  194. package/src/tools/search/http-agent.test.ts +133 -0
  195. package/src/tools/search/keenable-scraper.ts +6 -0
  196. package/src/tools/search/keenable-search.ts +6 -1
  197. package/src/tools/search/rerankers.ts +36 -10
  198. package/src/tools/search/search.ts +29 -11
  199. package/src/tools/search/serper-scraper.ts +6 -0
  200. package/src/tools/search/tavily-scraper.ts +6 -0
  201. package/src/tools/search/tavily-search.ts +2 -0
  202. package/src/tools/search/tool.ts +16 -0
  203. package/src/tools/search/types.ts +31 -33
  204. package/src/tools/subagent/SubagentExecutor.ts +96 -3
  205. package/src/types/graph.ts +7 -0
  206. package/src/types/run.ts +49 -1
  207. package/src/types/tools.ts +21 -0
  208. package/src/utils/index.ts +1 -0
  209. package/src/utils/misc.ts +19 -0
  210. package/src/utils/proxy.test.ts +176 -0
  211. package/src/utils/proxy.ts +93 -0
@@ -7,6 +7,7 @@ import {
7
7
  HumanMessage,
8
8
  AIMessageChunk,
9
9
  } from '@langchain/core/messages';
10
+ import type { ContentBlock as LangChainContentBlock } from '@langchain/core/messages';
10
11
  import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
11
12
  import type * as t from '@/types';
12
13
  import {
@@ -579,7 +580,9 @@ function cloneAIMessageWithContent(
579
580
  ...lcKwargs,
580
581
  value: {
581
582
  ...(lcKwargs.value as Record<string, unknown>),
582
- content,
583
+ ...(message.response_metadata.output_version === 'v1'
584
+ ? { content: undefined, contentBlocks: content }
585
+ : { content }),
583
586
  },
584
587
  };
585
588
  }
@@ -589,55 +592,1287 @@ function cloneAIMessageWithContent(
589
592
  ) as AIMessage;
590
593
  }
591
594
 
595
+ function cloneAIMessageWithResponsesReplayState(
596
+ message: AIMessage,
597
+ content: AIMessage['content'],
598
+ id: string | undefined,
599
+ additionalKwargs: AIMessage['additional_kwargs'],
600
+ responseMetadata: AIMessage['response_metadata']
601
+ ): AIMessage {
602
+ const descriptors = Object.getOwnPropertyDescriptors(message) as Record<
603
+ string,
604
+ PropertyDescriptor | undefined
605
+ >;
606
+ const replacements = {
607
+ content,
608
+ id,
609
+ additional_kwargs: additionalKwargs,
610
+ response_metadata: responseMetadata,
611
+ };
612
+ for (const [key, value] of Object.entries(replacements)) {
613
+ const descriptor = descriptors[key];
614
+ descriptors[key] = {
615
+ configurable: descriptor?.configurable ?? true,
616
+ enumerable: descriptor?.enumerable ?? true,
617
+ value,
618
+ writable: descriptor?.writable ?? true,
619
+ };
620
+ }
621
+ const lcKwargs = descriptors.lc_kwargs;
622
+ if (
623
+ lcKwargs != null &&
624
+ 'value' in lcKwargs &&
625
+ typeof lcKwargs.value === 'object' &&
626
+ lcKwargs.value != null
627
+ ) {
628
+ descriptors.lc_kwargs = {
629
+ ...lcKwargs,
630
+ value: {
631
+ ...(lcKwargs.value as Record<string, unknown>),
632
+ ...replacements,
633
+ ...(responseMetadata.output_version === 'v1'
634
+ ? { content: undefined, contentBlocks: content }
635
+ : {}),
636
+ },
637
+ };
638
+ }
639
+ return Object.create(
640
+ Object.getPrototypeOf(message),
641
+ descriptors as PropertyDescriptorMap
642
+ ) as AIMessage;
643
+ }
644
+
645
+ function hasReplayableEncryptedReasoning(reasoning: unknown): boolean {
646
+ if (reasoning == null || typeof reasoning !== 'object') {
647
+ return false;
648
+ }
649
+ const item = reasoning as {
650
+ encrypted_content?: unknown;
651
+ id?: unknown;
652
+ status?: unknown;
653
+ summary?: unknown;
654
+ type?: unknown;
655
+ };
656
+ return (
657
+ item.type === 'reasoning' &&
658
+ typeof item.id === 'string' &&
659
+ item.id.length > 0 &&
660
+ Array.isArray(item.summary) &&
661
+ typeof item.encrypted_content === 'string' &&
662
+ item.encrypted_content.length > 0 &&
663
+ (item.status === undefined ||
664
+ item.status === 'completed' ||
665
+ item.status === 'incomplete')
666
+ );
667
+ }
668
+
669
+ type ResponsesReplayProjection = 'fallback' | 'native';
670
+
671
+ export const OPENAI_RESPONSES_REPLAY_POSITIONS_KEY =
672
+ '__openai_responses_replay_positions__';
673
+
674
+ export type ResponsesReplayPosition = {
675
+ contentIndex?: number;
676
+ itemId: string;
677
+ kind: 'message' | 'output' | 'reasoning' | 'text';
678
+ outputIndex: number;
679
+ };
680
+
681
+ type CompletedGeneratedImages = {
682
+ blocks: PositionedResponsesReplayBlock[];
683
+ data: Set<string>;
684
+ ids: Set<string>;
685
+ };
686
+
687
+ type ResponsesReplayArtifacts = {
688
+ generatedImages: CompletedGeneratedImages;
689
+ messagePositions: ResponsesReplayPosition[];
690
+ positionsByItemId: Map<string, ResponsesReplayItemPosition>;
691
+ positionedServerToolResultIds: Set<string>;
692
+ serverToolResults: PositionedResponsesReplayBlock[];
693
+ textPositions: ResponsesReplayPosition[];
694
+ };
695
+
696
+ type ResponsesReplayItemPosition = Pick<
697
+ PositionedResponsesReplayBlock,
698
+ 'outputIndex' | 'textIndex'
699
+ >;
700
+
701
+ type PositionedResponsesReplayBlock = {
702
+ block: LangChainContentBlock.Standard;
703
+ outputIndex: number;
704
+ subIndex: number;
705
+ textIndex?: number;
706
+ };
707
+
708
+ type PositionedResponsesImage = {
709
+ block: LangChainContentBlock.Multimodal.Image;
710
+ textIndex: number;
711
+ };
712
+
713
+ type PositionedResponsesImages = {
714
+ blocks: PositionedResponsesImage[];
715
+ textCount: number;
716
+ };
717
+
718
+ function isProviderGeneratedImageBlock(
719
+ block: LangChainContentBlock.Multimodal.Image,
720
+ generatedImages: CompletedGeneratedImages,
721
+ allowDataFallback = true
722
+ ): boolean {
723
+ if (typeof block.id === 'string' && block.id.length > 0) {
724
+ return generatedImages.ids.has(block.id);
725
+ }
726
+ return (
727
+ allowDataFallback &&
728
+ typeof block.data === 'string' &&
729
+ generatedImages.data.has(block.data) &&
730
+ block.metadata != null &&
731
+ typeof block.metadata === 'object' &&
732
+ block.metadata.status === 'completed'
733
+ );
734
+ }
735
+
736
+ function hasMeaningfulServerToolOutput(output: unknown): boolean {
737
+ if (output == null || output === '') {
738
+ return false;
739
+ }
740
+ if (Array.isArray(output)) {
741
+ return output.length > 0;
742
+ }
743
+ if (typeof output !== 'object') {
744
+ return true;
745
+ }
746
+ try {
747
+ return Object.keys(output).length > 0;
748
+ } catch {
749
+ return false;
750
+ }
751
+ }
752
+
753
+ function createServerToolResultExtras(toolName?: string): {
754
+ librechatServerToolResult: { toolName?: string };
755
+ } {
756
+ return {
757
+ librechatServerToolResult: {
758
+ ...(toolName != null ? { toolName } : {}),
759
+ },
760
+ };
761
+ }
762
+
763
+ function createNeutralServerToolResult(
764
+ output: unknown,
765
+ status: 'error' | 'success',
766
+ maxChars: number,
767
+ toolName?: string
768
+ ): LangChainContentBlock.Text | undefined {
769
+ if (!hasMeaningfulServerToolOutput(output)) {
770
+ return undefined;
771
+ }
772
+ return {
773
+ type: 'text',
774
+ text: serializeToolContentBounded(
775
+ {
776
+ serverToolResult: {
777
+ librechatResponsesReplay: true,
778
+ ...(toolName != null ? { toolName } : {}),
779
+ status,
780
+ output,
781
+ },
782
+ },
783
+ maxChars
784
+ ),
785
+ extras: createServerToolResultExtras(toolName),
786
+ };
787
+ }
788
+
789
+ function isCompleteToolStreamContentBlock(block: unknown): boolean {
790
+ if (block == null || typeof block !== 'object') {
791
+ return true;
792
+ }
793
+ try {
794
+ if (isProxy(block)) {
795
+ return false;
796
+ }
797
+ const type = Object.getOwnPropertyDescriptor(block, 'type');
798
+ if (type == null) {
799
+ return true;
800
+ }
801
+ if (type.enumerable !== true || !('value' in type)) {
802
+ return false;
803
+ }
804
+ if (type.value !== 'text') {
805
+ return true;
806
+ }
807
+ const text = Object.getOwnPropertyDescriptor(block, 'text');
808
+ return (
809
+ text?.enumerable === true &&
810
+ 'value' in text &&
811
+ typeof text.value === 'string' &&
812
+ text.value !== ''
813
+ );
814
+ } catch {
815
+ return false;
816
+ }
817
+ }
818
+
819
+ function projectPreemptedResponsesV1Content(
820
+ content: AIMessage['content'],
821
+ reasoning: unknown,
822
+ maxChars: number,
823
+ replayProjection: ResponsesReplayProjection,
824
+ generatedImages?: CompletedGeneratedImages,
825
+ originalImages?: PositionedResponsesImages,
826
+ serverToolResults?: PositionedResponsesReplayBlock[],
827
+ positionedServerToolResultIds?: ReadonlySet<string>,
828
+ reasoningPosition?: ResponsesReplayItemPosition,
829
+ textPositions?: readonly ResponsesReplayPosition[],
830
+ messagePositions?: readonly ResponsesReplayPosition[]
831
+ ): {
832
+ content: AIMessage['content'];
833
+ preservedServerToolResult: boolean;
834
+ } {
835
+ if (!Array.isArray(content)) {
836
+ return { content, preservedServerToolResult: false };
837
+ }
838
+
839
+ const contentBlocks = content as LangChainContentBlock.Standard[];
840
+ const encryptedReasoning =
841
+ replayProjection === 'native' && hasReplayableEncryptedReasoning(reasoning)
842
+ ? reasoning
843
+ : undefined;
844
+ const positionedReasoning: PositionedResponsesReplayBlock | undefined =
845
+ encryptedReasoning != null && reasoningPosition != null
846
+ ? {
847
+ block: {
848
+ type: 'non_standard' as const,
849
+ value: encryptedReasoning,
850
+ },
851
+ ...reasoningPosition,
852
+ subIndex: 0,
853
+ }
854
+ : undefined;
855
+ const projected: LangChainContentBlock.Standard[] = [];
856
+ let changed = false;
857
+ let hasEncryptedReasoning = false;
858
+ let preservedServerToolResult = false;
859
+ let reasoningInsertionIndex: number | undefined;
860
+ let generatedImageIndex = 0;
861
+ let originalImageIndex = 0;
862
+ let reasoningPending = positionedReasoning;
863
+ let serverToolResultIndex = 0;
864
+ let sourceTextIndex = 0;
865
+ const serverToolNamesByCallId = new Map<string, string>();
866
+
867
+ const appendOriginalImages = (throughTextIndex?: number): void => {
868
+ if (replayProjection !== 'native' || originalImages == null) {
869
+ return;
870
+ }
871
+ while (originalImageIndex < originalImages.blocks.length) {
872
+ const positionedImage = originalImages.blocks[originalImageIndex];
873
+ if (
874
+ throughTextIndex != null &&
875
+ positionedImage.textIndex > throughTextIndex
876
+ ) {
877
+ return;
878
+ }
879
+ originalImageIndex++;
880
+ if (
881
+ generatedImages != null &&
882
+ isProviderGeneratedImageBlock(
883
+ positionedImage.block,
884
+ generatedImages,
885
+ false
886
+ )
887
+ ) {
888
+ continue;
889
+ }
890
+ projected.push(positionedImage.block);
891
+ changed = true;
892
+ }
893
+ };
894
+
895
+ const appendReplayBlocks = (
896
+ throughTextIndex?: number,
897
+ beforeOutputIndex?: number
898
+ ): void => {
899
+ for (;;) {
900
+ const generatedImage = generatedImages?.blocks[generatedImageIndex];
901
+ const serverToolResult = serverToolResults?.[serverToolResultIndex];
902
+ const candidates: Array<{
903
+ kind: 'generatedImage' | 'reasoning' | 'serverToolResult';
904
+ value: PositionedResponsesReplayBlock;
905
+ }> = [];
906
+ if (generatedImage != null) {
907
+ candidates.push({ kind: 'generatedImage', value: generatedImage });
908
+ }
909
+ if (serverToolResult != null) {
910
+ candidates.push({ kind: 'serverToolResult', value: serverToolResult });
911
+ }
912
+ if (reasoningPending != null) {
913
+ candidates.push({ kind: 'reasoning', value: reasoningPending });
914
+ }
915
+ if (candidates.length === 0) {
916
+ return;
917
+ }
918
+ candidates.sort((a, b) =>
919
+ comparePositionedResponsesReplayBlocks(a.value, b.value)
920
+ );
921
+ const selected = candidates[0];
922
+ const positionedResult = selected.value;
923
+ if (
924
+ throughTextIndex != null &&
925
+ (positionedResult.textIndex == null ||
926
+ positionedResult.textIndex > throughTextIndex)
927
+ ) {
928
+ return;
929
+ }
930
+ if (
931
+ beforeOutputIndex != null &&
932
+ positionedResult.outputIndex >= beforeOutputIndex
933
+ ) {
934
+ return;
935
+ }
936
+ if (selected.kind === 'generatedImage') {
937
+ generatedImageIndex++;
938
+ } else if (selected.kind === 'serverToolResult') {
939
+ serverToolResultIndex++;
940
+ } else {
941
+ reasoningPending = undefined;
942
+ }
943
+ if (
944
+ replayProjection === 'fallback' &&
945
+ positionedResult.block.type === 'text'
946
+ ) {
947
+ projected.push({
948
+ type: 'text',
949
+ text: positionedResult.block.text,
950
+ });
951
+ } else {
952
+ projected.push(positionedResult.block);
953
+ }
954
+ changed = true;
955
+ preservedServerToolResult ||= selected.kind === 'serverToolResult';
956
+ }
957
+ };
958
+
959
+ const appendPositionedOriginalImages = (throughTextIndex?: number): void => {
960
+ const nextOriginalImage = originalImages?.blocks[originalImageIndex];
961
+ if (
962
+ nextOriginalImage == null ||
963
+ (throughTextIndex != null &&
964
+ nextOriginalImage.textIndex > throughTextIndex)
965
+ ) {
966
+ return;
967
+ }
968
+ const followingTextPosition = textPositions?.[nextOriginalImage.textIndex];
969
+ const imageOnlyMessagePosition =
970
+ originalImages?.textCount === 0 ? messagePositions?.[0] : undefined;
971
+ const outputBoundary = followingTextPosition ?? imageOnlyMessagePosition;
972
+ if (outputBoundary != null) {
973
+ appendReplayBlocks(
974
+ nextOriginalImage.textIndex,
975
+ outputBoundary.outputIndex
976
+ );
977
+ }
978
+ appendOriginalImages(throughTextIndex);
979
+ };
980
+
981
+ for (let i = 0; i < contentBlocks.length; i++) {
982
+ const block = contentBlocks[i];
983
+ if (!isCompleteToolStreamContentBlock(block)) {
984
+ changed = true;
985
+ continue;
986
+ }
987
+ if (block.type === 'reasoning') {
988
+ reasoningInsertionIndex ??= projected.length;
989
+ changed = true;
990
+ continue;
991
+ }
992
+ if (block.type === 'non_standard') {
993
+ if (
994
+ replayProjection === 'native' &&
995
+ !hasEncryptedReasoning &&
996
+ hasReplayableEncryptedReasoning(block.value)
997
+ ) {
998
+ hasEncryptedReasoning = true;
999
+ if (positionedReasoning == null) {
1000
+ projected.push(block);
1001
+ } else {
1002
+ changed = true;
1003
+ }
1004
+ } else {
1005
+ changed = true;
1006
+ }
1007
+ continue;
1008
+ }
1009
+ if (block.type === 'text') {
1010
+ appendPositionedOriginalImages(sourceTextIndex);
1011
+ appendReplayBlocks(sourceTextIndex);
1012
+ if (replayProjection === 'fallback') {
1013
+ projected.push({ type: 'text', text: block.text });
1014
+ changed = true;
1015
+ } else {
1016
+ projected.push(block);
1017
+ }
1018
+ sourceTextIndex++;
1019
+ continue;
1020
+ }
1021
+ if (originalImages != null && sourceTextIndex >= originalImages.textCount) {
1022
+ appendPositionedOriginalImages();
1023
+ }
1024
+ if (
1025
+ block.type === 'server_tool_call' ||
1026
+ block.type === 'server_tool_call_chunk'
1027
+ ) {
1028
+ if (
1029
+ typeof block.id === 'string' &&
1030
+ block.id.length > 0 &&
1031
+ typeof block.name === 'string' &&
1032
+ block.name.length > 0
1033
+ ) {
1034
+ serverToolNamesByCallId.set(block.id, block.name);
1035
+ }
1036
+ changed = true;
1037
+ continue;
1038
+ }
1039
+ if (block.type === 'server_tool_call_result') {
1040
+ if (positionedServerToolResultIds?.has(block.toolCallId) === true) {
1041
+ changed = true;
1042
+ continue;
1043
+ }
1044
+ const result = createNeutralServerToolResult(
1045
+ block.output,
1046
+ block.status,
1047
+ maxChars,
1048
+ serverToolNamesByCallId.get(block.toolCallId)
1049
+ );
1050
+ changed = true;
1051
+ if (result != null) {
1052
+ projected.push(
1053
+ replayProjection === 'fallback'
1054
+ ? { type: 'text', text: result.text }
1055
+ : result
1056
+ );
1057
+ preservedServerToolResult = true;
1058
+ }
1059
+ continue;
1060
+ }
1061
+ if (block.type === 'image') {
1062
+ if (
1063
+ replayProjection === 'fallback' ||
1064
+ (generatedImages != null &&
1065
+ isProviderGeneratedImageBlock(block, generatedImages))
1066
+ ) {
1067
+ changed = true;
1068
+ continue;
1069
+ }
1070
+ }
1071
+ projected.push(block);
1072
+ }
1073
+
1074
+ if (
1075
+ encryptedReasoning != null &&
1076
+ !hasEncryptedReasoning &&
1077
+ positionedReasoning == null
1078
+ ) {
1079
+ const reasoningBlock: LangChainContentBlock.Standard = {
1080
+ type: 'non_standard',
1081
+ value: encryptedReasoning,
1082
+ };
1083
+ projected.splice(reasoningInsertionIndex ?? 0, 0, reasoningBlock);
1084
+ changed = true;
1085
+ }
1086
+ appendPositionedOriginalImages();
1087
+ appendReplayBlocks();
1088
+ return {
1089
+ content: changed ? toLangChainContent(projected) : content,
1090
+ preservedServerToolResult,
1091
+ };
1092
+ }
1093
+
1094
+ function getAuthoritativeResponsesOutput(message: AIMessage): unknown[] {
1095
+ const responseOutput = message.response_metadata.output;
1096
+ const toolOutputs = message.additional_kwargs.tool_outputs;
1097
+ if (Array.isArray(responseOutput) && responseOutput.length > 0) {
1098
+ return responseOutput;
1099
+ }
1100
+ return Array.isArray(toolOutputs) ? toolOutputs : [];
1101
+ }
1102
+
1103
+ function isResponsesReplayPosition(
1104
+ value: unknown
1105
+ ): value is ResponsesReplayPosition {
1106
+ if (value == null || typeof value !== 'object') {
1107
+ return false;
1108
+ }
1109
+ const position = value as Partial<ResponsesReplayPosition>;
1110
+ return (
1111
+ (position.kind === 'message' ||
1112
+ position.kind === 'output' ||
1113
+ position.kind === 'reasoning' ||
1114
+ position.kind === 'text') &&
1115
+ typeof position.itemId === 'string' &&
1116
+ position.itemId.length > 0 &&
1117
+ typeof position.outputIndex === 'number' &&
1118
+ Number.isSafeInteger(position.outputIndex) &&
1119
+ position.outputIndex >= 0 &&
1120
+ (position.contentIndex == null ||
1121
+ (typeof position.contentIndex === 'number' &&
1122
+ Number.isSafeInteger(position.contentIndex) &&
1123
+ position.contentIndex >= 0))
1124
+ );
1125
+ }
1126
+
1127
+ function getGeneratedImageMimeType(data: string): string {
1128
+ const bytes = Buffer.from(data.slice(0, 16), 'base64');
1129
+ if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
1130
+ return 'image/jpeg';
1131
+ }
1132
+ if (
1133
+ bytes[0] === 0x52 &&
1134
+ bytes[1] === 0x49 &&
1135
+ bytes[2] === 0x46 &&
1136
+ bytes[3] === 0x46 &&
1137
+ bytes[8] === 0x57 &&
1138
+ bytes[9] === 0x45 &&
1139
+ bytes[10] === 0x42 &&
1140
+ bytes[11] === 0x50
1141
+ ) {
1142
+ return 'image/webp';
1143
+ }
1144
+ return 'image/png';
1145
+ }
1146
+
1147
+ function getResponsesReplayItemKey(
1148
+ item: Record<string, unknown>
1149
+ ): string | undefined {
1150
+ if (typeof item.id === 'string' && item.id.length > 0) {
1151
+ return item.id;
1152
+ }
1153
+ return typeof item.call_id === 'string' && item.call_id.length > 0
1154
+ ? item.call_id
1155
+ : undefined;
1156
+ }
1157
+
1158
+ function comparePositionedResponsesReplayBlocks(
1159
+ a: PositionedResponsesReplayBlock,
1160
+ b: PositionedResponsesReplayBlock
1161
+ ): number {
1162
+ return (
1163
+ (a.textIndex ?? Number.MAX_SAFE_INTEGER) -
1164
+ (b.textIndex ?? Number.MAX_SAFE_INTEGER) ||
1165
+ a.outputIndex - b.outputIndex ||
1166
+ a.subIndex - b.subIndex
1167
+ );
1168
+ }
1169
+
1170
+ const RESPONSES_REPLAY_OUTPUT_TOOL_NAMES = {
1171
+ apply_patch_call_output: 'apply_patch',
1172
+ local_shell_call_output: 'local_shell',
1173
+ shell_call_output: 'shell',
1174
+ } as const;
1175
+
1176
+ function getResponsesReplayArtifacts(
1177
+ output: readonly unknown[],
1178
+ maxChars: number,
1179
+ replayProjection: ResponsesReplayProjection,
1180
+ replayPositionValue?: unknown
1181
+ ): ResponsesReplayArtifacts {
1182
+ const blocks: PositionedResponsesReplayBlock[] = [];
1183
+ const data = new Set<string>();
1184
+ const ids = new Set<string>();
1185
+ const emittedData = new Set<string>();
1186
+ const emittedIds = new Set<string>();
1187
+ const positionedServerToolResultIds = new Set<string>();
1188
+ const rawOutputIndices = new Map<Record<string, unknown>, number>();
1189
+ const serverToolResults: PositionedResponsesReplayBlock[] = [];
1190
+ const replayPositions = Array.isArray(replayPositionValue)
1191
+ ? replayPositionValue.filter(isResponsesReplayPosition)
1192
+ : [];
1193
+ const authoritativeOutputIndicesByItemId = new Map<string, number>();
1194
+ const messagePositionsByKey = new Map<string, ResponsesReplayPosition>();
1195
+ const outputPositionsByItemId = new Map<string, ResponsesReplayPosition>();
1196
+ const textPositionsByKey = new Map<string, ResponsesReplayPosition>();
1197
+ for (const position of replayPositions) {
1198
+ if (position.kind === 'output' || position.kind === 'reasoning') {
1199
+ outputPositionsByItemId.set(position.itemId, position);
1200
+ continue;
1201
+ }
1202
+ if (position.kind === 'message') {
1203
+ messagePositionsByKey.set(
1204
+ `${position.itemId}:${position.outputIndex}`,
1205
+ position
1206
+ );
1207
+ continue;
1208
+ }
1209
+ textPositionsByKey.set(
1210
+ `${position.itemId}:${position.outputIndex}:${position.contentIndex ?? 0}`,
1211
+ position
1212
+ );
1213
+ }
1214
+ let hasAuthoritativeMessage = false;
1215
+ for (let outputIndex = 0; outputIndex < output.length; outputIndex++) {
1216
+ const item = output[outputIndex];
1217
+ if (item == null || typeof item !== 'object') {
1218
+ continue;
1219
+ }
1220
+ const itemRecord = item as Record<string, unknown>;
1221
+ rawOutputIndices.set(itemRecord, outputIndex);
1222
+ const itemKey = getResponsesReplayItemKey(itemRecord);
1223
+ if (itemKey != null) {
1224
+ authoritativeOutputIndicesByItemId.set(itemKey, outputIndex);
1225
+ }
1226
+ if (!('type' in item) || item.type !== 'message') {
1227
+ continue;
1228
+ }
1229
+ hasAuthoritativeMessage = true;
1230
+ const messageItemId =
1231
+ 'id' in item && typeof item.id === 'string'
1232
+ ? item.id
1233
+ : `message-${outputIndex}`;
1234
+ messagePositionsByKey.set(`${messageItemId}:${outputIndex}`, {
1235
+ itemId: messageItemId,
1236
+ kind: 'message',
1237
+ outputIndex,
1238
+ });
1239
+ if (!('content' in item) || !Array.isArray(item.content)) {
1240
+ continue;
1241
+ }
1242
+ for (
1243
+ let contentIndex = 0;
1244
+ contentIndex < item.content.length;
1245
+ contentIndex++
1246
+ ) {
1247
+ const content = item.content[contentIndex];
1248
+ if (
1249
+ content == null ||
1250
+ typeof content !== 'object' ||
1251
+ !('type' in content) ||
1252
+ content.type !== 'output_text' ||
1253
+ !('text' in content) ||
1254
+ typeof content.text !== 'string' ||
1255
+ content.text.length === 0
1256
+ ) {
1257
+ continue;
1258
+ }
1259
+ const itemId =
1260
+ 'id' in item && typeof item.id === 'string'
1261
+ ? item.id
1262
+ : `message-${outputIndex}`;
1263
+ textPositionsByKey.set(`${itemId}:${outputIndex}:${contentIndex}`, {
1264
+ contentIndex,
1265
+ itemId,
1266
+ kind: 'text',
1267
+ outputIndex,
1268
+ });
1269
+ }
1270
+ }
1271
+ if (hasAuthoritativeMessage) {
1272
+ for (const [itemId, outputIndex] of authoritativeOutputIndicesByItemId) {
1273
+ outputPositionsByItemId.set(itemId, {
1274
+ itemId,
1275
+ kind: 'output',
1276
+ outputIndex,
1277
+ });
1278
+ }
1279
+ }
1280
+ const textPositions = [...textPositionsByKey.values()].sort(
1281
+ (a, b) =>
1282
+ a.outputIndex - b.outputIndex ||
1283
+ (a.contentIndex ?? 0) - (b.contentIndex ?? 0)
1284
+ );
1285
+ const messagePositions = [...messagePositionsByKey.values()].sort(
1286
+ (a, b) => a.outputIndex - b.outputIndex
1287
+ );
1288
+ const textCountBeforeOutputIndex = new Map<number, number>();
1289
+ const positionedOutputIndexSet = new Set<number>();
1290
+ for (const position of outputPositionsByItemId.values()) {
1291
+ positionedOutputIndexSet.add(position.outputIndex);
1292
+ }
1293
+ if (hasAuthoritativeMessage) {
1294
+ for (const outputIndex of rawOutputIndices.values()) {
1295
+ positionedOutputIndexSet.add(outputIndex);
1296
+ }
1297
+ }
1298
+ const positionedOutputIndices = [...positionedOutputIndexSet].sort(
1299
+ (a, b) => a - b
1300
+ );
1301
+ let textPositionIndex = 0;
1302
+ for (const outputIndex of positionedOutputIndices) {
1303
+ while (
1304
+ textPositionIndex < textPositions.length &&
1305
+ textPositions[textPositionIndex].outputIndex < outputIndex
1306
+ ) {
1307
+ textPositionIndex++;
1308
+ }
1309
+ textCountBeforeOutputIndex.set(outputIndex, textPositionIndex);
1310
+ }
1311
+ const positionsByItemId = new Map<string, ResponsesReplayItemPosition>();
1312
+ for (const [itemId, position] of outputPositionsByItemId) {
1313
+ positionsByItemId.set(itemId, {
1314
+ outputIndex: position.outputIndex,
1315
+ textIndex: textCountBeforeOutputIndex.get(position.outputIndex) ?? 0,
1316
+ });
1317
+ }
1318
+ const getPosition = (
1319
+ item: Record<string, unknown>
1320
+ ): ResponsesReplayItemPosition => {
1321
+ const itemId = getResponsesReplayItemKey(item);
1322
+ const position = itemId != null ? positionsByItemId.get(itemId) : undefined;
1323
+ if (position == null) {
1324
+ const rawOutputIndex = rawOutputIndices.get(item);
1325
+ return {
1326
+ outputIndex: rawOutputIndex ?? Number.MAX_SAFE_INTEGER,
1327
+ ...(hasAuthoritativeMessage && rawOutputIndex != null
1328
+ ? {
1329
+ textIndex: textCountBeforeOutputIndex.get(rawOutputIndex) ?? 0,
1330
+ }
1331
+ : {}),
1332
+ };
1333
+ }
1334
+ return position;
1335
+ };
1336
+ const pushServerToolResult = (
1337
+ block: LangChainContentBlock.Standard | undefined,
1338
+ item: Record<string, unknown>,
1339
+ subIndex = 0
1340
+ ): void => {
1341
+ if (block == null) {
1342
+ return;
1343
+ }
1344
+ const itemId = getResponsesReplayItemKey(item);
1345
+ if (itemId != null) {
1346
+ positionedServerToolResultIds.add(itemId);
1347
+ }
1348
+ serverToolResults.push({ block, ...getPosition(item), subIndex });
1349
+ };
1350
+ for (const item of output) {
1351
+ if (item == null || typeof item !== 'object' || !('type' in item)) {
1352
+ continue;
1353
+ }
1354
+ if (item.type === 'code_interpreter_call') {
1355
+ if (
1356
+ !('outputs' in item) ||
1357
+ !Array.isArray(item.outputs) ||
1358
+ !('status' in item)
1359
+ ) {
1360
+ continue;
1361
+ }
1362
+ const resultStatus = item.status === 'completed' ? 'success' : 'error';
1363
+ const returnCode = item.status === 'completed' ? 0 : 1;
1364
+ for (
1365
+ let resultIndex = 0;
1366
+ resultIndex < item.outputs.length;
1367
+ resultIndex++
1368
+ ) {
1369
+ const result = item.outputs[resultIndex];
1370
+ if (
1371
+ result == null ||
1372
+ typeof result !== 'object' ||
1373
+ !('type' in result)
1374
+ ) {
1375
+ continue;
1376
+ }
1377
+ if (
1378
+ result.type === 'logs' &&
1379
+ 'logs' in result &&
1380
+ typeof result.logs === 'string'
1381
+ ) {
1382
+ pushServerToolResult(
1383
+ createNeutralServerToolResult(
1384
+ {
1385
+ type: 'code_interpreter_output',
1386
+ returnCode,
1387
+ stdout: result.logs,
1388
+ },
1389
+ resultStatus,
1390
+ maxChars,
1391
+ 'code_interpreter'
1392
+ ),
1393
+ item,
1394
+ resultIndex
1395
+ );
1396
+ continue;
1397
+ }
1398
+ if (
1399
+ result.type !== 'image' ||
1400
+ !('url' in result) ||
1401
+ typeof result.url !== 'string' ||
1402
+ result.url.length === 0
1403
+ ) {
1404
+ continue;
1405
+ }
1406
+ const resultUrl: string = result.url;
1407
+ if (
1408
+ replayProjection === 'native' &&
1409
+ resultUrl.startsWith('data:image/')
1410
+ ) {
1411
+ pushServerToolResult(
1412
+ {
1413
+ type: 'image',
1414
+ url: resultUrl,
1415
+ extras: createServerToolResultExtras('code_interpreter'),
1416
+ },
1417
+ item,
1418
+ resultIndex
1419
+ );
1420
+ continue;
1421
+ }
1422
+ const mediaResult = createNeutralServerToolResult(
1423
+ { type: 'code_interpreter_image', url: resultUrl },
1424
+ resultStatus,
1425
+ maxChars,
1426
+ 'code_interpreter'
1427
+ );
1428
+ pushServerToolResult(mediaResult, item, resultIndex);
1429
+ }
1430
+ continue;
1431
+ }
1432
+ if (item.type === 'file_search_call') {
1433
+ const result = createNeutralServerToolResult(
1434
+ 'results' in item && Array.isArray(item.results)
1435
+ ? { results: item.results }
1436
+ : undefined,
1437
+ 'status' in item && item.status === 'completed' ? 'success' : 'error',
1438
+ maxChars,
1439
+ 'file_search'
1440
+ );
1441
+ pushServerToolResult(result, item);
1442
+ continue;
1443
+ }
1444
+ if (item.type === 'web_search_call') {
1445
+ const result = createNeutralServerToolResult(
1446
+ {
1447
+ ...('action' in item ? { action: item.action } : {}),
1448
+ ...('results' in item && Array.isArray(item.results)
1449
+ ? { results: item.results }
1450
+ : {}),
1451
+ },
1452
+ 'status' in item && item.status === 'completed' ? 'success' : 'error',
1453
+ maxChars,
1454
+ 'web_search'
1455
+ );
1456
+ pushServerToolResult(result, item);
1457
+ continue;
1458
+ }
1459
+ if (item.type === 'tool_search_output') {
1460
+ const result = createNeutralServerToolResult(
1461
+ 'tools' in item && Array.isArray(item.tools)
1462
+ ? { tools: item.tools }
1463
+ : undefined,
1464
+ 'status' in item && item.status === 'completed' ? 'success' : 'error',
1465
+ maxChars,
1466
+ 'tool_search'
1467
+ );
1468
+ pushServerToolResult(result, item);
1469
+ continue;
1470
+ }
1471
+ if (item.type === 'mcp_list_tools') {
1472
+ const hasError =
1473
+ 'error' in item &&
1474
+ typeof item.error === 'string' &&
1475
+ item.error.length > 0;
1476
+ const listToolsResult = createNeutralServerToolResult(
1477
+ {
1478
+ ...('server_label' in item && typeof item.server_label === 'string'
1479
+ ? { serverLabel: item.server_label }
1480
+ : {}),
1481
+ ...('tools' in item && Array.isArray(item.tools)
1482
+ ? { tools: item.tools }
1483
+ : {}),
1484
+ ...(hasError ? { error: item.error } : {}),
1485
+ },
1486
+ hasError ? 'error' : 'success',
1487
+ maxChars,
1488
+ 'mcp_list_tools'
1489
+ );
1490
+ pushServerToolResult(listToolsResult, item);
1491
+ continue;
1492
+ }
1493
+ if (item.type === 'mcp_call') {
1494
+ const hasOutput =
1495
+ 'output' in item &&
1496
+ typeof item.output === 'string' &&
1497
+ item.output.length > 0;
1498
+ const hasError =
1499
+ 'error' in item &&
1500
+ typeof item.error === 'string' &&
1501
+ item.error.length > 0;
1502
+ if (!hasOutput && !hasError) {
1503
+ continue;
1504
+ }
1505
+ const mcpOutput = {
1506
+ ...('name' in item && typeof item.name === 'string'
1507
+ ? { name: item.name }
1508
+ : {}),
1509
+ ...('server_label' in item && typeof item.server_label === 'string'
1510
+ ? { serverLabel: item.server_label }
1511
+ : {}),
1512
+ ...('output' in item && typeof item.output === 'string'
1513
+ ? { output: item.output }
1514
+ : {}),
1515
+ ...('error' in item && typeof item.error === 'string'
1516
+ ? { error: item.error }
1517
+ : {}),
1518
+ };
1519
+ const mcpResult = createNeutralServerToolResult(
1520
+ mcpOutput,
1521
+ hasError ||
1522
+ ('status' in item &&
1523
+ (item.status === 'failed' || item.status === 'incomplete'))
1524
+ ? 'error'
1525
+ : 'success',
1526
+ maxChars,
1527
+ 'name' in item && typeof item.name === 'string' && item.name.length > 0
1528
+ ? item.name
1529
+ : 'mcp'
1530
+ );
1531
+ pushServerToolResult(mcpResult, item);
1532
+ continue;
1533
+ }
1534
+ if (
1535
+ item.type === 'local_shell_call_output' ||
1536
+ item.type === 'shell_call_output' ||
1537
+ item.type === 'apply_patch_call_output'
1538
+ ) {
1539
+ if (!('output' in item)) {
1540
+ continue;
1541
+ }
1542
+ const result = createNeutralServerToolResult(
1543
+ item.output,
1544
+ 'status' in item &&
1545
+ (item.status === 'failed' || item.status === 'incomplete')
1546
+ ? 'error'
1547
+ : 'success',
1548
+ maxChars,
1549
+ RESPONSES_REPLAY_OUTPUT_TOOL_NAMES[item.type]
1550
+ );
1551
+ pushServerToolResult(result, item);
1552
+ continue;
1553
+ }
1554
+ if (item.type === 'program_output') {
1555
+ if (!('result' in item)) {
1556
+ continue;
1557
+ }
1558
+ const result = createNeutralServerToolResult(
1559
+ item.result,
1560
+ 'status' in item && item.status === 'incomplete' ? 'error' : 'success',
1561
+ maxChars,
1562
+ 'program'
1563
+ );
1564
+ pushServerToolResult(result, item);
1565
+ continue;
1566
+ }
1567
+ if (item.type !== 'image_generation_call') {
1568
+ continue;
1569
+ }
1570
+ if ('id' in item && typeof item.id === 'string' && item.id.length > 0) {
1571
+ ids.add(item.id);
1572
+ }
1573
+ if (
1574
+ !('result' in item) ||
1575
+ typeof item.result !== 'string' ||
1576
+ item.result.length === 0
1577
+ ) {
1578
+ continue;
1579
+ }
1580
+ data.add(item.result);
1581
+ if ('status' in item && item.status === 'completed') {
1582
+ const itemId =
1583
+ 'id' in item && typeof item.id === 'string' && item.id.length > 0
1584
+ ? item.id
1585
+ : undefined;
1586
+ if (
1587
+ (itemId != null && emittedIds.has(itemId)) ||
1588
+ (itemId == null && emittedData.has(item.result))
1589
+ ) {
1590
+ continue;
1591
+ }
1592
+ if (itemId != null) {
1593
+ emittedIds.add(itemId);
1594
+ } else {
1595
+ emittedData.add(item.result);
1596
+ }
1597
+ blocks.push({
1598
+ block: {
1599
+ type: 'image',
1600
+ mimeType: getGeneratedImageMimeType(item.result),
1601
+ data: item.result,
1602
+ extras: createServerToolResultExtras('image_generation'),
1603
+ },
1604
+ ...getPosition(item),
1605
+ subIndex: 0,
1606
+ });
1607
+ }
1608
+ }
1609
+ return {
1610
+ generatedImages: {
1611
+ blocks: blocks.sort(comparePositionedResponsesReplayBlocks),
1612
+ data,
1613
+ ids,
1614
+ },
1615
+ messagePositions,
1616
+ positionedServerToolResultIds,
1617
+ positionsByItemId,
1618
+ serverToolResults: serverToolResults.sort(
1619
+ comparePositionedResponsesReplayBlocks
1620
+ ),
1621
+ textPositions,
1622
+ };
1623
+ }
1624
+
1625
+ function getSelfContainedResponsesV0Images(
1626
+ message: AIMessage
1627
+ ): PositionedResponsesImages {
1628
+ if (!Array.isArray(message.content)) {
1629
+ return { blocks: [], textCount: 0 };
1630
+ }
1631
+ const images: PositionedResponsesImage[] = [];
1632
+ let textCount = 0;
1633
+ for (const block of message.content as LangChainContentBlock.Standard[]) {
1634
+ if (block.type === 'text') {
1635
+ if (isCompleteToolStreamContentBlock(block)) {
1636
+ textCount++;
1637
+ }
1638
+ continue;
1639
+ }
1640
+ if (
1641
+ block.type === 'image' &&
1642
+ (('fileId' in block &&
1643
+ typeof block.fileId === 'string' &&
1644
+ block.fileId.length > 0) ||
1645
+ ('url' in block &&
1646
+ typeof block.url === 'string' &&
1647
+ block.url.length > 0) ||
1648
+ ('data' in block &&
1649
+ ((typeof block.data === 'string' && block.data.length > 0) ||
1650
+ (block.data instanceof Uint8Array && block.data.length > 0))))
1651
+ ) {
1652
+ images.push({ block, textIndex: textCount });
1653
+ }
1654
+ }
1655
+ return { blocks: images, textCount };
1656
+ }
1657
+
1658
+ function getResponsesV0ContentBlocks(
1659
+ message: AIMessage,
1660
+ authoritativeOutput: unknown[]
1661
+ ): AIMessage['content'] {
1662
+ let content = message.content;
1663
+ if (typeof content === 'string') {
1664
+ content = toLangChainContent(
1665
+ content.length > 0 ? [{ type: 'text', text: content }] : []
1666
+ );
1667
+ }
1668
+ const translationMessage = cloneAIMessageWithResponsesReplayState(
1669
+ message,
1670
+ content,
1671
+ message.id,
1672
+ {
1673
+ ...message.additional_kwargs,
1674
+ tool_outputs: authoritativeOutput,
1675
+ },
1676
+ {
1677
+ ...message.response_metadata,
1678
+ model_provider: 'openai',
1679
+ }
1680
+ );
1681
+ return toLangChainContent(translationMessage.contentBlocks);
1682
+ }
1683
+
1684
+ function isPreemptedOpenAIResponsesMessage(message: AIMessage): boolean {
1685
+ const metadata = message.response_metadata;
1686
+ if (metadata.preempted !== true || metadata.model_provider !== 'openai') {
1687
+ return false;
1688
+ }
1689
+ if (
1690
+ message.id?.startsWith('msg_') === true ||
1691
+ message.id?.startsWith('resp_') === true ||
1692
+ (typeof metadata.id === 'string' && metadata.id.startsWith('resp_')) ||
1693
+ Array.isArray(metadata.output) ||
1694
+ metadata.tool_outputs != null ||
1695
+ Array.isArray(message.additional_kwargs.tool_outputs) ||
1696
+ message.additional_kwargs[OPENAI_RESPONSES_REPLAY_POSITIONS_KEY] != null ||
1697
+ message.additional_kwargs.__openai_function_call_ids__ != null ||
1698
+ message.additional_kwargs.__openai_custom_tool_call_ids__ != null ||
1699
+ (message.additional_kwargs.reasoning != null &&
1700
+ typeof message.additional_kwargs.reasoning === 'object')
1701
+ ) {
1702
+ return true;
1703
+ }
1704
+ return false;
1705
+ }
1706
+
592
1707
  /**
593
- * Drops incomplete streamed text-input fragments that some providers retain
594
- * beside the assembled parsed tool call. They are neither user-visible text
595
- * nor valid content blocks for a subsequent provider.
1708
+ * A sealed Responses turn cannot prove that provider-side item ids were
1709
+ * retained: the response object does not echo the request's `store` flag.
1710
+ * Project a provider-neutral clone while preserving self-contained encrypted
1711
+ * reasoning and the original graph/checkpoint message.
596
1712
  */
1713
+ function projectPreemptedOpenAIResponsesMessage(
1714
+ message: AIMessage,
1715
+ maxChars: number,
1716
+ replayProjection: ResponsesReplayProjection
1717
+ ): AIMessage {
1718
+ if (!isPreemptedOpenAIResponsesMessage(message)) {
1719
+ return message;
1720
+ }
1721
+
1722
+ const metadata = message.response_metadata;
1723
+ const additionalKwargs = { ...message.additional_kwargs };
1724
+ const retainsEncryptedReasoning =
1725
+ replayProjection === 'native' &&
1726
+ hasReplayableEncryptedReasoning(additionalKwargs.reasoning);
1727
+ const authoritativeOutput = getAuthoritativeResponsesOutput(message);
1728
+ const {
1729
+ generatedImages,
1730
+ messagePositions,
1731
+ positionedServerToolResultIds,
1732
+ positionsByItemId,
1733
+ serverToolResults,
1734
+ textPositions,
1735
+ } = getResponsesReplayArtifacts(
1736
+ authoritativeOutput,
1737
+ maxChars,
1738
+ replayProjection,
1739
+ additionalKwargs[OPENAI_RESPONSES_REPLAY_POSITIONS_KEY]
1740
+ );
1741
+ const reasoningItemId =
1742
+ retainsEncryptedReasoning &&
1743
+ additionalKwargs.reasoning != null &&
1744
+ typeof additionalKwargs.reasoning === 'object' &&
1745
+ 'id' in additionalKwargs.reasoning &&
1746
+ typeof additionalKwargs.reasoning.id === 'string'
1747
+ ? additionalKwargs.reasoning.id
1748
+ : undefined;
1749
+ const reasoningPosition =
1750
+ reasoningItemId != null
1751
+ ? positionsByItemId.get(reasoningItemId)
1752
+ : undefined;
1753
+ const isV1 = metadata.output_version === 'v1';
1754
+ const translatesV0 =
1755
+ !isV1 &&
1756
+ (replayProjection === 'fallback' || authoritativeOutput.length > 0);
1757
+ const originalImages =
1758
+ replayProjection === 'native' && translatesV0
1759
+ ? getSelfContainedResponsesV0Images(message)
1760
+ : undefined;
1761
+ let contentForProjection = message.content;
1762
+ if (!isV1 && translatesV0) {
1763
+ contentForProjection = getResponsesV0ContentBlocks(
1764
+ message,
1765
+ authoritativeOutput
1766
+ );
1767
+ }
1768
+ const projectedContent = projectPreemptedResponsesV1Content(
1769
+ contentForProjection,
1770
+ isV1 || translatesV0 ? additionalKwargs.reasoning : undefined,
1771
+ maxChars,
1772
+ replayProjection,
1773
+ replayProjection === 'native' ? generatedImages : undefined,
1774
+ originalImages,
1775
+ serverToolResults,
1776
+ positionedServerToolResultIds,
1777
+ reasoningPosition,
1778
+ textPositions,
1779
+ messagePositions
1780
+ );
1781
+ const promotesV0 =
1782
+ !isV1 &&
1783
+ (replayProjection === 'fallback' ||
1784
+ generatedImages.blocks.length > 0 ||
1785
+ projectedContent.preservedServerToolResult);
1786
+ const unpromotedV0Content =
1787
+ contentForProjection === message.content
1788
+ ? projectedContent.content
1789
+ : projectPreemptedResponsesV1Content(
1790
+ message.content,
1791
+ undefined,
1792
+ maxChars,
1793
+ replayProjection
1794
+ ).content;
1795
+ const content =
1796
+ isV1 || promotesV0 ? projectedContent.content : unpromotedV0Content;
1797
+ const hasUnsafeProviderReferences =
1798
+ content !== message.content ||
1799
+ message.id?.startsWith('msg_') === true ||
1800
+ (!retainsEncryptedReasoning && additionalKwargs.reasoning != null) ||
1801
+ additionalKwargs.tool_outputs != null ||
1802
+ additionalKwargs[OPENAI_RESPONSES_REPLAY_POSITIONS_KEY] != null ||
1803
+ additionalKwargs.__openai_function_call_ids__ != null ||
1804
+ additionalKwargs.__openai_custom_tool_call_ids__ != null ||
1805
+ metadata.id != null ||
1806
+ metadata.output != null ||
1807
+ metadata.tool_outputs != null;
1808
+ if (!hasUnsafeProviderReferences) {
1809
+ return message;
1810
+ }
1811
+ if (!retainsEncryptedReasoning) {
1812
+ delete additionalKwargs.reasoning;
1813
+ }
1814
+ delete additionalKwargs.tool_outputs;
1815
+ delete additionalKwargs[OPENAI_RESPONSES_REPLAY_POSITIONS_KEY];
1816
+ delete additionalKwargs.__openai_function_call_ids__;
1817
+ delete additionalKwargs.__openai_custom_tool_call_ids__;
1818
+
1819
+ const responseMetadata: AIMessage['response_metadata'] = { ...metadata };
1820
+ delete responseMetadata.id;
1821
+ delete responseMetadata.output;
1822
+ delete responseMetadata.tool_outputs;
1823
+ if (promotesV0) {
1824
+ responseMetadata.output_version = 'v1';
1825
+ }
1826
+
1827
+ return cloneAIMessageWithResponsesReplayState(
1828
+ message,
1829
+ content,
1830
+ message.id?.startsWith('msg_') === true ? undefined : message.id,
1831
+ additionalKwargs,
1832
+ responseMetadata
1833
+ );
1834
+ }
1835
+
1836
+ /** Applies sealed-Responses safety and drops incomplete streamed text input. */
597
1837
  export function projectToolStreamContentForProvider(
598
- messages: BaseMessage[]
1838
+ messages: BaseMessage[],
1839
+ responsesReplayProjection?: ResponsesReplayProjection,
1840
+ maxChars = HARD_MAX_TOOL_RESULT_CHARS
599
1841
  ): BaseMessage[] {
600
1842
  let projected: BaseMessage[] | undefined;
601
1843
  for (let i = 0; i < messages.length; i++) {
602
1844
  const message = messages[i];
603
- if (message.getType() !== 'ai' || !Array.isArray(message.content)) {
1845
+ if (message.getType() !== 'ai') {
604
1846
  continue;
605
1847
  }
606
- const content = message.content.filter((block) => {
607
- if (block == null || typeof block !== 'object') {
608
- return true;
609
- }
610
- try {
611
- if (isProxy(block)) {
612
- return false;
613
- }
614
- const type = Object.getOwnPropertyDescriptor(block, 'type');
615
- if (type == null) {
616
- return true;
617
- }
618
- if (type.enumerable !== true || !('value' in type)) {
619
- return false;
620
- }
621
- if (type.value !== 'text') {
622
- return true;
623
- }
624
- const text = Object.getOwnPropertyDescriptor(block, 'text');
625
- return (
626
- text?.enumerable === true &&
627
- 'value' in text &&
628
- typeof text.value === 'string' &&
629
- text.value !== ''
630
- );
631
- } catch {
632
- return false;
1848
+ const assistantMessage = message as AIMessage;
1849
+ if (
1850
+ responsesReplayProjection != null &&
1851
+ isPreemptedOpenAIResponsesMessage(assistantMessage)
1852
+ ) {
1853
+ const replaySafeMessage = projectPreemptedOpenAIResponsesMessage(
1854
+ assistantMessage,
1855
+ maxChars,
1856
+ responsesReplayProjection
1857
+ );
1858
+ if (replaySafeMessage !== assistantMessage) {
1859
+ projected ??= [...messages];
1860
+ projected[i] = replaySafeMessage;
633
1861
  }
634
- });
635
- if (content.length === message.content.length) {
1862
+ continue;
1863
+ }
1864
+ if (!Array.isArray(assistantMessage.content)) {
1865
+ continue;
1866
+ }
1867
+ const content = assistantMessage.content.filter(
1868
+ isCompleteToolStreamContentBlock
1869
+ );
1870
+ if (content.length === assistantMessage.content.length) {
636
1871
  continue;
637
1872
  }
638
1873
  projected ??= [...messages];
639
1874
  projected[i] = cloneAIMessageWithContent(
640
- message as AIMessage,
1875
+ assistantMessage,
641
1876
  toLangChainContent(content)
642
1877
  );
643
1878
  }
@@ -687,7 +1922,7 @@ function projectStructuredOpenAIToolContent(
687
1922
  function projectOpenAIToolMessageContentInternal(
688
1923
  messages: BaseMessage[],
689
1924
  maxChars: number,
690
- deduplicateResponsesComputerCalls: boolean,
1925
+ nativeResponsesProjection: boolean,
691
1926
  cacheControlledTextProjection: CacheControlledTextProjection
692
1927
  ): BaseMessage[] {
693
1928
  const pendingComputerCallIds: string[] = [];
@@ -699,8 +1934,21 @@ function projectOpenAIToolMessageContentInternal(
699
1934
  const isAssistant =
700
1935
  message.getType() === 'ai' || messageRole === 'assistant';
701
1936
  if (isAssistant) {
1937
+ let assistantMessage = message as AIMessage;
1938
+ if (nativeResponsesProjection) {
1939
+ const replaySafeMessage = projectPreemptedOpenAIResponsesMessage(
1940
+ assistantMessage,
1941
+ maxChars,
1942
+ 'native'
1943
+ );
1944
+ if (replaySafeMessage !== assistantMessage) {
1945
+ projected ??= [...messages];
1946
+ projected[i] = replaySafeMessage;
1947
+ assistantMessage = replaySafeMessage;
1948
+ }
1949
+ }
702
1950
  const parsedComputerCallIds = new Set<string>();
703
- const toolCalls = (message as AIMessage).tool_calls;
1951
+ const toolCalls = assistantMessage.tool_calls;
704
1952
  if (Array.isArray(toolCalls)) {
705
1953
  for (const toolCall of toolCalls) {
706
1954
  const record = toolCall as ToolCall & {
@@ -727,12 +1975,12 @@ function projectOpenAIToolMessageContentInternal(
727
1975
  }
728
1976
 
729
1977
  const rawOutput = (
730
- message.response_metadata as {
1978
+ assistantMessage.response_metadata as {
731
1979
  output?: unknown;
732
1980
  }
733
1981
  ).output;
734
1982
  const fallbackOutput = (
735
- message.additional_kwargs as {
1983
+ assistantMessage.additional_kwargs as {
736
1984
  tool_outputs?: unknown;
737
1985
  }
738
1986
  ).tool_outputs;
@@ -775,7 +2023,7 @@ function projectOpenAIToolMessageContentInternal(
775
2023
  }
776
2024
 
777
2025
  if (
778
- deduplicateResponsesComputerCalls &&
2026
+ nativeResponsesProjection &&
779
2027
  Array.isArray(toolCalls) &&
780
2028
  rawComputerCallIds.size > 0
781
2029
  ) {
@@ -794,7 +2042,7 @@ function projectOpenAIToolMessageContentInternal(
794
2042
  if (projectedToolCalls.length !== toolCalls.length) {
795
2043
  projected ??= [...messages];
796
2044
  projected[i] = cloneAIMessageWithToolCalls(
797
- message as AIMessage,
2045
+ assistantMessage,
798
2046
  projectedToolCalls,
799
2047
  rawComputerCallIds
800
2048
  );