@librechat/agents 3.2.21 → 3.2.32

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 (402) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +3 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/events.cjs.map +1 -1
  4. package/dist/cjs/graphs/Graph.cjs +200 -54
  5. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +13 -7
  8. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
  9. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  10. package/dist/cjs/hooks/types.cjs.map +1 -1
  11. package/dist/cjs/instrumentation.cjs +2 -2
  12. package/dist/cjs/instrumentation.cjs.map +1 -1
  13. package/dist/cjs/langfuse.cjs +17 -1
  14. package/dist/cjs/langfuse.cjs.map +1 -1
  15. package/dist/cjs/langfuseToolOutputTracing.cjs +2 -2
  16. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  17. package/dist/cjs/llm/anthropic/index.cjs +1 -1
  18. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  19. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +38 -3
  20. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  21. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +6 -2
  22. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  23. package/dist/cjs/llm/bedrock/index.cjs +2 -2
  24. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  25. package/dist/cjs/llm/bedrock/toolCache.cjs +8 -5
  26. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
  27. package/dist/cjs/llm/fake.cjs +16 -14
  28. package/dist/cjs/llm/fake.cjs.map +1 -1
  29. package/dist/cjs/llm/google/index.cjs +22 -0
  30. package/dist/cjs/llm/google/index.cjs.map +1 -1
  31. package/dist/cjs/llm/google/utils/common.cjs +88 -27
  32. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  33. package/dist/cjs/llm/init.cjs +2 -2
  34. package/dist/cjs/llm/invoke.cjs +108 -11
  35. package/dist/cjs/llm/invoke.cjs.map +1 -1
  36. package/dist/cjs/llm/openai/index.cjs +1 -1
  37. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  38. package/dist/cjs/llm/openai/utils/index.cjs +1 -1
  39. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  40. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  41. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  42. package/dist/cjs/main.cjs +1 -0
  43. package/dist/cjs/main.cjs.map +1 -1
  44. package/dist/cjs/messages/cache.cjs +29 -8
  45. package/dist/cjs/messages/cache.cjs.map +1 -1
  46. package/dist/cjs/messages/content.cjs.map +1 -1
  47. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  48. package/dist/cjs/messages/format.cjs +129 -17
  49. package/dist/cjs/messages/format.cjs.map +1 -1
  50. package/dist/cjs/messages/prune.cjs.map +1 -1
  51. package/dist/cjs/messages/reducer.cjs +1 -1
  52. package/dist/cjs/messages/reducer.cjs.map +1 -1
  53. package/dist/cjs/messages/tools.cjs +1 -1
  54. package/dist/cjs/messages/tools.cjs.map +1 -1
  55. package/dist/cjs/openai/index.cjs.map +1 -1
  56. package/dist/cjs/responses/index.cjs.map +1 -1
  57. package/dist/cjs/run.cjs +41 -20
  58. package/dist/cjs/run.cjs.map +1 -1
  59. package/dist/cjs/session/AgentSession.cjs +4 -4
  60. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  61. package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
  62. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
  63. package/dist/cjs/session/handlers.cjs +2 -2
  64. package/dist/cjs/session/handlers.cjs.map +1 -1
  65. package/dist/cjs/stream.cjs +248 -25
  66. package/dist/cjs/stream.cjs.map +1 -1
  67. package/dist/cjs/summarization/node.cjs.map +1 -1
  68. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +1 -1
  69. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  70. package/dist/cjs/tools/Calculator.cjs +1 -1
  71. package/dist/cjs/tools/Calculator.cjs.map +1 -1
  72. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  73. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  74. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  75. package/dist/cjs/tools/ToolNode.cjs +37 -18
  76. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  77. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  78. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  79. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +7 -4
  80. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
  81. package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs +4 -4
  82. package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -1
  83. package/dist/cjs/tools/handlers.cjs +2 -1
  84. package/dist/cjs/tools/handlers.cjs.map +1 -1
  85. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
  86. package/dist/cjs/tools/local/FileCheckpointer.cjs +2 -1
  87. package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
  88. package/dist/cjs/tools/local/LocalCodingTools.cjs +45 -19
  89. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  90. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
  91. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
  92. package/dist/cjs/tools/local/LocalExecutionTools.cjs +2 -2
  93. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
  94. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +4 -3
  95. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  96. package/dist/cjs/tools/local/attachments.cjs +0 -5
  97. package/dist/cjs/tools/local/attachments.cjs.map +1 -1
  98. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +4 -4
  99. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
  100. package/dist/cjs/tools/search/firecrawl.cjs +1 -1
  101. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  102. package/dist/cjs/tools/search/rerankers.cjs +7 -3
  103. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  104. package/dist/cjs/tools/search/tavily-search.cjs +1 -1
  105. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
  106. package/dist/cjs/tools/search/utils.cjs +76 -8
  107. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  108. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
  109. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  110. package/dist/cjs/utils/handlers.cjs +1 -1
  111. package/dist/cjs/utils/handlers.cjs.map +1 -1
  112. package/dist/cjs/utils/run.cjs +1 -1
  113. package/dist/cjs/utils/run.cjs.map +1 -1
  114. package/dist/esm/agents/AgentContext.mjs +3 -2
  115. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  116. package/dist/esm/events.mjs.map +1 -1
  117. package/dist/esm/graphs/Graph.mjs +200 -54
  118. package/dist/esm/graphs/Graph.mjs.map +1 -1
  119. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  120. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +13 -7
  121. package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
  122. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  123. package/dist/esm/hooks/types.mjs.map +1 -1
  124. package/dist/esm/instrumentation.mjs +2 -2
  125. package/dist/esm/instrumentation.mjs.map +1 -1
  126. package/dist/esm/langfuse.mjs +17 -2
  127. package/dist/esm/langfuse.mjs.map +1 -1
  128. package/dist/esm/langfuseToolOutputTracing.mjs +2 -2
  129. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  130. package/dist/esm/llm/anthropic/index.mjs +1 -1
  131. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  132. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +38 -3
  133. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  134. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +6 -2
  135. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  136. package/dist/esm/llm/bedrock/index.mjs +2 -2
  137. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  138. package/dist/esm/llm/bedrock/toolCache.mjs +8 -5
  139. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
  140. package/dist/esm/llm/fake.mjs +16 -14
  141. package/dist/esm/llm/fake.mjs.map +1 -1
  142. package/dist/esm/llm/google/index.mjs +23 -1
  143. package/dist/esm/llm/google/index.mjs.map +1 -1
  144. package/dist/esm/llm/google/utils/common.mjs +88 -27
  145. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  146. package/dist/esm/llm/init.mjs +2 -2
  147. package/dist/esm/llm/invoke.mjs +104 -7
  148. package/dist/esm/llm/invoke.mjs.map +1 -1
  149. package/dist/esm/llm/openai/index.mjs +1 -1
  150. package/dist/esm/llm/openai/index.mjs.map +1 -1
  151. package/dist/esm/llm/openai/utils/index.mjs +1 -1
  152. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  153. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  154. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  155. package/dist/esm/main.mjs +1 -1
  156. package/dist/esm/messages/cache.mjs +29 -8
  157. package/dist/esm/messages/cache.mjs.map +1 -1
  158. package/dist/esm/messages/content.mjs.map +1 -1
  159. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  160. package/dist/esm/messages/format.mjs +129 -18
  161. package/dist/esm/messages/format.mjs.map +1 -1
  162. package/dist/esm/messages/prune.mjs.map +1 -1
  163. package/dist/esm/messages/reducer.mjs +1 -1
  164. package/dist/esm/messages/reducer.mjs.map +1 -1
  165. package/dist/esm/messages/tools.mjs +1 -1
  166. package/dist/esm/messages/tools.mjs.map +1 -1
  167. package/dist/esm/openai/index.mjs.map +1 -1
  168. package/dist/esm/responses/index.mjs.map +1 -1
  169. package/dist/esm/run.mjs +41 -20
  170. package/dist/esm/run.mjs.map +1 -1
  171. package/dist/esm/session/AgentSession.mjs +4 -4
  172. package/dist/esm/session/AgentSession.mjs.map +1 -1
  173. package/dist/esm/session/JsonlSessionStore.mjs +2 -2
  174. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
  175. package/dist/esm/session/handlers.mjs +2 -2
  176. package/dist/esm/session/handlers.mjs.map +1 -1
  177. package/dist/esm/stream.mjs +248 -25
  178. package/dist/esm/stream.mjs.map +1 -1
  179. package/dist/esm/summarization/node.mjs.map +1 -1
  180. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +1 -1
  181. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  182. package/dist/esm/tools/Calculator.mjs +1 -1
  183. package/dist/esm/tools/Calculator.mjs.map +1 -1
  184. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  185. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  186. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  187. package/dist/esm/tools/ToolNode.mjs +37 -18
  188. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  189. package/dist/esm/tools/ToolSearch.mjs +1 -1
  190. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  191. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +7 -4
  192. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
  193. package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs +4 -4
  194. package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -1
  195. package/dist/esm/tools/handlers.mjs +2 -1
  196. package/dist/esm/tools/handlers.mjs.map +1 -1
  197. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
  198. package/dist/esm/tools/local/FileCheckpointer.mjs +2 -1
  199. package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
  200. package/dist/esm/tools/local/LocalCodingTools.mjs +45 -19
  201. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  202. package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
  203. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
  204. package/dist/esm/tools/local/LocalExecutionTools.mjs +2 -2
  205. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
  206. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +4 -3
  207. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  208. package/dist/esm/tools/local/attachments.mjs +0 -5
  209. package/dist/esm/tools/local/attachments.mjs.map +1 -1
  210. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +4 -4
  211. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  212. package/dist/esm/tools/search/firecrawl.mjs +1 -1
  213. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  214. package/dist/esm/tools/search/rerankers.mjs +8 -4
  215. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  216. package/dist/esm/tools/search/tavily-search.mjs +1 -1
  217. package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
  218. package/dist/esm/tools/search/utils.mjs +76 -9
  219. package/dist/esm/tools/search/utils.mjs.map +1 -1
  220. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
  221. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  222. package/dist/esm/utils/handlers.mjs +1 -1
  223. package/dist/esm/utils/handlers.mjs.map +1 -1
  224. package/dist/esm/utils/run.mjs +1 -1
  225. package/dist/esm/utils/run.mjs.map +1 -1
  226. package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +1 -1
  227. package/dist/types/events.d.ts +1 -1
  228. package/dist/types/graphs/Graph.d.ts +7 -1
  229. package/dist/types/hooks/executeHooks.d.ts +1 -1
  230. package/dist/types/hooks/types.d.ts +5 -0
  231. package/dist/types/langfuse.d.ts +4 -0
  232. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -1
  233. package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -1
  234. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +2 -2
  235. package/dist/types/llm/bedrock/index.d.ts +2 -2
  236. package/dist/types/llm/fake.d.ts +3 -3
  237. package/dist/types/llm/google/index.d.ts +1 -0
  238. package/dist/types/llm/google/types.d.ts +1 -1
  239. package/dist/types/llm/google/utils/common.d.ts +2 -2
  240. package/dist/types/llm/google/utils/tools.d.ts +1 -1
  241. package/dist/types/llm/google/utils/zod_to_genai_parameters.d.ts +1 -1
  242. package/dist/types/llm/openai/index.d.ts +2 -2
  243. package/dist/types/llm/openai/utils/index.d.ts +1 -1
  244. package/dist/types/llm/openrouter/index.d.ts +4 -4
  245. package/dist/types/messages/contextPruning.d.ts +1 -1
  246. package/dist/types/messages/format.d.ts +14 -4
  247. package/dist/types/messages/prune.d.ts +1 -1
  248. package/dist/types/session/JsonlSessionStore.d.ts +1 -1
  249. package/dist/types/session/handlers.d.ts +1 -1
  250. package/dist/types/session/types.d.ts +1 -1
  251. package/dist/types/summarization/node.d.ts +1 -1
  252. package/dist/types/tools/SubagentTool.d.ts +2 -2
  253. package/dist/types/tools/ToolNode.d.ts +9 -2
  254. package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +1 -1
  255. package/dist/types/tools/search/types.d.ts +1 -1
  256. package/dist/types/tools/search/utils.d.ts +11 -0
  257. package/dist/types/types/graph.d.ts +4 -4
  258. package/dist/types/types/llm.d.ts +4 -3
  259. package/dist/types/types/messages.d.ts +1 -1
  260. package/dist/types/types/run.d.ts +6 -6
  261. package/dist/types/types/stream.d.ts +2 -2
  262. package/dist/types/types/tools.d.ts +5 -1
  263. package/dist/types/utils/handlers.d.ts +2 -2
  264. package/dist/types/utils/run.d.ts +1 -1
  265. package/package.json +13 -10
  266. package/src/__tests__/stream.eagerEventExecution.test.ts +543 -6
  267. package/src/agents/AgentContext.ts +2 -2
  268. package/src/agents/__tests__/AgentContext.test.ts +3 -3
  269. package/src/agents/__tests__/promptCacheLiveHelpers.ts +1 -1
  270. package/src/events.ts +1 -1
  271. package/src/graphs/Graph.ts +329 -72
  272. package/src/graphs/MultiAgentGraph.ts +1 -1
  273. package/src/graphs/__tests__/Graph.reasoning.test.ts +919 -6
  274. package/src/graphs/__tests__/MultiAgentGraph.test.ts +1 -1
  275. package/src/graphs/__tests__/composition.smoke.test.ts +1 -1
  276. package/src/hooks/__tests__/HookRegistry.test.ts +1 -1
  277. package/src/hooks/__tests__/compactHooks.test.ts +8 -8
  278. package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +34 -22
  279. package/src/hooks/__tests__/executeHooks.test.ts +3 -3
  280. package/src/hooks/__tests__/integration.test.ts +3 -3
  281. package/src/hooks/__tests__/toolHooks.test.ts +10 -10
  282. package/src/hooks/createWorkspacePolicyHook.ts +17 -14
  283. package/src/hooks/executeHooks.ts +1 -1
  284. package/src/hooks/types.ts +5 -0
  285. package/src/instrumentation.ts +11 -11
  286. package/src/langfuse.ts +35 -1
  287. package/src/langfuseToolOutputTracing.ts +2 -2
  288. package/src/llm/anthropic/index.ts +1 -1
  289. package/src/llm/anthropic/llm.spec.ts +36 -0
  290. package/src/llm/anthropic/utils/message_inputs.ts +46 -4
  291. package/src/llm/anthropic/utils/message_outputs.ts +9 -7
  292. package/src/llm/anthropic/utils/output_parsers.ts +5 -5
  293. package/src/llm/anthropic/utils/streaming-tool-input.test.ts +186 -0
  294. package/src/llm/bedrock/index.ts +4 -4
  295. package/src/llm/bedrock/toolCache.test.ts +48 -9
  296. package/src/llm/bedrock/toolCache.ts +11 -6
  297. package/src/llm/fake.ts +30 -25
  298. package/src/llm/google/index.ts +43 -1
  299. package/src/llm/google/llm.spec.ts +173 -1
  300. package/src/llm/google/types.ts +1 -1
  301. package/src/llm/google/utils/common.ts +154 -45
  302. package/src/llm/google/utils/tools.ts +8 -8
  303. package/src/llm/google/utils/zod_to_genai_parameters.ts +4 -4
  304. package/src/llm/invoke.test.ts +3 -3
  305. package/src/llm/invoke.ts +170 -10
  306. package/src/llm/openai/index.ts +4 -4
  307. package/src/llm/openai/utils/index.ts +14 -14
  308. package/src/llm/openrouter/index.ts +4 -4
  309. package/src/llm/openrouter/reasoning.test.ts +2 -2
  310. package/src/llm/vertexai/fixThoughtSignatures.test.ts +1 -1
  311. package/src/llm/vertexai/index.ts +1 -1
  312. package/src/messages/cache.test.ts +144 -0
  313. package/src/messages/cache.ts +50 -13
  314. package/src/messages/content.ts +1 -1
  315. package/src/messages/contextPruning.ts +1 -1
  316. package/src/messages/format.ts +236 -43
  317. package/src/messages/formatAgentMessages.skills.test.ts +205 -26
  318. package/src/messages/formatAgentMessages.test.ts +841 -10
  319. package/src/messages/labelContentByAgent.test.ts +2 -2
  320. package/src/messages/prune.ts +1 -1
  321. package/src/messages/reducer.ts +1 -1
  322. package/src/messages/tools.ts +1 -1
  323. package/src/openai/__tests__/openai.test.ts +2 -2
  324. package/src/openai/index.ts +1 -1
  325. package/src/responses/__tests__/responses.test.ts +2 -2
  326. package/src/responses/index.ts +1 -1
  327. package/src/run.ts +68 -41
  328. package/src/session/AgentSession.ts +6 -6
  329. package/src/session/JsonlSessionStore.ts +3 -3
  330. package/src/session/__tests__/JsonlSessionStore.test.ts +5 -5
  331. package/src/session/__tests__/handlers.test.ts +2 -2
  332. package/src/session/handlers.ts +5 -5
  333. package/src/session/types.ts +1 -1
  334. package/src/specs/agent-handoffs.test.ts +1 -1
  335. package/src/specs/langfuse-callbacks.test.ts +2 -2
  336. package/src/specs/langfuse-metadata.test.ts +39 -0
  337. package/src/specs/langfuse-tool-output-tracing.test.ts +1 -1
  338. package/src/specs/multi-agent-summarization.test.ts +4 -4
  339. package/src/specs/subagent.test.ts +3 -3
  340. package/src/specs/summarization-unit.test.ts +1 -1
  341. package/src/specs/thinking-handoff.test.ts +1 -1
  342. package/src/splitStream.test.ts +48 -0
  343. package/src/stream.test.ts +53 -3
  344. package/src/stream.ts +450 -39
  345. package/src/summarization/__tests__/aggregator.test.ts +2 -2
  346. package/src/summarization/__tests__/node.test.ts +2 -2
  347. package/src/summarization/node.ts +1 -1
  348. package/src/tools/BashProgrammaticToolCalling.ts +5 -5
  349. package/src/tools/Calculator.ts +1 -1
  350. package/src/tools/CodeExecutor.ts +2 -4
  351. package/src/tools/SubagentTool.ts +2 -2
  352. package/src/tools/ToolNode.ts +37 -16
  353. package/src/tools/ToolSearch.ts +1 -1
  354. package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +4 -4
  355. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +12 -12
  356. package/src/tools/__tests__/LocalExecutionTools.test.ts +125 -93
  357. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +29 -5
  358. package/src/tools/__tests__/ReadFile.test.ts +1 -1
  359. package/src/tools/__tests__/SkillTool.test.ts +4 -4
  360. package/src/tools/__tests__/SubagentExecutor.test.ts +17 -13
  361. package/src/tools/__tests__/SubagentTool.test.ts +2 -2
  362. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +1 -1
  363. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +2 -5
  364. package/src/tools/__tests__/ToolNode.session.test.ts +1 -1
  365. package/src/tools/__tests__/ToolSearch.test.ts +1 -1
  366. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +1 -1
  367. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +35 -32
  368. package/src/tools/__tests__/directToolHooks.test.ts +41 -0
  369. package/src/tools/__tests__/handlers.test.ts +2 -2
  370. package/src/tools/__tests__/hitl.test.ts +11 -11
  371. package/src/tools/__tests__/localToolNames.test.ts +8 -6
  372. package/src/tools/__tests__/skillCatalog.test.ts +1 -1
  373. package/src/tools/__tests__/subagentHooks.test.ts +20 -10
  374. package/src/tools/__tests__/workspaceSeam.test.ts +20 -7
  375. package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +9 -6
  376. package/src/tools/cloudflare/CloudflareSandboxTools.ts +19 -19
  377. package/src/tools/handlers.ts +5 -5
  378. package/src/tools/local/CompileCheckTool.ts +4 -7
  379. package/src/tools/local/FileCheckpointer.ts +6 -5
  380. package/src/tools/local/LocalCodingTools.ts +100 -45
  381. package/src/tools/local/LocalExecutionEngine.ts +5 -5
  382. package/src/tools/local/LocalExecutionTools.ts +9 -9
  383. package/src/tools/local/LocalProgrammaticToolCalling.ts +5 -4
  384. package/src/tools/local/attachments.ts +0 -6
  385. package/src/tools/local/resolveLocalExecutionTools.ts +15 -15
  386. package/src/tools/search/firecrawl.ts +1 -1
  387. package/src/tools/search/jina-reranker.test.ts +148 -37
  388. package/src/tools/search/rerankers.ts +14 -4
  389. package/src/tools/search/tavily-search.ts +2 -2
  390. package/src/tools/search/types.ts +1 -1
  391. package/src/tools/search/utils.ts +99 -9
  392. package/src/tools/subagent/SubagentExecutor.ts +12 -6
  393. package/src/types/graph.ts +12 -12
  394. package/src/types/llm.ts +7 -6
  395. package/src/types/messages.ts +1 -1
  396. package/src/types/run.ts +7 -7
  397. package/src/types/stream.ts +2 -2
  398. package/src/types/tools.ts +5 -1
  399. package/src/utils/handlers.ts +2 -2
  400. package/src/utils/llmConfig.ts +1 -1
  401. package/src/utils/logging.ts +20 -10
  402. package/src/utils/run.ts +2 -2
