@librechat/agents 3.1.75 → 3.1.77-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/dist/cjs/graphs/Graph.cjs +22 -3
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hitl/askUserQuestion.cjs +67 -0
  4. package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -0
  5. package/dist/cjs/hooks/HookRegistry.cjs +54 -0
  6. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  7. package/dist/cjs/hooks/createToolPolicyHook.cjs +115 -0
  8. package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -0
  9. package/dist/cjs/hooks/executeHooks.cjs +40 -1
  10. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  11. package/dist/cjs/hooks/types.cjs +1 -0
  12. package/dist/cjs/hooks/types.cjs.map +1 -1
  13. package/dist/cjs/langchain/google-common.cjs +3 -0
  14. package/dist/cjs/langchain/google-common.cjs.map +1 -0
  15. package/dist/cjs/langchain/index.cjs +86 -0
  16. package/dist/cjs/langchain/index.cjs.map +1 -0
  17. package/dist/cjs/langchain/language_models/chat_models.cjs +3 -0
  18. package/dist/cjs/langchain/language_models/chat_models.cjs.map +1 -0
  19. package/dist/cjs/langchain/messages/tool.cjs +3 -0
  20. package/dist/cjs/langchain/messages/tool.cjs.map +1 -0
  21. package/dist/cjs/langchain/messages.cjs +51 -0
  22. package/dist/cjs/langchain/messages.cjs.map +1 -0
  23. package/dist/cjs/langchain/openai.cjs +3 -0
  24. package/dist/cjs/langchain/openai.cjs.map +1 -0
  25. package/dist/cjs/langchain/prompts.cjs +11 -0
  26. package/dist/cjs/langchain/prompts.cjs.map +1 -0
  27. package/dist/cjs/langchain/runnables.cjs +19 -0
  28. package/dist/cjs/langchain/runnables.cjs.map +1 -0
  29. package/dist/cjs/langchain/tools.cjs +23 -0
  30. package/dist/cjs/langchain/tools.cjs.map +1 -0
  31. package/dist/cjs/langchain/utils/env.cjs +11 -0
  32. package/dist/cjs/langchain/utils/env.cjs.map +1 -0
  33. package/dist/cjs/llm/anthropic/index.cjs +145 -52
  34. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  35. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  36. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +21 -14
  37. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  38. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +84 -70
  39. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  40. package/dist/cjs/llm/bedrock/index.cjs +1 -1
  41. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  42. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +213 -3
  43. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  44. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +2 -1
  45. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
  46. package/dist/cjs/llm/google/utils/common.cjs +5 -4
  47. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  48. package/dist/cjs/llm/openai/index.cjs +519 -655
  49. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  50. package/dist/cjs/llm/openai/utils/index.cjs +20 -458
  51. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  52. package/dist/cjs/llm/openrouter/index.cjs +57 -175
  53. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  54. package/dist/cjs/llm/vertexai/index.cjs +5 -3
  55. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  56. package/dist/cjs/main.cjs +112 -3
  57. package/dist/cjs/main.cjs.map +1 -1
  58. package/dist/cjs/messages/cache.cjs +2 -1
  59. package/dist/cjs/messages/cache.cjs.map +1 -1
  60. package/dist/cjs/messages/core.cjs +7 -6
  61. package/dist/cjs/messages/core.cjs.map +1 -1
  62. package/dist/cjs/messages/format.cjs +73 -15
  63. package/dist/cjs/messages/format.cjs.map +1 -1
  64. package/dist/cjs/messages/langchain.cjs +26 -0
  65. package/dist/cjs/messages/langchain.cjs.map +1 -0
  66. package/dist/cjs/messages/prune.cjs +7 -6
  67. package/dist/cjs/messages/prune.cjs.map +1 -1
  68. package/dist/cjs/run.cjs +400 -42
  69. package/dist/cjs/run.cjs.map +1 -1
  70. package/dist/cjs/tools/ToolNode.cjs +556 -56
  71. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  72. package/dist/cjs/tools/search/search.cjs +55 -66
  73. package/dist/cjs/tools/search/search.cjs.map +1 -1
  74. package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
  75. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
  76. package/dist/cjs/tools/search/tavily-search.cjs +372 -0
  77. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
  78. package/dist/cjs/tools/search/tool.cjs +26 -4
  79. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  80. package/dist/cjs/tools/search/utils.cjs +10 -3
  81. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  82. package/dist/esm/graphs/Graph.mjs +22 -3
  83. package/dist/esm/graphs/Graph.mjs.map +1 -1
  84. package/dist/esm/hitl/askUserQuestion.mjs +65 -0
  85. package/dist/esm/hitl/askUserQuestion.mjs.map +1 -0
  86. package/dist/esm/hooks/HookRegistry.mjs +54 -0
  87. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  88. package/dist/esm/hooks/createToolPolicyHook.mjs +113 -0
  89. package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -0
  90. package/dist/esm/hooks/executeHooks.mjs +40 -1
  91. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  92. package/dist/esm/hooks/types.mjs +1 -0
  93. package/dist/esm/hooks/types.mjs.map +1 -1
  94. package/dist/esm/langchain/google-common.mjs +2 -0
  95. package/dist/esm/langchain/google-common.mjs.map +1 -0
  96. package/dist/esm/langchain/index.mjs +5 -0
  97. package/dist/esm/langchain/index.mjs.map +1 -0
  98. package/dist/esm/langchain/language_models/chat_models.mjs +2 -0
  99. package/dist/esm/langchain/language_models/chat_models.mjs.map +1 -0
  100. package/dist/esm/langchain/messages/tool.mjs +2 -0
  101. package/dist/esm/langchain/messages/tool.mjs.map +1 -0
  102. package/dist/esm/langchain/messages.mjs +2 -0
  103. package/dist/esm/langchain/messages.mjs.map +1 -0
  104. package/dist/esm/langchain/openai.mjs +2 -0
  105. package/dist/esm/langchain/openai.mjs.map +1 -0
  106. package/dist/esm/langchain/prompts.mjs +2 -0
  107. package/dist/esm/langchain/prompts.mjs.map +1 -0
  108. package/dist/esm/langchain/runnables.mjs +2 -0
  109. package/dist/esm/langchain/runnables.mjs.map +1 -0
  110. package/dist/esm/langchain/tools.mjs +2 -0
  111. package/dist/esm/langchain/tools.mjs.map +1 -0
  112. package/dist/esm/langchain/utils/env.mjs +2 -0
  113. package/dist/esm/langchain/utils/env.mjs.map +1 -0
  114. package/dist/esm/llm/anthropic/index.mjs +146 -54
  115. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  116. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  117. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +21 -14
  118. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  119. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +84 -71
  120. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  121. package/dist/esm/llm/bedrock/index.mjs +1 -1
  122. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  123. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +214 -4
  124. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  125. package/dist/esm/llm/bedrock/utils/message_outputs.mjs +2 -1
  126. package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
  127. package/dist/esm/llm/google/utils/common.mjs +5 -4
  128. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  129. package/dist/esm/llm/openai/index.mjs +520 -656
  130. package/dist/esm/llm/openai/index.mjs.map +1 -1
  131. package/dist/esm/llm/openai/utils/index.mjs +23 -459
  132. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  133. package/dist/esm/llm/openrouter/index.mjs +57 -175
  134. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  135. package/dist/esm/llm/vertexai/index.mjs +5 -3
  136. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  137. package/dist/esm/main.mjs +7 -0
  138. package/dist/esm/main.mjs.map +1 -1
  139. package/dist/esm/messages/cache.mjs +2 -1
  140. package/dist/esm/messages/cache.mjs.map +1 -1
  141. package/dist/esm/messages/core.mjs +7 -6
  142. package/dist/esm/messages/core.mjs.map +1 -1
  143. package/dist/esm/messages/format.mjs +73 -15
  144. package/dist/esm/messages/format.mjs.map +1 -1
  145. package/dist/esm/messages/langchain.mjs +23 -0
  146. package/dist/esm/messages/langchain.mjs.map +1 -0
  147. package/dist/esm/messages/prune.mjs +7 -6
  148. package/dist/esm/messages/prune.mjs.map +1 -1
  149. package/dist/esm/run.mjs +400 -42
  150. package/dist/esm/run.mjs.map +1 -1
  151. package/dist/esm/tools/ToolNode.mjs +557 -57
  152. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  153. package/dist/esm/tools/search/search.mjs +55 -66
  154. package/dist/esm/tools/search/search.mjs.map +1 -1
  155. package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
  156. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
  157. package/dist/esm/tools/search/tavily-search.mjs +370 -0
  158. package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
  159. package/dist/esm/tools/search/tool.mjs +26 -4
  160. package/dist/esm/tools/search/tool.mjs.map +1 -1
  161. package/dist/esm/tools/search/utils.mjs +10 -3
  162. package/dist/esm/tools/search/utils.mjs.map +1 -1
  163. package/dist/types/graphs/Graph.d.ts +7 -0
  164. package/dist/types/hitl/askUserQuestion.d.ts +55 -0
  165. package/dist/types/hitl/index.d.ts +6 -0
  166. package/dist/types/hooks/HookRegistry.d.ts +58 -0
  167. package/dist/types/hooks/createToolPolicyHook.d.ts +87 -0
  168. package/dist/types/hooks/index.d.ts +4 -1
  169. package/dist/types/hooks/types.d.ts +109 -3
  170. package/dist/types/index.d.ts +10 -0
  171. package/dist/types/langchain/google-common.d.ts +1 -0
  172. package/dist/types/langchain/index.d.ts +8 -0
  173. package/dist/types/langchain/language_models/chat_models.d.ts +1 -0
  174. package/dist/types/langchain/messages/tool.d.ts +1 -0
  175. package/dist/types/langchain/messages.d.ts +2 -0
  176. package/dist/types/langchain/openai.d.ts +1 -0
  177. package/dist/types/langchain/prompts.d.ts +1 -0
  178. package/dist/types/langchain/runnables.d.ts +2 -0
  179. package/dist/types/langchain/tools.d.ts +2 -0
  180. package/dist/types/langchain/utils/env.d.ts +1 -0
  181. package/dist/types/llm/anthropic/index.d.ts +22 -9
  182. package/dist/types/llm/anthropic/types.d.ts +5 -1
  183. package/dist/types/llm/anthropic/utils/message_outputs.d.ts +13 -6
  184. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +1 -1
  185. package/dist/types/llm/openai/index.d.ts +21 -24
  186. package/dist/types/llm/openrouter/index.d.ts +11 -9
  187. package/dist/types/llm/vertexai/index.d.ts +1 -0
  188. package/dist/types/messages/cache.d.ts +4 -1
  189. package/dist/types/messages/format.d.ts +4 -1
  190. package/dist/types/messages/langchain.d.ts +27 -0
  191. package/dist/types/run.d.ts +117 -1
  192. package/dist/types/tools/ToolNode.d.ts +26 -1
  193. package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
  194. package/dist/types/tools/search/tavily-search.d.ts +4 -0
  195. package/dist/types/tools/search/types.d.ts +99 -5
  196. package/dist/types/tools/search/utils.d.ts +2 -2
  197. package/dist/types/types/graph.d.ts +23 -37
  198. package/dist/types/types/hitl.d.ts +272 -0
  199. package/dist/types/types/index.d.ts +1 -0
  200. package/dist/types/types/llm.d.ts +3 -3
  201. package/dist/types/types/run.d.ts +33 -0
  202. package/dist/types/types/stream.d.ts +1 -1
  203. package/dist/types/types/tools.d.ts +19 -0
  204. package/package.json +80 -17
  205. package/src/graphs/Graph.ts +33 -4
  206. package/src/graphs/__tests__/composition.smoke.test.ts +188 -0
  207. package/src/hitl/askUserQuestion.ts +72 -0
  208. package/src/hitl/index.ts +7 -0
  209. package/src/hooks/HookRegistry.ts +71 -0
  210. package/src/hooks/__tests__/createToolPolicyHook.test.ts +259 -0
  211. package/src/hooks/createToolPolicyHook.ts +184 -0
  212. package/src/hooks/executeHooks.ts +50 -1
  213. package/src/hooks/index.ts +6 -0
  214. package/src/hooks/types.ts +112 -0
  215. package/src/index.ts +22 -0
  216. package/src/langchain/google-common.ts +1 -0
  217. package/src/langchain/index.ts +8 -0
  218. package/src/langchain/language_models/chat_models.ts +1 -0
  219. package/src/langchain/messages/tool.ts +5 -0
  220. package/src/langchain/messages.ts +21 -0
  221. package/src/langchain/openai.ts +1 -0
  222. package/src/langchain/prompts.ts +1 -0
  223. package/src/langchain/runnables.ts +7 -0
  224. package/src/langchain/tools.ts +8 -0
  225. package/src/langchain/utils/env.ts +1 -0
  226. package/src/llm/anthropic/index.ts +252 -84
  227. package/src/llm/anthropic/llm.spec.ts +751 -102
  228. package/src/llm/anthropic/types.ts +9 -1
  229. package/src/llm/anthropic/utils/message_inputs.ts +37 -19
  230. package/src/llm/anthropic/utils/message_outputs.ts +119 -101
  231. package/src/llm/bedrock/index.ts +2 -2
  232. package/src/llm/bedrock/llm.spec.ts +341 -0
  233. package/src/llm/bedrock/utils/message_inputs.ts +303 -4
  234. package/src/llm/bedrock/utils/message_outputs.ts +2 -1
  235. package/src/llm/custom-chat-models.smoke.test.ts +836 -0
  236. package/src/llm/google/llm.spec.ts +339 -57
  237. package/src/llm/google/utils/common.ts +53 -48
  238. package/src/llm/openai/contentBlocks.test.ts +346 -0
  239. package/src/llm/openai/index.ts +856 -833
  240. package/src/llm/openai/utils/index.ts +107 -78
  241. package/src/llm/openai/utils/messages.test.ts +159 -0
  242. package/src/llm/openrouter/index.ts +124 -247
  243. package/src/llm/openrouter/reasoning.test.ts +8 -1
  244. package/src/llm/vertexai/index.ts +11 -5
  245. package/src/llm/vertexai/llm.spec.ts +28 -1
  246. package/src/messages/cache.test.ts +4 -3
  247. package/src/messages/cache.ts +3 -2
  248. package/src/messages/core.ts +16 -9
  249. package/src/messages/format.ts +96 -16
  250. package/src/messages/formatAgentMessages.test.ts +166 -1
  251. package/src/messages/langchain.ts +39 -0
  252. package/src/messages/prune.ts +12 -8
  253. package/src/run.ts +456 -47
  254. package/src/scripts/caching.ts +2 -3
  255. package/src/specs/summarization.test.ts +51 -58
  256. package/src/tools/ToolNode.ts +706 -63
  257. package/src/tools/__tests__/hitl.test.ts +3593 -0
  258. package/src/tools/search/search.ts +83 -73
  259. package/src/tools/search/tavily-scraper.ts +235 -0
  260. package/src/tools/search/tavily-search.ts +424 -0
  261. package/src/tools/search/tavily.test.ts +965 -0
  262. package/src/tools/search/tool.ts +36 -26
  263. package/src/tools/search/types.ts +133 -8
  264. package/src/tools/search/utils.ts +13 -5
  265. package/src/types/graph.ts +32 -87
  266. package/src/types/hitl.ts +303 -0
  267. package/src/types/index.ts +1 -0
  268. package/src/types/llm.ts +3 -3
  269. package/src/types/run.ts +33 -0
  270. package/src/types/stream.ts +1 -1
  271. package/src/types/tools.ts +19 -0
  272. package/src/utils/llmConfig.ts +1 -6
