@illuma-ai/agents 1.4.0-alpha.6 → 1.5.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 (653) hide show
  1. package/README.md +62 -0
  2. package/dist/cjs/agents/AgentContext.cjs +274 -67
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/common/enum.cjs +44 -13
  5. package/dist/cjs/common/enum.cjs.map +1 -1
  6. package/dist/cjs/graphs/Graph.cjs +182 -5
  7. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs +152 -1167
  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 +162 -0
  13. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -0
  14. package/dist/cjs/hooks/executeHooks.cjs +276 -0
  15. package/dist/cjs/hooks/executeHooks.cjs.map +1 -0
  16. package/dist/cjs/hooks/matchers.cjs +256 -0
  17. package/dist/cjs/hooks/matchers.cjs.map +1 -0
  18. package/dist/cjs/hooks/types.cjs +27 -0
  19. package/dist/cjs/hooks/types.cjs.map +1 -0
  20. package/dist/cjs/langchain/google-common.cjs +3 -0
  21. package/dist/cjs/langchain/google-common.cjs.map +1 -0
  22. package/dist/cjs/langchain/index.cjs +86 -0
  23. package/dist/cjs/langchain/index.cjs.map +1 -0
  24. package/dist/cjs/langchain/language_models/chat_models.cjs +3 -0
  25. package/dist/cjs/langchain/language_models/chat_models.cjs.map +1 -0
  26. package/dist/cjs/langchain/messages/tool.cjs +3 -0
  27. package/dist/cjs/langchain/messages/tool.cjs.map +1 -0
  28. package/dist/cjs/langchain/messages.cjs +51 -0
  29. package/dist/cjs/langchain/messages.cjs.map +1 -0
  30. package/dist/cjs/langchain/openai.cjs +3 -0
  31. package/dist/cjs/langchain/openai.cjs.map +1 -0
  32. package/dist/cjs/langchain/prompts.cjs +11 -0
  33. package/dist/cjs/langchain/prompts.cjs.map +1 -0
  34. package/dist/cjs/langchain/runnables.cjs +19 -0
  35. package/dist/cjs/langchain/runnables.cjs.map +1 -0
  36. package/dist/cjs/langchain/tools.cjs +23 -0
  37. package/dist/cjs/langchain/tools.cjs.map +1 -0
  38. package/dist/cjs/langchain/utils/env.cjs +11 -0
  39. package/dist/cjs/langchain/utils/env.cjs.map +1 -0
  40. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +5 -1
  41. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  42. package/dist/cjs/llm/bedrock/cacheSupport.cjs +55 -0
  43. package/dist/cjs/llm/bedrock/cacheSupport.cjs.map +1 -0
  44. package/dist/cjs/llm/bedrock/index.cjs +61 -33
  45. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  46. package/dist/cjs/llm/openai/index.cjs +1 -4
  47. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  48. package/dist/cjs/llm/openai/utils/index.cjs +27 -10
  49. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  50. package/dist/cjs/main.cjs +178 -127
  51. package/dist/cjs/main.cjs.map +1 -1
  52. package/dist/cjs/memory/citations.cjs +4 -4
  53. package/dist/cjs/memory/citations.cjs.map +1 -1
  54. package/dist/cjs/memory/constants.cjs +17 -17
  55. package/dist/cjs/memory/constants.cjs.map +1 -1
  56. package/dist/cjs/memory/mmr.cjs +1 -1
  57. package/dist/cjs/memory/mmr.cjs.map +1 -1
  58. package/dist/cjs/memory/paths.cjs +1 -1
  59. package/dist/cjs/memory/paths.cjs.map +1 -1
  60. package/dist/cjs/memory/recallTracking.cjs +3 -3
  61. package/dist/cjs/memory/recallTracking.cjs.map +1 -1
  62. package/dist/cjs/memory/temporalDecay.cjs +2 -2
  63. package/dist/cjs/memory/temporalDecay.cjs.map +1 -1
  64. package/dist/cjs/messages/cache.cjs +89 -0
  65. package/dist/cjs/messages/cache.cjs.map +1 -1
  66. package/dist/cjs/messages/contextPruning.cjs +156 -0
  67. package/dist/cjs/messages/contextPruning.cjs.map +1 -0
  68. package/dist/cjs/messages/contextPruningSettings.cjs +53 -0
  69. package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -0
  70. package/dist/cjs/messages/format.cjs +144 -20
  71. package/dist/cjs/messages/format.cjs.map +1 -1
  72. package/dist/cjs/messages/prune.cjs +505 -4
  73. package/dist/cjs/messages/prune.cjs.map +1 -1
  74. package/dist/cjs/run.cjs +141 -1
  75. package/dist/cjs/run.cjs.map +1 -1
  76. package/dist/cjs/tools/BashExecutor.cjs +235 -0
  77. package/dist/cjs/tools/BashExecutor.cjs.map +1 -0
  78. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +297 -0
  79. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -0
  80. package/dist/cjs/tools/CodeExecutor.cjs +45 -47
  81. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  82. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -11
  83. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  84. package/dist/cjs/tools/ReadFile.cjs +44 -0
  85. package/dist/cjs/tools/ReadFile.cjs.map +1 -0
  86. package/dist/cjs/tools/SkillTool.cjs +51 -0
  87. package/dist/cjs/tools/SkillTool.cjs.map +1 -0
  88. package/dist/cjs/tools/SubagentTool.cjs +93 -0
  89. package/dist/cjs/tools/SubagentTool.cjs.map +1 -0
  90. package/dist/cjs/tools/ToolNode.cjs +450 -24
  91. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  92. package/dist/cjs/tools/memory/memoryAppendTool.cjs +1 -1
  93. package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -1
  94. package/dist/cjs/tools/memory/memoryGetTool.cjs +2 -2
  95. package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -1
  96. package/dist/cjs/tools/memory/memorySearchTool.cjs +3 -3
  97. package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -1
  98. package/dist/cjs/tools/memory/shared.cjs +1 -1
  99. package/dist/cjs/tools/memory/shared.cjs.map +1 -1
  100. package/dist/cjs/tools/search/search.cjs +11 -3
  101. package/dist/cjs/tools/search/search.cjs.map +1 -1
  102. package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
  103. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
  104. package/dist/cjs/tools/search/tavily-search.cjs +372 -0
  105. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
  106. package/dist/cjs/tools/search/tool.cjs +28 -4
  107. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  108. package/dist/cjs/tools/search/utils.cjs +10 -3
  109. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  110. package/dist/cjs/tools/skillCatalog.cjs +84 -0
  111. package/dist/cjs/tools/skillCatalog.cjs.map +1 -0
  112. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +512 -0
  113. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -0
  114. package/dist/cjs/tools/toolOutputReferences.cjs +670 -0
  115. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -0
  116. package/dist/cjs/types/agent-cache.cjs +54 -0
  117. package/dist/cjs/types/agent-cache.cjs.map +1 -0
  118. package/dist/cjs/types/graph.cjs.map +1 -1
  119. package/dist/cjs/utils/truncation.cjs +135 -0
  120. package/dist/cjs/utils/truncation.cjs.map +1 -0
  121. package/dist/esm/agents/AgentContext.mjs +274 -67
  122. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  123. package/dist/esm/common/enum.mjs +44 -12
  124. package/dist/esm/common/enum.mjs.map +1 -1
  125. package/dist/esm/graphs/Graph.mjs +182 -5
  126. package/dist/esm/graphs/Graph.mjs.map +1 -1
  127. package/dist/esm/graphs/MultiAgentGraph.mjs +155 -1170
  128. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  129. package/dist/esm/graphs/phases/memoryFlushPhase.mjs +1 -1
  130. package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -1
  131. package/dist/esm/hooks/HookRegistry.mjs +160 -0
  132. package/dist/esm/hooks/HookRegistry.mjs.map +1 -0
  133. package/dist/esm/hooks/executeHooks.mjs +273 -0
  134. package/dist/esm/hooks/executeHooks.mjs.map +1 -0
  135. package/dist/esm/hooks/matchers.mjs +251 -0
  136. package/dist/esm/hooks/matchers.mjs.map +1 -0
  137. package/dist/esm/hooks/types.mjs +25 -0
  138. package/dist/esm/hooks/types.mjs.map +1 -0
  139. package/dist/esm/langchain/google-common.mjs +2 -0
  140. package/dist/esm/langchain/google-common.mjs.map +1 -0
  141. package/dist/esm/langchain/index.mjs +5 -0
  142. package/dist/esm/langchain/language_models/chat_models.mjs +2 -0
  143. package/dist/esm/langchain/language_models/chat_models.mjs.map +1 -0
  144. package/dist/esm/langchain/messages/tool.mjs +2 -0
  145. package/dist/esm/langchain/messages/tool.mjs.map +1 -0
  146. package/dist/esm/langchain/messages.mjs +2 -0
  147. package/dist/esm/langchain/messages.mjs.map +1 -0
  148. package/dist/esm/langchain/openai.mjs +2 -0
  149. package/dist/esm/langchain/openai.mjs.map +1 -0
  150. package/dist/esm/langchain/prompts.mjs +2 -0
  151. package/dist/esm/langchain/prompts.mjs.map +1 -0
  152. package/dist/esm/langchain/runnables.mjs +2 -0
  153. package/dist/esm/langchain/runnables.mjs.map +1 -0
  154. package/dist/esm/langchain/tools.mjs +2 -0
  155. package/dist/esm/langchain/tools.mjs.map +1 -0
  156. package/dist/esm/langchain/utils/env.mjs +2 -0
  157. package/dist/esm/langchain/utils/env.mjs.map +1 -0
  158. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +5 -1
  159. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  160. package/dist/esm/llm/bedrock/cacheSupport.mjs +52 -0
  161. package/dist/esm/llm/bedrock/cacheSupport.mjs.map +1 -0
  162. package/dist/esm/llm/bedrock/index.mjs +61 -34
  163. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  164. package/dist/esm/llm/openai/index.mjs +1 -4
  165. package/dist/esm/llm/openai/index.mjs.map +1 -1
  166. package/dist/esm/llm/openai/utils/index.mjs +27 -10
  167. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  168. package/dist/esm/main.mjs +21 -27
  169. package/dist/esm/main.mjs.map +1 -1
  170. package/dist/esm/memory/citations.mjs +4 -4
  171. package/dist/esm/memory/citations.mjs.map +1 -1
  172. package/dist/esm/memory/constants.mjs +17 -17
  173. package/dist/esm/memory/constants.mjs.map +1 -1
  174. package/dist/esm/memory/mmr.mjs +1 -1
  175. package/dist/esm/memory/mmr.mjs.map +1 -1
  176. package/dist/esm/memory/paths.mjs +1 -1
  177. package/dist/esm/memory/paths.mjs.map +1 -1
  178. package/dist/esm/memory/recallTracking.mjs +3 -3
  179. package/dist/esm/memory/recallTracking.mjs.map +1 -1
  180. package/dist/esm/memory/temporalDecay.mjs +2 -2
  181. package/dist/esm/memory/temporalDecay.mjs.map +1 -1
  182. package/dist/esm/messages/cache.mjs +89 -0
  183. package/dist/esm/messages/cache.mjs.map +1 -1
  184. package/dist/esm/messages/contextPruning.mjs +154 -0
  185. package/dist/esm/messages/contextPruning.mjs.map +1 -0
  186. package/dist/esm/messages/contextPruningSettings.mjs +50 -0
  187. package/dist/esm/messages/contextPruningSettings.mjs.map +1 -0
  188. package/dist/esm/messages/format.mjs +136 -12
  189. package/dist/esm/messages/format.mjs.map +1 -1
  190. package/dist/esm/messages/prune.mjs +504 -7
  191. package/dist/esm/messages/prune.mjs.map +1 -1
  192. package/dist/esm/run.mjs +141 -1
  193. package/dist/esm/run.mjs.map +1 -1
  194. package/dist/esm/tools/BashExecutor.mjs +227 -0
  195. package/dist/esm/tools/BashExecutor.mjs.map +1 -0
  196. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +288 -0
  197. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -0
  198. package/dist/esm/tools/CodeExecutor.mjs +45 -48
  199. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  200. package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -12
  201. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  202. package/dist/esm/tools/ReadFile.mjs +39 -0
  203. package/dist/esm/tools/ReadFile.mjs.map +1 -0
  204. package/dist/esm/tools/SkillTool.mjs +46 -0
  205. package/dist/esm/tools/SkillTool.mjs.map +1 -0
  206. package/dist/esm/tools/SubagentTool.mjs +86 -0
  207. package/dist/esm/tools/SubagentTool.mjs.map +1 -0
  208. package/dist/esm/tools/ToolNode.mjs +452 -26
  209. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  210. package/dist/esm/tools/memory/memoryAppendTool.mjs +1 -1
  211. package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -1
  212. package/dist/esm/tools/memory/memoryGetTool.mjs +2 -2
  213. package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -1
  214. package/dist/esm/tools/memory/memorySearchTool.mjs +3 -3
  215. package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -1
  216. package/dist/esm/tools/memory/shared.mjs +1 -1
  217. package/dist/esm/tools/memory/shared.mjs.map +1 -1
  218. package/dist/esm/tools/search/search.mjs +11 -3
  219. package/dist/esm/tools/search/search.mjs.map +1 -1
  220. package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
  221. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
  222. package/dist/esm/tools/search/tavily-search.mjs +370 -0
  223. package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
  224. package/dist/esm/tools/search/tool.mjs +28 -4
  225. package/dist/esm/tools/search/tool.mjs.map +1 -1
  226. package/dist/esm/tools/search/utils.mjs +10 -3
  227. package/dist/esm/tools/search/utils.mjs.map +1 -1
  228. package/dist/esm/tools/skillCatalog.mjs +82 -0
  229. package/dist/esm/tools/skillCatalog.mjs.map +1 -0
  230. package/dist/esm/tools/subagent/SubagentExecutor.mjs +506 -0
  231. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -0
  232. package/dist/esm/tools/toolOutputReferences.mjs +662 -0
  233. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -0
  234. package/dist/esm/types/agent-cache.mjs +52 -0
  235. package/dist/esm/types/agent-cache.mjs.map +1 -0
  236. package/dist/esm/types/graph.mjs.map +1 -1
  237. package/dist/esm/utils/truncation.mjs +128 -0
  238. package/dist/esm/utils/truncation.mjs.map +1 -0
  239. package/dist/types/agents/AgentContext.d.ts +101 -8
  240. package/dist/types/common/enum.d.ts +39 -12
  241. package/dist/types/common/index.d.ts +0 -1
  242. package/dist/types/graphs/Graph.d.ts +43 -0
  243. package/dist/types/graphs/MultiAgentGraph.d.ts +26 -150
  244. package/dist/types/graphs/index.d.ts +0 -1
  245. package/dist/types/graphs/phases/memoryFlushPhase.d.ts +2 -2
  246. package/dist/types/hooks/HookRegistry.d.ts +56 -0
  247. package/dist/types/hooks/executeHooks.d.ts +79 -0
  248. package/dist/types/hooks/index.d.ts +6 -0
  249. package/dist/types/hooks/matchers.d.ts +95 -0
  250. package/dist/types/hooks/types.d.ts +320 -0
  251. package/dist/types/index.d.ts +9 -9
  252. package/dist/types/langchain/google-common.d.ts +1 -0
  253. package/dist/types/langchain/index.d.ts +8 -0
  254. package/dist/types/langchain/language_models/chat_models.d.ts +1 -0
  255. package/dist/types/langchain/messages/tool.d.ts +1 -0
  256. package/dist/types/langchain/messages.d.ts +2 -0
  257. package/dist/types/langchain/openai.d.ts +1 -0
  258. package/dist/types/langchain/prompts.d.ts +1 -0
  259. package/dist/types/langchain/runnables.d.ts +2 -0
  260. package/dist/types/langchain/tools.d.ts +2 -0
  261. package/dist/types/langchain/utils/env.d.ts +1 -0
  262. package/dist/types/llm/bedrock/cacheSupport.d.ts +35 -0
  263. package/dist/types/llm/bedrock/index.d.ts +54 -1
  264. package/dist/types/llm/openai/index.d.ts +1 -1
  265. package/dist/types/memory/citations.d.ts +4 -4
  266. package/dist/types/memory/constants.d.ts +17 -17
  267. package/dist/types/memory/mmr.d.ts +3 -3
  268. package/dist/types/memory/paths.d.ts +1 -1
  269. package/dist/types/memory/temporalDecay.d.ts +2 -2
  270. package/dist/types/memory/types.d.ts +3 -3
  271. package/dist/types/messages/contextPruning.d.ts +42 -0
  272. package/dist/types/messages/contextPruningSettings.d.ts +44 -0
  273. package/dist/types/messages/format.d.ts +9 -1
  274. package/dist/types/messages/index.d.ts +2 -0
  275. package/dist/types/messages/prune.d.ts +91 -1
  276. package/dist/types/run.d.ts +2 -0
  277. package/dist/types/tools/BashExecutor.d.ts +76 -0
  278. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +72 -0
  279. package/dist/types/tools/CodeExecutor.d.ts +8 -26
  280. package/dist/types/tools/ReadFile.d.ts +28 -0
  281. package/dist/types/tools/SkillTool.d.ts +40 -0
  282. package/dist/types/tools/SubagentTool.d.ts +36 -0
  283. package/dist/types/tools/ToolNode.d.ts +77 -5
  284. package/dist/types/tools/memory/shared.d.ts +1 -1
  285. package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
  286. package/dist/types/tools/search/tavily-search.d.ts +4 -0
  287. package/dist/types/tools/search/types.d.ts +99 -5
  288. package/dist/types/tools/search/utils.d.ts +2 -2
  289. package/dist/types/tools/skillCatalog.d.ts +19 -0
  290. package/dist/types/tools/subagent/SubagentExecutor.d.ts +137 -0
  291. package/dist/types/tools/subagent/index.d.ts +2 -0
  292. package/dist/types/tools/subagent/types.d.ts +84 -0
  293. package/dist/types/tools/toolOutputReferences.d.ts +236 -0
  294. package/dist/types/types/agent-cache.d.ts +71 -0
  295. package/dist/types/types/graph.d.ts +163 -22
  296. package/dist/types/types/index.d.ts +3 -0
  297. package/dist/types/types/messages.d.ts +26 -0
  298. package/dist/types/types/run.d.ts +22 -0
  299. package/dist/types/types/skill.d.ts +9 -0
  300. package/dist/types/types/tools.d.ts +111 -0
  301. package/dist/types/utils/index.d.ts +1 -3
  302. package/dist/types/utils/truncation.d.ts +70 -0
  303. package/package.json +57 -17
  304. package/src/agents/AgentContext.ts +321 -78
  305. package/src/agents/__tests__/AgentContext.cacheTtl.live.test.ts +259 -0
  306. package/src/agents/__tests__/AgentContext.crossAgentTier1.live.test.ts +266 -0
  307. package/src/agents/__tests__/AgentContext.crossUserCache.live.test.ts +342 -0
  308. package/src/agents/__tests__/AgentContext.test.ts +632 -0
  309. package/src/common/__tests__/enum.test.ts +7 -17
  310. package/src/common/enum.ts +43 -12
  311. package/src/common/index.ts +0 -1
  312. package/src/graphs/Graph.ts +222 -2
  313. package/src/graphs/MultiAgentGraph.ts +154 -1466
  314. package/src/graphs/__tests__/MultiAgentGraph.test.ts +91 -0
  315. package/src/graphs/gapFeatures.test.ts +1 -1
  316. package/src/graphs/index.ts +0 -1
  317. package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +1 -1
  318. package/src/graphs/phases/memoryFlushPhase.ts +2 -2
  319. package/src/hooks/HookRegistry.ts +208 -0
  320. package/src/hooks/__tests__/HookRegistry.test.ts +190 -0
  321. package/src/hooks/__tests__/compactHooks.test.ts +214 -0
  322. package/src/hooks/__tests__/executeHooks.test.ts +1013 -0
  323. package/src/hooks/__tests__/integration.test.ts +337 -0
  324. package/src/hooks/__tests__/matchers.test.ts +238 -0
  325. package/src/hooks/__tests__/toolHooks.test.ts +665 -0
  326. package/src/hooks/executeHooks.ts +375 -0
  327. package/src/hooks/index.ts +57 -0
  328. package/src/hooks/matchers.ts +280 -0
  329. package/src/hooks/types.ts +404 -0
  330. package/src/index.ts +15 -24
  331. package/src/langchain/google-common.ts +1 -0
  332. package/src/langchain/index.ts +8 -0
  333. package/src/langchain/language_models/chat_models.ts +1 -0
  334. package/src/langchain/messages/tool.ts +5 -0
  335. package/src/langchain/messages.ts +21 -0
  336. package/src/langchain/openai.ts +1 -0
  337. package/src/langchain/prompts.ts +1 -0
  338. package/src/langchain/runnables.ts +7 -0
  339. package/src/langchain/tools.ts +8 -0
  340. package/src/langchain/utils/env.ts +1 -0
  341. package/src/llm/anthropic/utils/message_inputs.ts +10 -1
  342. package/src/llm/anthropic/utils/server-tool-inputs.test.ts +436 -0
  343. package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +166 -18
  344. package/src/llm/bedrock/cacheSupport.test.ts +99 -0
  345. package/src/llm/bedrock/cacheSupport.ts +53 -0
  346. package/src/llm/bedrock/index.ts +116 -41
  347. package/src/llm/openai/index.ts +2 -2
  348. package/src/llm/openai/utils/index.ts +31 -14
  349. package/src/memory/citations.ts +4 -4
  350. package/src/memory/constants.ts +17 -17
  351. package/src/memory/mmr.ts +3 -3
  352. package/src/memory/paths.ts +1 -1
  353. package/src/memory/recallTracking.ts +3 -3
  354. package/src/memory/temporalDecay.ts +2 -2
  355. package/src/memory/types.ts +3 -3
  356. package/src/messages/__tests__/contextPruning.test.ts +228 -0
  357. package/src/messages/cache.test.ts +62 -24
  358. package/src/messages/cache.ts +112 -0
  359. package/src/messages/contextPruning.ts +191 -0
  360. package/src/messages/contextPruningSettings.ts +90 -0
  361. package/src/messages/ensureThinkingBlock.test.ts +1 -1
  362. package/src/messages/format.ts +164 -12
  363. package/src/messages/formatAgentMessages.skills.test.ts +413 -0
  364. package/src/messages/formatAgentMessages.test.ts +1 -1
  365. package/src/messages/index.ts +2 -0
  366. package/src/messages/prune.ts +661 -4
  367. package/src/run.ts +155 -1
  368. package/src/scripts/multi-agent-chain.ts +2 -2
  369. package/src/scripts/multi-agent-document-review-chain.ts +2 -2
  370. package/src/scripts/multi-agent-hybrid-flow.ts +4 -4
  371. package/src/scripts/multi-agent-parallel.ts +3 -3
  372. package/src/scripts/multi-agent-sequence.ts +3 -3
  373. package/src/scripts/multi-agent-subagent.ts +246 -0
  374. package/src/scripts/multi-agent-supervisor.ts +5 -5
  375. package/src/scripts/poc-multi-agent-comprehensive.ts +8 -8
  376. package/src/scripts/sequential-full-metadata-test.ts +2 -2
  377. package/src/scripts/subagent-event-driven-debug.ts +190 -0
  378. package/src/scripts/subagent-tools-debug.ts +160 -0
  379. package/src/scripts/test-custom-prompt-key.ts +3 -3
  380. package/src/scripts/test-handoff-input.ts +1 -1
  381. package/src/scripts/test-handoff-steering.ts +3 -3
  382. package/src/scripts/test-multi-agent-list-handoff.ts +1 -1
  383. package/src/scripts/test-parallel-agent-labeling.ts +3 -3
  384. package/src/scripts/test-parallel-handoffs.ts +2 -2
  385. package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
  386. package/src/scripts/test-thinking-handoff.ts +1 -1
  387. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +1 -1
  388. package/src/scripts/test-tool-before-handoff-role-order.ts +1 -1
  389. package/src/scripts/test-tools-before-handoff.ts +1 -1
  390. package/src/specs/agent-handoffs.test.ts +26 -483
  391. package/src/specs/anthropic.simple.test.ts +61 -0
  392. package/src/specs/multi-agent-summarization.test.ts +396 -0
  393. package/src/specs/prune.orphans.test.ts +248 -0
  394. package/src/specs/prune.test.ts +104 -16
  395. package/src/specs/thinking-handoff.test.ts +19 -19
  396. package/src/tools/BashExecutor.ts +281 -0
  397. package/src/tools/BashProgrammaticToolCalling.ts +397 -0
  398. package/src/tools/CodeExecutor.ts +63 -54
  399. package/src/tools/ProgrammaticToolCalling.ts +29 -14
  400. package/src/tools/ReadFile.ts +39 -0
  401. package/src/tools/SkillTool.ts +46 -0
  402. package/src/tools/SubagentTool.ts +100 -0
  403. package/src/tools/ToolNode.ts +548 -26
  404. package/src/tools/__tests__/BashExecutor.test.ts +49 -0
  405. package/src/tools/__tests__/CodeExecutor.test.ts +37 -36
  406. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +60 -0
  407. package/src/tools/__tests__/ReadFile.test.ts +44 -0
  408. package/src/tools/__tests__/SkillTool.test.ts +442 -0
  409. package/src/tools/__tests__/SubagentExecutor.test.ts +1148 -0
  410. package/src/tools/__tests__/SubagentTool.test.ts +149 -0
  411. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +1438 -0
  412. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +479 -0
  413. package/src/tools/__tests__/skillCatalog.test.ts +161 -0
  414. package/src/tools/__tests__/subagentHooks.test.ts +210 -0
  415. package/src/tools/__tests__/toolOutputReferences.test.ts +415 -0
  416. package/src/tools/memory/memoryAppendTool.ts +1 -1
  417. package/src/tools/memory/memoryGetTool.ts +2 -2
  418. package/src/tools/memory/memorySearchTool.ts +3 -3
  419. package/src/tools/memory/shared.ts +1 -1
  420. package/src/tools/search/search.ts +12 -2
  421. package/src/tools/search/tavily-scraper.ts +235 -0
  422. package/src/tools/search/tavily-search.ts +424 -0
  423. package/src/tools/search/tavily.test.ts +965 -0
  424. package/src/tools/search/tool.ts +36 -2
  425. package/src/tools/search/types.ts +133 -8
  426. package/src/tools/search/utils.ts +13 -5
  427. package/src/tools/skillCatalog.ts +126 -0
  428. package/src/tools/subagent/SubagentExecutor.ts +676 -0
  429. package/src/tools/subagent/index.ts +13 -0
  430. package/src/tools/subagent/types.test.ts +70 -0
  431. package/src/tools/subagent/types.ts +115 -0
  432. package/src/tools/toolOutputReferences.ts +825 -0
  433. package/src/types/agent-cache.ts +74 -0
  434. package/src/types/graph.ts +172 -20
  435. package/src/types/index.ts +3 -0
  436. package/src/types/messages.ts +27 -0
  437. package/src/types/run.ts +22 -0
  438. package/src/types/skill.ts +11 -0
  439. package/src/types/tools.ts +118 -0
  440. package/src/utils/__tests__/truncation.test.ts +66 -0
  441. package/src/utils/index.ts +1 -3
  442. package/src/utils/truncation.ts +154 -0
  443. package/dist/cjs/common/spawnPath.cjs +0 -104
  444. package/dist/cjs/common/spawnPath.cjs.map +0 -1
  445. package/dist/cjs/content/ArtifactStore.cjs +0 -579
  446. package/dist/cjs/content/ArtifactStore.cjs.map +0 -1
  447. package/dist/cjs/content/ContentStore.cjs +0 -638
  448. package/dist/cjs/content/ContentStore.cjs.map +0 -1
  449. package/dist/cjs/content/contentAnalyzer.cjs +0 -91
  450. package/dist/cjs/content/contentAnalyzer.cjs.map +0 -1
  451. package/dist/cjs/content/index.cjs +0 -20
  452. package/dist/cjs/content/index.cjs.map +0 -1
  453. package/dist/cjs/content/mcpAutoCache.cjs +0 -115
  454. package/dist/cjs/content/mcpAutoCache.cjs.map +0 -1
  455. package/dist/cjs/graphs/HandoffRegistry.cjs +0 -143
  456. package/dist/cjs/graphs/HandoffRegistry.cjs.map +0 -1
  457. package/dist/cjs/providers/a2a/A2ACapabilityProvider.cjs +0 -288
  458. package/dist/cjs/providers/a2a/A2ACapabilityProvider.cjs.map +0 -1
  459. package/dist/cjs/providers/a2a/client.cjs +0 -92
  460. package/dist/cjs/providers/a2a/client.cjs.map +0 -1
  461. package/dist/cjs/providers/a2a/config.cjs +0 -38
  462. package/dist/cjs/providers/a2a/config.cjs.map +0 -1
  463. package/dist/cjs/providers/capabilityNaming.cjs +0 -43
  464. package/dist/cjs/providers/capabilityNaming.cjs.map +0 -1
  465. package/dist/cjs/providers/mcp/MCPCapabilityProvider.cjs +0 -244
  466. package/dist/cjs/providers/mcp/MCPCapabilityProvider.cjs.map +0 -1
  467. package/dist/cjs/providers/mcp/config.cjs +0 -42
  468. package/dist/cjs/providers/mcp/config.cjs.map +0 -1
  469. package/dist/cjs/providers/mcp/transport.cjs +0 -65
  470. package/dist/cjs/providers/mcp/transport.cjs.map +0 -1
  471. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs +0 -128
  472. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs.map +0 -1
  473. package/dist/cjs/providers/types.cjs +0 -51
  474. package/dist/cjs/providers/types.cjs.map +0 -1
  475. package/dist/cjs/tools/artifacts/schema.cjs +0 -86
  476. package/dist/cjs/tools/artifacts/schema.cjs.map +0 -1
  477. package/dist/cjs/tools/artifacts/tool.cjs +0 -219
  478. package/dist/cjs/tools/artifacts/tool.cjs.map +0 -1
  479. package/dist/cjs/tools/fileSearch/formatter.cjs +0 -93
  480. package/dist/cjs/tools/fileSearch/formatter.cjs.map +0 -1
  481. package/dist/cjs/tools/fileSearch/ragClient.cjs +0 -102
  482. package/dist/cjs/tools/fileSearch/ragClient.cjs.map +0 -1
  483. package/dist/cjs/tools/fileSearch/schema.cjs +0 -18
  484. package/dist/cjs/tools/fileSearch/schema.cjs.map +0 -1
  485. package/dist/cjs/tools/fileSearch/tool.cjs +0 -155
  486. package/dist/cjs/tools/fileSearch/tool.cjs.map +0 -1
  487. package/dist/cjs/tools/proxyTool.cjs +0 -102
  488. package/dist/cjs/tools/proxyTool.cjs.map +0 -1
  489. package/dist/cjs/utils/childAgentContext.cjs +0 -242
  490. package/dist/cjs/utils/childAgentContext.cjs.map +0 -1
  491. package/dist/cjs/utils/credentials.cjs +0 -142
  492. package/dist/cjs/utils/credentials.cjs.map +0 -1
  493. package/dist/cjs/utils/httpClient.cjs +0 -74
  494. package/dist/cjs/utils/httpClient.cjs.map +0 -1
  495. package/dist/cjs/utils/toolManifest.cjs +0 -100
  496. package/dist/cjs/utils/toolManifest.cjs.map +0 -1
  497. package/dist/esm/common/spawnPath.mjs +0 -95
  498. package/dist/esm/common/spawnPath.mjs.map +0 -1
  499. package/dist/esm/content/ArtifactStore.mjs +0 -576
  500. package/dist/esm/content/ArtifactStore.mjs.map +0 -1
  501. package/dist/esm/content/ContentStore.mjs +0 -635
  502. package/dist/esm/content/ContentStore.mjs.map +0 -1
  503. package/dist/esm/content/contentAnalyzer.mjs +0 -87
  504. package/dist/esm/content/contentAnalyzer.mjs.map +0 -1
  505. package/dist/esm/content/index.mjs +0 -5
  506. package/dist/esm/content/mcpAutoCache.mjs +0 -111
  507. package/dist/esm/content/mcpAutoCache.mjs.map +0 -1
  508. package/dist/esm/graphs/HandoffRegistry.mjs +0 -141
  509. package/dist/esm/graphs/HandoffRegistry.mjs.map +0 -1
  510. package/dist/esm/providers/a2a/A2ACapabilityProvider.mjs +0 -281
  511. package/dist/esm/providers/a2a/A2ACapabilityProvider.mjs.map +0 -1
  512. package/dist/esm/providers/a2a/client.mjs +0 -88
  513. package/dist/esm/providers/a2a/client.mjs.map +0 -1
  514. package/dist/esm/providers/a2a/config.mjs +0 -35
  515. package/dist/esm/providers/a2a/config.mjs.map +0 -1
  516. package/dist/esm/providers/capabilityNaming.mjs +0 -39
  517. package/dist/esm/providers/capabilityNaming.mjs.map +0 -1
  518. package/dist/esm/providers/mcp/MCPCapabilityProvider.mjs +0 -240
  519. package/dist/esm/providers/mcp/MCPCapabilityProvider.mjs.map +0 -1
  520. package/dist/esm/providers/mcp/config.mjs +0 -39
  521. package/dist/esm/providers/mcp/config.mjs.map +0 -1
  522. package/dist/esm/providers/mcp/transport.mjs +0 -63
  523. package/dist/esm/providers/mcp/transport.mjs.map +0 -1
  524. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs +0 -126
  525. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs.map +0 -1
  526. package/dist/esm/providers/types.mjs +0 -51
  527. package/dist/esm/providers/types.mjs.map +0 -1
  528. package/dist/esm/tools/artifacts/schema.mjs +0 -79
  529. package/dist/esm/tools/artifacts/schema.mjs.map +0 -1
  530. package/dist/esm/tools/artifacts/tool.mjs +0 -213
  531. package/dist/esm/tools/artifacts/tool.mjs.map +0 -1
  532. package/dist/esm/tools/fileSearch/formatter.mjs +0 -90
  533. package/dist/esm/tools/fileSearch/formatter.mjs.map +0 -1
  534. package/dist/esm/tools/fileSearch/ragClient.mjs +0 -98
  535. package/dist/esm/tools/fileSearch/ragClient.mjs.map +0 -1
  536. package/dist/esm/tools/fileSearch/schema.mjs +0 -15
  537. package/dist/esm/tools/fileSearch/schema.mjs.map +0 -1
  538. package/dist/esm/tools/fileSearch/tool.mjs +0 -152
  539. package/dist/esm/tools/fileSearch/tool.mjs.map +0 -1
  540. package/dist/esm/tools/proxyTool.mjs +0 -100
  541. package/dist/esm/tools/proxyTool.mjs.map +0 -1
  542. package/dist/esm/utils/childAgentContext.mjs +0 -237
  543. package/dist/esm/utils/childAgentContext.mjs.map +0 -1
  544. package/dist/esm/utils/credentials.mjs +0 -135
  545. package/dist/esm/utils/credentials.mjs.map +0 -1
  546. package/dist/esm/utils/httpClient.mjs +0 -70
  547. package/dist/esm/utils/httpClient.mjs.map +0 -1
  548. package/dist/esm/utils/toolManifest.mjs +0 -96
  549. package/dist/esm/utils/toolManifest.mjs.map +0 -1
  550. package/dist/types/common/spawnPath.d.ts +0 -59
  551. package/dist/types/content/ArtifactStore.d.ts +0 -223
  552. package/dist/types/content/ContentStore.d.ts +0 -140
  553. package/dist/types/content/contentAnalyzer.d.ts +0 -38
  554. package/dist/types/content/index.d.ts +0 -24
  555. package/dist/types/content/mcpAutoCache.d.ts +0 -89
  556. package/dist/types/content/types.d.ts +0 -75
  557. package/dist/types/graphs/HandoffRegistry.d.ts +0 -97
  558. package/dist/types/providers/a2a/A2ACapabilityProvider.d.ts +0 -89
  559. package/dist/types/providers/a2a/client.d.ts +0 -47
  560. package/dist/types/providers/a2a/config.d.ts +0 -18
  561. package/dist/types/providers/a2a/index.d.ts +0 -6
  562. package/dist/types/providers/a2a/types.d.ts +0 -173
  563. package/dist/types/providers/capabilityNaming.d.ts +0 -25
  564. package/dist/types/providers/index.d.ts +0 -12
  565. package/dist/types/providers/mcp/MCPCapabilityProvider.d.ts +0 -54
  566. package/dist/types/providers/mcp/config.d.ts +0 -20
  567. package/dist/types/providers/mcp/index.d.ts +0 -5
  568. package/dist/types/providers/mcp/transport.d.ts +0 -18
  569. package/dist/types/providers/mcp/types.d.ts +0 -112
  570. package/dist/types/providers/tools-server/ToolsServerCapabilityProvider.d.ts +0 -59
  571. package/dist/types/providers/tools-server/index.d.ts +0 -1
  572. package/dist/types/providers/types.d.ts +0 -184
  573. package/dist/types/tools/artifacts/index.d.ts +0 -3
  574. package/dist/types/tools/artifacts/schema.d.ts +0 -63
  575. package/dist/types/tools/artifacts/tool.d.ts +0 -16
  576. package/dist/types/tools/artifacts/types.d.ts +0 -127
  577. package/dist/types/tools/fileSearch/formatter.d.ts +0 -25
  578. package/dist/types/tools/fileSearch/index.d.ts +0 -5
  579. package/dist/types/tools/fileSearch/ragClient.d.ts +0 -32
  580. package/dist/types/tools/fileSearch/schema.d.ts +0 -13
  581. package/dist/types/tools/fileSearch/tool.d.ts +0 -18
  582. package/dist/types/tools/fileSearch/types.d.ts +0 -139
  583. package/dist/types/tools/proxyTool.d.ts +0 -62
  584. package/dist/types/tools/search/test.d.ts +0 -1
  585. package/dist/types/utils/childAgentContext.d.ts +0 -99
  586. package/dist/types/utils/credentials.d.ts +0 -77
  587. package/dist/types/utils/httpClient.d.ts +0 -46
  588. package/dist/types/utils/toolManifest.d.ts +0 -49
  589. package/src/common/__tests__/spawnPath.test.ts +0 -110
  590. package/src/common/spawnPath.ts +0 -101
  591. package/src/content/ArtifactStore.ts +0 -782
  592. package/src/content/ContentStore.ts +0 -753
  593. package/src/content/contentAnalyzer.ts +0 -105
  594. package/src/content/index.ts +0 -51
  595. package/src/content/mcpAutoCache.ts +0 -185
  596. package/src/content/types.ts +0 -82
  597. package/src/graphs/HandoffRegistry.ts +0 -199
  598. package/src/graphs/__tests__/HandoffRegistry.test.ts +0 -410
  599. package/src/graphs/__tests__/multi-agent-delegate.test.ts +0 -458
  600. package/src/graphs/__tests__/multi-agent-edges.test.ts +0 -276
  601. package/src/graphs/__tests__/multi-agent-nested-subgraph.test.ts +0 -221
  602. package/src/graphs/handoffValidation.test.ts +0 -353
  603. package/src/providers/__tests__/ToolsServerCapabilityProvider.integration.spec.ts +0 -79
  604. package/src/providers/__tests__/ToolsServerCapabilityProvider.test.ts +0 -271
  605. package/src/providers/__tests__/types.test.ts +0 -64
  606. package/src/providers/a2a/A2ACapabilityProvider.ts +0 -384
  607. package/src/providers/a2a/__tests__/A2ACapabilityProvider.integration.spec.ts +0 -345
  608. package/src/providers/a2a/__tests__/A2ACapabilityProvider.test.ts +0 -460
  609. package/src/providers/a2a/client.ts +0 -115
  610. package/src/providers/a2a/config.ts +0 -40
  611. package/src/providers/a2a/index.ts +0 -29
  612. package/src/providers/a2a/types.ts +0 -191
  613. package/src/providers/capabilityNaming.ts +0 -42
  614. package/src/providers/index.ts +0 -68
  615. package/src/providers/mcp/MCPCapabilityProvider.ts +0 -345
  616. package/src/providers/mcp/__tests__/MCPCapabilityProvider.integration.spec.ts +0 -386
  617. package/src/providers/mcp/__tests__/MCPCapabilityProvider.test.ts +0 -371
  618. package/src/providers/mcp/config.ts +0 -45
  619. package/src/providers/mcp/index.ts +0 -21
  620. package/src/providers/mcp/transport.ts +0 -76
  621. package/src/providers/mcp/types.ts +0 -139
  622. package/src/providers/tools-server/ToolsServerCapabilityProvider.ts +0 -249
  623. package/src/providers/tools-server/index.ts +0 -1
  624. package/src/providers/types.ts +0 -204
  625. package/src/scripts/test-bedrock-handoff-autonomous.ts +0 -267
  626. package/src/scripts/test-handoff-preamble.ts +0 -278
  627. package/src/specs/agent-handoffs-bedrock.integration.test.ts +0 -415
  628. package/src/tools/artifacts/__tests__/tool.test.ts +0 -259
  629. package/src/tools/artifacts/index.ts +0 -33
  630. package/src/tools/artifacts/schema.ts +0 -99
  631. package/src/tools/artifacts/tool.ts +0 -289
  632. package/src/tools/artifacts/types.ts +0 -162
  633. package/src/tools/fileSearch/__tests__/tool.test.ts +0 -261
  634. package/src/tools/fileSearch/formatter.ts +0 -129
  635. package/src/tools/fileSearch/index.ts +0 -23
  636. package/src/tools/fileSearch/ragClient.ts +0 -137
  637. package/src/tools/fileSearch/schema.ts +0 -19
  638. package/src/tools/fileSearch/tool.ts +0 -207
  639. package/src/tools/fileSearch/types.ts +0 -149
  640. package/src/tools/proxyTool.ts +0 -166
  641. package/src/tools/search/output.md +0 -2775
  642. package/src/tools/search/test.html +0 -884
  643. package/src/tools/search/test.md +0 -643
  644. package/src/tools/search/test.ts +0 -159
  645. package/src/utils/__tests__/childAgentContext.test.ts +0 -217
  646. package/src/utils/__tests__/credentials.test.ts +0 -130
  647. package/src/utils/__tests__/httpClient.test.ts +0 -75
  648. package/src/utils/__tests__/toolManifest.test.ts +0 -116
  649. package/src/utils/childAgentContext.ts +0 -259
  650. package/src/utils/credentials.ts +0 -157
  651. package/src/utils/httpClient.ts +0 -92
  652. package/src/utils/toolManifest.ts +0 -109
  653. /package/dist/esm/{content → langchain}/index.mjs.map +0 -0