package/src/llm/invoke.ts CHANGED
@@ -5,8 +5,8 @@ import type { ToolCall } from '@langchain/core/messages/tool';
5
5
  import type { BaseMessage } from '@langchain/core/messages';
6
6
  import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
7
7
  import type * as t from '@/types';
8
- import { manualToolStreamProviders } from '@/llm/providers';
9
8
  import { annotateMessagesForLLM } from '@/tools/toolOutputReferences';
9
+ import { manualToolStreamProviders } from '@/llm/providers';
10
10
  import { modifyDeltaProperties } from '@/messages';
11
11
  import { ChatModelStreamHandler } from '@/stream';
12
12
  import { GraphEvents, Providers } from '@/common';
@@ -51,6 +51,127 @@ export type InvokeContext = NonNullable<
51
51
  */
52
52
  export type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;
53
53
 
54
+ function getRegisteredDefaultChatStreamHandler(
55
+ context?: InvokeContext
56
+ ): ChatModelStreamHandler | undefined {
57
+ const handler = context?.handlerRegistry?.getHandler(
58
+ GraphEvents.CHAT_MODEL_STREAM
59
+ );
60
+ return handler instanceof ChatModelStreamHandler ? handler : undefined;
61
+ }
62
+
63
+ function hasReasoningDetails(chunk: AIMessageChunk): boolean {
64
+ const reasoningDetails = chunk.additional_kwargs.reasoning_details;
65
+ return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;
66
+ }
67
+
68
+ function removeOpenRouterFinalReasoningReplayContent({
69
+ current,
70
+ next,
71
+ provider,
72
+ }: {
73
+ current?: AIMessageChunk;
74
+ next: AIMessageChunk;
75
+ provider: Providers;
76
+ }): AIMessageChunk {
77
+ const content = getOpenRouterFinalReasoningContent({
78
+ current,
79
+ next,
80
+ provider,
81
+ });
82
+ if (content == null || content === next.content) {
83
+ return next;
84
+ }
85
+
86
+ return new AIMessageChunk(
87
+ Object.assign({}, next, {
88
+ content,
89
+ })
90
+ );
91
+ }
92
+
93
+ function getOpenRouterFinalReasoningContent({
94
+ current,
95
+ next,
96
+ provider,
97
+ }: {
98
+ current?: AIMessageChunk;
99
+ next: AIMessageChunk;
100
+ provider: Providers;
101
+ }): string | undefined {
102
+ if (
103
+ provider !== Providers.OPENROUTER ||
104
+ current == null ||
105
+ !hasReasoningDetails(next) ||
106
+ typeof current.content !== 'string' ||
107
+ current.content === '' ||
108
+ typeof next.content !== 'string' ||
109
+ next.content === ''
110
+ ) {
111
+ return undefined;
112
+ }
113
+ if (!next.content.startsWith(current.content)) {
114
+ return next.content;
115
+ }
116
+ return next.content.slice(current.content.length);
117
+ }
118
+
119
+ function removeReasoningDetails(
120
+ additionalKwargs: AIMessageChunk['additional_kwargs']
121
+ ): AIMessageChunk['additional_kwargs'] {
122
+ return Object.fromEntries(
123
+ Object.entries(additionalKwargs).filter(
124
+ ([key]) => key !== 'reasoning_details'
125
+ )
126
+ );
127
+ }
128
+
129
+ function getStreamHandlingChunk({
130
+ current,
131
+ next,
132
+ provider,
133
+ }: {
134
+ current?: AIMessageChunk;
135
+ next: AIMessageChunk;
136
+ provider: Providers;
137
+ }): AIMessageChunk | undefined {
138
+ const content = getOpenRouterFinalReasoningContent({
139
+ current,
140
+ next,
141
+ provider,
142
+ });
143
+ if (content == null) {
144
+ return next;
145
+ }
146
+ if (content === '') {
147
+ return undefined;
148
+ }
149
+ return new AIMessageChunk(
150
+ Object.assign({}, next, {
151
+ content,
152
+ additional_kwargs: removeReasoningDetails(next.additional_kwargs),
153
+ })
154
+ );
155
+ }
156
+
157
+ function appendStreamChunk({
158
+ current,
159
+ next,
160
+ provider,
161
+ }: {
162
+ current?: AIMessageChunk;
163
+ next: AIMessageChunk;
164
+ provider: Providers;
165
+ }): AIMessageChunk {
166
+ if (current == null) {
167
+ return next;
168
+ }
169
+ return concat(
170
+ current,
171
+ removeOpenRouterFinalReasoningReplayContent({ current, next, provider })
172
+ );
173
+ }
174
+
54
175
  /**
55
176
  * Invokes a chat model with the given messages, handling both streaming and
56
177
  * non-streaming paths.
@@ -90,23 +211,62 @@ export async function attemptInvoke(
90
211
  if (model.stream) {
91
212
  const stream = await model.stream(messagesForProvider, config);
92
213
  let finalChunk: AIMessageChunk | undefined;
214
+ const registeredStreamHandler =
215
+ getRegisteredDefaultChatStreamHandler(context);
93
216
 
94
217
  if (onChunk) {
95
218
  for await (const chunk of stream) {
96
219
  await onChunk(chunk);
97
- finalChunk = finalChunk ? concat(finalChunk, chunk) : chunk;
220
+ finalChunk = appendStreamChunk({
221
+ current: finalChunk,
222
+ next: chunk,
223
+ provider,
224
+ });
98
225
  }
99
- } else {
226
+ } else if (registeredStreamHandler == null) {
100
227
  const metadata = config?.metadata as Record<string, unknown> | undefined;
101
228
  const streamHandler = new ChatModelStreamHandler();
102
229
  for await (const chunk of stream) {
103
- await streamHandler.handle(
104
- GraphEvents.CHAT_MODEL_STREAM,
105
- { chunk },
106
- metadata,
107
- context
108
- );
109
- finalChunk = finalChunk ? concat(finalChunk, chunk) : chunk;
230
+ const handlingChunk = getStreamHandlingChunk({
231
+ current: finalChunk,
232
+ next: chunk,
233
+ provider,
234
+ });
235
+ if (handlingChunk != null) {
236
+ await streamHandler.handle(
237
+ GraphEvents.CHAT_MODEL_STREAM,
238
+ { chunk: handlingChunk },
239
+ metadata,
240
+ context
241
+ );
242
+ }
243
+ finalChunk = appendStreamChunk({
244
+ current: finalChunk,
245
+ next: chunk,
246
+ provider,
247
+ });
248
+ }
249
+ } else {
250
+ const metadata = config?.metadata as Record<string, unknown> | undefined;
251
+ for await (const chunk of stream) {
252
+ const handlingChunk = getStreamHandlingChunk({
253
+ current: finalChunk,
254
+ next: chunk,
255
+ provider,
256
+ });
257
+ if (handlingChunk != null && handlingChunk !== chunk) {
258
+ await registeredStreamHandler.handle(
259
+ GraphEvents.CHAT_MODEL_STREAM,
260
+ { chunk: handlingChunk },
261
+ metadata,
262
+ context
263
+ );
264
+ }
265
+ finalChunk = appendStreamChunk({
266
+ current: finalChunk,
267
+ next: chunk,
268
+ provider,
269
+ });
110
270
  }
111
271
  }
112
272
 
@@ -1,18 +1,18 @@
1
1
  import { AzureOpenAI as AzureOpenAIClient } from 'openai';
2
2
  import { ChatXAI as OriginalChatXAI } from '@langchain/xai';
3
3
  import { ChatGenerationChunk } from '@langchain/core/outputs';
4
+ import { ToolDefinition } from '@langchain/core/language_models/base';
5
+ import { ChatDeepSeek as OriginalChatDeepSeek } from '@langchain/deepseek';
6
+ import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
4
7
  import {
5
8
  AIMessage,
6
9
  AIMessageChunk,
7
10
  isAIMessage,
8
11
  } from '@langchain/core/messages';
9
- import { ToolDefinition } from '@langchain/core/language_models/base';
10
12
  import {
11
13
  convertToOpenAITool,
12
14
  isLangChainTool,
13
15
  } from '@langchain/core/utils/function_calling';
14
- import { ChatDeepSeek as OriginalChatDeepSeek } from '@langchain/deepseek';
15
- import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
16
16
  import {
17
17
  getEndpoint,
18
18
  OpenAIClient,
@@ -24,7 +24,6 @@ import {
24
24
  AzureChatOpenAIResponses as OriginalAzureChatOpenAIResponses,
25
25
  AzureChatOpenAICompletions as OriginalAzureChatOpenAICompletions,
26
26
  } from '@langchain/openai';
27
- import type { HeaderValue, HeadersLike } from './types';
28
27
  import type {
29
28
  BaseMessage,
30
29
  BaseMessageChunk,
@@ -34,6 +33,7 @@ import type { BindToolsInput } from '@langchain/core/language_models/chat_models
34
33
  import type { ChatGeneration, ChatResult } from '@langchain/core/outputs';
35
34
  import type { ChatXAIInput } from '@langchain/xai';
36
35
  import type * as t from '@langchain/openai';
36
+ import type { HeaderValue, HeadersLike } from './types';
37
37
  import { isReasoningModel, _convertMessagesToOpenAIParams } from './utils';
38
38
 
39
39
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@@ -1,12 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/ban-ts-comment */
2
2
  /* eslint-disable @typescript-eslint/explicit-function-return-type */
