@illuma-ai/agents 1.5.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +0 -62
  2. package/dist/cjs/agents/AgentContext.cjs +160 -259
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/common/enum.cjs +12 -12
  5. package/dist/cjs/common/enum.cjs.map +1 -1
  6. package/dist/cjs/graphs/Graph.cjs +30 -13
  7. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  10. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +1 -1
  11. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -1
  12. package/dist/cjs/hooks/HookRegistry.cjs +1 -1
  13. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  14. package/dist/cjs/hooks/matchers.cjs +2 -2
  15. package/dist/cjs/hooks/matchers.cjs.map +1 -1
  16. package/dist/cjs/hooks/types.cjs +1 -1
  17. package/dist/cjs/hooks/types.cjs.map +1 -1
  18. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +1 -5
  19. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  20. package/dist/cjs/llm/bedrock/index.cjs +33 -61
  21. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  22. package/dist/cjs/llm/openai/index.cjs +1 -1
  23. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  24. package/dist/cjs/llm/openai/utils/index.cjs +10 -27
  25. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  26. package/dist/cjs/main.cjs +3 -84
  27. package/dist/cjs/main.cjs.map +1 -1
  28. package/dist/cjs/memory/citations.cjs +4 -4
  29. package/dist/cjs/memory/citations.cjs.map +1 -1
  30. package/dist/cjs/memory/constants.cjs +17 -17
  31. package/dist/cjs/memory/constants.cjs.map +1 -1
  32. package/dist/cjs/memory/mmr.cjs +1 -1
  33. package/dist/cjs/memory/mmr.cjs.map +1 -1
  34. package/dist/cjs/memory/paths.cjs +1 -1
  35. package/dist/cjs/memory/paths.cjs.map +1 -1
  36. package/dist/cjs/memory/recallTracking.cjs +3 -3
  37. package/dist/cjs/memory/recallTracking.cjs.map +1 -1
  38. package/dist/cjs/memory/temporalDecay.cjs +2 -2
  39. package/dist/cjs/memory/temporalDecay.cjs.map +1 -1
  40. package/dist/cjs/messages/cache.cjs +0 -89
  41. package/dist/cjs/messages/cache.cjs.map +1 -1
  42. package/dist/cjs/messages/format.cjs +13 -71
  43. package/dist/cjs/messages/format.cjs.map +1 -1
  44. package/dist/cjs/tools/BashExecutor.cjs +11 -21
  45. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  46. package/dist/cjs/tools/CodeExecutor.cjs +13 -41
  47. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  48. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +11 -16
  49. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  50. package/dist/cjs/tools/ToolNode.cjs +78 -13
  51. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  52. package/dist/cjs/tools/memory/memoryAppendTool.cjs +1 -1
  53. package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -1
  54. package/dist/cjs/tools/memory/memoryGetTool.cjs +2 -2
  55. package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -1
  56. package/dist/cjs/tools/memory/memorySearchTool.cjs +3 -3
  57. package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -1
  58. package/dist/cjs/tools/memory/shared.cjs +1 -1
  59. package/dist/cjs/tools/memory/shared.cjs.map +1 -1
  60. package/dist/cjs/tools/search/search.cjs +3 -11
  61. package/dist/cjs/tools/search/search.cjs.map +1 -1
  62. package/dist/cjs/tools/search/tool.cjs +4 -28
  63. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  64. package/dist/cjs/tools/search/utils.cjs +3 -10
  65. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  66. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +48 -0
  67. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  68. package/dist/cjs/types/graph.cjs.map +1 -1
  69. package/dist/esm/agents/AgentContext.mjs +160 -259
  70. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  71. package/dist/esm/common/enum.mjs +12 -12
  72. package/dist/esm/common/enum.mjs.map +1 -1
  73. package/dist/esm/graphs/Graph.mjs +30 -13
  74. package/dist/esm/graphs/Graph.mjs.map +1 -1
  75. package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
  76. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  77. package/dist/esm/graphs/phases/memoryFlushPhase.mjs +1 -1
  78. package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -1
  79. package/dist/esm/hooks/HookRegistry.mjs +1 -1
  80. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  81. package/dist/esm/hooks/matchers.mjs +2 -2
  82. package/dist/esm/hooks/matchers.mjs.map +1 -1
  83. package/dist/esm/hooks/types.mjs +1 -1
  84. package/dist/esm/hooks/types.mjs.map +1 -1
  85. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -5
  86. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  87. package/dist/esm/llm/bedrock/index.mjs +34 -61
  88. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  89. package/dist/esm/llm/openai/index.mjs +1 -1
  90. package/dist/esm/llm/openai/index.mjs.map +1 -1
  91. package/dist/esm/llm/openai/utils/index.mjs +10 -27
  92. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  93. package/dist/esm/main.mjs +1 -5
  94. package/dist/esm/main.mjs.map +1 -1
  95. package/dist/esm/memory/citations.mjs +4 -4
  96. package/dist/esm/memory/citations.mjs.map +1 -1
  97. package/dist/esm/memory/constants.mjs +17 -17
  98. package/dist/esm/memory/constants.mjs.map +1 -1
  99. package/dist/esm/memory/mmr.mjs +1 -1
  100. package/dist/esm/memory/mmr.mjs.map +1 -1
  101. package/dist/esm/memory/paths.mjs +1 -1
  102. package/dist/esm/memory/paths.mjs.map +1 -1
  103. package/dist/esm/memory/recallTracking.mjs +3 -3
  104. package/dist/esm/memory/recallTracking.mjs.map +1 -1
  105. package/dist/esm/memory/temporalDecay.mjs +2 -2
  106. package/dist/esm/memory/temporalDecay.mjs.map +1 -1
  107. package/dist/esm/messages/cache.mjs +0 -89
  108. package/dist/esm/messages/cache.mjs.map +1 -1
  109. package/dist/esm/messages/format.mjs +13 -71
  110. package/dist/esm/messages/format.mjs.map +1 -1
  111. package/dist/esm/tools/BashExecutor.mjs +12 -22
  112. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  113. package/dist/esm/tools/CodeExecutor.mjs +14 -41
  114. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  115. package/dist/esm/tools/ProgrammaticToolCalling.mjs +12 -17
  116. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  117. package/dist/esm/tools/ToolNode.mjs +78 -13
  118. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  119. package/dist/esm/tools/memory/memoryAppendTool.mjs +1 -1
  120. package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -1
  121. package/dist/esm/tools/memory/memoryGetTool.mjs +2 -2
  122. package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -1
  123. package/dist/esm/tools/memory/memorySearchTool.mjs +3 -3
  124. package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -1
  125. package/dist/esm/tools/memory/shared.mjs +1 -1
  126. package/dist/esm/tools/memory/shared.mjs.map +1 -1
  127. package/dist/esm/tools/search/search.mjs +3 -11
  128. package/dist/esm/tools/search/search.mjs.map +1 -1
  129. package/dist/esm/tools/search/tool.mjs +4 -28
  130. package/dist/esm/tools/search/tool.mjs.map +1 -1
  131. package/dist/esm/tools/search/utils.mjs +3 -10
  132. package/dist/esm/tools/search/utils.mjs.map +1 -1
  133. package/dist/esm/tools/subagent/SubagentExecutor.mjs +48 -0
  134. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  135. package/dist/esm/types/graph.mjs.map +1 -1
  136. package/dist/types/agents/AgentContext.d.ts +25 -95
  137. package/dist/types/common/enum.d.ts +12 -12
  138. package/dist/types/graphs/Graph.d.ts +2 -2
  139. package/dist/types/graphs/phases/memoryFlushPhase.d.ts +2 -2
  140. package/dist/types/hooks/HookRegistry.d.ts +1 -1
  141. package/dist/types/hooks/matchers.d.ts +2 -2
  142. package/dist/types/hooks/types.d.ts +1 -1
  143. package/dist/types/index.d.ts +0 -1
  144. package/dist/types/llm/bedrock/index.d.ts +1 -54
  145. package/dist/types/llm/openai/index.d.ts +1 -1
  146. package/dist/types/memory/citations.d.ts +4 -4
  147. package/dist/types/memory/constants.d.ts +17 -17
  148. package/dist/types/memory/mmr.d.ts +3 -3
  149. package/dist/types/memory/paths.d.ts +1 -1
  150. package/dist/types/memory/temporalDecay.d.ts +2 -2
  151. package/dist/types/memory/types.d.ts +3 -3
  152. package/dist/types/messages/format.d.ts +2 -5
  153. package/dist/types/tools/CodeExecutor.d.ts +0 -6
  154. package/dist/types/tools/ToolNode.d.ts +3 -3
  155. package/dist/types/tools/memory/shared.d.ts +1 -1
  156. package/dist/types/tools/search/test.d.ts +1 -0
  157. package/dist/types/tools/search/types.d.ts +5 -99
  158. package/dist/types/tools/search/utils.d.ts +2 -2
  159. package/dist/types/tools/subagent/SubagentExecutor.d.ts +29 -0
  160. package/dist/types/types/graph.d.ts +30 -34
  161. package/dist/types/types/index.d.ts +0 -1
  162. package/dist/types/types/messages.d.ts +1 -1
  163. package/dist/types/types/run.d.ts +1 -3
  164. package/dist/types/types/tools.d.ts +5 -14
  165. package/package.json +1 -61
  166. package/src/agents/AgentContext.test.ts +176 -0
  167. package/src/agents/AgentContext.ts +179 -305
  168. package/src/agents/__tests__/AgentContext.test.ts +0 -632
  169. package/src/common/__tests__/enum.test.ts +1 -1
  170. package/src/common/enum.ts +12 -12
  171. package/src/graphs/Graph.ts +32 -13
  172. package/src/graphs/MultiAgentGraph.ts +1 -1
  173. package/src/graphs/gapFeatures.test.ts +1 -1
  174. package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +1 -1
  175. package/src/graphs/phases/memoryFlushPhase.ts +2 -2
  176. package/src/hooks/HookRegistry.ts +1 -1
  177. package/src/hooks/index.ts +1 -1
  178. package/src/hooks/matchers.ts +2 -2
  179. package/src/hooks/types.ts +1 -1
  180. package/src/index.ts +0 -6
  181. package/src/llm/anthropic/utils/message_inputs.ts +1 -10
  182. package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +18 -166
  183. package/src/llm/bedrock/index.ts +41 -116
  184. package/src/llm/openai/index.ts +2 -2
  185. package/src/llm/openai/utils/index.ts +14 -31
  186. package/src/memory/citations.ts +4 -4
  187. package/src/memory/constants.ts +17 -17
  188. package/src/memory/mmr.ts +3 -3
  189. package/src/memory/paths.ts +1 -1
  190. package/src/memory/recallTracking.ts +3 -3
  191. package/src/memory/temporalDecay.ts +2 -2
  192. package/src/memory/types.ts +3 -3
  193. package/src/messages/cache.test.ts +24 -62
  194. package/src/messages/cache.ts +0 -112
  195. package/src/messages/ensureThinkingBlock.test.ts +1 -1
  196. package/src/messages/format.ts +13 -92
  197. package/src/messages/formatAgentMessages.test.ts +1 -1
  198. package/src/scripts/subagent-configurable-inheritance.ts +263 -0
  199. package/src/scripts/subagent-event-driven-debug.ts +2 -2
  200. package/src/specs/anthropic.simple.test.ts +0 -61
  201. package/src/specs/prune.orphans.test.ts +1 -1
  202. package/src/tools/BashExecutor.ts +13 -37
  203. package/src/tools/CodeExecutor.ts +14 -59
  204. package/src/tools/ProgrammaticToolCalling.ts +14 -29
  205. package/src/tools/ToolNode.ts +75 -14
  206. package/src/tools/__tests__/CodeExecutor.test.ts +3 -3
  207. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -60
  208. package/src/tools/__tests__/SubagentExecutor.test.ts +157 -0
  209. package/src/tools/memory/memoryAppendTool.ts +1 -1
  210. package/src/tools/memory/memoryGetTool.ts +2 -2
  211. package/src/tools/memory/memorySearchTool.ts +3 -3
  212. package/src/tools/memory/shared.ts +1 -1
  213. package/src/tools/search/output.md +2775 -0
  214. package/src/tools/search/search.ts +2 -12
  215. package/src/tools/search/test.html +884 -0
  216. package/src/tools/search/test.md +643 -0
  217. package/src/tools/search/test.ts +159 -0
  218. package/src/tools/search/tool.ts +2 -36
  219. package/src/tools/search/types.ts +8 -133
  220. package/src/tools/search/utils.ts +5 -13
  221. package/src/tools/subagent/SubagentExecutor.ts +78 -0
  222. package/src/types/graph.ts +27 -34
  223. package/src/types/index.ts +0 -1
  224. package/src/types/messages.ts +1 -1
  225. package/src/types/run.ts +1 -3
  226. package/src/types/tools.ts +5 -14
  227. package/dist/cjs/langchain/google-common.cjs +0 -3
  228. package/dist/cjs/langchain/google-common.cjs.map +0 -1
  229. package/dist/cjs/langchain/index.cjs +0 -86
  230. package/dist/cjs/langchain/index.cjs.map +0 -1
  231. package/dist/cjs/langchain/language_models/chat_models.cjs +0 -3
  232. package/dist/cjs/langchain/language_models/chat_models.cjs.map +0 -1
  233. package/dist/cjs/langchain/messages/tool.cjs +0 -3
  234. package/dist/cjs/langchain/messages/tool.cjs.map +0 -1
  235. package/dist/cjs/langchain/messages.cjs +0 -51
  236. package/dist/cjs/langchain/messages.cjs.map +0 -1
  237. package/dist/cjs/langchain/openai.cjs +0 -3
  238. package/dist/cjs/langchain/openai.cjs.map +0 -1
  239. package/dist/cjs/langchain/prompts.cjs +0 -11
  240. package/dist/cjs/langchain/prompts.cjs.map +0 -1
  241. package/dist/cjs/langchain/runnables.cjs +0 -19
  242. package/dist/cjs/langchain/runnables.cjs.map +0 -1
  243. package/dist/cjs/langchain/tools.cjs +0 -23
  244. package/dist/cjs/langchain/tools.cjs.map +0 -1
  245. package/dist/cjs/langchain/utils/env.cjs +0 -11
  246. package/dist/cjs/langchain/utils/env.cjs.map +0 -1
  247. package/dist/cjs/llm/bedrock/cacheSupport.cjs +0 -55
  248. package/dist/cjs/llm/bedrock/cacheSupport.cjs.map +0 -1
  249. package/dist/cjs/tools/search/tavily-scraper.cjs +0 -189
  250. package/dist/cjs/tools/search/tavily-scraper.cjs.map +0 -1
  251. package/dist/cjs/tools/search/tavily-search.cjs +0 -372
  252. package/dist/cjs/tools/search/tavily-search.cjs.map +0 -1
  253. package/dist/cjs/types/agent-cache.cjs +0 -54
  254. package/dist/cjs/types/agent-cache.cjs.map +0 -1
  255. package/dist/esm/langchain/google-common.mjs +0 -2
  256. package/dist/esm/langchain/google-common.mjs.map +0 -1
  257. package/dist/esm/langchain/index.mjs +0 -5
  258. package/dist/esm/langchain/index.mjs.map +0 -1
  259. package/dist/esm/langchain/language_models/chat_models.mjs +0 -2
  260. package/dist/esm/langchain/language_models/chat_models.mjs.map +0 -1
  261. package/dist/esm/langchain/messages/tool.mjs +0 -2
  262. package/dist/esm/langchain/messages/tool.mjs.map +0 -1
  263. package/dist/esm/langchain/messages.mjs +0 -2
  264. package/dist/esm/langchain/messages.mjs.map +0 -1
  265. package/dist/esm/langchain/openai.mjs +0 -2
  266. package/dist/esm/langchain/openai.mjs.map +0 -1
  267. package/dist/esm/langchain/prompts.mjs +0 -2
  268. package/dist/esm/langchain/prompts.mjs.map +0 -1
  269. package/dist/esm/langchain/runnables.mjs +0 -2
  270. package/dist/esm/langchain/runnables.mjs.map +0 -1
  271. package/dist/esm/langchain/tools.mjs +0 -2
  272. package/dist/esm/langchain/tools.mjs.map +0 -1
  273. package/dist/esm/langchain/utils/env.mjs +0 -2
  274. package/dist/esm/langchain/utils/env.mjs.map +0 -1
  275. package/dist/esm/llm/bedrock/cacheSupport.mjs +0 -52
  276. package/dist/esm/llm/bedrock/cacheSupport.mjs.map +0 -1
  277. package/dist/esm/tools/search/tavily-scraper.mjs +0 -186
  278. package/dist/esm/tools/search/tavily-scraper.mjs.map +0 -1
  279. package/dist/esm/tools/search/tavily-search.mjs +0 -370
  280. package/dist/esm/tools/search/tavily-search.mjs.map +0 -1
  281. package/dist/esm/types/agent-cache.mjs +0 -52
  282. package/dist/esm/types/agent-cache.mjs.map +0 -1
  283. package/dist/types/langchain/google-common.d.ts +0 -1
  284. package/dist/types/langchain/index.d.ts +0 -8
  285. package/dist/types/langchain/language_models/chat_models.d.ts +0 -1
  286. package/dist/types/langchain/messages/tool.d.ts +0 -1
  287. package/dist/types/langchain/messages.d.ts +0 -2
  288. package/dist/types/langchain/openai.d.ts +0 -1
  289. package/dist/types/langchain/prompts.d.ts +0 -1
  290. package/dist/types/langchain/runnables.d.ts +0 -2
  291. package/dist/types/langchain/tools.d.ts +0 -2
  292. package/dist/types/langchain/utils/env.d.ts +0 -1
  293. package/dist/types/llm/bedrock/cacheSupport.d.ts +0 -35
  294. package/dist/types/tools/search/tavily-scraper.d.ts +0 -19
  295. package/dist/types/tools/search/tavily-search.d.ts +0 -4
  296. package/dist/types/tools/subagent/types.d.ts +0 -84
  297. package/dist/types/types/agent-cache.d.ts +0 -71
  298. package/src/agents/__tests__/AgentContext.cacheTtl.live.test.ts +0 -259
  299. package/src/agents/__tests__/AgentContext.crossAgentTier1.live.test.ts +0 -266
  300. package/src/agents/__tests__/AgentContext.crossUserCache.live.test.ts +0 -342
  301. package/src/langchain/google-common.ts +0 -1
  302. package/src/langchain/index.ts +0 -8
  303. package/src/langchain/language_models/chat_models.ts +0 -1
  304. package/src/langchain/messages/tool.ts +0 -5
  305. package/src/langchain/messages.ts +0 -21
  306. package/src/langchain/openai.ts +0 -1
  307. package/src/langchain/prompts.ts +0 -1
  308. package/src/langchain/runnables.ts +0 -7
  309. package/src/langchain/tools.ts +0 -8
  310. package/src/langchain/utils/env.ts +0 -1
  311. package/src/llm/anthropic/utils/server-tool-inputs.test.ts +0 -436
  312. package/src/llm/bedrock/cacheSupport.test.ts +0 -99
  313. package/src/llm/bedrock/cacheSupport.ts +0 -53
  314. package/src/tools/search/tavily-scraper.ts +0 -235
  315. package/src/tools/search/tavily-search.ts +0 -424
  316. package/src/tools/search/tavily.test.ts +0 -965
  317. package/src/tools/subagent/types.test.ts +0 -70
  318. package/src/tools/subagent/types.ts +0 -115
  319. package/src/types/agent-cache.ts +0 -74