@@ -40,6 +40,7 @@ import {
40
40
  schemaToGenerativeAIParameters,
41
41
  } from './zod_to_genai_parameters';
42
42
  import { GoogleGenerativeAIToolType } from '../types';
43
+ import { toLangChainContent } from '@/messages/langchain';
43
44
 
44
45
  export const _FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY =
45
46
  '__gemini_function_call_thought_signatures__';
@@ -575,30 +576,32 @@ export function convertResponseContentToChatGenerationChunk(
575
576
  }
576
577
  content = textParts.join('');
577
578
  } else if (candidateContent && Array.isArray(candidateContent.parts)) {
578
- content = candidateContent.parts
579
- .map((p) => {
580
- if ('text' in p && 'thought' in p && p.thought === true) {
581
- reasoningParts.push(p.text ?? '');
582
- return undefined;
583
- } else if ('text' in p) {
584
- return {
585
- type: 'text',
586
- text: p.text,
587
- };
588
- } else if ('executableCode' in p) {
589
- return {
590
- type: 'executableCode',
591
- executableCode: p.executableCode,
592
- };
593
- } else if ('codeExecutionResult' in p) {
594
- return {
595
- type: 'codeExecutionResult',
596
- codeExecutionResult: p.codeExecutionResult,
597
- };
598
- }
599
- return p;
600
- })
601
- .filter((p) => p !== undefined);
579
+ content = toLangChainContent(
580
+ candidateContent.parts
581
+ .map((p) => {
582
+ if ('text' in p && 'thought' in p && p.thought === true) {
583
+ reasoningParts.push(p.text ?? '');
584
+ return undefined;
585
+ } else if ('text' in p) {
586
+ return {
587
+ type: 'text',
588
+ text: p.text,
589
+ };
590
+ } else if ('executableCode' in p) {
591
+ return {
592
+ type: 'executableCode',
593
+ executableCode: p.executableCode,
594
+ };
595
+ } else if ('codeExecutionResult' in p) {
596
+ return {
597
+ type: 'codeExecutionResult',
598
+ codeExecutionResult: p.codeExecutionResult,
599
+ };
600
+ }
601
+ return p;
602
+ })
603
+ .filter((p) => p !== undefined)
604
+ );
602
605
  } else {
603
606
  // no content returned - likely due to abnormal stop reason, e.g. malformed function call
604
607
  content = [];
@@ -733,30 +736,32 @@ export function mapGenerateContentResultToChatResult(
733
736
  Array.isArray(candidateContent?.parts) &&
734
737
  candidateContent.parts.length > 0
735
738
  ) {
736
- content = candidateContent.parts
737
- .map((p) => {
738
- if ('text' in p && 'thought' in p && p.thought === true) {
739
- reasoningParts.push(p.text ?? '');
740
- return undefined;
741
- } else if ('text' in p) {
742
- return {
743
- type: 'text',
744
- text: p.text,
745
- };
746
- } else if ('executableCode' in p) {
747
- return {
748
- type: 'executableCode',
749
- executableCode: p.executableCode,
750
- };
751
- } else if ('codeExecutionResult' in p) {
752
- return {
753
- type: 'codeExecutionResult',
754
- codeExecutionResult: p.codeExecutionResult,
755
- };
756
- }
757
- return p;
758
- })
759
- .filter((p) => p !== undefined);
739
+ content = toLangChainContent(
740
+ candidateContent.parts
741
+ .map((p) => {
742
+ if ('text' in p && 'thought' in p && p.thought === true) {
743
+ reasoningParts.push(p.text ?? '');
744
+ return undefined;
745
+ } else if ('text' in p) {
746
+ return {
747
+ type: 'text',
748
+ text: p.text,
749
+ };
750
+ } else if ('executableCode' in p) {
751
+ return {
752
+ type: 'executableCode',
753
+ executableCode: p.executableCode,
754
+ };
755
+ } else if ('codeExecutionResult' in p) {
756
+ return {
757
+ type: 'codeExecutionResult',
758
+ codeExecutionResult: p.codeExecutionResult,
759
+ };
760
+ }
761
+ return p;
762
+ })
763
+ .filter((p) => p !== undefined)
764
+ );
760
765
  } else {
761
766
  content = [];
762
767
  }
@@ -0,0 +1,346 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import {
3
+ AIMessage,
4
+ AIMessageChunk,
5
+ type ContentBlock,
6
+ } from '@langchain/core/messages';
7
+
8
+ describe('OpenAI content block translator compatibility', () => {
9
+ describe('Chat Completions', () => {
10
+ test('translates string content and tool calls to v1 content blocks', () => {
11
+ const message = new AIMessage({
12
+ content: 'Hello from OpenAI',
13
+ tool_calls: [
14
+ {
15
+ id: 'call_123',
16
+ name: 'get_weather',
17
+ args: { location: 'San Francisco' },
18
+ },
19
+ ],
20
+ response_metadata: { model_provider: 'openai' },
21
+ });
22
+
23
+ const expected: Array<ContentBlock.Standard> = [
24
+ { type: 'text', text: 'Hello from OpenAI' },
25
+ {
26
+ type: 'tool_call',
27
+ id: 'call_123',
28
+ name: 'get_weather',
29
+ args: { location: 'San Francisco' },
30
+ },
31
+ ];
32
+
33
+ expect(message.contentBlocks).toEqual(expected);
34
+ expect(message.content).not.toEqual(expected);
35
+
36
+ const v1Message = new AIMessage({
37
+ content: message.contentBlocks,
38
+ response_metadata: { output_version: 'v1' },
39
+ });
40
+ expect(v1Message.contentBlocks).toEqual(expected);
41
+ expect(v1Message.content).toEqual(expected);
42
+ });
43
+
44
+ test('does not include empty text block when content is empty string with tool calls', () => {
45
+ const message = new AIMessage({
46
+ content: '',
47
+ tool_calls: [
48
+ {
49
+ id: 'call_123',
50
+ name: 'get_value',
51
+ args: { key: 'a' },
52
+ },
53
+ {
54
+ id: 'call_456',
55
+ name: 'get_value',
56
+ args: { key: 'b' },
57
+ },
58
+ ],
59
+ response_metadata: { model_provider: 'openai' },
60
+ });
61
+
62
+ expect(message.contentBlocks).toEqual([
63
+ {
64
+ type: 'tool_call',
65
+ id: 'call_123',
66
+ name: 'get_value',
67
+ args: { key: 'a' },
68
+ },
69
+ {
70
+ type: 'tool_call',
71
+ id: 'call_456',
72
+ name: 'get_value',
73
+ args: { key: 'b' },
74
+ },
75
+ ]);
76
+ });
77
+
78
+ test('translates chat completion chunks with parsed tool call chunks', () => {
79
+ const chunk1 = new AIMessageChunk({
80
+ content: [{ type: 'text', text: 'Looking ', index: 0 }],
81
+ response_metadata: { model_provider: 'openai' },
82
+ });
83
+ const chunk2 = new AIMessageChunk({
84
+ content: [{ type: 'text', text: 'up.', index: 0 }],
85
+ tool_call_chunks: [
86
+ {
87
+ type: 'tool_call_chunk',
88
+ id: 'call_abc',
89
+ name: 'search',
90
+ args: '{"query":"weather"}',
91
+ index: 0,
92
+ },
93
+ ],
94
+ response_metadata: { model_provider: 'openai' },
95
+ });
96
+
97
+ expect(chunk1.concat(chunk2).contentBlocks).toEqual([
98
+ { type: 'text', text: 'Looking up.', index: 0 },
99
+ {
100
+ type: 'tool_call',
101
+ id: 'call_abc',
102
+ name: 'search',
103
+ args: { query: 'weather' },
104
+ },
105
+ ]);
106
+ });
107
+ });
108
+
109
+ describe('Responses', () => {
110
+ test('translates Responses messages to v1 content blocks', () => {
111
+ const code = ['print(', 'hello', ')'].join(String.fromCharCode(39));
112
+ const responseTextBlock = {
113
+ type: 'text',
114
+ text: 'Here is a result.',
115
+ annotations: [
116
+ {
117
+ type: 'url_citation',
118
+ url: 'https://example.com',
119
+ title: 'Example',
120
+ start_index: 0,
121
+ end_index: 4,
122
+ },
123
+ {
124
+ type: 'file_citation',
125
+ file_id: 'file_123',
126
+ filename: 'doc.pdf',
127
+ index: 10,
128
+ },
129
+ ],
130
+ } as ContentBlock.Text;
131
+ const message = new AIMessage({
132
+ content: [responseTextBlock],
133
+ tool_calls: [
134
+ { id: 'call_456', name: 'summarize', args: { length: 'short' } },
135
+ ],
136
+ additional_kwargs: {
137
+ reasoning: { summary: [{ text: 'Thinking...' }, { text: ' Done.' }] },
138
+ tool_outputs: [
139
+ {
140
+ id: 'call_456',
141
+ type: 'code_interpreter_call',
142
+ code,
143
+ status: 'completed',
144
+ outputs: [{ type: 'logs', logs: 'hello' }],
145
+ },
146
+ ],
147
+ },
148
+ response_metadata: { model_provider: 'openai' },
149
+ });
150
+
151
+ const expected: Array<ContentBlock.Standard> = [
152
+ { type: 'reasoning', reasoning: 'Thinking... Done.' },
153
+ {
154
+ type: 'text',
155
+ text: 'Here is a result.',
156
+ annotations: [
157
+ {
158
+ type: 'citation',
159
+ url: 'https://example.com',
160
+ title: 'Example',
161
+ startIndex: 0,
162
+ endIndex: 4,
163
+ },
164
+ {
165
+ type: 'citation',
166
+ title: 'doc.pdf',
167
+ startIndex: 10,
168
+ endIndex: 10,
169
+ fileId: 'file_123',
170
+ },
171
+ ],
172
+ },
173
+ {
174
+ type: 'tool_call',
175
+ id: 'call_456',
176
+ name: 'summarize',
177
+ args: { length: 'short' },
178
+ },
179
+ {
180
+ type: 'server_tool_call',
181
+ name: 'code_interpreter',
182
+ id: 'call_456',
183
+ args: { code },
184
+ },
185
+ {
186
+ type: 'server_tool_call_result',
187
+ toolCallId: 'call_456',
188
+ status: 'success',
189
+ output: {
190
+ type: 'code_interpreter_output',
191
+ returnCode: 0,
192
+ stderr: undefined,
193
+ stdout: 'hello',
194
+ },
195
+ },
196
+ ];
197
+
198
+ expect(message.contentBlocks).toEqual(expected);
199
+
200
+ const v1Message = new AIMessage({
201
+ content: message.contentBlocks,
202
+ response_metadata: { output_version: 'v1' },
203
+ });
204
+ expect(v1Message.contentBlocks).toEqual(expected);
205
+ expect(v1Message.content).toEqual(expected);
206
+ });
207
+
208
+ test('translates image_generation_call to image content block', () => {
209
+ const message = new AIMessage({
210
+ content: [{ type: 'text', text: 'Here is your image:' }],
211
+ additional_kwargs: {
212
+ tool_outputs: [
213
+ {
214
+ type: 'image_generation_call',
215
+ id: 'ig_abc123',
216
+ status: 'completed',
217
+ result: 'base64ImageData',
218
+ revised_prompt: 'A beautiful sunset over the ocean',
219
+ },
220
+ ],
221
+ },
222
+ response_metadata: { model_provider: 'openai' },
223
+ });
224
+
225
+ expect(message.contentBlocks).toEqual([
226
+ { type: 'text', text: 'Here is your image:' },
227
+ {
228
+ type: 'image',
229
+ mimeType: 'image/png',
230
+ data: 'base64ImageData',
231
+ id: 'ig_abc123',
232
+ metadata: {
233
+ status: 'completed',
234
+ },
235
+ },
236
+ {
237
+ type: 'non_standard',
238
+ value: {
239
+ type: 'image_generation_call',
240
+ id: 'ig_abc123',
241
+ status: 'completed',
242
+ result: 'base64ImageData',
243
+ revised_prompt: 'A beautiful sunset over the ocean',
244
+ },
245
+ },
246
+ ]);
247
+ });
248
+
249
+ test('translates web_search_call and file_search_call to server tool blocks', () => {
250
+ const message = new AIMessage({
251
+ content: [{ type: 'text', text: 'Search results:' }],
252
+ additional_kwargs: {
253
+ tool_outputs: [
254
+ {
255
+ type: 'web_search_call',
256
+ id: 'ws_abc456',
257
+ status: 'completed',
258
+ action: {
259
+ type: 'search',
260
+ query: 'melbourne australia news today',
261
+ sources: [{ type: 'url', url: 'https://example.com/news' }],
262
+ },
263
+ },
264
+ {
265
+ type: 'file_search_call',
266
+ id: 'fs_abc123',
267
+ status: 'completed',
268
+ queries: ['quarterly report', 'revenue 2025'],
269
+ results: [
270
+ {
271
+ file_id: 'file_001',
272
+ filename: 'report.pdf',
273
+ score: 0.95,
274
+ text: 'Revenue grew 15% in Q3...',
275
+ },
276
+ ],
277
+ },
278
+ ],
279
+ },
280
+ response_metadata: { model_provider: 'openai' },
281
+ });
282
+
283
+ expect(message.contentBlocks).toEqual([
284
+ { type: 'text', text: 'Search results:' },
285
+ {
286
+ type: 'server_tool_call',
287
+ name: 'web_search',
288
+ id: 'ws_abc456',
289
+ args: { query: 'melbourne australia news today' },
290
+ },
291
+ {
292
+ type: 'server_tool_call_result',
293
+ toolCallId: 'ws_abc456',
294
+ status: 'success',
295
+ output: {
296
+ action: {
297
+ type: 'search',
298
+ query: 'melbourne australia news today',
299
+ sources: [{ type: 'url', url: 'https://example.com/news' }],
300
+ },
301
+ },
302
+ },
303
+ {
304
+ type: 'server_tool_call',
305
+ name: 'file_search',
306
+ id: 'fs_abc123',
307
+ args: { queries: ['quarterly report', 'revenue 2025'] },
308
+ },
309
+ {
310
+ type: 'server_tool_call_result',
311
+ toolCallId: 'fs_abc123',
312
+ status: 'success',
313
+ output: {
314
+ results: [
315
+ {
316
+ file_id: 'file_001',
317
+ filename: 'report.pdf',
318
+ score: 0.95,
319
+ text: 'Revenue grew 15% in Q3...',
320
+ },
321
+ ],
322
+ },
323
+ },
324
+ ]);
325
+ });
326
+
327
+ test('moves phase into extras on text content blocks', () => {
328
+ const textBlock = {
329
+ type: 'text',
330
+ text: 'The weather is sunny.',
331
+ annotations: [],
332
+ phase: 'final_answer',
333
+ } as ContentBlock.Text & { phase: string };
334
+ const message = new AIMessage({
335
+ content: [textBlock],
336
+ response_metadata: { model_provider: 'openai' },
337
+ });
338
+
339
+ const contentTextBlock = message.contentBlocks.find(
340
+ (block): block is ContentBlock.Text => block.type === 'text'
341
+ );
342
+ expect(contentTextBlock).toBeDefined();
343
+ expect(contentTextBlock?.extras).toEqual({ phase: 'final_answer' });
344
+ });
345
+ });
346
+ });