3
3
  import { type OpenAI as OpenAIClient } from 'openai';
4
- import type {
5
- ChatCompletionContentPartText,
6
- ChatCompletionContentPartImage,
7
- ChatCompletionContentPartInputAudio,
8
- ChatCompletionContentPart,
9
- } from 'openai/resources/chat/completions';
4
+ import { ChatGenerationChunk } from '@langchain/core/outputs';
5
+ import {
6
+ convertLangChainToolCallToOpenAI,
7
+ makeInvalidToolCall,
8
+ parseToolCall,
9
+ } from '@langchain/core/output_parsers/openai_tools';
10
10
  import {
11
11
  AIMessage,
12
12
  AIMessageChunk,
@@ -25,24 +25,24 @@ import {
25
25
  convertToProviderContentBlock,
26
26
  isDataContentBlock,
27
27
  } from '@langchain/core/messages';
28
- import { ChatGenerationChunk } from '@langchain/core/outputs';
29
- import {
30
- convertLangChainToolCallToOpenAI,
31
- makeInvalidToolCall,
32
- parseToolCall,
33
- } from '@langchain/core/output_parsers/openai_tools';
34
- import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
28
+ import type {
29
+ ChatCompletionContentPartText,
30
+ ChatCompletionContentPartImage,
31
+ ChatCompletionContentPartInputAudio,
32
+ ChatCompletionContentPart,
33
+ } from 'openai/resources/chat/completions';
35
34
  import type {
36
35
  OpenAICallOptions,
37
36
  OpenAIChatInput,
38
37
  ChatOpenAIReasoningSummary,
39
38
  } from '@langchain/openai';
40
- import { toLangChainContent } from '@/messages/langchain';
39
+ import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
41
40
  import {
42
41
  STREAMED_TOOL_CALL_SEAL_METADATA_KEY,
43
42
  STREAMED_TOOL_CALL_ADAPTER_METADATA_KEY,
44
43
  OPENAI_RESPONSES_STREAMED_TOOL_CALL_ADAPTER,
45
44
  } from '@/tools/streamedToolCallSeals';
45
+ import { toLangChainContent } from '@/messages/langchain';
46
46
 
47
47
  export type { OpenAICallOptions, OpenAIChatInput };
48
48
 
@@ -1,12 +1,12 @@
1
- import { ChatOpenAI, emitStreamChunkCallback } from '@/llm/openai';
2
- import type { BaseMessage } from '@langchain/core/messages';
3
- import type { ChatGenerationChunk } from '@langchain/core/outputs';
4
- import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
5
1
  import type {
6
2
  ChatOpenAICallOptions,
7
3
  OpenAIChatInput,
8
4
  OpenAIClient,
9
5
  } from '@langchain/openai';
6
+ import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
7
+ import type { ChatGenerationChunk } from '@langchain/core/outputs';
8
+ import type { BaseMessage } from '@langchain/core/messages';
9
+ import { ChatOpenAI, emitStreamChunkCallback } from '@/llm/openai';
10
10
 
11
11
  export type OpenRouterReasoningEffort =
12
12
  | 'xhigh'
@@ -1,6 +1,6 @@
1
- import { ChatOpenRouter } from './index';
2
- import type { OpenRouterReasoning, ChatOpenRouterCallOptions } from './index';
3
1
  import type { OpenAIChatInput } from '@langchain/openai';
2
+ import type { OpenRouterReasoning, ChatOpenRouterCallOptions } from './index';
3
+ import { ChatOpenRouter } from './index';
4
4
 
5
5
  type CreateRouterOptions = Partial<
6
6
  ChatOpenRouterCallOptions &
@@ -1,6 +1,6 @@
1
1
  import { expect, test, describe } from '@jest/globals';
2
- import type { GeminiContent } from '@langchain/google-common';
3
2
  import { AIMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
3
+ import type { GeminiContent } from '@langchain/google-common';
4
4
  import { fixThoughtSignatures } from './index';
5
5
 
6
6
  const SIG_A = 'AY89a1/sigA==';
@@ -1,5 +1,6 @@
1
1
  import { ChatGoogle } from '@langchain/google-gauth';
2
2
  import { ChatConnection } from '@langchain/google-common';
3
+ import { AIMessageChunk, isAIMessage } from '@langchain/core/messages';
3
4
  import type {
4
5
  GeminiContent,
5
6
  GeminiRequest,
@@ -8,7 +9,6 @@ import type {
8
9
  } from '@langchain/google-common';
9
10
  import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
10
11
  import type { BaseMessage, UsageMetadata } from '@langchain/core/messages';
11
- import { AIMessageChunk, isAIMessage } from '@langchain/core/messages';
12
12
  import type { ChatGenerationChunk } from '@langchain/core/outputs';
13
13
  import type { GoogleThinkingConfig, VertexAIClientOptions } from '@/types';
14
14
 
@@ -13,9 +13,11 @@ import {
13
13
  stripBedrockCacheControl,
14
14
  addBedrockCacheControl,
15
15
  addCacheControl,
16
+ addCacheControlToStablePrefixMessages,
16
17
  } from './cache';
17
18
  import { _convertMessagesToOpenAIParams } from '@/llm/openai/utils';
18
19
  import { toLangChainContent } from './langchain';
20
+ import { formatAgentMessages } from './format';
19
21
  import { ContentTypes } from '@/common/enum';
20
22
 
21
23
  type CacheControlBlock = MessageContentComplex & {
@@ -766,6 +768,127 @@ describe('addBedrockCacheControl (Bedrock cache checkpoints)', () => {
766
768
  });
767
769
  });
768
770
 
771
+ describe('synthetic skill/meta messages are not cache-anchored', () => {
772
+ const hasAnthropicMarker = (m: BaseMessage): boolean =>
773
+ Array.isArray(m.content) &&
774
+ m.content.some((block) => 'cache_control' in block);
775
+
776
+ const hasBedrockCachePoint = (m: BaseMessage): boolean =>
777
+ Array.isArray(m.content) &&
778
+ m.content.some((block) => 'cachePoint' in block);
779
+
780
+ const skillBody = (skillName: string, content = 'SKILL BODY'): HumanMessage =>
781
+ new HumanMessage({
782
+ content,
783
+ additional_kwargs: { isMeta: true, source: 'skill', skillName },
784
+ });
785
+
786
+ it('Anthropic: skips a trailing synthetic skill message; markers land on the real user messages', () => {
787
+ const messages: BaseMessage[] = [
788
+ new HumanMessage('First real question'),
789
+ new AIMessage('Answer'),
790
+ new HumanMessage('Second real question'),
791
+ skillBody('pdf-analyzer'),
792
+ ];
793
+
794
+ const result = addCacheControl<BaseMessage>(messages);
795
+
796
+ expect(hasAnthropicMarker(result[3])).toBe(false);
797
+ expect(hasAnthropicMarker(result[2])).toBe(true);
798
+ expect(hasAnthropicMarker(result[0])).toBe(true);
799
+ });
800
+
801
+ it('Anthropic: strips a stale marker from a synthetic skill message without re-adding one', () => {
802
+ const stale = new HumanMessage({
803
+ content: toLangChainContent([
804
+ {
805
+ type: 'text',
806
+ text: 'SKILL BODY',
807
+ cache_control: { type: 'ephemeral' },
808
+ } as MessageContentComplex,
809
+ ]),
810
+ additional_kwargs: {
811
+ isMeta: true,
812
+ source: 'skill',
813
+ skillName: 'pdf-analyzer',
814
+ },
815
+ });
816
+ const messages: BaseMessage[] = [
817
+ new HumanMessage('Real question'),
818
+ new AIMessage('Answer'),
819
+ stale,
820
+ ];
821
+
822
+ const result = addCacheControl<BaseMessage>(messages);
823
+
824
+ expect(hasAnthropicMarker(result[2])).toBe(false);
825
+ expect(hasAnthropicMarker(result[0])).toBe(true);
826
+ });
827
+
828
+ it('Anthropic: detects skill messages by additional_kwargs.source even without isMeta', () => {
829
+ const messages: BaseMessage[] = [
830
+ new HumanMessage('Real question'),
831
+ new AIMessage('Answer'),
832
+ new HumanMessage({
833
+ content: 'SKILL BODY',
834
+ additional_kwargs: { source: 'skill', skillName: 'pdf-analyzer' },
835
+ }),
836
+ ];
837
+
838
+ const result = addCacheControl<BaseMessage>(messages);
839
+
840
+ expect(hasAnthropicMarker(result[2])).toBe(false);
841
+ expect(hasAnthropicMarker(result[0])).toBe(true);
842
+ });
843
+
844
+ it('Bedrock: skips a trailing synthetic skill message; cachePoints land on the real user messages', () => {
845
+ const messages: BaseMessage[] = [
846
+ new HumanMessage('First real question'),
847
+ new AIMessage('Answer'),
848
+ new HumanMessage('Second real question'),
849
+ skillBody('pdf-analyzer'),
850
+ ];
851
+
852
+ const result = addBedrockCacheControl<BaseMessage>(messages);
853
+
854
+ expect(hasBedrockCachePoint(result[3])).toBe(false);
855
+ expect(hasBedrockCachePoint(result[2])).toBe(true);
856
+ expect(hasBedrockCachePoint(result[0])).toBe(true);
857
+ });
858
+
859
+ it('stable-prefix fallback: anchors the real user message, not a synthetic skill message', () => {
860
+ // Mirrors AgentContext's dynamic-tail path: the only assistant message is a
861
+ // skill-only tool call (no text), so the assistant-only pass adds no marker
862
+ // and the cacheable fallback runs. It must skip the reconstructed skill
863
+ // HumanMessage and anchor the real user message instead.
864
+ const messages: BaseMessage[] = [
865
+ new HumanMessage('Real stable question'),
866
+ new AIMessage({
867
+ content: toLangChainContent([
868
+ {
869
+ type: 'tool_use',
870
+ id: 'call_1',
871
+ name: 'skill',
872
+ input: { skillName: 'pdf-analyzer' },
873
+ } as MessageContentComplex,
874
+ ]),
875
+ tool_calls: [
876
+ { id: 'call_1', name: 'skill', args: { skillName: 'pdf-analyzer' } },
877
+ ],
878
+ }),
879
+ skillBody('pdf-analyzer'),
880
+ ];
881
+
882
+ const result = addCacheControlToStablePrefixMessages<BaseMessage>(
883
+ messages,
884
+ 2
885
+ );
886
+
887
+ expect(hasAnthropicMarker(result[2])).toBe(false);
888
+ expect(hasAnthropicMarker(result[0])).toBe(true);
889
+ });
890
+ });
891
+
769
892
  describe('stripAnthropicCacheControl', () => {
770
893
  it('removes cache_control fields from content blocks', () => {
771
894
  const messages: TestMsg[] = [
@@ -1474,6 +1597,27 @@ describe('Multi-turn cache cleanup', () => {
1474
1597
  });
1475
1598
 
1476
1599
  describe('LangChain message type preservation', () => {
1600
+ it('preserves direct roles for formatted LangChain messages after addCacheControl', () => {
1601
+ const { messages } = formatAgentMessages([
1602
+ { role: 'user', content: 'Hello' },
1603
+ { role: 'assistant', content: 'Hi there' },
1604
+ { role: 'user', content: 'Thanks' },
1605
+ ]);
1606
+
1607
+ const result = addCacheControl(messages);
1608
+
1609
+ expect(result.map((message) => message.role)).toEqual([
1610
+ 'user',
1611
+ 'assistant',
1612
+ 'user',
1613
+ ]);
1614
+ expect(result[0]).toBeInstanceOf(HumanMessage);
1615
+ expect(result[0]).not.toBe(messages[0]);
1616
+ expect(Object.keys(result[0])).not.toContain('role');
1617
+ expect(result[1]).toBe(messages[1]);
1618
+ expect(result[2]).not.toBe(messages[2]);
1619
+ });
1620
+
1477
1621
  it('should preserve instanceof for LangChain messages after addCacheControl', () => {
1478
1622
  const messages: BaseMessage[] = [
1479
1623
  new HumanMessage({ content: [{ type: 'text', text: 'Hello' }] }),
@@ -6,10 +6,11 @@ import {
6
6
  SystemMessage,
7
7
  MessageContentComplex,
8
8
  } from '@langchain/core/messages';
9
- import type { AnthropicMessage } from '@/types/messages';
10
9
  import type Anthropic from '@anthropic-ai/sdk';
11
- import { ContentTypes } from '@/common/enum';
10
+ import type { AnthropicMessage } from '@/types/messages';
12
11
  import { toLangChainContent } from './langchain';
12
+ import { ContentTypes } from '@/common/enum';
13
+ import { withMessageRole } from './format';
13
14
 
14
15
  type MessageWithContent = {
15
16
  content?: string | MessageContentComplex[];
@@ -56,19 +57,31 @@ function cloneMessage<T extends MessageWithContent>(
56
57
  const msgType = message.getType();
57
58
  switch (msgType) {
58
59
  case 'ai':
59
- return new AIMessage({
60
- ...baseParams,
61
- tool_calls: (message as unknown as AIMessage).tool_calls,
62
- }) as unknown as T;
60
+ return withMessageRole(
61
+ new AIMessage({
62
+ ...baseParams,
63
+ tool_calls: (message as unknown as AIMessage).tool_calls,
64
+ }),
65
+ 'assistant'
66
+ ) as unknown as T;
63
67
  case 'human':
64
- return new HumanMessage(baseParams) as unknown as T;
68
+ return withMessageRole(
69
+ new HumanMessage(baseParams),
70
+ 'user'
71
+ ) as unknown as T;
65
72
  case 'system':
66
- return new SystemMessage(baseParams) as unknown as T;
73
+ return withMessageRole(
74
+ new SystemMessage(baseParams),
75
+ 'system'
76
+ ) as unknown as T;
67
77
  case 'tool':
68
- return new ToolMessage({
69
- ...baseParams,
70
- tool_call_id: (message as unknown as ToolMessage).tool_call_id,
71
- }) as unknown as T;
78
+ return withMessageRole(
79
+ new ToolMessage({
80
+ ...baseParams,
81
+ tool_call_id: (message as unknown as ToolMessage).tool_call_id,
82
+ }),
83
+ 'tool'
84
+ ) as unknown as T;
72
85
  default:
73
86
  break;
74
87
  }
@@ -169,6 +182,7 @@ export function addCacheControl<T extends AnthropicMessage | BaseMessage>(
169
182
  const needsCacheAdd =
170
183
  userMessagesModified < 2 &&
171
184
  isUserMessage &&
185
+ !isSyntheticMetaMessage(originalMessage) &&
172
186
  (typeof content === 'string' || hasArrayContent);
173
187
 
174
188
  // Skip messages that don't need any work
@@ -250,6 +264,26 @@ function getMessageRole(message: MessageWithContent): string | undefined {
250
264
  return undefined;
251
265
  }
252
266
 
267
+ const SKILL_MESSAGE_SOURCE = 'skill';
268
+
269
+ /**
270
+ * Synthetic skill/meta messages (reconstructed skill bodies, primed SKILL.md
271
+ * instructions) are re-injected every turn and are not stable conversation
272
+ * turns. They must not anchor a fresh prompt-cache marker — doing so pins the
273
+ * cache to a volatile/duplicated prefix. Stale markers are still stripped from
274
+ * them; only the *adding* of new markers is suppressed. Detected via
275
+ * `additional_kwargs.isMeta === true` or `additional_kwargs.source === 'skill'`.
276
+ */
277
+ function isSyntheticMetaMessage(message: MessageWithContent): boolean {
278
+ const { additional_kwargs: kwargs } = message as {
279
+ additional_kwargs?: { isMeta?: unknown; source?: unknown };
280
+ };
281
+ if (kwargs == null) {
282
+ return false;
283
+ }
284
+ return kwargs.isMeta === true || kwargs.source === SKILL_MESSAGE_SOURCE;
285
+ }
286
+
253
287
  function isCacheableConversationMessage(message: MessageWithContent): boolean {
254
288
  const role = getMessageRole(message);
255
289
  return (
@@ -292,7 +326,9 @@ function addCacheControlToRecentMessages<
292
326
  const content = originalMessage.content;
293
327
  const hasArrayContent = Array.isArray(content);
294
328
  const canAddCache =
295
- cachePointsAdded < maxCachePoints && canUseMessage(originalMessage);
329
+ cachePointsAdded < maxCachePoints &&
330
+ canUseMessage(originalMessage) &&
331
+ !isSyntheticMetaMessage(originalMessage);
296
332
 
297
333
  if (!canAddCache && !hasArrayContent) {
298
334
  continue;
@@ -523,6 +559,7 @@ export function addBedrockCacheControl<
523
559
  isUserMessage &&
524
560
  !isToolMessage &&
525
561
  !isEmptyString &&
562
+ !isSyntheticMetaMessage(originalMessage) &&
526
563
  (typeof content === 'string' || hasArrayContent);
527
564
 
528
565
  if (!needsCacheAdd && !hasArrayContent && !hasSerializationProps) {
@@ -1,5 +1,5 @@
1
- import { ContentTypes } from '@/common';
2
1
  import type { BaseMessage } from '@langchain/core/messages';
2
+ import { ContentTypes } from '@/common';
3
3
 
4
4
  /**
5
5
  * Formats an array of messages for LangChain, making sure all content fields are strings
@@ -13,9 +13,9 @@
13
13
  */
14
14
 
15
15
  import { ToolMessage, type BaseMessage } from '@langchain/core/messages';
16
+ import type { ContextPruningSettings } from './contextPruningSettings';
16
17
  import type { ContextPruningConfig } from '@/types/graph';
17
18
  import type { TokenCounter } from '@/types/run';
18
- import type { ContextPruningSettings } from './contextPruningSettings';
19
19
  import { resolveContextPruningSettings } from './contextPruningSettings';
20
20
 
21
21
  /**