@@ -0,0 +1,479 @@
1
+ import { AIMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
2
+ import { describe, it, expect } from '@jest/globals';
3
+ import {
4
+ annotateMessagesForLLM,
5
+ ToolOutputReferenceRegistry,
6
+ TOOL_OUTPUT_REF_KEY,
7
+ TOOL_OUTPUT_UNRESOLVED_KEY,
8
+ } from '../toolOutputReferences';
9
+
10
+ function makeToolMessage(fields: {
11
+ content: ToolMessage['content'];
12
+ name?: string;
13
+ tool_call_id?: string;
14
+ status?: 'success' | 'error';
15
+ artifact?: unknown;
16
+ additional_kwargs?: Record<string, unknown>;
17
+ }): ToolMessage {
18
+ return new ToolMessage({
19
+ name: fields.name ?? 'echo',
20
+ tool_call_id: fields.tool_call_id ?? 'tc1',
21
+ status: fields.status ?? 'success',
22
+ artifact: fields.artifact,
23
+ additional_kwargs: fields.additional_kwargs,
24
+ content: fields.content,
25
+ });
26
+ }
27
+
28
+ describe('annotateMessagesForLLM', () => {
29
+ it('returns the input array reference when registry is undefined', () => {
30
+ const messages = [
31
+ new HumanMessage('hi'),
32
+ makeToolMessage({
33
+ content: 'data',
34
+ additional_kwargs: { _refKey: 'tool0turn0' },
35
+ }),
36
+ ];
37
+ const out = annotateMessagesForLLM(messages, undefined, 'r1');
38
+ expect(out).toBe(messages);
39
+ });
40
+
41
+ it('does not iterate any message when the feature is disabled (registry undefined)', () => {
42
+ /**
43
+ * Hard guarantee: when the host hasn't enabled
44
+ * `RunConfig.toolOutputReferences`, calling
45
+ * `annotateMessagesForLLM` must short-circuit at O(1) without
46
+ * touching a single ToolMessage. We assert by spying on
47
+ * `_getType` — the first per-message call inside the loop — and
48
+ * confirming it was never invoked.
49
+ */
50
+ const messages = [
51
+ makeToolMessage({ content: 'a' }),
52
+ makeToolMessage({ content: 'b' }),
53
+ makeToolMessage({
54
+ content: 'c',
55
+ additional_kwargs: { _refKey: 'tool0turn0' },
56
+ }),
57
+ ];
58
+ const spies = messages.map((m) => jest.spyOn(m, '_getType'));
59
+ const out = annotateMessagesForLLM(messages, undefined, undefined);
60
+ expect(out).toBe(messages);
61
+ for (const spy of spies) {
62
+ expect(spy).not.toHaveBeenCalled();
63
+ spy.mockRestore();
64
+ }
65
+ });
66
+
67
+ it('returns the input array reference when no ToolMessage carries metadata', () => {
68
+ const registry = new ToolOutputReferenceRegistry();
69
+ registry.set('r1', 'tool0turn0', 'stored');
70
+ const messages = [
71
+ new HumanMessage('hi'),
72
+ makeToolMessage({ content: 'data' }),
73
+ new AIMessage('answer'),
74
+ ];
75
+ const out = annotateMessagesForLLM(messages, registry, 'r1');
76
+ expect(out).toBe(messages);
77
+ });
78
+
79
+ it('annotates string content when _refKey is live in the registry', () => {
80
+ const registry = new ToolOutputReferenceRegistry();
81
+ registry.set('r1', 'tool0turn0', 'stored-raw');
82
+ const tm = makeToolMessage({
83
+ content: 'output',
84
+ additional_kwargs: { _refKey: 'tool0turn0' },
85
+ });
86
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
87
+ expect(out[0].content).toBe('[ref: tool0turn0]\noutput');
88
+ expect(tm.content).toBe('output');
89
+ expect(out).not.toBe([tm]);
90
+ expect(out[0]).not.toBe(tm);
91
+ });
92
+
93
+ it('leaves content untouched but strips framework metadata when _refKey is stale', () => {
94
+ /**
95
+ * Stale `_refKey` (not in registry) doesn't trigger annotation,
96
+ * but the message still gets projected so framework keys are
97
+ * removed from `additional_kwargs` before the bytes leave for the
98
+ * provider. This protects against custom or future serializers
99
+ * that transmit `additional_kwargs`.
100
+ */
101
+ const registry = new ToolOutputReferenceRegistry();
102
+ const tm = makeToolMessage({
103
+ content: 'output',
104
+ additional_kwargs: { _refKey: 'tool0turn0', userField: 'preserved' },
105
+ });
106
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
107
+ expect(out[0].content).toBe('output');
108
+ expect(out[0]).not.toBe(tm);
109
+ const projectedKwargs = (out[0] as ToolMessage).additional_kwargs;
110
+ expect(projectedKwargs._refKey).toBeUndefined();
111
+ expect(projectedKwargs.userField).toBe('preserved');
112
+ expect(tm.additional_kwargs._refKey).toBe('tool0turn0');
113
+ });
114
+
115
+ it('always applies _unresolvedRefs even when there is no registry entry', () => {
116
+ const registry = new ToolOutputReferenceRegistry();
117
+ const tm = makeToolMessage({
118
+ content: 'output',
119
+ additional_kwargs: { _unresolvedRefs: ['tool9turn9'] },
120
+ });
121
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
122
+ expect(out[0].content).toBe('output\n[unresolved refs: tool9turn9]');
123
+ });
124
+
125
+ it('injects _ref into JSON-object string content', () => {
126
+ const registry = new ToolOutputReferenceRegistry();
127
+ registry.set('r1', 'tool0turn0', '{"a":1}');
128
+ const tm = makeToolMessage({
129
+ content: '{"a":1,"b":"x"}',
130
+ additional_kwargs: { _refKey: 'tool0turn0' },
131
+ });
132
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
133
+ const parsed = JSON.parse(out[0].content as string);
134
+ expect(parsed[TOOL_OUTPUT_REF_KEY]).toBe('tool0turn0');
135
+ expect(parsed.a).toBe(1);
136
+ expect(parsed.b).toBe('x');
137
+ });
138
+
139
+ it('injects both _ref and _unresolved_refs into JSON-object content', () => {
140
+ /**
141
+ * Combined path: when a ToolMessage carries both a live `_refKey`
142
+ * and unresolved-ref hints, JSON-object content should receive
143
+ * both `_ref` and `_unresolved_refs` fields rather than falling
144
+ * back to the prefix/trailer form. Exercises
145
+ * `annotateToolOutputWithReference`'s collision-detection logic
146
+ * through the projection entry point.
147
+ */
148
+ const registry = new ToolOutputReferenceRegistry();
149
+ registry.set('r1', 'tool0turn0', '{"a":1}');
150
+ const tm = makeToolMessage({
151
+ content: '{"a":1,"b":"x"}',
152
+ additional_kwargs: {
153
+ _refKey: 'tool0turn0',
154
+ _unresolvedRefs: ['tool9turn9'],
155
+ },
156
+ });
157
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
158
+ const parsed = JSON.parse(out[0].content as string);
159
+ expect(parsed[TOOL_OUTPUT_REF_KEY]).toBe('tool0turn0');
160
+ expect(parsed[TOOL_OUTPUT_UNRESOLVED_KEY]).toEqual(['tool9turn9']);
161
+ expect(parsed.a).toBe(1);
162
+ expect(parsed.b).toBe('x');
163
+ });
164
+
165
+ it('uses [ref: …] prefix for non-JSON string content', () => {
166
+ const registry = new ToolOutputReferenceRegistry();
167
+ registry.set('r1', 'tool0turn0', 'raw');
168
+ const tm = makeToolMessage({
169
+ content: 'plain output',
170
+ additional_kwargs: { _refKey: 'tool0turn0' },
171
+ });
172
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
173
+ expect(out[0].content).toBe('[ref: tool0turn0]\nplain output');
174
+ });
175
+
176
+ it('prepends an unresolved-refs warning text block to multi-part content', () => {
177
+ const registry = new ToolOutputReferenceRegistry();
178
+ const tm = makeToolMessage({
179
+ content: [
180
+ { type: 'text', text: 'data' },
181
+ { type: 'image_url', image_url: { url: 'data:...' } },
182
+ ] as unknown as ToolMessage['content'],
183
+ additional_kwargs: { _unresolvedRefs: ['tool9turn9'] },
184
+ });
185
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
186
+ const blocks = out[0].content as Array<{ type: string; text?: string }>;
187
+ expect(blocks).toHaveLength(3);
188
+ expect(blocks[0].type).toBe('text');
189
+ expect(blocks[0].text).toBe('[unresolved refs: tool9turn9]');
190
+ expect(blocks[1].type).toBe('text');
191
+ expect(blocks[1].text).toBe('data');
192
+ expect(blocks[2].type).toBe('image_url');
193
+ });
194
+
195
+ it('preserves artifact on the projected ToolMessage', () => {
196
+ /**
197
+ * Hosts attach `artifact` to ToolMessages via the
198
+ * `content_and_artifact` response format (e.g. code execution
199
+ * sessions, MCP tools that return structured side-data). The
200
+ * projection must round-trip the artifact untouched so downstream
201
+ * consumers (audit logs, code-session tracking) keep working.
202
+ */
203
+ const registry = new ToolOutputReferenceRegistry();
204
+ registry.set('r1', 'tool0turn0', 'raw');
205
+ const artifact = {
206
+ session_id: 'abc',
207
+ files: [{ id: 'f1', name: 'a.txt' }],
208
+ };
209
+ const tm = makeToolMessage({
210
+ content: 'output',
211
+ artifact,
212
+ additional_kwargs: { _refKey: 'tool0turn0' },
213
+ });
214
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
215
+ const projected = out[0] as ToolMessage;
216
+ expect(projected.artifact).toBe(artifact);
217
+ expect(projected.content).toBe('[ref: tool0turn0]\noutput');
218
+ });
219
+
220
+ it('does not mutate the original ToolMessage instance or its content', () => {
221
+ const registry = new ToolOutputReferenceRegistry();
222
+ registry.set('r1', 'tool0turn0', 'raw');
223
+ const tm = makeToolMessage({
224
+ content: 'output',
225
+ additional_kwargs: { _refKey: 'tool0turn0' },
226
+ });
227
+ const originalContent = tm.content;
228
+ const originalKwargs = { ...tm.additional_kwargs };
229
+ annotateMessagesForLLM([tm], registry, 'r1');
230
+ expect(tm.content).toBe(originalContent);
231
+ expect(tm.additional_kwargs).toEqual(originalKwargs);
232
+ });
233
+
234
+ it('strips framework ref metadata from the projected additional_kwargs but preserves other fields', () => {
235
+ /**
236
+ * Defensive: even though LangChain's standard provider serializers
237
+ * do not transmit `additional_kwargs`, a custom adapter or future
238
+ * LangChain change could. Strip our three framework-owned keys on
239
+ * the projection so the metadata never reaches the wire under any
240
+ * serializer behavior. Non-framework fields stay put.
241
+ */
242
+ const registry = new ToolOutputReferenceRegistry();
243
+ registry.set('r1', 'tool0turn0', 'raw');
244
+ const tm = makeToolMessage({
245
+ content: 'output',
246
+ additional_kwargs: {
247
+ _refKey: 'tool0turn0',
248
+ _refScope: 'r1',
249
+ _unresolvedRefs: ['tool9turn9'],
250
+ someOtherField: 'preserved',
251
+ },
252
+ });
253
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
254
+ const projected = out[0] as ToolMessage;
255
+ expect(projected.additional_kwargs._refKey).toBeUndefined();
256
+ expect(projected.additional_kwargs._refScope).toBeUndefined();
257
+ expect(projected.additional_kwargs._unresolvedRefs).toBeUndefined();
258
+ expect(projected.additional_kwargs.someOtherField).toBe('preserved');
259
+ expect(tm.additional_kwargs._refKey).toBe('tool0turn0');
260
+ expect(tm.additional_kwargs._refScope).toBe('r1');
261
+ });
262
+
263
+ it('leaves additional_kwargs empty when stripping removed every framework key', () => {
264
+ /**
265
+ * `stripFrameworkRefMetadata` returns `undefined` when no non-
266
+ * framework keys remain, and the LangChain `ToolMessage`
267
+ * constructor normalizes that to `{}` — so the projected message
268
+ * exposes an empty object, not the original kwargs.
269
+ */
270
+ const registry = new ToolOutputReferenceRegistry();
271
+ registry.set('r1', 'tool0turn0', 'raw');
272
+ const tm = makeToolMessage({
273
+ content: 'output',
274
+ additional_kwargs: {
275
+ _refKey: 'tool0turn0',
276
+ _refScope: 'r1',
277
+ },
278
+ });
279
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
280
+ const projected = out[0] as ToolMessage;
281
+ expect(projected.additional_kwargs).toEqual({});
282
+ });
283
+
284
+ it('passes through non-ToolMessages unchanged in the projected array', () => {
285
+ const registry = new ToolOutputReferenceRegistry();
286
+ registry.set('r1', 'tool0turn0', 'raw');
287
+ const human = new HumanMessage('hi');
288
+ const ai = new AIMessage('answer');
289
+ const tm = makeToolMessage({
290
+ content: 'output',
291
+ additional_kwargs: { _refKey: 'tool0turn0' },
292
+ });
293
+ const out = annotateMessagesForLLM([human, ai, tm], registry, 'r1');
294
+ expect(out[0]).toBe(human);
295
+ expect(out[1]).toBe(ai);
296
+ expect(out[2]).not.toBe(tm);
297
+ });
298
+
299
+ it('projects (to strip metadata) but does not annotate when only stale _refKey is present', () => {
300
+ const registry = new ToolOutputReferenceRegistry();
301
+ registry.set('r1', 'tool1turn0', 'somethingelse');
302
+ const tm = makeToolMessage({
303
+ content: 'output',
304
+ additional_kwargs: { _refKey: 'tool0turn0' },
305
+ });
306
+ const messages = [tm];
307
+ const out = annotateMessagesForLLM(messages, registry, 'r1');
308
+ expect(out).not.toBe(messages);
309
+ expect(out[0].content).toBe('output');
310
+ expect((out[0] as ToolMessage).additional_kwargs._refKey).toBeUndefined();
311
+ });
312
+
313
+ it('returns the input array reference-equal when no ToolMessage carries any framework metadata', () => {
314
+ const registry = new ToolOutputReferenceRegistry();
315
+ registry.set('r1', 'tool0turn0', 'stored');
316
+ const messages = [
317
+ new HumanMessage('hi'),
318
+ makeToolMessage({
319
+ content: 'output',
320
+ additional_kwargs: { unrelated: 'value' },
321
+ }),
322
+ new AIMessage('answer'),
323
+ ];
324
+ const out = annotateMessagesForLLM(messages, registry, 'r1');
325
+ expect(out).toBe(messages);
326
+ });
327
+
328
+ it('annotates only the live ref when both ref and unresolved are present', () => {
329
+ const registry = new ToolOutputReferenceRegistry();
330
+ registry.set('r1', 'tool0turn0', 'raw');
331
+ const tm = makeToolMessage({
332
+ content: 'output',
333
+ additional_kwargs: {
334
+ _refKey: 'tool0turn0',
335
+ _unresolvedRefs: ['tool9turn9'],
336
+ },
337
+ });
338
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
339
+ expect(out[0].content).toBe(
340
+ '[ref: tool0turn0]\noutput\n[unresolved refs: tool9turn9]'
341
+ );
342
+ });
343
+
344
+ it('uses _refScope for the registry lookup when present, ignoring runId', () => {
345
+ /**
346
+ * Anonymous ToolNode batches register under a synthetic scope
347
+ * (`\0anon-<n>`) that `config.configurable.run_id` cannot recover.
348
+ * The transform must follow the message-stamped `_refScope`
349
+ * instead of the config-derived runId.
350
+ */
351
+ const registry = new ToolOutputReferenceRegistry();
352
+ const anonScope = '\0anon-3';
353
+ registry.set(anonScope, 'tool0turn0', 'raw');
354
+
355
+ const tm = makeToolMessage({
356
+ content: 'output',
357
+ additional_kwargs: {
358
+ _refKey: 'tool0turn0',
359
+ _refScope: anonScope,
360
+ },
361
+ });
362
+ const out = annotateMessagesForLLM([tm], registry, undefined);
363
+ expect(out[0].content).toBe('[ref: tool0turn0]\noutput');
364
+ });
365
+
366
+ it('falls back to runId when _refScope is absent (legacy / pre-scope messages)', () => {
367
+ const registry = new ToolOutputReferenceRegistry();
368
+ registry.set('r1', 'tool0turn0', 'raw');
369
+ const tm = makeToolMessage({
370
+ content: 'output',
371
+ additional_kwargs: { _refKey: 'tool0turn0' },
372
+ });
373
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
374
+ expect(out[0].content).toBe('[ref: tool0turn0]\noutput');
375
+ });
376
+
377
+ it('coerces a non-array _unresolvedRefs to empty without throwing', () => {
378
+ /**
379
+ * Defensive against malformed hydrated messages:
380
+ * `additional_kwargs._unresolvedRefs` is untyped at the LangChain
381
+ * layer, so a persisted message could carry a string/object/null
382
+ * by mistake. The transform must not crash the run on
383
+ * `.length` / `.join` — coerce to an empty list, strip the
384
+ * malformed key, and proceed.
385
+ */
386
+ const registry = new ToolOutputReferenceRegistry();
387
+ const tm = makeToolMessage({
388
+ content: 'output',
389
+ additional_kwargs: {
390
+ _unresolvedRefs: 'tool9turn9' as unknown as string[],
391
+ },
392
+ });
393
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
394
+ expect(out[0].content).toBe('output');
395
+ expect(
396
+ (out[0] as ToolMessage).additional_kwargs._unresolvedRefs
397
+ ).toBeUndefined();
398
+ });
399
+
400
+ it('filters non-string entries out of _unresolvedRefs', () => {
401
+ const registry = new ToolOutputReferenceRegistry();
402
+ const tm = makeToolMessage({
403
+ content: 'output',
404
+ additional_kwargs: {
405
+ _unresolvedRefs: [
406
+ 'tool9turn9',
407
+ 42,
408
+ null,
409
+ { not: 'a string' },
410
+ 'tool8turn8',
411
+ ] as unknown as string[],
412
+ },
413
+ });
414
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
415
+ expect(out[0].content).toBe(
416
+ 'output\n[unresolved refs: tool9turn9, tool8turn8]'
417
+ );
418
+ });
419
+
420
+ it('ignores a non-string _refKey rather than poisoning the registry lookup', () => {
421
+ const registry = new ToolOutputReferenceRegistry();
422
+ registry.set('r1', 'tool0turn0', 'raw');
423
+ const tm = makeToolMessage({
424
+ content: 'output',
425
+ additional_kwargs: {
426
+ _refKey: { malformed: true } as unknown as string,
427
+ },
428
+ });
429
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
430
+ expect(out[0].content).toBe('output');
431
+ expect((out[0] as ToolMessage).additional_kwargs._refKey).toBeUndefined();
432
+ });
433
+
434
+ it('skips a ToolMessage whose additional_kwargs is a primitive without throwing', () => {
435
+ /**
436
+ * The `in` operator throws `TypeError` on primitives, so without
437
+ * a runtime object guard, a hydrated ToolMessage carrying e.g.
438
+ * `additional_kwargs: 'not-an-object'` (from a buggy serializer)
439
+ * would crash `attemptInvoke` before the provider call. Verify
440
+ * we skip that message and process subsequent live-ref messages
441
+ * normally.
442
+ */
443
+ const registry = new ToolOutputReferenceRegistry();
444
+ registry.set('r1', 'tool0turn0', 'raw');
445
+
446
+ const malformed = new ToolMessage({
447
+ name: 'echo',
448
+ tool_call_id: 'mal',
449
+ status: 'success',
450
+ content: 'malformed-output',
451
+ });
452
+ /* Force a primitive past LangChain's typed setter via a cast. */
453
+ (malformed as unknown as { additional_kwargs: unknown }).additional_kwargs =
454
+ 'not-an-object' as unknown;
455
+
456
+ const live = makeToolMessage({
457
+ content: 'live-output',
458
+ additional_kwargs: { _refKey: 'tool0turn0' },
459
+ });
460
+
461
+ const out = annotateMessagesForLLM([malformed, live], registry, 'r1');
462
+ /* Malformed message passes through unchanged; live ref still annotates. */
463
+ expect(out[0]).toBe(malformed);
464
+ expect(out[1].content).toBe('[ref: tool0turn0]\nlive-output');
465
+ });
466
+
467
+ it('treats stale _refKey but live unresolved as unresolved-only', () => {
468
+ const registry = new ToolOutputReferenceRegistry();
469
+ const tm = makeToolMessage({
470
+ content: 'output',
471
+ additional_kwargs: {
472
+ _refKey: 'tool0turn0',
473
+ _unresolvedRefs: ['tool9turn9'],
474
+ },
475
+ });
476
+ const out = annotateMessagesForLLM([tm], registry, 'r1');
477
+ expect(out[0].content).toBe('output\n[unresolved refs: tool9turn9]');
478
+ });
479
+ });
@@ -0,0 +1,161 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { formatSkillCatalog } from '../skillCatalog';
3
+ import type { SkillCatalogEntry } from '@/types';
4
+
5
+ describe('formatSkillCatalog', () => {
6
+ it('returns empty string for empty array', () => {
7
+ expect(formatSkillCatalog([])).toBe('');
8
+ });
9
+
10
+ it('formats a single skill with header', () => {
11
+ const skills: SkillCatalogEntry[] = [
12
+ {
13
+ name: 'pdf-processor',
14
+ description: 'Processes PDF files into structured data.',
15
+ },
16
+ ];
17
+ const result = formatSkillCatalog(skills);
18
+ expect(result).toBe(
19
+ '## Available Skills\n\n- pdf-processor: Processes PDF files into structured data.'
20
+ );
21
+ });
22
+
23
+ it('formats multiple skills within budget', () => {
24
+ const skills: SkillCatalogEntry[] = [
25
+ { name: 'pdf-processor', description: 'Processes PDF files.' },
26
+ { name: 'review-pr', description: 'Reviews pull requests.' },
27
+ { name: 'meeting-notes', description: 'Formats meeting transcripts.' },
28
+ ];
29
+ const result = formatSkillCatalog(skills);
30
+ expect(result).toContain('## Available Skills');
31
+ expect(result).toContain('- pdf-processor: Processes PDF files.');
32
+ expect(result).toContain('- review-pr: Reviews pull requests.');
33
+ expect(result).toContain('- meeting-notes: Formats meeting transcripts.');
34
+ });
35
+
36
+ it('caps per-entry descriptions at maxEntryChars', () => {
37
+ const longDesc = 'A'.repeat(300);
38
+ const skills: SkillCatalogEntry[] = [
39
+ { name: 'long-skill', description: longDesc },
40
+ ];
41
+ const result = formatSkillCatalog(skills);
42
+ expect(result).toContain('- long-skill: ' + 'A'.repeat(249) + '\u2026');
43
+ expect(result).not.toContain('A'.repeat(300));
44
+ });
45
+
46
+ it('truncates descriptions proportionally when over budget', () => {
47
+ const skills: SkillCatalogEntry[] = Array.from({ length: 10 }, (_, i) => ({
48
+ name: `sk-${i}`,
49
+ description: 'D'.repeat(200),
50
+ }));
51
+ // Budget = 10000 * 0.01 * 4 = 400 chars — enough for names + short descs, not full 200-char descs
52
+ const result = formatSkillCatalog(skills, {
53
+ contextWindowTokens: 10000,
54
+ budgetPercent: 0.01,
55
+ charsPerToken: 4,
56
+ });
57
+ expect(result).toContain('## Available Skills');
58
+ for (let i = 0; i < 10; i++) {
59
+ expect(result).toContain(`sk-${i}`);
60
+ }
61
+ // Full 200-char descriptions should be truncated
62
+ expect(result).not.toContain('D'.repeat(200));
63
+ });
64
+
65
+ it('falls back to names-only when extremely over budget', () => {
66
+ const skills: SkillCatalogEntry[] = Array.from({ length: 10 }, (_, i) => ({
67
+ name: `s${i}`,
68
+ description: 'Very detailed description that is quite long and verbose.',
69
+ }));
70
+ // Budget = 2000 * 0.01 * 4 = 80 chars — enough for names-only but not descriptions
71
+ const result = formatSkillCatalog(skills, {
72
+ contextWindowTokens: 2000,
73
+ budgetPercent: 0.01,
74
+ charsPerToken: 4,
75
+ });
76
+ expect(result).toContain('## Available Skills');
77
+ expect(result).toContain('- s0');
78
+ // Verify entry lines have no descriptions (names-only format)
79
+ const entryLines = result.split('\n').filter((l) => l.startsWith('- '));
80
+ for (const line of entryLines) {
81
+ expect(line).toMatch(/^- s\d+$/);
82
+ }
83
+ });
84
+
85
+ it('respects custom options', () => {
86
+ const skills: SkillCatalogEntry[] = [
87
+ { name: 'test', description: 'A'.repeat(100) },
88
+ ];
89
+ const result = formatSkillCatalog(skills, { maxEntryChars: 50 });
90
+ expect(result).toContain('A'.repeat(49) + '\u2026');
91
+ expect(result).not.toContain('A'.repeat(100));
92
+ });
93
+
94
+ it('includes skills with descriptions shorter than minDescLength', () => {
95
+ const skills: SkillCatalogEntry[] = [
96
+ { name: 'short', description: 'Hi' },
97
+ { name: 'normal', description: 'A normal description here.' },
98
+ ];
99
+ const result = formatSkillCatalog(skills);
100
+ expect(result).toContain('- short: Hi');
101
+ expect(result).toContain('- normal: A normal description here.');
102
+ });
103
+
104
+ it('handles all skills with zero-length descriptions as names-only', () => {
105
+ const skills: SkillCatalogEntry[] = [
106
+ { name: 'alpha', description: '' },
107
+ { name: 'beta', description: '' },
108
+ ];
109
+ const result = formatSkillCatalog(skills);
110
+ expect(result).toBe('## Available Skills\n\n- alpha\n- beta');
111
+ });
112
+
113
+ it('has no trailing or leading whitespace', () => {
114
+ const skills: SkillCatalogEntry[] = [
115
+ { name: 'test', description: 'A test skill.' },
116
+ ];
117
+ const result = formatSkillCatalog(skills);
118
+ expect(result).toBe(result.trim());
119
+ const lines = result.split('\n');
120
+ for (const line of lines) {
121
+ expect(line).toBe(line.trimEnd());
122
+ }
123
+ });
124
+
125
+ it('truncates names-only list when even names exceed budget', () => {
126
+ const skills: SkillCatalogEntry[] = Array.from({ length: 100 }, (_, i) => ({
127
+ name: `skill-with-a-long-name-${i}`,
128
+ description: 'Some description.',
129
+ }));
130
+ // Budget so small that even names-only for 100 skills exceeds it
131
+ const result = formatSkillCatalog(skills, {
132
+ contextWindowTokens: 100,
133
+ budgetPercent: 0.01,
134
+ charsPerToken: 4,
135
+ });
136
+ // Should still have the header and at least one entry, but not all 100
137
+ if (result === '') {
138
+ // Budget too small for even one entry — valid edge case
139
+ expect(result).toBe('');
140
+ } else {
141
+ expect(result).toContain('## Available Skills');
142
+ const entryLines = result.split('\n').filter((l) => l.startsWith('- '));
143
+ expect(entryLines.length).toBeLessThan(100);
144
+ expect(entryLines.length).toBeGreaterThan(0);
145
+ expect(result.length).toBeLessThanOrEqual(100 * 0.01 * 4);
146
+ }
147
+ });
148
+
149
+ it('ignores displayTitle in output', () => {
150
+ const skills: SkillCatalogEntry[] = [
151
+ {
152
+ name: 'my-skill',
153
+ description: 'Does stuff.',
154
+ displayTitle: 'My Fancy Skill',
155
+ },
156
+ ];
157
+ const result = formatSkillCatalog(skills);
158
+ expect(result).not.toContain('My Fancy Skill');
159
+ expect(result).toContain('- my-skill: Does stuff.');
160
+ });
161
+ });