@@ -376,67 +376,6 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
376
376
  );
377
377
  });
378
378
 
379
- test(`${capitalizeFirstLetter(provider)}: follow-up after assistant message with only whitespace text content`, async () => {
380
- /**
381
- * Regression for discussion #12806.
382
- *
383
- * The Anthropic API has two distinct rejection rules (verified against
384
- * the live API):
385
- * 1. Strict empty `text: ''` → rejected anywhere
386
- * "messages: text content blocks must be non-empty"
387
- * 2. Whitespace-only `text: ' '` / '\n' / '\t' → rejected when the
388
- * assistant message has no other accepted blocks (no tool blocks,
389
- * no non-whitespace text)
390
- * "messages: text content blocks must contain non-whitespace text"
391
- *
392
- * Anthropic responses for some prompts include a whitespace-only text
393
- * block as the sole text content. Re-sending that history on a
394
- * follow-up turn triggers rule 2.
395
- *
396
- * The wire-send filter in `_formatContent` must drop any text block
397
- * whose trimmed content is empty. The previous filter used strict
398
- * `text === ''` only, which caught rule 1 but not rule 2.
399
- */
400
- const llmConfig = getLLMConfig(provider);
401
- const customHandlers1 = setupCustomHandlers();
402
-
403
- const followUpRun = await Run.create<t.IState>({
404
- runId: 'repro-12806-followup',
405
- graphConfig: {
406
- type: 'standard',
407
- llmConfig,
408
- instructions: 'You are a friendly AI assistant.',
409
- },
410
- returnContent: true,
411
- skipCleanup: true,
412
- customHandlers: customHandlers1,
413
- });
414
-
415
- // Build history with an assistant message whose entire content array
416
- // is a single whitespace-only text block. This is the precise shape
417
- // the API rejects under rule 2 above.
418
- conversationHistory = [
419
- new HumanMessage('hi'),
420
- new (require('@langchain/core/messages').AIMessage)({
421
- content: [{ type: 'text', text: ' ' }],
422
- }),
423
- new HumanMessage('please respond with a short greeting'),
424
- ];
425
-
426
- // With the fix: `_formatContent` drops the whitespace text block,
427
- // the assistant content becomes an empty array, and the API accepts.
428
- // Without the fix: the whitespace block is forwarded and the API
429
- // rejects with "messages: text content blocks must contain non-whitespace text".
430
- const finalContentParts = await followUpRun.processStream(
431
- { messages: conversationHistory },
432
- config
433
- );
434
- expect(finalContentParts).toBeDefined();
435
- const finalMessages = followUpRun.getRunMessages();
436
- expect(finalMessages).toBeDefined();
437
- expect(finalMessages?.length).toBeGreaterThan(0);
438
- });
439
-
440
379
  test('should handle errors appropriately', async () => {
441
380
  // Test error scenarios
442
381
  await expect(async () => {
@@ -1,4 +1,4 @@
1
- // Orphan tool message repair + sanitize coverage. Ported from the reference implementation's
1
+ // Orphan tool message repair + sanitize coverage. Ported from upstream's
2
2
  // `prune.test.ts` "Tool Message Handling" + top-level `sanitizeOrphanToolBlocks`
3
3
  // describes — kept in a dedicated file to keep the diff against illuma's
4
4
  // existing prune.test.ts narrow.
@@ -4,23 +4,17 @@ import { HttpsProxyAgent } from 'https-proxy-agent';
4
4
  import { getEnvironmentVariable } from '@langchain/core/utils/env';
5
5
  import { tool, DynamicStructuredTool } from '@langchain/core/tools';
6
6
  import type * as t from '@/types';
7
- import { getCodeBaseURL, renderFileSection } from './CodeExecutor';
7
+ import { imageExtRegex, getCodeBaseURL } from './CodeExecutor';
8
8
  import { Constants, EnvVar } from '@/common';
9
9
 
10
10
  config();
11
11
 
12
+ const imageMessage = 'Image is already displayed to the user';
12
13
  const otherMessage = 'File is already downloaded by the user';
13
- const inheritedFileMessage =
14
- 'Available as an input — already known to the user';
15
14
  const accessMessage =
16
15
  'Note: Files from previous executions are automatically available and can be modified.';
17
16
  const emptyOutputMessage =
18
17
  "stdout: Empty. Ensure you're writing output explicitly.\n";
19
- const inheritedFilesHeader =
20
- 'Available files (inputs, not generated by this execution):';
21
- const generatedFilesHeader = 'Generated files:';
22
- const inheritedNote =
23
- 'Note: Files in "Available files" are inputs the user (or a skill) already provided to the sandbox. They were not produced by this execution and you should not present them as new outputs in your response.';
24
18
 
25
19
  export const BashExecutionToolSchema = {
26
20
  type: 'object',
@@ -221,38 +215,20 @@ function createBashExecutionTool(
221
215
  }
222
216
  if (result.stderr) formattedOutput += `stderr:\n${result.stderr}\n`;
223
217
  if (result.files && result.files.length > 0) {
224
- /* Split inherited (read-only / unchanged-input passthroughs from
225
- * codeapi) from genuine generated outputs. The LLM was previously
226
- * shown skill files under "Generated files:" with the message
227
- * "File is already downloaded by the user", which led it to
228
- * (a) believe it had just produced files it merely referenced
229
- * and (b) sometimes invent paths like /mnt/user-data/uploads/
230
- * trying to find the "originals". Labeling them as inputs makes
231
- * the mental model accurate. */
232
- const inheritedFiles = result.files.filter(
233
- (f) => f.inherited === true
234
- );
235
- const generatedFiles = result.files.filter(
236
- (f) => f.inherited !== true
237
- );
218
+ formattedOutput += 'Generated files:\n';
238
219
 
239
- formattedOutput += renderFileSection(
240
- generatedFilesHeader,
241
- generatedFiles,
242
- otherMessage
243
- );
244
- formattedOutput += renderFileSection(
245
- inheritedFilesHeader,
246
- inheritedFiles,
247
- inheritedFileMessage
248
- );
220
+ const fileCount = result.files.length;
221
+ for (let i = 0; i < fileCount; i++) {
222
+ const file = result.files[i];
223
+ const isImage = imageExtRegex.test(file.name);
224
+ formattedOutput += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;
249
225
 
250
- if (generatedFiles.length > 0) {
251
- formattedOutput += `\n\n${accessMessage}`;
252
- }
253
- if (inheritedFiles.length > 0) {
254
- formattedOutput += `\n\n${inheritedNote}`;
226
+ if (i < fileCount - 1) {
227
+ formattedOutput += fileCount <= 3 ? ', ' : ',\n';
228
+ }
255
229
  }
230
+
231
+ formattedOutput += `\n\n${accessMessage}`;
256
232
  return [
257
233
  formattedOutput.trim(),
258
234
  {
@@ -15,41 +15,10 @@ export const getCodeBaseURL = (): string =>
15
15
 
16
16
  const imageMessage = 'Image is already displayed to the user';
17
17
  const otherMessage = 'File is already downloaded by the user';
18
- const inheritedFileMessage =
19
- 'Available as an input — already known to the user';
20
18
  const accessMessage =
21
19
  'Note: Files from previous executions are automatically available and can be modified.';
22
20
  const emptyOutputMessage =
23
21
  "stdout: Empty. Ensure you're writing output explicitly.\n";
24
- const inheritedFilesHeader =
25
- 'Available files (inputs, not generated by this execution):';
26
- const generatedFilesHeader = 'Generated files:';
27
- const inheritedNote =
28
- 'Note: Files in "Available files" are inputs the user (or a skill) already provided to the sandbox. They were not produced by this execution and you should not present them as new outputs in your response.';
29
-
30
- /**
31
- * Renders one section of the post-execution file listing. Used by the
32
- * code/bash tool formatters to keep generated outputs and inherited
33
- * inputs visually separated. See BashExecutor for full docs.
34
- */
35
- export function renderFileSection(
36
- header: string,
37
- files: t.FileRefs,
38
- defaultMessage: string
39
- ): string {
40
- if (files.length === 0) return '';
41
- let out = `${header}\n`;
42
- for (let i = 0; i < files.length; i++) {
43
- const file = files[i];
44
- const isImage = imageExtRegex.test(file.name);
45
- out += `- /mnt/data/${file.name} | ${isImage ? imageMessage : defaultMessage}`;
46
- if (i < files.length - 1) {
47
- out += files.length <= 3 ? ', ' : ',\n';
48
- }
49
- }
50
- out += '\n';
51
- return out;
52
- }
53
22
 
54
23
  const SUPPORTED_LANGUAGES = [
55
24
  'py',
@@ -69,10 +38,9 @@ const SUPPORTED_LANGUAGES = [
69
38
 
70
39
  // Minimal schema for raw code execution. Only what the /exec endpoint needs.
71
40
  // Higher-level workflows (versioning, edit, store, replay) are consumer
72
- // concerns — downstream consumers may layer their own write/edit/execute
73
- // schemas on top via wrapper utilities and content-store backed Zod
74
- // schemas. Keeping this tool narrow means every consumer gets a clean,
75
- // schema-stable primitive.
41
+ // concerns — e.g. ranger layers its own write/edit/execute schema on top via
42
+ // codeEditWrapper and a separate ContentStore-backed Zod schema. Keeping this
43
+ // tool narrow means every consumer gets a clean, schema-stable primitive.
76
44
  export const CodeExecutionToolSchema = {
77
45
  type: 'object',
78
46
  properties: {
@@ -334,33 +302,20 @@ function createCodeExecutionTool(
334
302
  }
335
303
 
336
304
  if (result.files && result.files.length > 0) {
337
- /* Split inherited (read-only passthrough) inputs from real
338
- * generated outputs so the LLM doesn't conflate skill files
339
- * with newly-produced artifacts. */
340
- const inheritedFiles = result.files.filter(
341
- (f) => f.inherited === true
342
- );
343
- const generatedFiles = result.files.filter(
344
- (f) => f.inherited !== true
345
- );
305
+ formattedOutput += 'Generated files:\n';
346
306
 
347
- formattedOutput += renderFileSection(
348
- generatedFilesHeader,
349
- generatedFiles,
350
- otherMessage
351
- );
352
- formattedOutput += renderFileSection(
353
- inheritedFilesHeader,
354
- inheritedFiles,
355
- inheritedFileMessage
356
- );
307
+ const fileCount = result.files.length;
308
+ for (let i = 0; i < fileCount; i++) {
309
+ const file = result.files[i];
310
+ const isImage = imageExtRegex.test(file.name);
311
+ formattedOutput += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;
357
312
 
358
- if (generatedFiles.length > 0) {
359
- formattedOutput += `\n\n${accessMessage}`;
360
- }
361
- if (inheritedFiles.length > 0) {
362
- formattedOutput += `\n\n${inheritedNote}`;
313
+ if (i < fileCount - 1) {
314
+ formattedOutput += fileCount <= 3 ? ', ' : ',\n';
315
+ }
363
316
  }
317
+
318
+ formattedOutput += `\n\n${accessMessage}`;
364
319
  return [
365
320
  formattedOutput.trim(),
366
321
  {
@@ -6,7 +6,7 @@ import { getEnvironmentVariable } from '@langchain/core/utils/env';
6
6
  import { tool, DynamicStructuredTool } from '@langchain/core/tools';
7
7
  import type { ToolCall } from '@langchain/core/messages/tool';
8
8
  import type * as t from '@/types';
9
- import { getCodeBaseURL, renderFileSection } from './CodeExecutor';
9
+ import { imageExtRegex, getCodeBaseURL } from './CodeExecutor';
10
10
  import { EnvVar, Constants } from '@/common';
11
11
 
12
12
  config();
@@ -15,14 +15,8 @@ config();
15
15
  // Constants
16
16
  // ============================================================================
17
17
 
18
+ const imageMessage = 'Image is already displayed to the user';
18
19
  const otherMessage = 'File is already downloaded by the user';
19
- const inheritedFileMessage =
20
- 'Available as an input — already known to the user';
21
- const inheritedFilesHeader =
22
- 'Available files (inputs, not generated by this execution):';
23
- const generatedFilesHeader = 'Generated files:';
24
- const inheritedNote =
25
- 'Note: Files in "Available files" are inputs the user (or a skill) already provided to the sandbox. They were not produced by this execution and you should not present them as new outputs in your response.';
26
20
  const accessMessage =
27
21
  'Note: Files from previous executions are automatically available and can be modified.';
28
22
  const emptyOutputMessage =
@@ -569,29 +563,20 @@ export function formatCompletedResponse(
569
563
  }
570
564
 
571
565
  if (response.files && response.files.length > 0) {
572
- /* See BashExecutor for the rationale: split inherited (read-only
573
- * passthrough) inputs from real generated outputs so the LLM doesn't
574
- * conflate skill files with newly-produced artifacts. */
575
- const inheritedFiles = response.files.filter((f) => f.inherited === true);
576
- const generatedFiles = response.files.filter((f) => f.inherited !== true);
577
-
578
- formatted += renderFileSection(
579
- generatedFilesHeader,
580
- generatedFiles,
581
- otherMessage
582
- );
583
- formatted += renderFileSection(
584
- inheritedFilesHeader,
585
- inheritedFiles,
586
- inheritedFileMessage
587
- );
566
+ formatted += 'Generated files:\n';
588
567
 
589
- if (generatedFiles.length > 0) {
590
- formatted += `\n\n${accessMessage}`;
591
- }
592
- if (inheritedFiles.length > 0) {
593
- formatted += `\n\n${inheritedNote}`;
568
+ const fileCount = response.files.length;
569
+ for (let i = 0; i < fileCount; i++) {
570
+ const file = response.files[i];
571
+ const isImage = imageExtRegex.test(file.name);
572
+ formatted += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;
573
+
574
+ if (i < fileCount - 1) {
575
+ formatted += fileCount <= 3 ? ', ' : ',\n';
576
+ }
594
577
  }
578
+
579
+ formatted += `\n\n${accessMessage}`;
595
580
  }
596
581
 
597
582
  return [
@@ -37,7 +37,7 @@ import type { ResolvedArgsByCallId } from '@/tools/toolOutputReferences';
37
37
  /**
38
38
  * Per-call batch context for `runTool`. Bundles every optional batch-scoped
39
39
  * value the method needs so the signature stays at three positional params
40
- * even as new context fields are added. (PR #117).
40
+ * even as new context fields are added. Upstream-aligned (PR #117).
41
41
  */
42
42
  type RunToolBatchContext = {
43
43
  /** Position of this call within the parent ToolNode batch. */
@@ -125,7 +125,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
125
125
  private hookRegistry?: HookRegistry;
126
126
  /**
127
127
  * Tool output reference registry threaded down from Run / Graph
128
- * (PR #114). When set, dispatchToolEvents resolves
128
+ * (upstream PR #114). When set, dispatchToolEvents resolves
129
129
  * `{{tool<i>turn<n>}}` placeholders in args before invoking each tool
130
130
  * and stores successful outputs under their stable reference keys for
131
131
  * subsequent calls in the same run to pipe through.
@@ -363,7 +363,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
363
363
  /**
364
364
  * Runs a single tool call with error handling.
365
365
  *
366
- * @param batchContext Optional per-batch context (PR #117).
366
+ * @param batchContext Optional per-batch context (upstream PR #117).
367
367
  * Threaded from `run()` for tool output reference annotation. The
368
368
  * `batchScopeId` field carries an anonymous synthetic scope when the
369
369
  * caller has no `run_id`, so concurrent batches don't collide on the
@@ -581,7 +581,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
581
581
  });
582
582
 
583
583
  /**
584
- * Tool output reference metadata (PR #117). Register the
584
+ * Tool output reference metadata (upstream PR #117). Register the
585
585
  * raw, untruncated content so future `{{...}}` substitutions deliver
586
586
  * the full payload, and stamp `_refKey` / `_refScope` /
587
587
  * `_unresolvedRefs` into `additional_kwargs` for the lazy
@@ -754,19 +754,74 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
754
754
  }
755
755
  }
756
756
 
757
- // Field-level recovery: extract missing fields from the raw buffer
757
+ // Field-level recovery: extract missing fields from the raw buffer.
758
+ //
759
+ // Only attempt this when `parsedArgs` is missing top-level keys that
760
+ // appear at depth 0 in the raw JSON. The previous implementation walked
761
+ // EVERY `"key":` match the regex found — including keys nested inside
762
+ // arrays/objects like `steps[].options[].label` — and tried to "recover"
763
+ // them as top-level fields. That produced confusing log spam (false
764
+ // positives like `recoveredFields=[question,label,value]` for a clean
765
+ // multi-step `ask_user` call) and could inject stray top-level fields
766
+ // into `args` for downstream tool execution.
758
767
  const parsedArgs = typeof args === 'object' ? { ...args } : {};
759
768
  const recoveredFields: string[] = [];
760
769
 
761
- // Extract field names from the raw JSON string
762
- const fieldPattern = /"([^"]+)"\s*:/g;
763
- let match;
764
- const rawFieldNames: string[] = [];
765
- while ((match = fieldPattern.exec(rawArgs)) !== null) {
766
- rawFieldNames.push(match[1]);
770
+ /**
771
+ * Extract only TOP-LEVEL keys from the raw JSON. Walks the buffer
772
+ * tracking brace/bracket depth so nested keys are skipped.
773
+ */
774
+ const topLevelKeys: string[] = [];
775
+ let depth = 0;
776
+ let inString = false;
777
+ let escaped = false;
778
+ let i = 0;
779
+ while (i < rawArgs.length) {
780
+ const ch = rawArgs[i];
781
+ if (escaped) {
782
+ escaped = false;
783
+ i++;
784
+ continue;
785
+ }
786
+ if (inString) {
787
+ if (ch === '\\') escaped = true;
788
+ else if (ch === '"') inString = false;
789
+ i++;
790
+ continue;
791
+ }
792
+ if (ch === '"') {
793
+ // Possible key — only count if at depth 1 (inside the outer object)
794
+ const start = i + 1;
795
+ let j = start;
796
+ while (j < rawArgs.length) {
797
+ const c = rawArgs[j];
798
+ if (c === '\\') {
799
+ j += 2;
800
+ continue;
801
+ }
802
+ if (c === '"') break;
803
+ j++;
804
+ }
805
+ if (j < rawArgs.length) {
806
+ // Skip whitespace after closing quote and check for ':'
807
+ let k = j + 1;
808
+ while (k < rawArgs.length && /\s/.test(rawArgs[k])) k++;
809
+ if (depth === 1 && rawArgs[k] === ':') {
810
+ topLevelKeys.push(rawArgs.slice(start, j));
811
+ }
812
+ i = j + 1;
813
+ continue;
814
+ }
815
+ inString = true;
816
+ i++;
817
+ continue;
818
+ }
819
+ if (ch === '{' || ch === '[') depth++;
820
+ else if (ch === '}' || ch === ']') depth--;
821
+ i++;
767
822
  }
768
823
 
769
- for (const fieldName of rawFieldNames) {
824
+ for (const fieldName of topLevelKeys) {
770
825
  if (
771
826
  parsedArgs[fieldName] == null ||
772
827
  parsedArgs[fieldName] === '' ||
@@ -991,6 +1046,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
991
1046
  // Dispatch ON_RUN_STEP_COMPLETED via custom event (same path as dispatchToolEvents)
992
1047
  const stepId = this.toolCallStepIds?.get(toolCallId) ?? '';
993
1048
  if (!stepId) {
1049
+ // eslint-disable-next-line no-console
1050
+ console.warn(
1051
+ `[ToolNode.handleRunToolCompletions] missing stepId for toolCallId=${toolCallId} ` +
1052
+ `name=${call.name} — ON_RUN_STEP_COMPLETED skipped, output will not be persisted. ` +
1053
+ `If this is a resumed HITL tool call, verify Graph.resetValues preserves toolCallStepIds when keepContent=true.`
1054
+ );
994
1055
  continue;
995
1056
  }
996
1057
 
@@ -1195,7 +1256,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1195
1256
  }
1196
1257
 
1197
1258
  /**
1198
- * Tool output reference resolution (PR #114): walk each call's
1259
+ * Tool output reference resolution (upstream PR #114): walk each call's
1199
1260
  * args and substitute `{{tool<i>turn<n>}}` placeholders with the stored
1200
1261
  * raw output. Captured here BEFORE request shaping so the substituted
1201
1262
  * args flow through the rest of the dispatch unchanged.
@@ -1380,7 +1441,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1380
1441
  ? result.content
1381
1442
  : JSON.stringify(result.content);
1382
1443
  /**
1383
- * Tool output reference — lazy annotation (PRs #114 + #117).
1444
+ * Tool output reference — lazy annotation (upstream PRs #114 + #117).
1384
1445
  * Register the raw output under `tool<idx>turn<turn>` in the
1385
1446
  * registry but leave `ToolMessage.content` clean. Stamp the ref
1386
1447
  * metadata into `additional_kwargs` instead. The lazy
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Validates the execute_code tool exposes a minimal raw-execution schema —
5
5
  * `{ lang, code, args }`. Higher-level workflows (versioning, code_id,
6
- * old_str/new_str edits) are consumer concerns (e.g. consumer-side
7
- * wrapper utilities) and must NOT appear in this schema.
6
+ * old_str/new_str edits) are consumer concerns (e.g. ranger's
7
+ * codeEditWrapper) and must NOT appear in this schema.
8
8
  */
9
9
  import {
10
10
  CodeExecutionToolSchema,
@@ -37,7 +37,7 @@ describe('CodeExecutionToolSchema', () => {
37
37
  });
38
38
 
39
39
  it('does NOT expose consumer-workflow fields (code_id, old_str, new_str, replace_all)', () => {
40
- // These live in consumer-side wrapper schemas, not here.
40
+ // These live in ranger's codeEditWrapper Zod schema, not here.
41
41
  const props = CodeExecutionToolSchema.properties as Record<string, unknown>;
42
42
  expect(props.code_id).toBeUndefined();
43
43
  expect(props.old_str).toBeUndefined();
@@ -664,66 +664,6 @@ for member in team:
664
664
  expect(output).toContain('chart.png');
665
665
  expect(output).toContain('Image is already displayed to the user');
666
666
  });
667
-
668
- it('splits inherited inputs from generated outputs into distinct sections', () => {
669
- const response: t.ProgrammaticExecutionResponse = {
670
- status: 'completed',
671
- stdout: 'analysis done\n',
672
- stderr: '',
673
- files: [
674
- { id: 'g1', name: 'report.pdf' },
675
- { id: 'i1', name: 'pptx/SKILL.md', inherited: true },
676
- { id: 'i2', name: 'pptx/scripts/clean.py', inherited: true },
677
- { id: 'g2', name: 'chart.png' },
678
- ],
679
- session_id: 'sess_abc123',
680
- };
681
-
682
- const [output, artifact] = formatCompletedResponse(response);
683
-
684
- /* Generated section lists only outputs the run produced. */
685
- const generatedIdx = output.indexOf('Generated files:');
686
- const inheritedIdx = output.indexOf('Available files (inputs');
687
- expect(generatedIdx).toBeGreaterThan(-1);
688
- expect(inheritedIdx).toBeGreaterThan(generatedIdx);
689
-
690
- /* Slice each section so we can assert membership without
691
- * cross-talk between the two listings. */
692
- const generatedSection = output.slice(generatedIdx, inheritedIdx);
693
- const inheritedSection = output.slice(inheritedIdx);
694
-
695
- expect(generatedSection).toContain('report.pdf');
696
- expect(generatedSection).toContain('chart.png');
697
- expect(generatedSection).not.toContain('SKILL.md');
698
-
699
- expect(inheritedSection).toContain('pptx/SKILL.md');
700
- expect(inheritedSection).toContain('pptx/scripts/clean.py');
701
- expect(inheritedSection).toContain('Available as an input');
702
-
703
- /* The artifact still carries every file so the host can still
704
- * thread per-file ids through to subsequent calls. */
705
- expect(artifact.files).toHaveLength(4);
706
- });
707
-
708
- it('omits the Generated files header when every entry is inherited', () => {
709
- const response: t.ProgrammaticExecutionResponse = {
710
- status: 'completed',
711
- stdout: 'cat: ok\n',
712
- stderr: '',
713
- files: [
714
- { id: 'i1', name: 'pptx/SKILL.md', inherited: true },
715
- { id: 'i2', name: 'pptx/editing.md', inherited: true },
716
- ],
717
- session_id: 'sess_abc123',
718
- };
719
-
720
- const [output] = formatCompletedResponse(response);
721
-
722
- expect(output).not.toContain('Generated files:');
723
- expect(output).toContain('Available files (inputs');
724
- expect(output).toContain('pptx/SKILL.md');
725
- expect(output).toContain('pptx/editing.md');
726
- });
727
667
  });
728
668
 
729
669
  describe('createProgrammaticToolCallingTool - Manual Invocation', () => {