@illuma-ai/agents 1.0.81

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 (558) hide show
  1. package/README.md +485 -0
  2. package/dist/cjs/agents/AgentContext.cjs +734 -0
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -0
  4. package/dist/cjs/common/enum.cjs +190 -0
  5. package/dist/cjs/common/enum.cjs.map +1 -0
  6. package/dist/cjs/events.cjs +172 -0
  7. package/dist/cjs/events.cjs.map +1 -0
  8. package/dist/cjs/graphs/Graph.cjs +1615 -0
  9. package/dist/cjs/graphs/Graph.cjs.map +1 -0
  10. package/dist/cjs/graphs/MultiAgentGraph.cjs +890 -0
  11. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -0
  12. package/dist/cjs/instrumentation.cjs +21 -0
  13. package/dist/cjs/instrumentation.cjs.map +1 -0
  14. package/dist/cjs/llm/anthropic/index.cjs +292 -0
  15. package/dist/cjs/llm/anthropic/index.cjs.map +1 -0
  16. package/dist/cjs/llm/anthropic/types.cjs +50 -0
  17. package/dist/cjs/llm/anthropic/types.cjs.map +1 -0
  18. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +630 -0
  19. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -0
  20. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +218 -0
  21. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -0
  22. package/dist/cjs/llm/anthropic/utils/tools.cjs +29 -0
  23. package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -0
  24. package/dist/cjs/llm/bedrock/index.cjs +282 -0
  25. package/dist/cjs/llm/bedrock/index.cjs.map +1 -0
  26. package/dist/cjs/llm/fake.cjs +97 -0
  27. package/dist/cjs/llm/fake.cjs.map +1 -0
  28. package/dist/cjs/llm/google/index.cjs +216 -0
  29. package/dist/cjs/llm/google/index.cjs.map +1 -0
  30. package/dist/cjs/llm/google/utils/common.cjs +647 -0
  31. package/dist/cjs/llm/google/utils/common.cjs.map +1 -0
  32. package/dist/cjs/llm/openai/index.cjs +1028 -0
  33. package/dist/cjs/llm/openai/index.cjs.map +1 -0
  34. package/dist/cjs/llm/openai/utils/index.cjs +765 -0
  35. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -0
  36. package/dist/cjs/llm/openrouter/index.cjs +212 -0
  37. package/dist/cjs/llm/openrouter/index.cjs.map +1 -0
  38. package/dist/cjs/llm/providers.cjs +43 -0
  39. package/dist/cjs/llm/providers.cjs.map +1 -0
  40. package/dist/cjs/llm/text.cjs +69 -0
  41. package/dist/cjs/llm/text.cjs.map +1 -0
  42. package/dist/cjs/llm/vertexai/index.cjs +329 -0
  43. package/dist/cjs/llm/vertexai/index.cjs.map +1 -0
  44. package/dist/cjs/main.cjs +240 -0
  45. package/dist/cjs/main.cjs.map +1 -0
  46. package/dist/cjs/messages/cache.cjs +387 -0
  47. package/dist/cjs/messages/cache.cjs.map +1 -0
  48. package/dist/cjs/messages/content.cjs +53 -0
  49. package/dist/cjs/messages/content.cjs.map +1 -0
  50. package/dist/cjs/messages/core.cjs +367 -0
  51. package/dist/cjs/messages/core.cjs.map +1 -0
  52. package/dist/cjs/messages/format.cjs +761 -0
  53. package/dist/cjs/messages/format.cjs.map +1 -0
  54. package/dist/cjs/messages/ids.cjs +23 -0
  55. package/dist/cjs/messages/ids.cjs.map +1 -0
  56. package/dist/cjs/messages/prune.cjs +398 -0
  57. package/dist/cjs/messages/prune.cjs.map +1 -0
  58. package/dist/cjs/messages/tools.cjs +96 -0
  59. package/dist/cjs/messages/tools.cjs.map +1 -0
  60. package/dist/cjs/run.cjs +328 -0
  61. package/dist/cjs/run.cjs.map +1 -0
  62. package/dist/cjs/schemas/validate.cjs +324 -0
  63. package/dist/cjs/schemas/validate.cjs.map +1 -0
  64. package/dist/cjs/splitStream.cjs +210 -0
  65. package/dist/cjs/splitStream.cjs.map +1 -0
  66. package/dist/cjs/stream.cjs +620 -0
  67. package/dist/cjs/stream.cjs.map +1 -0
  68. package/dist/cjs/tools/BrowserTools.cjs +248 -0
  69. package/dist/cjs/tools/BrowserTools.cjs.map +1 -0
  70. package/dist/cjs/tools/Calculator.cjs +66 -0
  71. package/dist/cjs/tools/Calculator.cjs.map +1 -0
  72. package/dist/cjs/tools/CodeExecutor.cjs +234 -0
  73. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -0
  74. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +636 -0
  75. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -0
  76. package/dist/cjs/tools/ToolNode.cjs +548 -0
  77. package/dist/cjs/tools/ToolNode.cjs.map +1 -0
  78. package/dist/cjs/tools/ToolSearch.cjs +909 -0
  79. package/dist/cjs/tools/ToolSearch.cjs.map +1 -0
  80. package/dist/cjs/tools/handlers.cjs +255 -0
  81. package/dist/cjs/tools/handlers.cjs.map +1 -0
  82. package/dist/cjs/tools/schema.cjs +31 -0
  83. package/dist/cjs/tools/schema.cjs.map +1 -0
  84. package/dist/cjs/tools/search/anthropic.cjs +40 -0
  85. package/dist/cjs/tools/search/anthropic.cjs.map +1 -0
  86. package/dist/cjs/tools/search/content.cjs +140 -0
  87. package/dist/cjs/tools/search/content.cjs.map +1 -0
  88. package/dist/cjs/tools/search/firecrawl.cjs +179 -0
  89. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -0
  90. package/dist/cjs/tools/search/format.cjs +203 -0
  91. package/dist/cjs/tools/search/format.cjs.map +1 -0
  92. package/dist/cjs/tools/search/highlights.cjs +245 -0
  93. package/dist/cjs/tools/search/highlights.cjs.map +1 -0
  94. package/dist/cjs/tools/search/rerankers.cjs +174 -0
  95. package/dist/cjs/tools/search/rerankers.cjs.map +1 -0
  96. package/dist/cjs/tools/search/schema.cjs +117 -0
  97. package/dist/cjs/tools/search/schema.cjs.map +1 -0
  98. package/dist/cjs/tools/search/search.cjs +566 -0
  99. package/dist/cjs/tools/search/search.cjs.map +1 -0
  100. package/dist/cjs/tools/search/serper-scraper.cjs +132 -0
  101. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -0
  102. package/dist/cjs/tools/search/tool.cjs +456 -0
  103. package/dist/cjs/tools/search/tool.cjs.map +1 -0
  104. package/dist/cjs/tools/search/utils.cjs +66 -0
  105. package/dist/cjs/tools/search/utils.cjs.map +1 -0
  106. package/dist/cjs/types/graph.cjs +29 -0
  107. package/dist/cjs/types/graph.cjs.map +1 -0
  108. package/dist/cjs/utils/contextAnalytics.cjs +66 -0
  109. package/dist/cjs/utils/contextAnalytics.cjs.map +1 -0
  110. package/dist/cjs/utils/events.cjs +31 -0
  111. package/dist/cjs/utils/events.cjs.map +1 -0
  112. package/dist/cjs/utils/graph.cjs +16 -0
  113. package/dist/cjs/utils/graph.cjs.map +1 -0
  114. package/dist/cjs/utils/handlers.cjs +70 -0
  115. package/dist/cjs/utils/handlers.cjs.map +1 -0
  116. package/dist/cjs/utils/llm.cjs +27 -0
  117. package/dist/cjs/utils/llm.cjs.map +1 -0
  118. package/dist/cjs/utils/misc.cjs +56 -0
  119. package/dist/cjs/utils/misc.cjs.map +1 -0
  120. package/dist/cjs/utils/run.cjs +73 -0
  121. package/dist/cjs/utils/run.cjs.map +1 -0
  122. package/dist/cjs/utils/schema.cjs +27 -0
  123. package/dist/cjs/utils/schema.cjs.map +1 -0
  124. package/dist/cjs/utils/title.cjs +125 -0
  125. package/dist/cjs/utils/title.cjs.map +1 -0
  126. package/dist/cjs/utils/tokens.cjs +125 -0
  127. package/dist/cjs/utils/tokens.cjs.map +1 -0
  128. package/dist/cjs/utils/toonFormat.cjs +388 -0
  129. package/dist/cjs/utils/toonFormat.cjs.map +1 -0
  130. package/dist/esm/agents/AgentContext.mjs +732 -0
  131. package/dist/esm/agents/AgentContext.mjs.map +1 -0
  132. package/dist/esm/common/enum.mjs +190 -0
  133. package/dist/esm/common/enum.mjs.map +1 -0
  134. package/dist/esm/events.mjs +164 -0
  135. package/dist/esm/events.mjs.map +1 -0
  136. package/dist/esm/graphs/Graph.mjs +1612 -0
  137. package/dist/esm/graphs/Graph.mjs.map +1 -0
  138. package/dist/esm/graphs/MultiAgentGraph.mjs +888 -0
  139. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -0
  140. package/dist/esm/instrumentation.mjs +19 -0
  141. package/dist/esm/instrumentation.mjs.map +1 -0
  142. package/dist/esm/llm/anthropic/index.mjs +290 -0
  143. package/dist/esm/llm/anthropic/index.mjs.map +1 -0
  144. package/dist/esm/llm/anthropic/types.mjs +48 -0
  145. package/dist/esm/llm/anthropic/types.mjs.map +1 -0
  146. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +627 -0
  147. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -0
  148. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +216 -0
  149. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -0
  150. package/dist/esm/llm/anthropic/utils/tools.mjs +27 -0
  151. package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -0
  152. package/dist/esm/llm/bedrock/index.mjs +280 -0
  153. package/dist/esm/llm/bedrock/index.mjs.map +1 -0
  154. package/dist/esm/llm/fake.mjs +94 -0
  155. package/dist/esm/llm/fake.mjs.map +1 -0
  156. package/dist/esm/llm/google/index.mjs +214 -0
  157. package/dist/esm/llm/google/index.mjs.map +1 -0
  158. package/dist/esm/llm/google/utils/common.mjs +638 -0
  159. package/dist/esm/llm/google/utils/common.mjs.map +1 -0
  160. package/dist/esm/llm/openai/index.mjs +1018 -0
  161. package/dist/esm/llm/openai/index.mjs.map +1 -0
  162. package/dist/esm/llm/openai/utils/index.mjs +759 -0
  163. package/dist/esm/llm/openai/utils/index.mjs.map +1 -0
  164. package/dist/esm/llm/openrouter/index.mjs +210 -0
  165. package/dist/esm/llm/openrouter/index.mjs.map +1 -0
  166. package/dist/esm/llm/providers.mjs +39 -0
  167. package/dist/esm/llm/providers.mjs.map +1 -0
  168. package/dist/esm/llm/text.mjs +67 -0
  169. package/dist/esm/llm/text.mjs.map +1 -0
  170. package/dist/esm/llm/vertexai/index.mjs +327 -0
  171. package/dist/esm/llm/vertexai/index.mjs.map +1 -0
  172. package/dist/esm/main.mjs +37 -0
  173. package/dist/esm/main.mjs.map +1 -0
  174. package/dist/esm/messages/cache.mjs +382 -0
  175. package/dist/esm/messages/cache.mjs.map +1 -0
  176. package/dist/esm/messages/content.mjs +51 -0
  177. package/dist/esm/messages/content.mjs.map +1 -0
  178. package/dist/esm/messages/core.mjs +359 -0
  179. package/dist/esm/messages/core.mjs.map +1 -0
  180. package/dist/esm/messages/format.mjs +752 -0
  181. package/dist/esm/messages/format.mjs.map +1 -0
  182. package/dist/esm/messages/ids.mjs +21 -0
  183. package/dist/esm/messages/ids.mjs.map +1 -0
  184. package/dist/esm/messages/prune.mjs +393 -0
  185. package/dist/esm/messages/prune.mjs.map +1 -0
  186. package/dist/esm/messages/tools.mjs +93 -0
  187. package/dist/esm/messages/tools.mjs.map +1 -0
  188. package/dist/esm/run.mjs +325 -0
  189. package/dist/esm/run.mjs.map +1 -0
  190. package/dist/esm/schemas/validate.mjs +317 -0
  191. package/dist/esm/schemas/validate.mjs.map +1 -0
  192. package/dist/esm/splitStream.mjs +207 -0
  193. package/dist/esm/splitStream.mjs.map +1 -0
  194. package/dist/esm/stream.mjs +616 -0
  195. package/dist/esm/stream.mjs.map +1 -0
  196. package/dist/esm/tools/BrowserTools.mjs +244 -0
  197. package/dist/esm/tools/BrowserTools.mjs.map +1 -0
  198. package/dist/esm/tools/Calculator.mjs +41 -0
  199. package/dist/esm/tools/Calculator.mjs.map +1 -0
  200. package/dist/esm/tools/CodeExecutor.mjs +226 -0
  201. package/dist/esm/tools/CodeExecutor.mjs.map +1 -0
  202. package/dist/esm/tools/ProgrammaticToolCalling.mjs +622 -0
  203. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -0
  204. package/dist/esm/tools/ToolNode.mjs +545 -0
  205. package/dist/esm/tools/ToolNode.mjs.map +1 -0
  206. package/dist/esm/tools/ToolSearch.mjs +870 -0
  207. package/dist/esm/tools/ToolSearch.mjs.map +1 -0
  208. package/dist/esm/tools/handlers.mjs +250 -0
  209. package/dist/esm/tools/handlers.mjs.map +1 -0
  210. package/dist/esm/tools/schema.mjs +28 -0
  211. package/dist/esm/tools/schema.mjs.map +1 -0
  212. package/dist/esm/tools/search/anthropic.mjs +37 -0
  213. package/dist/esm/tools/search/anthropic.mjs.map +1 -0
  214. package/dist/esm/tools/search/content.mjs +119 -0
  215. package/dist/esm/tools/search/content.mjs.map +1 -0
  216. package/dist/esm/tools/search/firecrawl.mjs +176 -0
  217. package/dist/esm/tools/search/firecrawl.mjs.map +1 -0
  218. package/dist/esm/tools/search/format.mjs +201 -0
  219. package/dist/esm/tools/search/format.mjs.map +1 -0
  220. package/dist/esm/tools/search/highlights.mjs +243 -0
  221. package/dist/esm/tools/search/highlights.mjs.map +1 -0
  222. package/dist/esm/tools/search/rerankers.mjs +168 -0
  223. package/dist/esm/tools/search/rerankers.mjs.map +1 -0
  224. package/dist/esm/tools/search/schema.mjs +104 -0
  225. package/dist/esm/tools/search/schema.mjs.map +1 -0
  226. package/dist/esm/tools/search/search.mjs +563 -0
  227. package/dist/esm/tools/search/search.mjs.map +1 -0
  228. package/dist/esm/tools/search/serper-scraper.mjs +129 -0
  229. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -0
  230. package/dist/esm/tools/search/tool.mjs +454 -0
  231. package/dist/esm/tools/search/tool.mjs.map +1 -0
  232. package/dist/esm/tools/search/utils.mjs +61 -0
  233. package/dist/esm/tools/search/utils.mjs.map +1 -0
  234. package/dist/esm/types/graph.mjs +26 -0
  235. package/dist/esm/types/graph.mjs.map +1 -0
  236. package/dist/esm/utils/contextAnalytics.mjs +64 -0
  237. package/dist/esm/utils/contextAnalytics.mjs.map +1 -0
  238. package/dist/esm/utils/events.mjs +29 -0
  239. package/dist/esm/utils/events.mjs.map +1 -0
  240. package/dist/esm/utils/graph.mjs +13 -0
  241. package/dist/esm/utils/graph.mjs.map +1 -0
  242. package/dist/esm/utils/handlers.mjs +68 -0
  243. package/dist/esm/utils/handlers.mjs.map +1 -0
  244. package/dist/esm/utils/llm.mjs +24 -0
  245. package/dist/esm/utils/llm.mjs.map +1 -0
  246. package/dist/esm/utils/misc.mjs +53 -0
  247. package/dist/esm/utils/misc.mjs.map +1 -0
  248. package/dist/esm/utils/run.mjs +70 -0
  249. package/dist/esm/utils/run.mjs.map +1 -0
  250. package/dist/esm/utils/schema.mjs +24 -0
  251. package/dist/esm/utils/schema.mjs.map +1 -0
  252. package/dist/esm/utils/title.mjs +122 -0
  253. package/dist/esm/utils/title.mjs.map +1 -0
  254. package/dist/esm/utils/tokens.mjs +121 -0
  255. package/dist/esm/utils/tokens.mjs.map +1 -0
  256. package/dist/esm/utils/toonFormat.mjs +381 -0
  257. package/dist/esm/utils/toonFormat.mjs.map +1 -0
  258. package/dist/types/agents/AgentContext.d.ts +293 -0
  259. package/dist/types/common/enum.d.ts +155 -0
  260. package/dist/types/common/index.d.ts +1 -0
  261. package/dist/types/events.d.ts +31 -0
  262. package/dist/types/graphs/Graph.d.ts +216 -0
  263. package/dist/types/graphs/MultiAgentGraph.d.ts +104 -0
  264. package/dist/types/graphs/index.d.ts +2 -0
  265. package/dist/types/index.d.ts +21 -0
  266. package/dist/types/instrumentation.d.ts +1 -0
  267. package/dist/types/llm/anthropic/index.d.ts +39 -0
  268. package/dist/types/llm/anthropic/types.d.ts +37 -0
  269. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +14 -0
  270. package/dist/types/llm/anthropic/utils/message_outputs.d.ts +14 -0
  271. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +22 -0
  272. package/dist/types/llm/anthropic/utils/tools.d.ts +3 -0
  273. package/dist/types/llm/bedrock/index.d.ts +141 -0
  274. package/dist/types/llm/bedrock/types.d.ts +27 -0
  275. package/dist/types/llm/bedrock/utils/index.d.ts +5 -0
  276. package/dist/types/llm/bedrock/utils/message_inputs.d.ts +31 -0
  277. package/dist/types/llm/bedrock/utils/message_outputs.d.ts +33 -0
  278. package/dist/types/llm/fake.d.ts +31 -0
  279. package/dist/types/llm/google/index.d.ts +24 -0
  280. package/dist/types/llm/google/types.d.ts +42 -0
  281. package/dist/types/llm/google/utils/common.d.ts +34 -0
  282. package/dist/types/llm/google/utils/tools.d.ts +10 -0
  283. package/dist/types/llm/google/utils/zod_to_genai_parameters.d.ts +14 -0
  284. package/dist/types/llm/openai/index.d.ts +127 -0
  285. package/dist/types/llm/openai/types.d.ts +10 -0
  286. package/dist/types/llm/openai/utils/index.d.ts +29 -0
  287. package/dist/types/llm/openrouter/index.d.ts +15 -0
  288. package/dist/types/llm/providers.d.ts +5 -0
  289. package/dist/types/llm/text.d.ts +21 -0
  290. package/dist/types/llm/vertexai/index.d.ts +293 -0
  291. package/dist/types/messages/cache.d.ts +54 -0
  292. package/dist/types/messages/content.d.ts +7 -0
  293. package/dist/types/messages/core.d.ts +14 -0
  294. package/dist/types/messages/format.d.ts +137 -0
  295. package/dist/types/messages/ids.d.ts +3 -0
  296. package/dist/types/messages/index.d.ts +7 -0
  297. package/dist/types/messages/prune.d.ts +52 -0
  298. package/dist/types/messages/reducer.d.ts +9 -0
  299. package/dist/types/messages/tools.d.ts +17 -0
  300. package/dist/types/mockStream.d.ts +32 -0
  301. package/dist/types/prompts/collab.d.ts +1 -0
  302. package/dist/types/prompts/index.d.ts +2 -0
  303. package/dist/types/prompts/taskmanager.d.ts +41 -0
  304. package/dist/types/run.d.ts +41 -0
  305. package/dist/types/schemas/index.d.ts +1 -0
  306. package/dist/types/schemas/validate.d.ts +59 -0
  307. package/dist/types/splitStream.d.ts +37 -0
  308. package/dist/types/stream.d.ts +15 -0
  309. package/dist/types/test/mockTools.d.ts +28 -0
  310. package/dist/types/tools/BrowserTools.d.ts +87 -0
  311. package/dist/types/tools/Calculator.d.ts +34 -0
  312. package/dist/types/tools/CodeExecutor.d.ts +57 -0
  313. package/dist/types/tools/ProgrammaticToolCalling.d.ts +138 -0
  314. package/dist/types/tools/ToolNode.d.ts +51 -0
  315. package/dist/types/tools/ToolSearch.d.ts +219 -0
  316. package/dist/types/tools/handlers.d.ts +22 -0
  317. package/dist/types/tools/schema.d.ts +12 -0
  318. package/dist/types/tools/search/anthropic.d.ts +16 -0
  319. package/dist/types/tools/search/content.d.ts +4 -0
  320. package/dist/types/tools/search/firecrawl.d.ts +54 -0
  321. package/dist/types/tools/search/format.d.ts +5 -0
  322. package/dist/types/tools/search/highlights.d.ts +13 -0
  323. package/dist/types/tools/search/index.d.ts +3 -0
  324. package/dist/types/tools/search/rerankers.d.ts +38 -0
  325. package/dist/types/tools/search/schema.d.ts +103 -0
  326. package/dist/types/tools/search/search.d.ts +8 -0
  327. package/dist/types/tools/search/serper-scraper.d.ts +59 -0
  328. package/dist/types/tools/search/test.d.ts +1 -0
  329. package/dist/types/tools/search/tool.d.ts +3 -0
  330. package/dist/types/tools/search/types.d.ts +575 -0
  331. package/dist/types/tools/search/utils.d.ts +10 -0
  332. package/dist/types/types/graph.d.ts +399 -0
  333. package/dist/types/types/index.d.ts +5 -0
  334. package/dist/types/types/llm.d.ts +105 -0
  335. package/dist/types/types/messages.d.ts +4 -0
  336. package/dist/types/types/run.d.ts +112 -0
  337. package/dist/types/types/stream.d.ts +308 -0
  338. package/dist/types/types/tools.d.ts +296 -0
  339. package/dist/types/utils/contextAnalytics.d.ts +37 -0
  340. package/dist/types/utils/events.d.ts +6 -0
  341. package/dist/types/utils/graph.d.ts +2 -0
  342. package/dist/types/utils/handlers.d.ts +34 -0
  343. package/dist/types/utils/index.d.ts +9 -0
  344. package/dist/types/utils/llm.d.ts +3 -0
  345. package/dist/types/utils/llmConfig.d.ts +3 -0
  346. package/dist/types/utils/logging.d.ts +1 -0
  347. package/dist/types/utils/misc.d.ts +7 -0
  348. package/dist/types/utils/run.d.ts +27 -0
  349. package/dist/types/utils/schema.d.ts +8 -0
  350. package/dist/types/utils/title.d.ts +4 -0
  351. package/dist/types/utils/tokens.d.ts +28 -0
  352. package/dist/types/utils/toonFormat.d.ts +111 -0
  353. package/package.json +190 -0
  354. package/src/agents/AgentContext.test.ts +458 -0
  355. package/src/agents/AgentContext.ts +972 -0
  356. package/src/agents/__tests__/AgentContext.test.ts +805 -0
  357. package/src/agents/__tests__/resolveStructuredOutputMode.test.ts +137 -0
  358. package/src/common/enum.ts +203 -0
  359. package/src/common/index.ts +2 -0
  360. package/src/events.ts +223 -0
  361. package/src/graphs/Graph.ts +2228 -0
  362. package/src/graphs/MultiAgentGraph.ts +1063 -0
  363. package/src/graphs/__tests__/structured-output.integration.test.ts +809 -0
  364. package/src/graphs/__tests__/structured-output.test.ts +183 -0
  365. package/src/graphs/index.ts +2 -0
  366. package/src/index.ts +34 -0
  367. package/src/instrumentation.ts +22 -0
  368. package/src/llm/anthropic/Jacob_Lee_Resume_2023.pdf +0 -0
  369. package/src/llm/anthropic/index.ts +413 -0
  370. package/src/llm/anthropic/llm.spec.ts +1442 -0
  371. package/src/llm/anthropic/types.ts +140 -0
  372. package/src/llm/anthropic/utils/message_inputs.ts +757 -0
  373. package/src/llm/anthropic/utils/message_outputs.ts +289 -0
  374. package/src/llm/anthropic/utils/output_parsers.ts +133 -0
  375. package/src/llm/anthropic/utils/tools.ts +29 -0
  376. package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +495 -0
  377. package/src/llm/bedrock/index.ts +411 -0
  378. package/src/llm/bedrock/llm.spec.ts +616 -0
  379. package/src/llm/bedrock/types.ts +51 -0
  380. package/src/llm/bedrock/utils/index.ts +18 -0
  381. package/src/llm/bedrock/utils/message_inputs.ts +563 -0
  382. package/src/llm/bedrock/utils/message_outputs.ts +310 -0
  383. package/src/llm/fake.ts +133 -0
  384. package/src/llm/google/data/gettysburg10.wav +0 -0
  385. package/src/llm/google/data/hotdog.jpg +0 -0
  386. package/src/llm/google/index.ts +337 -0
  387. package/src/llm/google/llm.spec.ts +934 -0
  388. package/src/llm/google/types.ts +56 -0
  389. package/src/llm/google/utils/common.ts +873 -0
  390. package/src/llm/google/utils/tools.ts +160 -0
  391. package/src/llm/google/utils/zod_to_genai_parameters.ts +86 -0
  392. package/src/llm/openai/index.ts +1366 -0
  393. package/src/llm/openai/types.ts +24 -0
  394. package/src/llm/openai/utils/index.ts +1035 -0
  395. package/src/llm/openai/utils/isReasoningModel.test.ts +90 -0
  396. package/src/llm/openrouter/index.ts +291 -0
  397. package/src/llm/providers.ts +52 -0
  398. package/src/llm/text.ts +94 -0
  399. package/src/llm/vertexai/index.ts +359 -0
  400. package/src/messages/__tests__/tools.test.ts +473 -0
  401. package/src/messages/cache.test.ts +1261 -0
  402. package/src/messages/cache.ts +518 -0
  403. package/src/messages/content.test.ts +362 -0
  404. package/src/messages/content.ts +63 -0
  405. package/src/messages/core.ts +473 -0
  406. package/src/messages/ensureThinkingBlock.test.ts +468 -0
  407. package/src/messages/format.ts +1029 -0
  408. package/src/messages/formatAgentMessages.test.ts +1513 -0
  409. package/src/messages/formatAgentMessages.tools.test.ts +419 -0
  410. package/src/messages/formatMessage.test.ts +693 -0
  411. package/src/messages/ids.ts +26 -0
  412. package/src/messages/index.ts +7 -0
  413. package/src/messages/labelContentByAgent.test.ts +887 -0
  414. package/src/messages/prune.ts +568 -0
  415. package/src/messages/reducer.ts +80 -0
  416. package/src/messages/shiftIndexTokenCountMap.test.ts +81 -0
  417. package/src/messages/tools.ts +108 -0
  418. package/src/mockStream.ts +99 -0
  419. package/src/prompts/collab.ts +6 -0
  420. package/src/prompts/index.ts +2 -0
  421. package/src/prompts/taskmanager.ts +61 -0
  422. package/src/run.ts +467 -0
  423. package/src/schemas/index.ts +2 -0
  424. package/src/schemas/schema-preparation.test.ts +500 -0
  425. package/src/schemas/validate.test.ts +358 -0
  426. package/src/schemas/validate.ts +454 -0
  427. package/src/scripts/abort.ts +157 -0
  428. package/src/scripts/ant_web_search.ts +158 -0
  429. package/src/scripts/ant_web_search_edge_case.ts +162 -0
  430. package/src/scripts/ant_web_search_error_edge_case.ts +148 -0
  431. package/src/scripts/args.ts +48 -0
  432. package/src/scripts/caching.ts +132 -0
  433. package/src/scripts/cli.ts +172 -0
  434. package/src/scripts/cli2.ts +133 -0
  435. package/src/scripts/cli3.ts +184 -0
  436. package/src/scripts/cli4.ts +191 -0
  437. package/src/scripts/cli5.ts +191 -0
  438. package/src/scripts/code_exec.ts +213 -0
  439. package/src/scripts/code_exec_files.ts +236 -0
  440. package/src/scripts/code_exec_multi_session.ts +241 -0
  441. package/src/scripts/code_exec_ptc.ts +334 -0
  442. package/src/scripts/code_exec_session.ts +282 -0
  443. package/src/scripts/code_exec_simple.ts +147 -0
  444. package/src/scripts/content.ts +138 -0
  445. package/src/scripts/empty_input.ts +137 -0
  446. package/src/scripts/handoff-test.ts +135 -0
  447. package/src/scripts/image.ts +178 -0
  448. package/src/scripts/memory.ts +97 -0
  449. package/src/scripts/multi-agent-chain.ts +331 -0
  450. package/src/scripts/multi-agent-conditional.ts +221 -0
  451. package/src/scripts/multi-agent-document-review-chain.ts +197 -0
  452. package/src/scripts/multi-agent-hybrid-flow.ts +310 -0
  453. package/src/scripts/multi-agent-parallel-start.ts +265 -0
  454. package/src/scripts/multi-agent-parallel.ts +394 -0
  455. package/src/scripts/multi-agent-sequence.ts +217 -0
  456. package/src/scripts/multi-agent-supervisor.ts +365 -0
  457. package/src/scripts/multi-agent-test.ts +186 -0
  458. package/src/scripts/parallel-asymmetric-tools-test.ts +274 -0
  459. package/src/scripts/parallel-full-metadata-test.ts +240 -0
  460. package/src/scripts/parallel-tools-test.ts +340 -0
  461. package/src/scripts/programmatic_exec.ts +396 -0
  462. package/src/scripts/programmatic_exec_agent.ts +231 -0
  463. package/src/scripts/search.ts +146 -0
  464. package/src/scripts/sequential-full-metadata-test.ts +197 -0
  465. package/src/scripts/simple.ts +225 -0
  466. package/src/scripts/single-agent-metadata-test.ts +198 -0
  467. package/src/scripts/stream.ts +140 -0
  468. package/src/scripts/test-custom-prompt-key.ts +145 -0
  469. package/src/scripts/test-handoff-input.ts +170 -0
  470. package/src/scripts/test-handoff-preamble.ts +277 -0
  471. package/src/scripts/test-multi-agent-list-handoff.ts +417 -0
  472. package/src/scripts/test-parallel-agent-labeling.ts +325 -0
  473. package/src/scripts/test-parallel-handoffs.ts +291 -0
  474. package/src/scripts/test-thinking-handoff-bedrock.ts +153 -0
  475. package/src/scripts/test-thinking-handoff.ts +155 -0
  476. package/src/scripts/test-tools-before-handoff.ts +226 -0
  477. package/src/scripts/test_code_api.ts +361 -0
  478. package/src/scripts/thinking-bedrock.ts +159 -0
  479. package/src/scripts/thinking.ts +171 -0
  480. package/src/scripts/tool_search.ts +162 -0
  481. package/src/scripts/tools.ts +177 -0
  482. package/src/specs/agent-handoffs.test.ts +888 -0
  483. package/src/specs/anthropic.simple.test.ts +387 -0
  484. package/src/specs/azure.simple.test.ts +364 -0
  485. package/src/specs/cache.simple.test.ts +396 -0
  486. package/src/specs/deepseek.simple.test.ts +283 -0
  487. package/src/specs/emergency-prune.test.ts +407 -0
  488. package/src/specs/moonshot.simple.test.ts +358 -0
  489. package/src/specs/openai.simple.test.ts +311 -0
  490. package/src/specs/openrouter.simple.test.ts +107 -0
  491. package/src/specs/prune.test.ts +901 -0
  492. package/src/specs/reasoning.test.ts +201 -0
  493. package/src/specs/spec.utils.ts +3 -0
  494. package/src/specs/thinking-handoff.test.ts +620 -0
  495. package/src/specs/thinking-prune.test.ts +703 -0
  496. package/src/specs/token-distribution-edge-case.test.ts +316 -0
  497. package/src/specs/token-memoization.test.ts +32 -0
  498. package/src/specs/tool-error.test.ts +198 -0
  499. package/src/splitStream.test.ts +691 -0
  500. package/src/splitStream.ts +234 -0
  501. package/src/stream.test.ts +94 -0
  502. package/src/stream.ts +801 -0
  503. package/src/test/mockTools.ts +386 -0
  504. package/src/tools/BrowserTools.ts +393 -0
  505. package/src/tools/Calculator.test.ts +278 -0
  506. package/src/tools/Calculator.ts +46 -0
  507. package/src/tools/CodeExecutor.ts +270 -0
  508. package/src/tools/ProgrammaticToolCalling.ts +785 -0
  509. package/src/tools/ToolNode.ts +674 -0
  510. package/src/tools/ToolSearch.ts +1095 -0
  511. package/src/tools/__tests__/BrowserTools.test.ts +265 -0
  512. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +319 -0
  513. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +1006 -0
  514. package/src/tools/__tests__/ToolSearch.integration.test.ts +162 -0
  515. package/src/tools/__tests__/ToolSearch.test.ts +1003 -0
  516. package/src/tools/handlers.ts +363 -0
  517. package/src/tools/schema.ts +37 -0
  518. package/src/tools/search/anthropic.ts +51 -0
  519. package/src/tools/search/content.test.ts +173 -0
  520. package/src/tools/search/content.ts +147 -0
  521. package/src/tools/search/firecrawl.ts +210 -0
  522. package/src/tools/search/format.ts +250 -0
  523. package/src/tools/search/highlights.ts +320 -0
  524. package/src/tools/search/index.ts +3 -0
  525. package/src/tools/search/jina-reranker.test.ts +130 -0
  526. package/src/tools/search/output.md +2775 -0
  527. package/src/tools/search/rerankers.ts +242 -0
  528. package/src/tools/search/schema.ts +113 -0
  529. package/src/tools/search/search.ts +768 -0
  530. package/src/tools/search/serper-scraper.ts +155 -0
  531. package/src/tools/search/test.html +884 -0
  532. package/src/tools/search/test.md +643 -0
  533. package/src/tools/search/test.ts +159 -0
  534. package/src/tools/search/tool.ts +657 -0
  535. package/src/tools/search/types.ts +665 -0
  536. package/src/tools/search/utils.ts +79 -0
  537. package/src/types/graph.test.ts +218 -0
  538. package/src/types/graph.ts +533 -0
  539. package/src/types/index.ts +6 -0
  540. package/src/types/llm.ts +140 -0
  541. package/src/types/messages.ts +4 -0
  542. package/src/types/run.ts +128 -0
  543. package/src/types/stream.ts +417 -0
  544. package/src/types/tools.ts +355 -0
  545. package/src/utils/contextAnalytics.ts +103 -0
  546. package/src/utils/events.ts +32 -0
  547. package/src/utils/graph.ts +11 -0
  548. package/src/utils/handlers.ts +107 -0
  549. package/src/utils/index.ts +9 -0
  550. package/src/utils/llm.ts +26 -0
  551. package/src/utils/llmConfig.ts +208 -0
  552. package/src/utils/logging.ts +48 -0
  553. package/src/utils/misc.ts +57 -0
  554. package/src/utils/run.ts +106 -0
  555. package/src/utils/schema.ts +35 -0
  556. package/src/utils/title.ts +177 -0
  557. package/src/utils/tokens.ts +142 -0
  558. package/src/utils/toonFormat.ts +475 -0
@@ -0,0 +1,2228 @@
1
+ /* eslint-disable no-console */
2
+ // src/graphs/Graph.ts
3
+ import { nanoid } from 'nanoid';
4
+ import { concat } from '@langchain/core/utils/stream';
5
+ import { ToolNode } from '@langchain/langgraph/prebuilt';
6
+ import { ChatVertexAI } from '@langchain/google-vertexai';
7
+ import {
8
+ START,
9
+ END,
10
+ Command,
11
+ StateGraph,
12
+ Annotation,
13
+ messagesStateReducer,
14
+ } from '@langchain/langgraph';
15
+ import {
16
+ Runnable,
17
+ RunnableConfig,
18
+ RunnableLambda,
19
+ } from '@langchain/core/runnables';
20
+ import {
21
+ ToolMessage,
22
+ SystemMessage,
23
+ AIMessageChunk,
24
+ HumanMessage,
25
+ } from '@langchain/core/messages';
26
+ import type {
27
+ BaseMessageFields,
28
+ UsageMetadata,
29
+ BaseMessage,
30
+ } from '@langchain/core/messages';
31
+ import type { ToolCall } from '@langchain/core/messages/tool';
32
+ import type * as t from '@/types';
33
+ import {
34
+ GraphNodeKeys,
35
+ ContentTypes,
36
+ GraphEvents,
37
+ Providers,
38
+ StepTypes,
39
+ MessageTypes,
40
+ Constants,
41
+ } from '@/common';
42
+ import {
43
+ formatAnthropicArtifactContent,
44
+ ensureThinkingBlockInMessages,
45
+ convertMessagesToContent,
46
+ addBedrockCacheControl,
47
+ modifyDeltaProperties,
48
+ formatArtifactPayload,
49
+ formatContentStrings,
50
+ createPruneMessages,
51
+ addCacheControl,
52
+ extractToolDiscoveries,
53
+ } from '@/messages';
54
+ import {
55
+ resetIfNotEmpty,
56
+ isOpenAILike,
57
+ isGoogleLike,
58
+ joinKeys,
59
+ sleep,
60
+ } from '@/utils';
61
+ import {
62
+ buildContextAnalytics,
63
+ type ContextAnalytics,
64
+ } from '@/utils/contextAnalytics';
65
+ import { getChatModelClass, manualToolStreamProviders } from '@/llm/providers';
66
+ import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
67
+ import { ChatOpenAI, AzureChatOpenAI } from '@/llm/openai';
68
+ import { safeDispatchCustomEvent } from '@/utils/events';
69
+ import { createSchemaOnlyTools } from '@/tools/schema';
70
+ import { prepareSchemaForProvider } from '@/schemas/validate';
71
+ import { AgentContext } from '@/agents/AgentContext';
72
+ import { StructuredOutputRefusalError, StructuredOutputTruncatedError } from '@/types/graph';
73
+ import { createFakeStreamingLLM } from '@/llm/fake';
74
+ import { HandlerRegistry } from '@/events';
75
+
76
+ const { AGENT, TOOLS } = GraphNodeKeys;
77
+
78
+ export abstract class Graph<
79
+ T extends t.BaseGraphState = t.BaseGraphState,
80
+ _TNodeName extends string = string,
81
+ > {
82
+ abstract resetValues(): void;
83
+ abstract initializeTools({
84
+ currentTools,
85
+ currentToolMap,
86
+ }: {
87
+ currentTools?: t.GraphTools;
88
+ currentToolMap?: t.ToolMap;
89
+ }): CustomToolNode<T> | ToolNode<T>;
90
+ abstract initializeModel({
91
+ currentModel,
92
+ tools,
93
+ clientOptions,
94
+ }: {
95
+ currentModel?: t.ChatModel;
96
+ tools?: t.GraphTools;
97
+ clientOptions?: t.ClientOptions;
98
+ }): Runnable;
99
+ abstract getRunMessages(): BaseMessage[] | undefined;
100
+ abstract getContentParts(): t.MessageContentComplex[] | undefined;
101
+ abstract generateStepId(stepKey: string): [string, number];
102
+ abstract getKeyList(
103
+ metadata: Record<string, unknown> | undefined
104
+ ): (string | number | undefined)[];
105
+ abstract getStepKey(metadata: Record<string, unknown> | undefined): string;
106
+ abstract checkKeyList(keyList: (string | number | undefined)[]): boolean;
107
+ abstract getStepIdByKey(stepKey: string, index?: number): string;
108
+ abstract getRunStep(stepId: string): t.RunStep | undefined;
109
+ abstract dispatchRunStep(
110
+ stepKey: string,
111
+ stepDetails: t.StepDetails,
112
+ metadata?: Record<string, unknown>
113
+ ): Promise<string>;
114
+ abstract dispatchRunStepDelta(
115
+ id: string,
116
+ delta: t.ToolCallDelta
117
+ ): Promise<void>;
118
+ abstract dispatchMessageDelta(
119
+ id: string,
120
+ delta: t.MessageDelta
121
+ ): Promise<void>;
122
+ abstract dispatchReasoningDelta(
123
+ stepId: string,
124
+ delta: t.ReasoningDelta
125
+ ): Promise<void>;
126
+ abstract handleToolCallCompleted(
127
+ data: t.ToolEndData,
128
+ metadata?: Record<string, unknown>,
129
+ omitOutput?: boolean
130
+ ): Promise<void>;
131
+
132
+ abstract createCallModel(
133
+ agentId?: string,
134
+ currentModel?: t.ChatModel
135
+ ): (state: T, config?: RunnableConfig) => Promise<Partial<T>>;
136
+ messageStepHasToolCalls: Map<string, boolean> = new Map();
137
+ messageIdsByStepKey: Map<string, string> = new Map();
138
+ prelimMessageIdsByStepKey: Map<string, string> = new Map();
139
+ config: RunnableConfig | undefined;
140
+ contentData: t.RunStep[] = [];
141
+ stepKeyIds: Map<string, string[]> = new Map<string, string[]>();
142
+ contentIndexMap: Map<string, number> = new Map();
143
+ toolCallStepIds: Map<string, string> = new Map();
144
+ signal?: AbortSignal;
145
+ /** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
146
+ invokedToolIds?: Set<string>;
147
+ handlerRegistry: HandlerRegistry | undefined;
148
+ /**
149
+ * Tool session contexts for automatic state persistence across tool invocations.
150
+ * Keyed by tool name (e.g., Constants.EXECUTE_CODE).
151
+ * Currently supports code execution session tracking (session_id, files).
152
+ */
153
+ sessions: t.ToolSessionMap = new Map();
154
+ }
155
+
156
+ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
157
+ overrideModel?: t.ChatModel;
158
+ /** Optional compile options passed into workflow.compile() */
159
+ compileOptions?: t.CompileOptions | undefined;
160
+ messages: BaseMessage[] = [];
161
+ runId: string | undefined;
162
+ startIndex: number = 0;
163
+ signal?: AbortSignal;
164
+ /** Map of agent contexts by agent ID */
165
+ agentContexts: Map<string, AgentContext> = new Map();
166
+ /** Default agent ID to use */
167
+ defaultAgentId: string;
168
+ /** Normalized finish/stop reason from the last LLM invocation */
169
+ lastFinishReason: string | undefined;
170
+
171
+ constructor({
172
+ // parent-level graph inputs
173
+ runId,
174
+ signal,
175
+ agents,
176
+ tokenCounter,
177
+ indexTokenCountMap,
178
+ }: t.StandardGraphInput) {
179
+ super();
180
+ this.runId = runId;
181
+ this.signal = signal;
182
+
183
+ if (agents.length === 0) {
184
+ throw new Error('At least one agent configuration is required');
185
+ }
186
+
187
+ for (const agentConfig of agents) {
188
+ const agentContext = AgentContext.fromConfig(
189
+ agentConfig,
190
+ tokenCounter,
191
+ indexTokenCountMap
192
+ );
193
+
194
+ this.agentContexts.set(agentConfig.agentId, agentContext);
195
+ }
196
+
197
+ this.defaultAgentId = agents[0].agentId;
198
+ }
199
+
200
+ /* Init */
201
+
202
+ resetValues(keepContent?: boolean): void {
203
+ this.messages = [];
204
+ this.lastFinishReason = undefined;
205
+ this.config = resetIfNotEmpty(this.config, undefined);
206
+ if (keepContent !== true) {
207
+ this.contentData = resetIfNotEmpty(this.contentData, []);
208
+ this.contentIndexMap = resetIfNotEmpty(this.contentIndexMap, new Map());
209
+ }
210
+ this.stepKeyIds = resetIfNotEmpty(this.stepKeyIds, new Map());
211
+ this.toolCallStepIds = resetIfNotEmpty(this.toolCallStepIds, new Map());
212
+ this.messageIdsByStepKey = resetIfNotEmpty(
213
+ this.messageIdsByStepKey,
214
+ new Map()
215
+ );
216
+ this.messageStepHasToolCalls = resetIfNotEmpty(
217
+ this.messageStepHasToolCalls,
218
+ new Map()
219
+ );
220
+ this.prelimMessageIdsByStepKey = resetIfNotEmpty(
221
+ this.prelimMessageIdsByStepKey,
222
+ new Map()
223
+ );
224
+ this.invokedToolIds = resetIfNotEmpty(this.invokedToolIds, undefined);
225
+ for (const context of this.agentContexts.values()) {
226
+ context.reset();
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Returns the normalized finish/stop reason from the last LLM invocation.
232
+ * Used by callers to detect when the response was truncated due to max_tokens.
233
+ */
234
+ getLastFinishReason(): string | undefined {
235
+ return this.lastFinishReason;
236
+ }
237
+
238
+ /**
239
+ * Estimates a human-friendly description of the conversation timeframe based on message count.
240
+ * Uses rough heuristics to provide context about how much history is available.
241
+ *
242
+ * @param messageCount - Number of messages in the remaining context
243
+ * @returns A friendly description like "the last few minutes", "the past hour", etc.
244
+ */
245
+ getContextTimeframeDescription(messageCount: number): string {
246
+ // Rough heuristics based on typical conversation patterns:
247
+ // - Very active chat: ~20-30 messages per hour
248
+ // - Normal chat: ~10-15 messages per hour
249
+ // - Slow/thoughtful chat: ~5-8 messages per hour
250
+ // We use a middle estimate of ~12 messages per hour
251
+
252
+ if (messageCount <= 5) {
253
+ return 'just the last few exchanges';
254
+ } else if (messageCount <= 15) {
255
+ return 'the last several minutes';
256
+ } else if (messageCount <= 30) {
257
+ return 'roughly the past hour';
258
+ } else if (messageCount <= 60) {
259
+ return 'the past couple of hours';
260
+ } else if (messageCount <= 150) {
261
+ return 'the past few hours';
262
+ } else if (messageCount <= 300) {
263
+ return 'roughly a day\'s worth';
264
+ } else if (messageCount <= 700) {
265
+ return 'the past few days';
266
+ } else {
267
+ return 'about a week or more';
268
+ }
269
+ }
270
+
271
+ /* Run Step Processing */
272
+
273
+ getRunStep(stepId: string): t.RunStep | undefined {
274
+ const index = this.contentIndexMap.get(stepId);
275
+ if (index !== undefined) {
276
+ return this.contentData[index];
277
+ }
278
+ return undefined;
279
+ }
280
+
281
+ getAgentContext(metadata: Record<string, unknown> | undefined): AgentContext {
282
+ if (!metadata) {
283
+ throw new Error('No metadata provided to retrieve agent context');
284
+ }
285
+
286
+ const currentNode = metadata.langgraph_node as string;
287
+ if (!currentNode) {
288
+ throw new Error(
289
+ 'No langgraph_node in metadata to retrieve agent context'
290
+ );
291
+ }
292
+
293
+ let agentId: string | undefined;
294
+ if (currentNode.startsWith(AGENT)) {
295
+ agentId = currentNode.substring(AGENT.length);
296
+ } else if (currentNode.startsWith(TOOLS)) {
297
+ agentId = currentNode.substring(TOOLS.length);
298
+ }
299
+
300
+ const agentContext = this.agentContexts.get(agentId ?? '');
301
+ if (!agentContext) {
302
+ throw new Error(`No agent context found for agent ID ${agentId}`);
303
+ }
304
+
305
+ return agentContext;
306
+ }
307
+
308
+ getStepKey(metadata: Record<string, unknown> | undefined): string {
309
+ if (!metadata) return '';
310
+
311
+ const keyList = this.getKeyList(metadata);
312
+ if (this.checkKeyList(keyList)) {
313
+ throw new Error('Missing metadata');
314
+ }
315
+
316
+ return joinKeys(keyList);
317
+ }
318
+
319
+ getStepIdByKey(stepKey: string, index?: number): string {
320
+ const stepIds = this.stepKeyIds.get(stepKey);
321
+ if (!stepIds) {
322
+ throw new Error(`No step IDs found for stepKey ${stepKey}`);
323
+ }
324
+
325
+ if (index === undefined) {
326
+ return stepIds[stepIds.length - 1];
327
+ }
328
+
329
+ return stepIds[index];
330
+ }
331
+
332
+ generateStepId(stepKey: string): [string, number] {
333
+ const stepIds = this.stepKeyIds.get(stepKey);
334
+ let newStepId: string | undefined;
335
+ let stepIndex = 0;
336
+ if (stepIds) {
337
+ stepIndex = stepIds.length;
338
+ newStepId = `step_${nanoid()}`;
339
+ stepIds.push(newStepId);
340
+ this.stepKeyIds.set(stepKey, stepIds);
341
+ } else {
342
+ newStepId = `step_${nanoid()}`;
343
+ this.stepKeyIds.set(stepKey, [newStepId]);
344
+ }
345
+
346
+ return [newStepId, stepIndex];
347
+ }
348
+
349
+ getKeyList(
350
+ metadata: Record<string, unknown> | undefined
351
+ ): (string | number | undefined)[] {
352
+ if (!metadata) return [];
353
+
354
+ const keyList = [
355
+ metadata.run_id as string,
356
+ metadata.thread_id as string,
357
+ metadata.langgraph_node as string,
358
+ metadata.langgraph_step as number,
359
+ metadata.checkpoint_ns as string,
360
+ ];
361
+
362
+ const agentContext = this.getAgentContext(metadata);
363
+ if (
364
+ agentContext.currentTokenType === ContentTypes.THINK ||
365
+ agentContext.currentTokenType === 'think_and_text'
366
+ ) {
367
+ keyList.push('reasoning');
368
+ } else if (agentContext.tokenTypeSwitch === 'content') {
369
+ keyList.push('post-reasoning');
370
+ }
371
+
372
+ if (this.invokedToolIds != null && this.invokedToolIds.size > 0) {
373
+ keyList.push(this.invokedToolIds.size + '');
374
+ }
375
+
376
+ return keyList;
377
+ }
378
+
379
+ checkKeyList(keyList: (string | number | undefined)[]): boolean {
380
+ return keyList.some((key) => key === undefined);
381
+ }
382
+
383
+ /* Misc.*/
384
+
385
+ getRunMessages(): BaseMessage[] | undefined {
386
+ return this.messages.slice(this.startIndex);
387
+ }
388
+
389
+ getContentParts(): t.MessageContentComplex[] | undefined {
390
+ return convertMessagesToContent(this.messages.slice(this.startIndex));
391
+ }
392
+
393
+ /**
394
+ * Get all run steps, optionally filtered by agent ID
395
+ */
396
+ getRunSteps(agentId?: string): t.RunStep[] {
397
+ if (agentId == null || agentId === '') {
398
+ return [...this.contentData];
399
+ }
400
+ return this.contentData.filter((step) => step.agentId === agentId);
401
+ }
402
+
403
+ /**
404
+ * Get run steps grouped by agent ID
405
+ */
406
+ getRunStepsByAgent(): Map<string, t.RunStep[]> {
407
+ const stepsByAgent = new Map<string, t.RunStep[]>();
408
+
409
+ for (const step of this.contentData) {
410
+ if (step.agentId == null || step.agentId === '') continue;
411
+
412
+ const steps = stepsByAgent.get(step.agentId) ?? [];
413
+ steps.push(step);
414
+ stepsByAgent.set(step.agentId, steps);
415
+ }
416
+
417
+ return stepsByAgent;
418
+ }
419
+
420
+ /**
421
+ * Get agent IDs that participated in this run
422
+ */
423
+ getActiveAgentIds(): string[] {
424
+ const agentIds = new Set<string>();
425
+ for (const step of this.contentData) {
426
+ if (step.agentId != null && step.agentId !== '') {
427
+ agentIds.add(step.agentId);
428
+ }
429
+ }
430
+ return Array.from(agentIds);
431
+ }
432
+
433
+ /**
434
+ * Maps contentPart indices to agent IDs for post-run analysis
435
+ * Returns a map where key is the contentPart index and value is the agentId
436
+ */
437
+ getContentPartAgentMap(): Map<number, string> {
438
+ const contentPartAgentMap = new Map<number, string>();
439
+
440
+ for (const step of this.contentData) {
441
+ if (
442
+ step.agentId != null &&
443
+ step.agentId !== '' &&
444
+ Number.isFinite(step.index)
445
+ ) {
446
+ contentPartAgentMap.set(step.index, step.agentId);
447
+ }
448
+ }
449
+
450
+ return contentPartAgentMap;
451
+ }
452
+
453
+ /**
454
+ * Get the context breakdown from the primary agent for admin token tracking.
455
+ * Returns detailed token counts for instructions, tools, etc.
456
+ */
457
+ getContextBreakdown(): {
458
+ instructions: number;
459
+ artifacts: number;
460
+ tools: number;
461
+ toolCount: number;
462
+ toolContext: number;
463
+ total: number;
464
+ toolsDetail: Array<{ name: string; tokens: number }>;
465
+ toolContextDetail: Array<{ name: string; tokens: number }>;
466
+ } | null {
467
+ const primaryContext = this.agentContexts.get(this.defaultAgentId);
468
+ if (!primaryContext) {
469
+ return null;
470
+ }
471
+ return primaryContext.getContextBreakdown();
472
+ }
473
+
474
+ /**
475
+ * Get the latest context analytics from the graph.
476
+ * Returns metrics like utilization %, TOON stats, message breakdown.
477
+ */
478
+ getContextAnalytics(): ContextAnalytics | null {
479
+ return this.lastContextAnalytics ?? null;
480
+ }
481
+
482
+ /** Store the latest context analytics for retrieval after run */
483
+ private lastContextAnalytics: ContextAnalytics | null = null;
484
+
485
+ /* Graph */
486
+
487
+ createSystemRunnable({
488
+ provider,
489
+ clientOptions,
490
+ instructions,
491
+ additional_instructions,
492
+ }: {
493
+ provider?: Providers;
494
+ clientOptions?: t.ClientOptions;
495
+ instructions?: string;
496
+ additional_instructions?: string;
497
+ }): t.SystemRunnable | undefined {
498
+ let finalInstructions: string | BaseMessageFields | undefined =
499
+ instructions;
500
+ if (additional_instructions != null && additional_instructions !== '') {
501
+ finalInstructions =
502
+ finalInstructions != null && finalInstructions
503
+ ? `${finalInstructions}\n\n${additional_instructions}`
504
+ : additional_instructions;
505
+ }
506
+
507
+ if (
508
+ finalInstructions != null &&
509
+ finalInstructions &&
510
+ provider === Providers.ANTHROPIC &&
511
+ (clientOptions as t.AnthropicClientOptions).promptCache === true
512
+ ) {
513
+ finalInstructions = {
514
+ content: [
515
+ {
516
+ type: 'text',
517
+ text: instructions,
518
+ cache_control: { type: 'ephemeral' },
519
+ },
520
+ ],
521
+ };
522
+ }
523
+
524
+ if (finalInstructions != null && finalInstructions !== '') {
525
+ const systemMessage = new SystemMessage(finalInstructions);
526
+ return RunnableLambda.from((messages: BaseMessage[]) => {
527
+ return [systemMessage, ...messages];
528
+ }).withConfig({ runName: 'prompt' });
529
+ }
530
+ }
531
+
532
+ initializeTools({
533
+ currentTools,
534
+ currentToolMap,
535
+ agentContext,
536
+ }: {
537
+ currentTools?: t.GraphTools;
538
+ currentToolMap?: t.ToolMap;
539
+ agentContext?: AgentContext;
540
+ }): CustomToolNode<t.BaseGraphState> | ToolNode<t.BaseGraphState> {
541
+ const toolDefinitions = agentContext?.toolDefinitions;
542
+ const eventDrivenMode =
543
+ toolDefinitions != null && toolDefinitions.length > 0;
544
+
545
+ if (eventDrivenMode) {
546
+ const schemaTools = createSchemaOnlyTools(toolDefinitions);
547
+ const toolDefMap = new Map(toolDefinitions.map((def) => [def.name, def]));
548
+
549
+ return new CustomToolNode<t.BaseGraphState>({
550
+ tools: schemaTools as t.GenericTool[],
551
+ toolMap: new Map(schemaTools.map((tool) => [tool.name, tool])),
552
+ toolCallStepIds: this.toolCallStepIds,
553
+ errorHandler: (data, metadata) =>
554
+ StandardGraph.handleToolCallErrorStatic(this, data, metadata),
555
+ toolRegistry: agentContext?.toolRegistry,
556
+ sessions: this.sessions,
557
+ eventDrivenMode: true,
558
+ toolDefinitions: toolDefMap,
559
+ agentId: agentContext?.agentId,
560
+ });
561
+ }
562
+
563
+ return new CustomToolNode<t.BaseGraphState>({
564
+ tools: (currentTools as t.GenericTool[] | undefined) ?? [],
565
+ toolMap: currentToolMap,
566
+ toolCallStepIds: this.toolCallStepIds,
567
+ errorHandler: (data, metadata) =>
568
+ StandardGraph.handleToolCallErrorStatic(this, data, metadata),
569
+ toolRegistry: agentContext?.toolRegistry,
570
+ sessions: this.sessions,
571
+ });
572
+ }
573
+
574
+ initializeModel({
575
+ provider,
576
+ tools,
577
+ clientOptions,
578
+ }: {
579
+ provider: Providers;
580
+ tools?: t.GraphTools;
581
+ clientOptions?: t.ClientOptions;
582
+ }): Runnable {
583
+ const ChatModelClass = getChatModelClass(provider);
584
+ const model = new ChatModelClass(clientOptions ?? {});
585
+
586
+ if (
587
+ isOpenAILike(provider) &&
588
+ (model instanceof ChatOpenAI || model instanceof AzureChatOpenAI)
589
+ ) {
590
+ model.temperature = (clientOptions as t.OpenAIClientOptions)
591
+ .temperature as number;
592
+ model.topP = (clientOptions as t.OpenAIClientOptions).topP as number;
593
+ model.frequencyPenalty = (clientOptions as t.OpenAIClientOptions)
594
+ .frequencyPenalty as number;
595
+ model.presencePenalty = (clientOptions as t.OpenAIClientOptions)
596
+ .presencePenalty as number;
597
+ model.n = (clientOptions as t.OpenAIClientOptions).n as number;
598
+ } else if (
599
+ provider === Providers.VERTEXAI &&
600
+ model instanceof ChatVertexAI
601
+ ) {
602
+ model.temperature = (clientOptions as t.VertexAIClientOptions)
603
+ .temperature as number;
604
+ model.topP = (clientOptions as t.VertexAIClientOptions).topP as number;
605
+ model.topK = (clientOptions as t.VertexAIClientOptions).topK as number;
606
+ model.topLogprobs = (clientOptions as t.VertexAIClientOptions)
607
+ .topLogprobs as number;
608
+ model.frequencyPenalty = (clientOptions as t.VertexAIClientOptions)
609
+ .frequencyPenalty as number;
610
+ model.presencePenalty = (clientOptions as t.VertexAIClientOptions)
611
+ .presencePenalty as number;
612
+ model.maxOutputTokens = (clientOptions as t.VertexAIClientOptions)
613
+ .maxOutputTokens as number;
614
+ }
615
+
616
+ if (!tools || tools.length === 0) {
617
+ return model as unknown as Runnable;
618
+ }
619
+
620
+ return (model as t.ModelWithTools).bindTools(tools);
621
+ }
622
+
623
+ overrideTestModel(
624
+ responses: string[],
625
+ sleep?: number,
626
+ toolCalls?: ToolCall[]
627
+ ): void {
628
+ this.overrideModel = createFakeStreamingLLM({
629
+ responses,
630
+ sleep,
631
+ toolCalls,
632
+ });
633
+ }
634
+
635
+ getNewModel({
636
+ provider,
637
+ clientOptions,
638
+ }: {
639
+ provider: Providers;
640
+ clientOptions?: t.ClientOptions;
641
+ }): t.ChatModelInstance {
642
+ const ChatModelClass = getChatModelClass(provider);
643
+ return new ChatModelClass(clientOptions ?? {});
644
+ }
645
+
646
+ getUsageMetadata(
647
+ finalMessage?: BaseMessage
648
+ ): Partial<UsageMetadata> | undefined {
649
+ if (
650
+ finalMessage &&
651
+ 'usage_metadata' in finalMessage &&
652
+ finalMessage.usage_metadata != null
653
+ ) {
654
+ return finalMessage.usage_metadata as Partial<UsageMetadata>;
655
+ }
656
+ }
657
+
658
+ /** Execute model invocation with streaming support */
659
+ private async attemptInvoke(
660
+ {
661
+ currentModel,
662
+ finalMessages,
663
+ provider,
664
+ tools,
665
+ }: {
666
+ currentModel?: t.ChatModel;
667
+ finalMessages: BaseMessage[];
668
+ provider: Providers;
669
+ tools?: t.GraphTools;
670
+ },
671
+ config?: RunnableConfig
672
+ ): Promise<Partial<t.BaseGraphState>> {
673
+ const model = this.overrideModel ?? currentModel;
674
+ if (!model) {
675
+ throw new Error('No model found');
676
+ }
677
+
678
+ if ((tools?.length ?? 0) > 0 && manualToolStreamProviders.has(provider)) {
679
+ if (!model.stream) {
680
+ throw new Error('Model does not support stream');
681
+ }
682
+ const stream = await model.stream(finalMessages, config);
683
+ let finalChunk: AIMessageChunk | undefined;
684
+ for await (const chunk of stream) {
685
+ await safeDispatchCustomEvent(
686
+ GraphEvents.CHAT_MODEL_STREAM,
687
+ { chunk, emitted: true },
688
+ config
689
+ );
690
+ finalChunk = finalChunk ? concat(finalChunk, chunk) : chunk;
691
+ }
692
+ finalChunk = modifyDeltaProperties(provider, finalChunk);
693
+ return { messages: [finalChunk as AIMessageChunk] };
694
+ } else {
695
+ const finalMessage = await model.invoke(finalMessages, config);
696
+ if ((finalMessage.tool_calls?.length ?? 0) > 0) {
697
+ finalMessage.tool_calls = finalMessage.tool_calls?.filter(
698
+ (tool_call: ToolCall) => !!tool_call.name
699
+ );
700
+ }
701
+ return { messages: [finalMessage] };
702
+ }
703
+ }
704
+
705
+ /**
706
+ * Execute model invocation with structured output.
707
+ * Uses native constrained decoding (jsonSchema method) for supported providers,
708
+ * or falls back to withStructuredOutput with functionCalling/jsonMode.
709
+ *
710
+ * Native mode uses provider APIs directly:
711
+ * - Anthropic: output_config.format via LangChain's method: 'json_schema'
712
+ * - OpenAI/Azure: response_format.json_schema via LangChain's method: 'jsonSchema'
713
+ * - Bedrock: falls back to functionCalling (LangChain doesn't support native yet)
714
+ */
715
+ private async attemptStructuredInvoke(
716
+ {
717
+ currentModel,
718
+ finalMessages,
719
+ schema,
720
+ structuredOutputConfig,
721
+ provider,
722
+ agentContext,
723
+ }: {
724
+ currentModel: t.ChatModelInstance;
725
+ finalMessages: BaseMessage[];
726
+ schema: Record<string, unknown>;
727
+ structuredOutputConfig: t.StructuredOutputConfig;
728
+ provider?: Providers;
729
+ agentContext?: AgentContext;
730
+ },
731
+ config?: RunnableConfig
732
+ ): Promise<{
733
+ structuredResponse: Record<string, unknown>;
734
+ rawMessage?: AIMessageChunk;
735
+ }> {
736
+ const model = this.overrideModel ?? currentModel;
737
+ if (!model) {
738
+ throw new Error('No model found');
739
+ }
740
+
741
+ // Check if model supports withStructuredOutput
742
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
743
+ if (typeof (model as any).withStructuredOutput !== 'function') {
744
+ throw new Error(
745
+ `The selected model does not support structured output. ` +
746
+ `Please use a model that supports JSON schema output (e.g., OpenAI GPT-4, Anthropic Claude, Google Gemini) ` +
747
+ `or disable structured output for this agent.`
748
+ );
749
+ }
750
+
751
+ const {
752
+ name = 'StructuredResponse',
753
+ includeRaw: _includeRaw = false,
754
+ handleErrors = true,
755
+ maxRetries = 2,
756
+ } = structuredOutputConfig;
757
+
758
+ // Resolve the structured output method using AgentContext's provider-aware logic
759
+ let method: t.ResolvedStructuredOutputMethod;
760
+ if (agentContext) {
761
+ const resolved = agentContext.resolveStructuredOutputMode();
762
+ method = resolved.method;
763
+ if (resolved.warnings.length > 0) {
764
+ console.warn('[Graph] Structured output mode warnings:', resolved.warnings);
765
+ }
766
+ } else {
767
+ // Legacy fallback: use the old mode-based resolution
768
+ const mode = structuredOutputConfig.mode ?? 'auto';
769
+ if (mode === 'tool') {
770
+ method = 'functionCalling';
771
+ } else if (mode === 'provider') {
772
+ method = provider === Providers.BEDROCK ? 'functionCalling' : 'jsonMode';
773
+ } else {
774
+ method = undefined;
775
+ }
776
+ }
777
+
778
+ // Prepare schema for provider-specific constraints when using native/jsonSchema mode
779
+ let preparedSchema = schema;
780
+ if (method === 'jsonSchema' && provider) {
781
+ const { schema: prepared, warnings } = prepareSchemaForProvider(
782
+ schema,
783
+ provider,
784
+ structuredOutputConfig.strict !== false,
785
+ );
786
+ preparedSchema = prepared;
787
+ if (warnings.length > 0) {
788
+ console.log('[Graph] Schema preparation warnings:', warnings);
789
+ }
790
+ }
791
+
792
+ // Use withStructuredOutput to bind the schema
793
+ // Always use includeRaw: true internally so we can debug what's returned
794
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
795
+ const structuredModel = (model as any).withStructuredOutput(preparedSchema, {
796
+ name,
797
+ method: method === 'native' ? undefined : method,
798
+ includeRaw: true, // Always true internally for debugging
799
+ strict: structuredOutputConfig.strict !== false,
800
+ });
801
+
802
+ console.log('[Graph] Structured output config:', {
803
+ name,
804
+ method,
805
+ provider,
806
+ schemaKeys: Object.keys(preparedSchema),
807
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
808
+ modelName: (model as any).model || (model as any).modelId || 'unknown',
809
+ });
810
+
811
+ let lastError: Error | undefined;
812
+ let attempts = 0;
813
+
814
+ while (attempts <= maxRetries) {
815
+ try {
816
+ // Note: We pass the original config here. The stream aggregator will filter out
817
+ // the synthetic "response" tool call events from withStructuredOutput()
818
+ const result = await structuredModel.invoke(finalMessages, config);
819
+
820
+ // Debug: log what we got back
821
+ console.log('[Graph] Structured output raw result type:', typeof result);
822
+
823
+ // Check for refusal or truncation in the raw message
824
+ if (result?.raw) {
825
+ const rawMsg = result.raw as AIMessageChunk;
826
+ console.log('[Graph] Raw message content type:', typeof rawMsg?.content);
827
+ console.log('[Graph] Raw message tool_calls:', rawMsg?.tool_calls?.length ?? 0);
828
+ if (rawMsg?.content && typeof rawMsg.content === 'string' && rawMsg.content.length > 0) {
829
+ console.log('[Graph] Raw message text content (first 200):', rawMsg.content.substring(0, 200));
830
+ }
831
+
832
+ // Check stop reason for refusal or truncation
833
+ const responseMetadata = rawMsg.response_metadata ?? {};
834
+ const stopReason =
835
+ responseMetadata.stop_reason ?? // Anthropic
836
+ responseMetadata.finish_reason ?? // OpenAI
837
+ responseMetadata.stopReason; // Bedrock
838
+
839
+ if (stopReason === 'max_tokens' || stopReason === 'length') {
840
+ throw new StructuredOutputTruncatedError(stopReason);
841
+ }
842
+
843
+ // Check for Anthropic refusal (stop_reason won't be 'refusal' but content may indicate it)
844
+ // OpenAI uses message.refusal field
845
+ const refusal = (rawMsg as AIMessageChunk & { refusal?: string }).refusal;
846
+ if (refusal) {
847
+ throw new StructuredOutputRefusalError(refusal);
848
+ }
849
+ }
850
+
851
+ // Handle response - we always use includeRaw internally
852
+ if (result?.raw && result?.parsed !== undefined) {
853
+ return {
854
+ structuredResponse: result.parsed as Record<string, unknown>,
855
+ rawMessage: result.raw as AIMessageChunk,
856
+ };
857
+ }
858
+
859
+ // Fallback for models that don't support includeRaw
860
+ return {
861
+ structuredResponse: result as Record<string, unknown>,
862
+ };
863
+ } catch (error) {
864
+ // Don't retry on refusal or truncation errors — they need user action
865
+ if (
866
+ error instanceof StructuredOutputRefusalError ||
867
+ error instanceof StructuredOutputTruncatedError
868
+ ) {
869
+ throw error;
870
+ }
871
+
872
+ lastError = error as Error;
873
+ attempts++;
874
+
875
+ // If error handling is disabled, throw immediately
876
+ if (handleErrors === false) {
877
+ throw error;
878
+ }
879
+
880
+ // If we've exhausted retries, throw
881
+ if (attempts > maxRetries) {
882
+ throw new Error(
883
+ `Structured output failed after ${maxRetries + 1} attempts: ${lastError.message}`
884
+ );
885
+ }
886
+
887
+ // Add error message to conversation for retry
888
+ const errorMessage =
889
+ typeof handleErrors === 'string'
890
+ ? handleErrors
891
+ : `The response did not match the expected schema. Error: ${lastError.message}. Please try again with a valid response.`;
892
+
893
+ console.warn(
894
+ `[Graph] Structured output attempt ${attempts} failed: ${lastError.message}. Retrying...`
895
+ );
896
+
897
+ // Add the error as a human message for context
898
+ finalMessages = [
899
+ ...finalMessages,
900
+ new HumanMessage({
901
+ content: `[VALIDATION ERROR]\n${errorMessage}`,
902
+ }),
903
+ ];
904
+ }
905
+ }
906
+
907
+ throw lastError ?? new Error('Structured output failed');
908
+ }
909
+
910
+ cleanupSignalListener(currentModel?: t.ChatModel): void {
911
+ if (!this.signal) {
912
+ return;
913
+ }
914
+ const model = this.overrideModel ?? currentModel;
915
+ if (!model) {
916
+ return;
917
+ }
918
+ const client = (model as ChatOpenAI | undefined)?.exposedClient;
919
+ if (!client?.abortHandler) {
920
+ return;
921
+ }
922
+ this.signal.removeEventListener('abort', client.abortHandler);
923
+ client.abortHandler = undefined;
924
+ }
925
+
926
+ /**
927
+ * Perform structured output invocation: creates a fresh model without tools bound,
928
+ * removes thinking configuration, invokes with the schema, emits the event,
929
+ * and returns a clean AIMessageChunk without tool_calls.
930
+ *
931
+ * Used by both the immediate path (no tools) and the deferred path (after tool use).
932
+ */
933
+ private async performStructuredOutput({
934
+ agentContext,
935
+ finalMessages,
936
+ config,
937
+ }: {
938
+ agentContext: AgentContext;
939
+ finalMessages: BaseMessage[];
940
+ config: RunnableConfig;
941
+ }): Promise<Partial<t.BaseGraphState>> {
942
+ const schema = agentContext.getStructuredOutputSchema();
943
+ if (!schema) {
944
+ throw new Error('Structured output schema is not configured');
945
+ }
946
+
947
+ // Get a fresh model WITHOUT tools bound
948
+ // bindTools() returns RunnableBinding which lacks withStructuredOutput
949
+ // Also disable thinking mode - Anthropic/Bedrock doesn't allow tool_choice with thinking enabled
950
+ const structuredClientOptions = { ...agentContext.clientOptions } as t.ClientOptions;
951
+
952
+ // Determine if streaming is possible for this structured output mode
953
+ // Native/jsonSchema modes can stream; tool/functionCalling modes cannot (synthetic tool calls break UX)
954
+ const resolved = agentContext.resolveStructuredOutputMode();
955
+ const canStream = resolved.method === 'jsonSchema' || resolved.method === 'jsonMode';
956
+ if (!canStream) {
957
+ // Disable streaming for function calling mode (synthetic tool calls break streaming UX)
958
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
959
+ (structuredClientOptions as any).streaming = false;
960
+ }
961
+
962
+ // For native/jsonSchema mode, Anthropic's constrained decoding works with thinking enabled
963
+ // (grammar only applies to final output, not thinking blocks). For function calling mode,
964
+ // thinking must be disabled because forced tool_choice is incompatible with thinking.
965
+ const needsThinkingDisabled = resolved.method !== 'jsonSchema';
966
+
967
+ if (needsThinkingDisabled) {
968
+ // Remove thinking configuration for Bedrock
969
+ if (agentContext.provider === Providers.BEDROCK) {
970
+ const bedrockOpts = structuredClientOptions as t.BedrockAnthropicClientOptions;
971
+ if (bedrockOpts.additionalModelRequestFields) {
972
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
973
+ const additionalFields = Object.assign({}, bedrockOpts.additionalModelRequestFields) as any;
974
+ delete additionalFields.thinking;
975
+ delete additionalFields.budgetTokens;
976
+ bedrockOpts.additionalModelRequestFields = additionalFields;
977
+ }
978
+ }
979
+
980
+ // Remove thinking configuration for Anthropic direct API
981
+ if (agentContext.provider === Providers.ANTHROPIC) {
982
+ const anthropicOpts = structuredClientOptions as t.AnthropicClientOptions;
983
+ if (anthropicOpts.thinking) {
984
+ delete anthropicOpts.thinking;
985
+ }
986
+ }
987
+ }
988
+
989
+ const structuredModel = this.getNewModel({
990
+ provider: agentContext.provider,
991
+ clientOptions: structuredClientOptions,
992
+ });
993
+
994
+ const { structuredResponse, rawMessage } =
995
+ await this.attemptStructuredInvoke(
996
+ {
997
+ currentModel: structuredModel,
998
+ finalMessages,
999
+ schema,
1000
+ structuredOutputConfig: agentContext.structuredOutput!,
1001
+ provider: agentContext.provider,
1002
+ agentContext,
1003
+ },
1004
+ config
1005
+ );
1006
+
1007
+ // Emit structured output event
1008
+ await safeDispatchCustomEvent(
1009
+ GraphEvents.ON_STRUCTURED_OUTPUT,
1010
+ {
1011
+ structuredResponse,
1012
+ schema,
1013
+ raw: rawMessage,
1014
+ },
1015
+ config
1016
+ );
1017
+
1018
+ // Create a clean message WITHOUT tool_calls for structured output.
1019
+ // The rawMessage contains a tool_call for the structured output schema (e.g., "response"),
1020
+ // which would cause the graph router to send it to the tool node.
1021
+ // We return a clean AI message that ends the graph.
1022
+ let cleanMessage: AIMessageChunk | undefined;
1023
+ if (rawMessage) {
1024
+ cleanMessage = new AIMessageChunk({
1025
+ content: JSON.stringify(structuredResponse, null, 2),
1026
+ id: rawMessage.id,
1027
+ response_metadata: rawMessage.response_metadata,
1028
+ usage_metadata: rawMessage.usage_metadata,
1029
+ });
1030
+ }
1031
+
1032
+ return {
1033
+ messages: cleanMessage ? [cleanMessage] : [],
1034
+ structuredResponse,
1035
+ };
1036
+ }
1037
+
1038
+ createCallModel(agentId = 'default') {
1039
+ return async (
1040
+ state: t.BaseGraphState,
1041
+ config?: RunnableConfig
1042
+ ): Promise<Partial<t.BaseGraphState>> => {
1043
+ /**
1044
+ * Get agent context - it must exist by this point
1045
+ */
1046
+ const agentContext = this.agentContexts.get(agentId);
1047
+ if (!agentContext) {
1048
+ throw new Error(`Agent context not found for agentId: ${agentId}`);
1049
+ }
1050
+
1051
+ if (!config) {
1052
+ throw new Error('No config provided');
1053
+ }
1054
+
1055
+ let { messages } = state;
1056
+
1057
+ // CACHE OPTIMIZATION: Inject dynamicContext as a HumanMessage at the start of conversation
1058
+ // This keeps the system message static (cacheable) while providing dynamic context
1059
+ // (timestamps, user info, tool context) as conversation content instead.
1060
+ // Only inject on the first turn when messages don't already have the context marker.
1061
+ if (
1062
+ agentContext.dynamicContext &&
1063
+ messages.length > 0 &&
1064
+ !messages.some(
1065
+ (m) =>
1066
+ m instanceof HumanMessage &&
1067
+ typeof m.content === 'string' &&
1068
+ m.content.startsWith('[SESSION_CONTEXT]')
1069
+ )
1070
+ ) {
1071
+ const dynamicContextMessage = new HumanMessage({
1072
+ content: `[SESSION_CONTEXT]\n${agentContext.dynamicContext}`,
1073
+ });
1074
+ const ackMessage = new AIMessageChunk({
1075
+ content:
1076
+ 'Understood. I have noted the session context including the current date/time (CST) and will apply it appropriately.',
1077
+ });
1078
+ messages = [dynamicContextMessage, ackMessage, ...messages];
1079
+ }
1080
+
1081
+ // Extract tool discoveries from current turn only (similar to formatArtifactPayload pattern)
1082
+ const discoveredNames = extractToolDiscoveries(messages);
1083
+ if (discoveredNames.length > 0) {
1084
+ agentContext.markToolsAsDiscovered(discoveredNames);
1085
+ }
1086
+
1087
+ const toolsForBinding = agentContext.getToolsForBinding();
1088
+ let model =
1089
+ this.overrideModel ??
1090
+ this.initializeModel({
1091
+ tools: toolsForBinding,
1092
+ provider: agentContext.provider,
1093
+ clientOptions: agentContext.clientOptions,
1094
+ });
1095
+
1096
+ if (agentContext.systemRunnable) {
1097
+ model = agentContext.systemRunnable.pipe(model as Runnable);
1098
+ }
1099
+
1100
+ if (agentContext.tokenCalculationPromise) {
1101
+ await agentContext.tokenCalculationPromise;
1102
+ }
1103
+ if (!config.signal) {
1104
+ config.signal = this.signal;
1105
+ }
1106
+ this.config = config;
1107
+
1108
+ let messagesToUse = messages;
1109
+
1110
+ // ====================================================================
1111
+ // PRE-PRUNING DELEGATION CHECK
1112
+ // Before pruning strips messages (losing context), check if we should
1113
+ // delegate instead. If context would be pruned AND the agent has the
1114
+ // task tool, inject a delegation hint and SKIP pruning — preserving
1115
+ // the content for the LLM to understand what to delegate.
1116
+ // ====================================================================
1117
+ let delegationInjectedPrePrune = false;
1118
+ const hasTaskToolPrePrune = agentContext.tools?.some(
1119
+ (tool) => {
1120
+ const toolName = typeof tool === 'object' && 'name' in tool
1121
+ ? (tool as { name: string }).name
1122
+ : '';
1123
+ return toolName === 'task';
1124
+ }
1125
+ );
1126
+
1127
+ if (
1128
+ hasTaskToolPrePrune &&
1129
+ agentContext.tokenCounter &&
1130
+ agentContext.maxContextTokens != null
1131
+ ) {
1132
+ // Estimate total tokens in messages BEFORE pruning
1133
+ let prePruneTokens = 0;
1134
+ for (const msg of messages) {
1135
+ prePruneTokens += agentContext.tokenCounter(msg);
1136
+ }
1137
+ // Add instruction tokens (system prompt)
1138
+ prePruneTokens += agentContext.instructionTokens ?? 0;
1139
+
1140
+ const prePruneUtilization = (prePruneTokens / agentContext.maxContextTokens) * 100;
1141
+
1142
+ if (prePruneUtilization > 70) {
1143
+ console.warn(
1144
+ `[Graph] PRE-PRUNE delegation check: ${prePruneUtilization.toFixed(1)}% utilization ` +
1145
+ `(${prePruneTokens}/${agentContext.maxContextTokens} tokens). ` +
1146
+ `Injecting delegation hint INSTEAD of pruning.`
1147
+ );
1148
+ delegationInjectedPrePrune = true;
1149
+ }
1150
+ }
1151
+
1152
+ if (
1153
+ !agentContext.pruneMessages &&
1154
+ agentContext.tokenCounter &&
1155
+ agentContext.maxContextTokens != null &&
1156
+ agentContext.indexTokenCountMap[0] != null
1157
+ ) {
1158
+ const isAnthropicWithThinking =
1159
+ (agentContext.provider === Providers.ANTHROPIC &&
1160
+ (agentContext.clientOptions as t.AnthropicClientOptions).thinking !=
1161
+ null) ||
1162
+ (agentContext.provider === Providers.BEDROCK &&
1163
+ (agentContext.clientOptions as t.BedrockAnthropicInput)
1164
+ .additionalModelRequestFields?.['thinking'] != null) ||
1165
+ (agentContext.provider === Providers.OPENAI &&
1166
+ (
1167
+ (agentContext.clientOptions as t.OpenAIClientOptions).modelKwargs
1168
+ ?.thinking as t.AnthropicClientOptions['thinking']
1169
+ )?.type === 'enabled');
1170
+
1171
+ agentContext.pruneMessages = createPruneMessages({
1172
+ startIndex: this.startIndex,
1173
+ provider: agentContext.provider,
1174
+ tokenCounter: agentContext.tokenCounter,
1175
+ maxTokens: agentContext.maxContextTokens,
1176
+ thinkingEnabled: isAnthropicWithThinking,
1177
+ indexTokenCountMap: agentContext.indexTokenCountMap,
1178
+ });
1179
+ }
1180
+
1181
+ if (agentContext.pruneMessages && !delegationInjectedPrePrune) {
1182
+ const { context, indexTokenCountMap, messagesToRefine } = agentContext.pruneMessages({
1183
+ messages,
1184
+ usageMetadata: agentContext.currentUsage,
1185
+ // startOnMessageType: 'human',
1186
+ });
1187
+ agentContext.indexTokenCountMap = indexTokenCountMap;
1188
+ messagesToUse = context;
1189
+
1190
+ // Summarize discarded messages if callback provided
1191
+ if (messagesToRefine && messagesToRefine.length > 0 && agentContext.summarizeCallback) {
1192
+ try {
1193
+ const summary = await agentContext.summarizeCallback(messagesToRefine);
1194
+ if (summary) {
1195
+ const summaryMsg = new SystemMessage(`[Conversation Summary]\n${summary}`);
1196
+ // Insert after system message (if present), before conversation messages
1197
+ const systemIdx = messagesToUse[0]?.getType() === 'system' ? 1 : 0;
1198
+ messagesToUse = [
1199
+ ...messagesToUse.slice(0, systemIdx),
1200
+ summaryMsg,
1201
+ ...messagesToUse.slice(systemIdx),
1202
+ ];
1203
+ }
1204
+ } catch (err) {
1205
+ console.error('[Graph] Summarization callback failed:', err);
1206
+ }
1207
+ }
1208
+ } else if (delegationInjectedPrePrune) {
1209
+ console.info('[Graph] Skipping pruning — delegation will handle context pressure');
1210
+ }
1211
+
1212
+ let finalMessages = messagesToUse;
1213
+ if (agentContext.useLegacyContent) {
1214
+ finalMessages = formatContentStrings(finalMessages);
1215
+ }
1216
+
1217
+ const lastMessageX =
1218
+ finalMessages.length >= 2
1219
+ ? finalMessages[finalMessages.length - 2]
1220
+ : null;
1221
+ const lastMessageY =
1222
+ finalMessages.length >= 1
1223
+ ? finalMessages[finalMessages.length - 1]
1224
+ : null;
1225
+
1226
+ if (
1227
+ agentContext.provider === Providers.BEDROCK &&
1228
+ lastMessageX instanceof AIMessageChunk &&
1229
+ lastMessageY?.getType() === MessageTypes.TOOL &&
1230
+ typeof lastMessageX.content === 'string'
1231
+ ) {
1232
+ finalMessages[finalMessages.length - 2].content = '';
1233
+ }
1234
+
1235
+ // Use getType() instead of instanceof to avoid module mismatch issues
1236
+ const isLatestToolMessage = lastMessageY?.getType() === MessageTypes.TOOL;
1237
+
1238
+ if (
1239
+ isLatestToolMessage &&
1240
+ agentContext.provider === Providers.ANTHROPIC
1241
+ ) {
1242
+ formatAnthropicArtifactContent(finalMessages);
1243
+ } else if (
1244
+ isLatestToolMessage &&
1245
+ ((isOpenAILike(agentContext.provider) &&
1246
+ agentContext.provider !== Providers.DEEPSEEK) ||
1247
+ isGoogleLike(agentContext.provider))
1248
+ ) {
1249
+ formatArtifactPayload(finalMessages);
1250
+ }
1251
+
1252
+ /**
1253
+ * Handle edge case: when switching from a non-thinking agent to a thinking-enabled agent,
1254
+ * convert AI messages with tool calls to HumanMessages to avoid thinking block requirements.
1255
+ * This is required by Anthropic/Bedrock when thinking is enabled.
1256
+ *
1257
+ * IMPORTANT: This MUST happen BEFORE cache control is applied.
1258
+ * If we add cachePoint to an AI message first, then convert that AI message to a HumanMessage,
1259
+ * the cachePoint is lost. By converting first, we ensure cache control is applied to the
1260
+ * final message structure that will be sent to the API.
1261
+ */
1262
+ const isAnthropicWithThinking =
1263
+ (agentContext.provider === Providers.ANTHROPIC &&
1264
+ (agentContext.clientOptions as t.AnthropicClientOptions).thinking !=
1265
+ null) ||
1266
+ (agentContext.provider === Providers.BEDROCK &&
1267
+ (agentContext.clientOptions as t.BedrockAnthropicInput)
1268
+ .additionalModelRequestFields?.['thinking'] != null);
1269
+
1270
+ if (isAnthropicWithThinking) {
1271
+ finalMessages = ensureThinkingBlockInMessages(
1272
+ finalMessages,
1273
+ agentContext.provider
1274
+ );
1275
+ }
1276
+
1277
+ // Apply cache control AFTER thinking block handling to ensure cachePoints aren't lost
1278
+ // when AI messages are converted to HumanMessages
1279
+ if (agentContext.provider === Providers.ANTHROPIC) {
1280
+ const anthropicOptions = agentContext.clientOptions as
1281
+ | t.AnthropicClientOptions
1282
+ | undefined;
1283
+ if (anthropicOptions?.promptCache === true) {
1284
+ finalMessages = addCacheControl<BaseMessage>(finalMessages);
1285
+ }
1286
+ } else if (agentContext.provider === Providers.BEDROCK) {
1287
+ const bedrockOptions = agentContext.clientOptions as
1288
+ | t.BedrockAnthropicClientOptions
1289
+ | undefined;
1290
+ // Both Claude and Nova models support cachePoint in system and messages
1291
+ // (Llama, Titan, and other models do NOT support cachePoint)
1292
+ const modelId = bedrockOptions?.model?.toLowerCase() ?? '';
1293
+ const supportsCaching =
1294
+ modelId.includes('claude') ||
1295
+ modelId.includes('anthropic') ||
1296
+ modelId.includes('nova');
1297
+ if (bedrockOptions?.promptCache === true && supportsCaching) {
1298
+ finalMessages = addBedrockCacheControl<BaseMessage>(finalMessages);
1299
+ }
1300
+ }
1301
+
1302
+ if (
1303
+ agentContext.lastStreamCall != null &&
1304
+ agentContext.streamBuffer != null
1305
+ ) {
1306
+ const timeSinceLastCall = Date.now() - agentContext.lastStreamCall;
1307
+ if (timeSinceLastCall < agentContext.streamBuffer) {
1308
+ const timeToWait =
1309
+ Math.ceil((agentContext.streamBuffer - timeSinceLastCall) / 1000) *
1310
+ 1000;
1311
+ await sleep(timeToWait);
1312
+ }
1313
+ }
1314
+
1315
+ agentContext.lastStreamCall = Date.now();
1316
+
1317
+ let result: Partial<t.BaseGraphState> | undefined;
1318
+ const fallbacks =
1319
+ (agentContext.clientOptions as t.LLMConfig | undefined)?.fallbacks ??
1320
+ [];
1321
+
1322
+ if (finalMessages.length === 0) {
1323
+ throw new Error(
1324
+ JSON.stringify({
1325
+ type: 'empty_messages',
1326
+ info: 'Message pruning removed all messages as none fit in the context window. Please increase the context window size or make your message shorter.',
1327
+ })
1328
+ );
1329
+ }
1330
+
1331
+ // Get model info for analytics
1332
+ const bedrockOpts = agentContext.clientOptions as
1333
+ | t.BedrockAnthropicClientOptions
1334
+ | undefined;
1335
+ const modelId =
1336
+ bedrockOpts?.model ||
1337
+ (agentContext.clientOptions as t.AnthropicClientOptions | undefined)
1338
+ ?.modelName;
1339
+ const thinkingConfig =
1340
+ bedrockOpts?.additionalModelRequestFields?.['thinking'] ||
1341
+ (agentContext.clientOptions as t.AnthropicClientOptions | undefined)
1342
+ ?.thinking;
1343
+
1344
+ // Build and emit context analytics for traces
1345
+ const contextAnalytics = buildContextAnalytics(finalMessages, {
1346
+ tokenCounter: agentContext.tokenCounter,
1347
+ maxContextTokens: agentContext.maxContextTokens,
1348
+ instructionTokens: agentContext.instructionTokens,
1349
+ indexTokenCountMap: agentContext.indexTokenCountMap,
1350
+ });
1351
+
1352
+ // Store for retrieval via getContextAnalytics() after run completes
1353
+ this.lastContextAnalytics = contextAnalytics;
1354
+
1355
+ await safeDispatchCustomEvent(
1356
+ GraphEvents.ON_CONTEXT_ANALYTICS,
1357
+ {
1358
+ provider: agentContext.provider,
1359
+ model: modelId,
1360
+ thinkingEnabled: thinkingConfig != null,
1361
+ cacheEnabled: bedrockOpts?.promptCache === true,
1362
+ analytics: contextAnalytics,
1363
+ },
1364
+ config
1365
+ );
1366
+
1367
+ // ====================================================================
1368
+ // CONTEXT PRESSURE AWARENESS — Intelligent Sub-Agent Delegation
1369
+ //
1370
+ // Two triggers for delegation hints:
1371
+ // 1. DOCUMENT COUNT: When 3+ documents are detected in the conversation,
1372
+ // inject a delegation hint on the FIRST iteration (before the LLM
1373
+ // has called any tools). This ensures the agent delegates upfront
1374
+ // rather than trying to process all documents itself.
1375
+ // 2. TOKEN UTILIZATION: At EVERY iteration, if context is filling up
1376
+ // (70%/85%), inject escalating hints to delegate remaining work.
1377
+ //
1378
+ // This runs mid-chain — so even if tool responses push context up
1379
+ // after the first LLM call, subsequent iterations get the hint.
1380
+ // ====================================================================
1381
+ const hasTaskToolInContext = agentContext.tools?.some(
1382
+ (tool) => {
1383
+ const toolName = typeof tool === 'object' && 'name' in tool
1384
+ ? (tool as { name: string }).name
1385
+ : '';
1386
+ return toolName === 'task';
1387
+ }
1388
+ );
1389
+
1390
+ if (
1391
+ hasTaskToolInContext &&
1392
+ contextAnalytics.utilizationPercent != null &&
1393
+ contextAnalytics.maxContextTokens != null
1394
+ ) {
1395
+ const utilization = contextAnalytics.utilizationPercent;
1396
+ const totalTokens = contextAnalytics.totalTokens;
1397
+ const maxTokens = contextAnalytics.maxContextTokens;
1398
+ const remainingTokens = maxTokens - totalTokens;
1399
+
1400
+ // Count attached documents by scanning for document patterns in HumanMessages:
1401
+ // 1. # "filename" headers in "Attached document(s):" blocks (text content)
1402
+ // 2. **filename1, filename2** in "The user has attached:" blocks (embedded files)
1403
+ // 3. Filenames in file_search tool results
1404
+ let documentCount = 0;
1405
+ const documentNames: string[] = [];
1406
+ for (const msg of finalMessages) {
1407
+ const content = typeof msg.content === 'string'
1408
+ ? msg.content
1409
+ : Array.isArray(msg.content)
1410
+ ? msg.content.map((p: unknown) => {
1411
+ const part = p as Record<string, unknown>;
1412
+ return String(part.text || part.content || '');
1413
+ }).join(' ')
1414
+ : '';
1415
+ // Pattern 1: # "filename" headers in attached document blocks
1416
+ const docMatches = content.match(/# "([^"]+)"/g);
1417
+ if (docMatches) {
1418
+ for (const match of docMatches) {
1419
+ const name = match.replace(/# "/, '').replace(/"$/, '');
1420
+ if (!documentNames.includes(name)) {
1421
+ documentNames.push(name);
1422
+ documentCount++;
1423
+ }
1424
+ }
1425
+ }
1426
+ // Pattern 2: "The user has attached: **file1, file2**" (embedded files)
1427
+ const attachedMatch = content.match(/user has attached:\s*\*\*([^*]+)\*\*/i);
1428
+ if (attachedMatch) {
1429
+ const names = attachedMatch[1].split(',').map((n: string) => n.trim()).filter(Boolean);
1430
+ for (const name of names) {
1431
+ if (!documentNames.includes(name)) {
1432
+ documentNames.push(name);
1433
+ documentCount++;
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+
1439
+ // BASELINE LOG: Always fires so we can verify this code path runs
1440
+ console.info(
1441
+ `[Graph] Context utilization: ${utilization.toFixed(1)}% ` +
1442
+ `(${totalTokens}/${maxTokens} tokens, ${remainingTokens} remaining) | ` +
1443
+ `hasTaskTool: true | messages: ${finalMessages.length} | docs: ${documentCount}`
1444
+ );
1445
+
1446
+ // TRIGGER 1: Multi-document delegation (3+ documents detected)
1447
+ // Only inject on first iteration (no AI messages yet = agent hasn't responded)
1448
+ const hasAiResponse = finalMessages.some(
1449
+ (m) => m._getType?.() === 'ai' || m._getType?.() === 'tool'
1450
+ );
1451
+ if (documentCount >= 3 && !hasAiResponse) {
1452
+ const pressureMsg = new HumanMessage({
1453
+ content: `[MULTI-DOCUMENT PROCESSING — ${documentCount} documents detected]\n` +
1454
+ `Documents: ${documentNames.join(', ')}\n\n` +
1455
+ `You have ${documentCount} documents attached. For thorough analysis, use the "task" tool ` +
1456
+ `to delegate each document (or group of related documents) to a sub-agent.\n` +
1457
+ `Each sub-agent has its own fresh context window and can use file_search to retrieve the full document content.\n` +
1458
+ `After all sub-agents complete, synthesize their results into a comprehensive response.\n\n` +
1459
+ `This approach ensures each document gets full attention without context limitations.`,
1460
+ });
1461
+ finalMessages = [...finalMessages, pressureMsg];
1462
+ console.info(
1463
+ `[Graph] Multi-document delegation hint injected for ${documentCount} documents: ` +
1464
+ `${documentNames.join(', ')}`
1465
+ );
1466
+ }
1467
+
1468
+ // TRIGGER 2: Token utilization thresholds (mid-chain safety net)
1469
+ // Also fires when we skipped pruning due to delegationInjectedPrePrune
1470
+ if (utilization > 85 || (delegationInjectedPrePrune && utilization > 50)) {
1471
+ // CRITICAL: Context is high — MANDATE delegation
1472
+ const pressureMsg = new HumanMessage({
1473
+ content: `[CONTEXT BUDGET CRITICAL — ${utilization.toFixed(0)}% used]\n` +
1474
+ `You have used ${totalTokens} of ${maxTokens} tokens (${remainingTokens} remaining).\n` +
1475
+ `Your context is very large. You MUST use the "task" tool to delegate work to sub-agents.\n` +
1476
+ `Each sub-agent runs in its own fresh context window and can use file_search to access documents.\n` +
1477
+ `Do NOT attempt to process documents directly — delegate each document to a sub-agent, then synthesize results.`,
1478
+ });
1479
+ finalMessages = [...finalMessages, pressureMsg];
1480
+ console.warn(
1481
+ `[Graph] Context pressure CRITICAL (${utilization.toFixed(0)}%): ` +
1482
+ `Injected mandatory delegation hint. ${remainingTokens} tokens remaining. ` +
1483
+ `prePruneSkipped: ${delegationInjectedPrePrune}`
1484
+ );
1485
+ } else if (utilization > 70) {
1486
+ // WARNING: Context filling up — suggest delegation
1487
+ const pressureMsg = new HumanMessage({
1488
+ content: `[CONTEXT BUDGET WARNING — ${utilization.toFixed(0)}% used]\n` +
1489
+ `You have used ${totalTokens} of ${maxTokens} tokens (${remainingTokens} remaining).\n` +
1490
+ `Your context is filling up. Consider using the "task" tool to delegate complex operations to sub-agents.\n` +
1491
+ `Sub-agents run in fresh context windows and won't consume your remaining budget.`,
1492
+ });
1493
+ finalMessages = [...finalMessages, pressureMsg];
1494
+ console.info(
1495
+ `[Graph] Context pressure WARNING (${utilization.toFixed(0)}%): ` +
1496
+ `Injected delegation suggestion. ${remainingTokens} tokens remaining.`
1497
+ );
1498
+ }
1499
+ }
1500
+
1501
+ // Structured output mode: when the agent has NO tools, produce structured JSON immediately.
1502
+ // When the agent HAS tools, we defer structured output until after tool use completes
1503
+ // (see the deferred structured output block after attemptInvoke below).
1504
+ const hasTools = (toolsForBinding?.length ?? 0) > 0;
1505
+ if (
1506
+ agentContext.isStructuredOutputMode &&
1507
+ agentContext.structuredOutput &&
1508
+ !hasTools
1509
+ ) {
1510
+ try {
1511
+ const structuredResult = await this.performStructuredOutput({
1512
+ agentContext,
1513
+ finalMessages,
1514
+ config,
1515
+ });
1516
+ agentContext.currentUsage = this.getUsageMetadata(
1517
+ structuredResult.messages?.[0]
1518
+ );
1519
+ this.cleanupSignalListener();
1520
+ return structuredResult;
1521
+ } catch (structuredError) {
1522
+ console.error('[Graph] Structured output failed:', structuredError);
1523
+ throw structuredError;
1524
+ }
1525
+ }
1526
+
1527
+ try {
1528
+ result = await this.attemptInvoke(
1529
+ {
1530
+ currentModel: model,
1531
+ finalMessages,
1532
+ provider: agentContext.provider,
1533
+ tools: agentContext.tools,
1534
+ },
1535
+ config
1536
+ );
1537
+ } catch (primaryError) {
1538
+ // Check if this is a "input too long" error from Bedrock/Anthropic
1539
+ const errorMessage =
1540
+ (primaryError as Error).message.toLowerCase() ?? '';
1541
+ const isInputTooLongError =
1542
+ errorMessage.includes('too long') ||
1543
+ errorMessage.includes('input is too long') ||
1544
+ errorMessage.includes('context length') ||
1545
+ errorMessage.includes('maximum context') ||
1546
+ errorMessage.includes('validationexception') ||
1547
+ errorMessage.includes('prompt is too long');
1548
+
1549
+ // Log when we detect the error
1550
+ if (isInputTooLongError) {
1551
+ console.warn(
1552
+ '[Graph] Detected input too long error:',
1553
+ errorMessage.substring(0, 200)
1554
+ );
1555
+ console.warn('[Graph] Checking emergency pruning conditions:', {
1556
+ hasPruneMessages: !!agentContext.pruneMessages,
1557
+ hasTokenCounter: !!agentContext.tokenCounter,
1558
+ maxContextTokens: agentContext.maxContextTokens,
1559
+ indexTokenMapKeys: Object.keys(agentContext.indexTokenCountMap)
1560
+ .length,
1561
+ });
1562
+ }
1563
+
1564
+ // If input too long and we have pruning capability OR tokenCounter, retry with progressively more aggressive pruning
1565
+ // Note: We can create emergency pruneMessages dynamically if we have tokenCounter and maxContextTokens
1566
+ const canPrune =
1567
+ agentContext.tokenCounter && agentContext.maxContextTokens;
1568
+ if (isInputTooLongError && canPrune) {
1569
+ // Progressive reduction: 50% -> 25% -> 10% of original context
1570
+ const reductionLevels = [0.5, 0.25, 0.1];
1571
+
1572
+ for (const reductionFactor of reductionLevels) {
1573
+ if (result) break; // Exit if we got a result
1574
+
1575
+ const reducedMaxTokens = Math.floor(
1576
+ agentContext.maxContextTokens! * reductionFactor
1577
+ );
1578
+ console.warn(
1579
+ `[Graph] Input too long. Retrying with ${reductionFactor * 100}% context (${reducedMaxTokens} tokens)...`
1580
+ );
1581
+
1582
+ // Build fresh indexTokenCountMap if missing/incomplete
1583
+ // This is needed when messages were dynamically added without updating the token map
1584
+ let tokenMapForPruning = agentContext.indexTokenCountMap;
1585
+ if (Object.keys(tokenMapForPruning).length < messages.length) {
1586
+ console.warn(
1587
+ '[Graph] Building fresh token count map for emergency pruning...'
1588
+ );
1589
+ tokenMapForPruning = {};
1590
+ for (let i = 0; i < messages.length; i++) {
1591
+ tokenMapForPruning[i] = agentContext.tokenCounter!(messages[i]);
1592
+ }
1593
+ }
1594
+
1595
+ const emergencyPrune = createPruneMessages({
1596
+ startIndex: this.startIndex,
1597
+ provider: agentContext.provider,
1598
+ tokenCounter: agentContext.tokenCounter!,
1599
+ maxTokens: reducedMaxTokens,
1600
+ thinkingEnabled: false, // Disable thinking for emergency prune
1601
+ indexTokenCountMap: tokenMapForPruning,
1602
+ });
1603
+
1604
+ const { context: reducedMessages } = emergencyPrune({
1605
+ messages,
1606
+ usageMetadata: agentContext.currentUsage,
1607
+ });
1608
+
1609
+ // Skip if we can't fit any messages
1610
+ if (reducedMessages.length === 0) {
1611
+ console.warn(
1612
+ `[Graph] Cannot fit any messages at ${reductionFactor * 100}% reduction, trying next level...`
1613
+ );
1614
+ continue;
1615
+ }
1616
+
1617
+ // Calculate how many messages were pruned and estimate context timeframe
1618
+ const prunedCount = finalMessages.length - reducedMessages.length;
1619
+ const remainingCount = reducedMessages.length;
1620
+ const estimatedContextDescription =
1621
+ this.getContextTimeframeDescription(remainingCount);
1622
+
1623
+ // Inject a personalized context message to inform the agent about pruning
1624
+ const pruneNoticeMessage = new HumanMessage({
1625
+ content: `[CONTEXT NOTICE]
1626
+ Our conversation has grown quite long, so I've focused on ${estimatedContextDescription} of our chat (${remainingCount} recent messages). ${prunedCount} earlier messages are no longer in my immediate memory.
1627
+
1628
+ If I seem to be missing something we discussed earlier, just give me a quick reminder and I'll pick right back up! I'm still fully engaged and ready to help with whatever you need.`,
1629
+ });
1630
+
1631
+ // Insert the notice after the system message (if any) but before conversation
1632
+ const hasSystemMessage = reducedMessages[0]?.getType() === 'system';
1633
+ const insertIndex = hasSystemMessage ? 1 : 0;
1634
+
1635
+ // Create new array with the pruning notice
1636
+ const messagesWithNotice = [
1637
+ ...reducedMessages.slice(0, insertIndex),
1638
+ pruneNoticeMessage,
1639
+ ...reducedMessages.slice(insertIndex),
1640
+ ];
1641
+
1642
+ let retryMessages = agentContext.useLegacyContent
1643
+ ? formatContentStrings(messagesWithNotice)
1644
+ : messagesWithNotice;
1645
+
1646
+ // Apply thinking block handling first (before cache control)
1647
+ // This ensures AI+Tool sequences are converted to HumanMessages
1648
+ // before we add cache points that could be lost in the conversion
1649
+ if (isAnthropicWithThinking) {
1650
+ retryMessages = ensureThinkingBlockInMessages(
1651
+ retryMessages,
1652
+ agentContext.provider
1653
+ );
1654
+ }
1655
+
1656
+ // Apply Bedrock cache control if needed (after thinking block handling)
1657
+ if (agentContext.provider === Providers.BEDROCK) {
1658
+ const bedrockOptions = agentContext.clientOptions as
1659
+ | t.BedrockAnthropicClientOptions
1660
+ | undefined;
1661
+ const modelId = bedrockOptions?.model?.toLowerCase() ?? '';
1662
+ const supportsCaching =
1663
+ modelId.includes('claude') ||
1664
+ modelId.includes('anthropic') ||
1665
+ modelId.includes('nova');
1666
+ if (bedrockOptions?.promptCache === true && supportsCaching) {
1667
+ retryMessages =
1668
+ addBedrockCacheControl<BaseMessage>(retryMessages);
1669
+ }
1670
+ }
1671
+
1672
+ try {
1673
+ result = await this.attemptInvoke(
1674
+ {
1675
+ currentModel: model,
1676
+ finalMessages: retryMessages,
1677
+ provider: agentContext.provider,
1678
+ tools: agentContext.tools,
1679
+ },
1680
+ config
1681
+ );
1682
+ // Success with reduced context
1683
+ console.info(
1684
+ `[Graph] ✅ Retry successful at ${reductionFactor * 100}% with ${reducedMessages.length} messages (reduced from ${finalMessages.length})`
1685
+ );
1686
+ } catch (retryError) {
1687
+ const retryErrorMsg =
1688
+ (retryError as Error).message.toLowerCase() ?? '';
1689
+ const stillTooLong =
1690
+ retryErrorMsg.includes('too long') ||
1691
+ retryErrorMsg.includes('context length') ||
1692
+ retryErrorMsg.includes('validationexception');
1693
+
1694
+ if (stillTooLong && reductionFactor > 0.1) {
1695
+ console.warn(
1696
+ `[Graph] Still too long at ${reductionFactor * 100}%, trying more aggressive pruning...`
1697
+ );
1698
+ } else {
1699
+ console.error(
1700
+ `[Graph] Retry at ${reductionFactor * 100}% failed:`,
1701
+ (retryError as Error).message
1702
+ );
1703
+ }
1704
+ }
1705
+ }
1706
+ }
1707
+
1708
+ // If we got a result from retry, skip fallbacks
1709
+ if (result) {
1710
+ // result already set from retry
1711
+ } else {
1712
+ let lastError: unknown = primaryError;
1713
+ for (const fb of fallbacks) {
1714
+ try {
1715
+ let model = this.getNewModel({
1716
+ provider: fb.provider,
1717
+ clientOptions: fb.clientOptions,
1718
+ });
1719
+ const bindableTools = agentContext.tools;
1720
+ model = (
1721
+ !bindableTools || bindableTools.length === 0
1722
+ ? model
1723
+ : model.bindTools(bindableTools)
1724
+ ) as t.ChatModelInstance;
1725
+ result = await this.attemptInvoke(
1726
+ {
1727
+ currentModel: model,
1728
+ finalMessages,
1729
+ provider: fb.provider,
1730
+ tools: agentContext.tools,
1731
+ },
1732
+ config
1733
+ );
1734
+ lastError = undefined;
1735
+ break;
1736
+ } catch (e) {
1737
+ lastError = e;
1738
+ continue;
1739
+ }
1740
+ }
1741
+ if (lastError !== undefined) {
1742
+ throw lastError;
1743
+ }
1744
+ }
1745
+ }
1746
+
1747
+ if (!result) {
1748
+ throw new Error('No result after model invocation');
1749
+ }
1750
+ agentContext.currentUsage = this.getUsageMetadata(result.messages?.[0]);
1751
+
1752
+ // Extract and normalize the LLM's finish/stop reason for auto-continuation support
1753
+ const finalMsg = result.messages?.[0];
1754
+ if (finalMsg && 'response_metadata' in finalMsg) {
1755
+ const meta = finalMsg.response_metadata as Record<string, unknown>;
1756
+ // Bedrock streaming nests stopReason inside messageStop: { stopReason: '...' }
1757
+ const messageStop = meta.messageStop as Record<string, unknown> | undefined;
1758
+ this.lastFinishReason =
1759
+ (meta.finish_reason as string) ?? // OpenAI/Azure
1760
+ (meta.stop_reason as string) ?? // Anthropic direct API
1761
+ (meta.stopReason as string) ?? // Bedrock invoke (non-streaming)
1762
+ (messageStop?.stopReason as string) ?? // Bedrock streaming
1763
+ (meta.finishReason as string) ?? // VertexAI/Google
1764
+ undefined;
1765
+ }
1766
+
1767
+ this.cleanupSignalListener();
1768
+
1769
+ // DEFERRED STRUCTURED OUTPUT: When the agent has tools AND structured output configured,
1770
+ // we let the agent use tools normally via attemptInvoke(). Once the agent's response
1771
+ // has NO tool_calls (it's done with tools), we produce the final structured JSON response.
1772
+ if (
1773
+ agentContext.isStructuredOutputMode &&
1774
+ agentContext.structuredOutput &&
1775
+ result
1776
+ ) {
1777
+ const lastMessage = result.messages?.[0];
1778
+ const resultHasToolCalls =
1779
+ lastMessage &&
1780
+ 'tool_calls' in lastMessage &&
1781
+ ((lastMessage as AIMessageChunk).tool_calls?.length ?? 0) > 0;
1782
+
1783
+ if (!resultHasToolCalls) {
1784
+ try {
1785
+ // Build messages for structured output: include the full conversation
1786
+ // plus the agent's text response from attemptInvoke, so the structured
1787
+ // output model has full context (tool results + agent reasoning).
1788
+ const messagesForStructured = [...finalMessages];
1789
+ if (lastMessage) {
1790
+ messagesForStructured.push(lastMessage);
1791
+ }
1792
+
1793
+ const structuredResult = await this.performStructuredOutput({
1794
+ agentContext,
1795
+ finalMessages: messagesForStructured,
1796
+ config,
1797
+ });
1798
+
1799
+ // Accumulate token usage from both API calls
1800
+ const structuredUsage = this.getUsageMetadata(
1801
+ structuredResult.messages?.[0]
1802
+ );
1803
+ if (structuredUsage && agentContext.currentUsage) {
1804
+ agentContext.currentUsage = {
1805
+ input_tokens:
1806
+ (agentContext.currentUsage.input_tokens ?? 0) +
1807
+ (structuredUsage.input_tokens ?? 0),
1808
+ output_tokens:
1809
+ (agentContext.currentUsage.output_tokens ?? 0) +
1810
+ (structuredUsage.output_tokens ?? 0),
1811
+ total_tokens:
1812
+ (agentContext.currentUsage.total_tokens ?? 0) +
1813
+ (structuredUsage.total_tokens ?? 0),
1814
+ };
1815
+ } else if (structuredUsage) {
1816
+ agentContext.currentUsage = structuredUsage;
1817
+ }
1818
+
1819
+ return structuredResult;
1820
+ } catch (structuredError) {
1821
+ // Graceful fallback: the agent completed its work with tools,
1822
+ // but we couldn't format the output as structured JSON.
1823
+ // Return the unstructured text response from attemptInvoke.
1824
+ console.error(
1825
+ '[Graph] Deferred structured output failed after successful tool use:',
1826
+ structuredError
1827
+ );
1828
+ console.warn(
1829
+ '[Graph] Falling back to unstructured response from tool-use phase'
1830
+ );
1831
+ return result;
1832
+ }
1833
+ }
1834
+ }
1835
+
1836
+ return result;
1837
+ };
1838
+ }
1839
+
1840
+ createAgentNode(agentId: string): t.CompiledAgentWorfklow {
1841
+ const agentContext = this.agentContexts.get(agentId);
1842
+ if (!agentContext) {
1843
+ throw new Error(`Agent context not found for agentId: ${agentId}`);
1844
+ }
1845
+
1846
+ const agentNode = `${AGENT}${agentId}` as const;
1847
+ const toolNode = `${TOOLS}${agentId}` as const;
1848
+
1849
+ const routeMessage = (
1850
+ state: t.BaseGraphState,
1851
+ config?: RunnableConfig
1852
+ ): string => {
1853
+ this.config = config;
1854
+ return toolsCondition(state, toolNode, this.invokedToolIds);
1855
+ };
1856
+
1857
+ const StateAnnotation = Annotation.Root({
1858
+ messages: Annotation<BaseMessage[]>({
1859
+ reducer: messagesStateReducer,
1860
+ default: () => [],
1861
+ }),
1862
+ });
1863
+
1864
+ const workflow = new StateGraph(StateAnnotation)
1865
+ .addNode(agentNode, this.createCallModel(agentId))
1866
+ .addNode(
1867
+ toolNode,
1868
+ this.initializeTools({
1869
+ currentTools: agentContext.tools,
1870
+ currentToolMap: agentContext.toolMap,
1871
+ agentContext,
1872
+ })
1873
+ )
1874
+ .addEdge(START, agentNode)
1875
+ .addConditionalEdges(agentNode, routeMessage)
1876
+ .addEdge(toolNode, agentContext.toolEnd ? END : agentNode);
1877
+
1878
+ // Cast to unknown to avoid tight coupling to external types; options are opt-in
1879
+ return workflow.compile(this.compileOptions as unknown as never);
1880
+ }
1881
+
1882
+ createWorkflow(): t.CompiledStateWorkflow {
1883
+ /** Use the default (first) agent for now */
1884
+ const agentNode = this.createAgentNode(this.defaultAgentId);
1885
+ const StateAnnotation = Annotation.Root({
1886
+ messages: Annotation<BaseMessage[]>({
1887
+ reducer: (a, b) => {
1888
+ if (!a.length) {
1889
+ this.startIndex = a.length + b.length;
1890
+ }
1891
+ const result = messagesStateReducer(a, b);
1892
+ this.messages = result;
1893
+ return result;
1894
+ },
1895
+ default: () => [],
1896
+ }),
1897
+ });
1898
+ const workflow = new StateGraph(StateAnnotation)
1899
+ .addNode(this.defaultAgentId, agentNode, { ends: [END] })
1900
+ .addEdge(START, this.defaultAgentId)
1901
+ .compile();
1902
+
1903
+ return workflow;
1904
+ }
1905
+
1906
+ /**
1907
+ * Indicates if this is a multi-agent graph.
1908
+ * Override in MultiAgentGraph to return true.
1909
+ * Used to conditionally include agentId in RunStep for frontend rendering.
1910
+ */
1911
+ protected isMultiAgentGraph(): boolean {
1912
+ return false;
1913
+ }
1914
+
1915
+ /**
1916
+ * Get the parallel group ID for an agent, if any.
1917
+ * Override in MultiAgentGraph to provide actual group IDs.
1918
+ * Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order.
1919
+ * @param _agentId - The agent ID to look up
1920
+ * @returns undefined for StandardGraph (no parallel groups), or group number for MultiAgentGraph
1921
+ */
1922
+ protected getParallelGroupIdForAgent(_agentId: string): number | undefined {
1923
+ return undefined;
1924
+ }
1925
+
1926
+ /* Dispatchers */
1927
+
1928
+ /**
1929
+ * Dispatches a run step to the client, returns the step ID
1930
+ */
1931
+ async dispatchRunStep(
1932
+ stepKey: string,
1933
+ stepDetails: t.StepDetails,
1934
+ metadata?: Record<string, unknown>
1935
+ ): Promise<string> {
1936
+ if (!this.config) {
1937
+ throw new Error('No config provided');
1938
+ }
1939
+
1940
+ const [stepId, stepIndex] = this.generateStepId(stepKey);
1941
+ if (stepDetails.type === StepTypes.TOOL_CALLS && stepDetails.tool_calls) {
1942
+ for (const tool_call of stepDetails.tool_calls) {
1943
+ const toolCallId = tool_call.id ?? '';
1944
+ if (!toolCallId || this.toolCallStepIds.has(toolCallId)) {
1945
+ continue;
1946
+ }
1947
+ this.toolCallStepIds.set(toolCallId, stepId);
1948
+ }
1949
+ }
1950
+
1951
+ const runStep: t.RunStep = {
1952
+ stepIndex,
1953
+ id: stepId,
1954
+ type: stepDetails.type,
1955
+ index: this.contentData.length,
1956
+ stepDetails,
1957
+ usage: null,
1958
+ };
1959
+
1960
+ const runId = this.runId ?? '';
1961
+ if (runId) {
1962
+ runStep.runId = runId;
1963
+ }
1964
+
1965
+ /**
1966
+ * Extract agentId and parallelGroupId from metadata
1967
+ * Only set agentId for MultiAgentGraph (so frontend knows when to show agent labels)
1968
+ */
1969
+ if (metadata) {
1970
+ try {
1971
+ const agentContext = this.getAgentContext(metadata);
1972
+ if (this.isMultiAgentGraph() && agentContext.agentId) {
1973
+ // Only include agentId for MultiAgentGraph - enables frontend to show agent labels
1974
+ runStep.agentId = agentContext.agentId;
1975
+ // Set group ID if this agent is part of a parallel group
1976
+ // Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order
1977
+ const groupId = this.getParallelGroupIdForAgent(agentContext.agentId);
1978
+ if (groupId != null) {
1979
+ runStep.groupId = groupId;
1980
+ }
1981
+ }
1982
+ } catch (_e) {
1983
+ /** If we can't get agent context, that's okay - agentId remains undefined */
1984
+ }
1985
+ }
1986
+
1987
+ this.contentData.push(runStep);
1988
+ this.contentIndexMap.set(stepId, runStep.index);
1989
+ await safeDispatchCustomEvent(
1990
+ GraphEvents.ON_RUN_STEP,
1991
+ runStep,
1992
+ this.config
1993
+ );
1994
+ return stepId;
1995
+ }
1996
+
1997
+ async handleToolCallCompleted(
1998
+ data: t.ToolEndData,
1999
+ metadata?: Record<string, unknown>,
2000
+ omitOutput?: boolean
2001
+ ): Promise<void> {
2002
+ if (!this.config) {
2003
+ throw new Error('No config provided');
2004
+ }
2005
+
2006
+ if (!data.output) {
2007
+ return;
2008
+ }
2009
+
2010
+ const { input, output: _output } = data;
2011
+ if ((_output as Command | undefined)?.lg_name === 'Command') {
2012
+ return;
2013
+ }
2014
+ const output = _output as ToolMessage;
2015
+ const { tool_call_id } = output;
2016
+ const stepId = this.toolCallStepIds.get(tool_call_id) ?? '';
2017
+ if (!stepId) {
2018
+ throw new Error(`No stepId found for tool_call_id ${tool_call_id}`);
2019
+ }
2020
+
2021
+ const runStep = this.getRunStep(stepId);
2022
+ if (!runStep) {
2023
+ throw new Error(`No run step found for stepId ${stepId}`);
2024
+ }
2025
+
2026
+ /**
2027
+ * Extract and store code execution session context from artifacts.
2028
+ * Each file is stamped with its source session_id to support multi-session file tracking.
2029
+ * When the same filename appears in a later execution, the newer version replaces the old.
2030
+ */
2031
+ const toolName = output.name;
2032
+ if (
2033
+ toolName === Constants.EXECUTE_CODE ||
2034
+ toolName === Constants.PROGRAMMATIC_TOOL_CALLING
2035
+ ) {
2036
+ const artifact = output.artifact as t.CodeExecutionArtifact | undefined;
2037
+ const newFiles = artifact?.files ?? [];
2038
+ const hasNewFiles = newFiles.length > 0;
2039
+
2040
+ if (
2041
+ hasNewFiles &&
2042
+ artifact?.session_id != null &&
2043
+ artifact.session_id !== ''
2044
+ ) {
2045
+ /**
2046
+ * Stamp each new file with its source session_id.
2047
+ * This enables files from different executions (parallel or sequential)
2048
+ * to be tracked and passed to subsequent calls.
2049
+ */
2050
+ const filesWithSession: t.FileRefs = newFiles.map((file) => ({
2051
+ ...file,
2052
+ session_id: artifact.session_id,
2053
+ }));
2054
+
2055
+ const existingSession = this.sessions.get(Constants.EXECUTE_CODE) as
2056
+ | t.CodeSessionContext
2057
+ | undefined;
2058
+ const existingFiles = existingSession?.files ?? [];
2059
+
2060
+ /**
2061
+ * Merge files, preferring latest versions by name.
2062
+ * If a file with the same name exists, replace it with the new version.
2063
+ * This handles cases where files are edited/recreated in subsequent executions.
2064
+ */
2065
+ const newFileNames = new Set(filesWithSession.map((f) => f.name));
2066
+ const filteredExisting = existingFiles.filter(
2067
+ (f) => !newFileNames.has(f.name)
2068
+ );
2069
+
2070
+ this.sessions.set(Constants.EXECUTE_CODE, {
2071
+ /** Keep latest session_id for reference/fallback */
2072
+ session_id: artifact.session_id,
2073
+ /** Accumulated files with latest versions preferred */
2074
+ files: [...filteredExisting, ...filesWithSession],
2075
+ lastUpdated: Date.now(),
2076
+ });
2077
+ }
2078
+ }
2079
+
2080
+ const dispatchedOutput =
2081
+ typeof output.content === 'string'
2082
+ ? output.content
2083
+ : JSON.stringify(output.content);
2084
+
2085
+ const args = typeof input === 'string' ? input : input.input;
2086
+ const tool_call = {
2087
+ args: typeof args === 'string' ? args : JSON.stringify(args),
2088
+ name: output.name ?? '',
2089
+ id: output.tool_call_id,
2090
+ output: omitOutput === true ? '' : dispatchedOutput,
2091
+ progress: 1,
2092
+ };
2093
+
2094
+ await this.handlerRegistry
2095
+ ?.getHandler(GraphEvents.ON_RUN_STEP_COMPLETED)
2096
+ ?.handle(
2097
+ GraphEvents.ON_RUN_STEP_COMPLETED,
2098
+ {
2099
+ result: {
2100
+ id: stepId,
2101
+ index: runStep.index,
2102
+ type: 'tool_call',
2103
+ tool_call,
2104
+ } as t.ToolCompleteEvent,
2105
+ },
2106
+ metadata,
2107
+ this
2108
+ );
2109
+ }
2110
+ /**
2111
+ * Static version of handleToolCallError to avoid creating strong references
2112
+ * that prevent garbage collection
2113
+ */
2114
+ static async handleToolCallErrorStatic(
2115
+ graph: StandardGraph,
2116
+ data: t.ToolErrorData,
2117
+ metadata?: Record<string, unknown>
2118
+ ): Promise<void> {
2119
+ if (!graph.config) {
2120
+ throw new Error('No config provided');
2121
+ }
2122
+
2123
+ if (!data.id) {
2124
+ console.warn('No Tool ID provided for Tool Error');
2125
+ return;
2126
+ }
2127
+
2128
+ const stepId = graph.toolCallStepIds.get(data.id) ?? '';
2129
+ if (!stepId) {
2130
+ throw new Error(`No stepId found for tool_call_id ${data.id}`);
2131
+ }
2132
+
2133
+ const { name, input: args, error } = data;
2134
+
2135
+ const runStep = graph.getRunStep(stepId);
2136
+ if (!runStep) {
2137
+ throw new Error(`No run step found for stepId ${stepId}`);
2138
+ }
2139
+
2140
+ const tool_call: t.ProcessedToolCall = {
2141
+ id: data.id,
2142
+ name: name || '',
2143
+ args: typeof args === 'string' ? args : JSON.stringify(args),
2144
+ output: `Error processing tool${error?.message != null ? `: ${error.message}` : ''}`,
2145
+ progress: 1,
2146
+ };
2147
+
2148
+ await graph.handlerRegistry
2149
+ ?.getHandler(GraphEvents.ON_RUN_STEP_COMPLETED)
2150
+ ?.handle(
2151
+ GraphEvents.ON_RUN_STEP_COMPLETED,
2152
+ {
2153
+ result: {
2154
+ id: stepId,
2155
+ index: runStep.index,
2156
+ type: 'tool_call',
2157
+ tool_call,
2158
+ } as t.ToolCompleteEvent,
2159
+ },
2160
+ metadata,
2161
+ graph
2162
+ );
2163
+ }
2164
+
2165
+ /**
2166
+ * Instance method that delegates to the static method
2167
+ * Kept for backward compatibility
2168
+ */
2169
+ async handleToolCallError(
2170
+ data: t.ToolErrorData,
2171
+ metadata?: Record<string, unknown>
2172
+ ): Promise<void> {
2173
+ await StandardGraph.handleToolCallErrorStatic(this, data, metadata);
2174
+ }
2175
+
2176
+ async dispatchRunStepDelta(
2177
+ id: string,
2178
+ delta: t.ToolCallDelta
2179
+ ): Promise<void> {
2180
+ if (!this.config) {
2181
+ throw new Error('No config provided');
2182
+ } else if (!id) {
2183
+ throw new Error('No step ID found');
2184
+ }
2185
+ const runStepDelta: t.RunStepDeltaEvent = {
2186
+ id,
2187
+ delta,
2188
+ };
2189
+ await safeDispatchCustomEvent(
2190
+ GraphEvents.ON_RUN_STEP_DELTA,
2191
+ runStepDelta,
2192
+ this.config
2193
+ );
2194
+ }
2195
+
2196
+ async dispatchMessageDelta(id: string, delta: t.MessageDelta): Promise<void> {
2197
+ if (!this.config) {
2198
+ throw new Error('No config provided');
2199
+ }
2200
+ const messageDelta: t.MessageDeltaEvent = {
2201
+ id,
2202
+ delta,
2203
+ };
2204
+ await safeDispatchCustomEvent(
2205
+ GraphEvents.ON_MESSAGE_DELTA,
2206
+ messageDelta,
2207
+ this.config
2208
+ );
2209
+ }
2210
+
2211
+ dispatchReasoningDelta = async (
2212
+ stepId: string,
2213
+ delta: t.ReasoningDelta
2214
+ ): Promise<void> => {
2215
+ if (!this.config) {
2216
+ throw new Error('No config provided');
2217
+ }
2218
+ const reasoningDelta: t.ReasoningDeltaEvent = {
2219
+ id: stepId,
2220
+ delta,
2221
+ };
2222
+ await safeDispatchCustomEvent(
2223
+ GraphEvents.ON_REASONING_DELTA,
2224
+ reasoningDelta,
2225
+ this.config
2226
+ );
2227
+ };
2228
+ }