@mcp-abap-adt/llm-agent-libs 12.0.2 → 12.0.4

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 (361) hide show
  1. package/dist/adapters/index.d.ts +4 -0
  2. package/dist/adapters/index.d.ts.map +1 -0
  3. package/dist/adapters/index.js +4 -0
  4. package/dist/adapters/index.js.map +1 -0
  5. package/dist/adapters/llm-adapter.d.ts +24 -0
  6. package/dist/adapters/llm-adapter.d.ts.map +1 -0
  7. package/dist/adapters/llm-adapter.js +345 -0
  8. package/dist/adapters/llm-adapter.js.map +1 -0
  9. package/dist/adapters/llm-provider-bridge.d.ts +30 -0
  10. package/dist/adapters/llm-provider-bridge.d.ts.map +1 -0
  11. package/dist/adapters/llm-provider-bridge.js +122 -0
  12. package/dist/adapters/llm-provider-bridge.js.map +1 -0
  13. package/dist/adapters/non-streaming-llm.d.ts +18 -0
  14. package/dist/adapters/non-streaming-llm.d.ts.map +1 -0
  15. package/dist/adapters/non-streaming-llm.js +44 -0
  16. package/dist/adapters/non-streaming-llm.js.map +1 -0
  17. package/dist/agent.d.ts +227 -0
  18. package/dist/agent.d.ts.map +1 -0
  19. package/dist/agent.js +1534 -0
  20. package/dist/agent.js.map +1 -0
  21. package/dist/builder.d.ts +260 -0
  22. package/dist/builder.d.ts.map +1 -0
  23. package/dist/builder.js +949 -0
  24. package/dist/builder.js.map +1 -0
  25. package/dist/classifier/index.d.ts +3 -0
  26. package/dist/classifier/index.d.ts.map +1 -0
  27. package/dist/classifier/index.js +2 -0
  28. package/dist/classifier/index.js.map +1 -0
  29. package/dist/classifier/llm-classifier.d.ts +20 -0
  30. package/dist/classifier/llm-classifier.d.ts.map +1 -0
  31. package/dist/classifier/llm-classifier.js +128 -0
  32. package/dist/classifier/llm-classifier.js.map +1 -0
  33. package/dist/config/config-watcher.d.ts +54 -0
  34. package/dist/config/config-watcher.d.ts.map +1 -0
  35. package/dist/config/config-watcher.js +107 -0
  36. package/dist/config/config-watcher.js.map +1 -0
  37. package/dist/config/index.d.ts +2 -0
  38. package/dist/config/index.d.ts.map +1 -0
  39. package/dist/config/index.js +2 -0
  40. package/dist/config/index.js.map +1 -0
  41. package/dist/context/context-assembler.d.ts +49 -0
  42. package/dist/context/context-assembler.d.ts.map +1 -0
  43. package/dist/context/context-assembler.js +184 -0
  44. package/dist/context/context-assembler.js.map +1 -0
  45. package/dist/context/index.d.ts +3 -0
  46. package/dist/context/index.d.ts.map +1 -0
  47. package/dist/context/index.js +2 -0
  48. package/dist/context/index.js.map +1 -0
  49. package/dist/health/health-checker.d.ts +21 -0
  50. package/dist/health/health-checker.d.ts.map +1 -0
  51. package/dist/health/health-checker.js +52 -0
  52. package/dist/health/health-checker.js.map +1 -0
  53. package/dist/health/index.d.ts +3 -0
  54. package/dist/health/index.d.ts.map +1 -0
  55. package/dist/health/index.js +2 -0
  56. package/dist/health/index.js.map +1 -0
  57. package/dist/health/types.d.ts +2 -0
  58. package/dist/health/types.d.ts.map +1 -0
  59. package/dist/health/types.js +2 -0
  60. package/dist/health/types.js.map +1 -0
  61. package/dist/history/history-memory.d.ts +12 -0
  62. package/dist/history/history-memory.d.ts.map +1 -0
  63. package/dist/history/history-memory.js +26 -0
  64. package/dist/history/history-memory.js.map +1 -0
  65. package/dist/history/history-summarizer.d.ts +10 -0
  66. package/dist/history/history-summarizer.d.ts.map +1 -0
  67. package/dist/history/history-summarizer.js +27 -0
  68. package/dist/history/history-summarizer.js.map +1 -0
  69. package/dist/history/index.d.ts +3 -0
  70. package/dist/history/index.d.ts.map +1 -0
  71. package/dist/history/index.js +3 -0
  72. package/dist/history/index.js.map +1 -0
  73. package/dist/index.d.ts +37 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +86 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/interfaces/mcp-connection-strategy.d.ts +2 -0
  78. package/dist/interfaces/mcp-connection-strategy.d.ts.map +1 -0
  79. package/dist/interfaces/mcp-connection-strategy.js +2 -0
  80. package/dist/interfaces/mcp-connection-strategy.js.map +1 -0
  81. package/dist/interfaces/model-resolver.d.ts +2 -0
  82. package/dist/interfaces/model-resolver.d.ts.map +1 -0
  83. package/dist/interfaces/model-resolver.js +2 -0
  84. package/dist/interfaces/model-resolver.js.map +1 -0
  85. package/dist/interfaces/pipeline.d.ts +123 -0
  86. package/dist/interfaces/pipeline.d.ts.map +1 -0
  87. package/dist/interfaces/pipeline.js +13 -0
  88. package/dist/interfaces/pipeline.js.map +1 -0
  89. package/dist/logger/console-logger.d.ts +7 -0
  90. package/dist/logger/console-logger.d.ts.map +1 -0
  91. package/dist/logger/console-logger.js +12 -0
  92. package/dist/logger/console-logger.js.map +1 -0
  93. package/dist/logger/default-request-logger.d.ts +17 -0
  94. package/dist/logger/default-request-logger.d.ts.map +1 -0
  95. package/dist/logger/default-request-logger.js +86 -0
  96. package/dist/logger/default-request-logger.js.map +1 -0
  97. package/dist/logger/index.d.ts +3 -0
  98. package/dist/logger/index.d.ts.map +1 -0
  99. package/dist/logger/index.js +2 -0
  100. package/dist/logger/index.js.map +1 -0
  101. package/dist/logger/noop-request-logger.d.ts +11 -0
  102. package/dist/logger/noop-request-logger.d.ts.map +1 -0
  103. package/dist/logger/noop-request-logger.js +20 -0
  104. package/dist/logger/noop-request-logger.js.map +1 -0
  105. package/dist/logger/session-logger.d.ts +10 -0
  106. package/dist/logger/session-logger.d.ts.map +1 -0
  107. package/dist/logger/session-logger.js +40 -0
  108. package/dist/logger/session-logger.js.map +1 -0
  109. package/dist/metrics/in-memory-metrics.d.ts +36 -0
  110. package/dist/metrics/in-memory-metrics.d.ts.map +1 -0
  111. package/dist/metrics/in-memory-metrics.js +87 -0
  112. package/dist/metrics/in-memory-metrics.js.map +1 -0
  113. package/dist/metrics/index.d.ts +4 -0
  114. package/dist/metrics/index.d.ts.map +1 -0
  115. package/dist/metrics/index.js +3 -0
  116. package/dist/metrics/index.js.map +1 -0
  117. package/dist/metrics/noop-metrics.d.ts +17 -0
  118. package/dist/metrics/noop-metrics.d.ts.map +1 -0
  119. package/dist/metrics/noop-metrics.js +22 -0
  120. package/dist/metrics/noop-metrics.js.map +1 -0
  121. package/dist/metrics/types.d.ts +2 -0
  122. package/dist/metrics/types.d.ts.map +1 -0
  123. package/dist/metrics/types.js +2 -0
  124. package/dist/metrics/types.js.map +1 -0
  125. package/dist/otel/index.d.ts +2 -0
  126. package/dist/otel/index.d.ts.map +1 -0
  127. package/dist/otel/index.js +2 -0
  128. package/dist/otel/index.js.map +1 -0
  129. package/dist/otel/otel-tracer-adapter.d.ts +15 -0
  130. package/dist/otel/otel-tracer-adapter.d.ts.map +1 -0
  131. package/dist/otel/otel-tracer-adapter.js +62 -0
  132. package/dist/otel/otel-tracer-adapter.js.map +1 -0
  133. package/dist/pipeline/condition-evaluator.d.ts +35 -0
  134. package/dist/pipeline/condition-evaluator.d.ts.map +1 -0
  135. package/dist/pipeline/condition-evaluator.js +143 -0
  136. package/dist/pipeline/condition-evaluator.js.map +1 -0
  137. package/dist/pipeline/context.d.ts +119 -0
  138. package/dist/pipeline/context.d.ts.map +1 -0
  139. package/dist/pipeline/context.js +19 -0
  140. package/dist/pipeline/context.js.map +1 -0
  141. package/dist/pipeline/default-pipeline.d.ts +69 -0
  142. package/dist/pipeline/default-pipeline.d.ts.map +1 -0
  143. package/dist/pipeline/default-pipeline.js +276 -0
  144. package/dist/pipeline/default-pipeline.js.map +1 -0
  145. package/dist/pipeline/executor.d.ts +54 -0
  146. package/dist/pipeline/executor.d.ts.map +1 -0
  147. package/dist/pipeline/executor.js +133 -0
  148. package/dist/pipeline/executor.js.map +1 -0
  149. package/dist/pipeline/handlers/assemble.d.ts +16 -0
  150. package/dist/pipeline/handlers/assemble.d.ts.map +1 -0
  151. package/dist/pipeline/handlers/assemble.js +72 -0
  152. package/dist/pipeline/handlers/assemble.js.map +1 -0
  153. package/dist/pipeline/handlers/build-tool-query.d.ts +30 -0
  154. package/dist/pipeline/handlers/build-tool-query.d.ts.map +1 -0
  155. package/dist/pipeline/handlers/build-tool-query.js +80 -0
  156. package/dist/pipeline/handlers/build-tool-query.js.map +1 -0
  157. package/dist/pipeline/handlers/classify.d.ts +21 -0
  158. package/dist/pipeline/handlers/classify.d.ts.map +1 -0
  159. package/dist/pipeline/handlers/classify.js +60 -0
  160. package/dist/pipeline/handlers/classify.js.map +1 -0
  161. package/dist/pipeline/handlers/expand.d.ts +16 -0
  162. package/dist/pipeline/handlers/expand.d.ts.map +1 -0
  163. package/dist/pipeline/handlers/expand.js +24 -0
  164. package/dist/pipeline/handlers/expand.js.map +1 -0
  165. package/dist/pipeline/handlers/history-upsert.d.ts +29 -0
  166. package/dist/pipeline/handlers/history-upsert.d.ts.map +1 -0
  167. package/dist/pipeline/handlers/history-upsert.js +75 -0
  168. package/dist/pipeline/handlers/history-upsert.js.map +1 -0
  169. package/dist/pipeline/handlers/index.d.ts +31 -0
  170. package/dist/pipeline/handlers/index.d.ts.map +1 -0
  171. package/dist/pipeline/handlers/index.js +44 -0
  172. package/dist/pipeline/handlers/index.js.map +1 -0
  173. package/dist/pipeline/handlers/rag-query.d.ts +27 -0
  174. package/dist/pipeline/handlers/rag-query.d.ts.map +1 -0
  175. package/dist/pipeline/handlers/rag-query.js +117 -0
  176. package/dist/pipeline/handlers/rag-query.js.map +1 -0
  177. package/dist/pipeline/handlers/rerank.d.ts +16 -0
  178. package/dist/pipeline/handlers/rerank.d.ts.map +1 -0
  179. package/dist/pipeline/handlers/rerank.js +27 -0
  180. package/dist/pipeline/handlers/rerank.js.map +1 -0
  181. package/dist/pipeline/handlers/skill-select.d.ts +18 -0
  182. package/dist/pipeline/handlers/skill-select.d.ts.map +1 -0
  183. package/dist/pipeline/handlers/skill-select.js +88 -0
  184. package/dist/pipeline/handlers/skill-select.js.map +1 -0
  185. package/dist/pipeline/handlers/summarize.d.ts +17 -0
  186. package/dist/pipeline/handlers/summarize.d.ts.map +1 -0
  187. package/dist/pipeline/handlers/summarize.js +56 -0
  188. package/dist/pipeline/handlers/summarize.js.map +1 -0
  189. package/dist/pipeline/handlers/tool-loop.d.ts +32 -0
  190. package/dist/pipeline/handlers/tool-loop.d.ts.map +1 -0
  191. package/dist/pipeline/handlers/tool-loop.js +734 -0
  192. package/dist/pipeline/handlers/tool-loop.js.map +1 -0
  193. package/dist/pipeline/handlers/tool-select.d.ts +21 -0
  194. package/dist/pipeline/handlers/tool-select.d.ts.map +1 -0
  195. package/dist/pipeline/handlers/tool-select.js +109 -0
  196. package/dist/pipeline/handlers/tool-select.js.map +1 -0
  197. package/dist/pipeline/handlers/translate.d.ts +17 -0
  198. package/dist/pipeline/handlers/translate.d.ts.map +1 -0
  199. package/dist/pipeline/handlers/translate.js +42 -0
  200. package/dist/pipeline/handlers/translate.js.map +1 -0
  201. package/dist/pipeline/index.d.ts +14 -0
  202. package/dist/pipeline/index.d.ts.map +1 -0
  203. package/dist/pipeline/index.js +16 -0
  204. package/dist/pipeline/index.js.map +1 -0
  205. package/dist/pipeline/stage-handler.d.ts +27 -0
  206. package/dist/pipeline/stage-handler.d.ts.map +1 -0
  207. package/dist/pipeline/stage-handler.js +25 -0
  208. package/dist/pipeline/stage-handler.js.map +1 -0
  209. package/dist/pipeline/types.d.ts +5 -0
  210. package/dist/pipeline/types.d.ts.map +1 -0
  211. package/dist/pipeline/types.js +2 -0
  212. package/dist/pipeline/types.js.map +1 -0
  213. package/dist/plugins/index.d.ts +4 -0
  214. package/dist/plugins/index.d.ts.map +1 -0
  215. package/dist/plugins/index.js +3 -0
  216. package/dist/plugins/index.js.map +1 -0
  217. package/dist/plugins/loader.d.ts +67 -0
  218. package/dist/plugins/loader.d.ts.map +1 -0
  219. package/dist/plugins/loader.js +114 -0
  220. package/dist/plugins/loader.js.map +1 -0
  221. package/dist/plugins/types.d.ts +24 -0
  222. package/dist/plugins/types.d.ts.map +1 -0
  223. package/dist/plugins/types.js +86 -0
  224. package/dist/plugins/types.js.map +1 -0
  225. package/dist/policy/heuristic-injection-detector.d.ts +5 -0
  226. package/dist/policy/heuristic-injection-detector.d.ts.map +1 -0
  227. package/dist/policy/heuristic-injection-detector.js +36 -0
  228. package/dist/policy/heuristic-injection-detector.js.map +1 -0
  229. package/dist/policy/index.d.ts +6 -0
  230. package/dist/policy/index.d.ts.map +1 -0
  231. package/dist/policy/index.js +5 -0
  232. package/dist/policy/index.js.map +1 -0
  233. package/dist/policy/mixed-tool-call-handler.d.ts +15 -0
  234. package/dist/policy/mixed-tool-call-handler.d.ts.map +1 -0
  235. package/dist/policy/mixed-tool-call-handler.js +49 -0
  236. package/dist/policy/mixed-tool-call-handler.js.map +1 -0
  237. package/dist/policy/pending-tool-results-registry.d.ts +25 -0
  238. package/dist/policy/pending-tool-results-registry.d.ts.map +1 -0
  239. package/dist/policy/pending-tool-results-registry.js +50 -0
  240. package/dist/policy/pending-tool-results-registry.js.map +1 -0
  241. package/dist/policy/tool-availability-registry.d.ts +21 -0
  242. package/dist/policy/tool-availability-registry.d.ts.map +1 -0
  243. package/dist/policy/tool-availability-registry.js +77 -0
  244. package/dist/policy/tool-availability-registry.js.map +1 -0
  245. package/dist/policy/tool-policy-guard.d.ts +8 -0
  246. package/dist/policy/tool-policy-guard.d.ts.map +1 -0
  247. package/dist/policy/tool-policy-guard.js +40 -0
  248. package/dist/policy/tool-policy-guard.js.map +1 -0
  249. package/dist/policy/types.d.ts +51 -0
  250. package/dist/policy/types.d.ts.map +1 -0
  251. package/dist/policy/types.js +17 -0
  252. package/dist/policy/types.js.map +1 -0
  253. package/dist/providers.d.ts +48 -0
  254. package/dist/providers.d.ts.map +1 -0
  255. package/dist/providers.js +192 -0
  256. package/dist/providers.js.map +1 -0
  257. package/dist/reranker/index.d.ts +4 -0
  258. package/dist/reranker/index.d.ts.map +1 -0
  259. package/dist/reranker/index.js +3 -0
  260. package/dist/reranker/index.js.map +1 -0
  261. package/dist/reranker/llm-reranker.d.ts +10 -0
  262. package/dist/reranker/llm-reranker.d.ts.map +1 -0
  263. package/dist/reranker/llm-reranker.js +55 -0
  264. package/dist/reranker/llm-reranker.js.map +1 -0
  265. package/dist/reranker/noop-reranker.d.ts +6 -0
  266. package/dist/reranker/noop-reranker.d.ts.map +1 -0
  267. package/dist/reranker/noop-reranker.js +6 -0
  268. package/dist/reranker/noop-reranker.js.map +1 -0
  269. package/dist/reranker/types.d.ts +2 -0
  270. package/dist/reranker/types.d.ts.map +1 -0
  271. package/dist/reranker/types.js +2 -0
  272. package/dist/reranker/types.js.map +1 -0
  273. package/dist/resilience/rate-limiter-llm.d.ts +17 -0
  274. package/dist/resilience/rate-limiter-llm.d.ts.map +1 -0
  275. package/dist/resilience/rate-limiter-llm.js +30 -0
  276. package/dist/resilience/rate-limiter-llm.js.map +1 -0
  277. package/dist/resilience/retry-llm.d.ts +32 -0
  278. package/dist/resilience/retry-llm.d.ts.map +1 -0
  279. package/dist/resilience/retry-llm.js +103 -0
  280. package/dist/resilience/retry-llm.js.map +1 -0
  281. package/dist/resilience/token-bucket-rate-limiter.d.ts +21 -0
  282. package/dist/resilience/token-bucket-rate-limiter.d.ts.map +1 -0
  283. package/dist/resilience/token-bucket-rate-limiter.js +32 -0
  284. package/dist/resilience/token-bucket-rate-limiter.js.map +1 -0
  285. package/dist/session/index.d.ts +4 -0
  286. package/dist/session/index.d.ts.map +1 -0
  287. package/dist/session/index.js +3 -0
  288. package/dist/session/index.js.map +1 -0
  289. package/dist/session/noop-session-manager.d.ts +8 -0
  290. package/dist/session/noop-session-manager.d.ts.map +1 -0
  291. package/dist/session/noop-session-manager.js +11 -0
  292. package/dist/session/noop-session-manager.js.map +1 -0
  293. package/dist/session/session-manager.d.ts +13 -0
  294. package/dist/session/session-manager.d.ts.map +1 -0
  295. package/dist/session/session-manager.js +20 -0
  296. package/dist/session/session-manager.js.map +1 -0
  297. package/dist/session/types.d.ts +2 -0
  298. package/dist/session/types.d.ts.map +1 -0
  299. package/dist/session/types.js +2 -0
  300. package/dist/session/types.js.map +1 -0
  301. package/dist/skills/claude-skill-manager.d.ts +24 -0
  302. package/dist/skills/claude-skill-manager.d.ts.map +1 -0
  303. package/dist/skills/claude-skill-manager.js +81 -0
  304. package/dist/skills/claude-skill-manager.js.map +1 -0
  305. package/dist/skills/codex-skill-manager.d.ts +20 -0
  306. package/dist/skills/codex-skill-manager.d.ts.map +1 -0
  307. package/dist/skills/codex-skill-manager.js +75 -0
  308. package/dist/skills/codex-skill-manager.js.map +1 -0
  309. package/dist/skills/filesystem-skill-manager.d.ts +17 -0
  310. package/dist/skills/filesystem-skill-manager.d.ts.map +1 -0
  311. package/dist/skills/filesystem-skill-manager.js +48 -0
  312. package/dist/skills/filesystem-skill-manager.js.map +1 -0
  313. package/dist/skills/filesystem-skill.d.ts +25 -0
  314. package/dist/skills/filesystem-skill.d.ts.map +1 -0
  315. package/dist/skills/filesystem-skill.js +110 -0
  316. package/dist/skills/filesystem-skill.js.map +1 -0
  317. package/dist/skills/index.d.ts +9 -0
  318. package/dist/skills/index.d.ts.map +1 -0
  319. package/dist/skills/index.js +9 -0
  320. package/dist/skills/index.js.map +1 -0
  321. package/dist/skills/skill-utils.d.ts +15 -0
  322. package/dist/skills/skill-utils.d.ts.map +1 -0
  323. package/dist/skills/skill-utils.js +46 -0
  324. package/dist/skills/skill-utils.js.map +1 -0
  325. package/dist/testing/index.d.ts +118 -0
  326. package/dist/testing/index.d.ts.map +1 -0
  327. package/dist/testing/index.js +351 -0
  328. package/dist/testing/index.js.map +1 -0
  329. package/dist/tracer/index.d.ts +3 -0
  330. package/dist/tracer/index.d.ts.map +1 -0
  331. package/dist/tracer/index.js +2 -0
  332. package/dist/tracer/index.js.map +1 -0
  333. package/dist/tracer/noop-tracer.d.ts +5 -0
  334. package/dist/tracer/noop-tracer.d.ts.map +1 -0
  335. package/dist/tracer/noop-tracer.js +16 -0
  336. package/dist/tracer/noop-tracer.js.map +1 -0
  337. package/dist/tracer/types.d.ts +2 -0
  338. package/dist/tracer/types.d.ts.map +1 -0
  339. package/dist/tracer/types.js +2 -0
  340. package/dist/tracer/types.js.map +1 -0
  341. package/dist/utils/lazy.d.ts +51 -0
  342. package/dist/utils/lazy.d.ts.map +1 -0
  343. package/dist/utils/lazy.js +127 -0
  344. package/dist/utils/lazy.js.map +1 -0
  345. package/dist/utils/parse-frontmatter.d.ts +15 -0
  346. package/dist/utils/parse-frontmatter.d.ts.map +1 -0
  347. package/dist/utils/parse-frontmatter.js +22 -0
  348. package/dist/utils/parse-frontmatter.js.map +1 -0
  349. package/dist/validator/index.d.ts +3 -0
  350. package/dist/validator/index.d.ts.map +1 -0
  351. package/dist/validator/index.js +2 -0
  352. package/dist/validator/index.js.map +1 -0
  353. package/dist/validator/noop-validator.d.ts +6 -0
  354. package/dist/validator/noop-validator.d.ts.map +1 -0
  355. package/dist/validator/noop-validator.js +6 -0
  356. package/dist/validator/noop-validator.js.map +1 -0
  357. package/dist/validator/types.d.ts +3 -0
  358. package/dist/validator/types.d.ts.map +1 -0
  359. package/dist/validator/types.js +2 -0
  360. package/dist/validator/types.js.map +1 -0
  361. package/package.json +14 -11
@@ -0,0 +1,734 @@
1
+ /**
2
+ * ToolLoopHandler — streaming LLM call + MCP tool execution loop.
3
+ *
4
+ * This is the "terminal" pipeline stage. It takes over the streaming yield
5
+ * mechanism to push SSE chunks back to the consumer.
6
+ *
7
+ * Reads: `ctx.assembledMessages`, `ctx.activeTools`, `ctx.toolClientMap`,
8
+ * `ctx.externalTools`, `ctx.mainLlm`
9
+ * Writes: yields chunks via `ctx.yield()`, updates `ctx.timing`
10
+ *
11
+ * ## Config
12
+ *
13
+ * | Field | Type | Default | Description |
14
+ * |--------------------|--------|-------------|---------------------------------|
15
+ * | `maxIterations` | number | from ctx | Max tool-loop iterations |
16
+ * | `maxToolCalls` | number | from ctx | Max total tool calls per request|
17
+ * | `heartbeatIntervalMs` | number | 5000 | SSE heartbeat interval (ms) |
18
+ *
19
+ * ## Includes
20
+ *
21
+ * - Output validation (re-prompts on invalid LLM output)
22
+ * - Tool call classification (internal / external / hallucinated / blocked)
23
+ * - Concurrent tool execution with heartbeat
24
+ * - Tool availability tracking (temporary blacklist)
25
+ */
26
+ import { getStreamToolCallName, toToolCallDelta, } from '@mcp-abap-adt/llm-agent';
27
+ import { OrchestratorError } from '../../agent.js';
28
+ import { fireInternalToolsAsync } from '../../policy/mixed-tool-call-handler.js';
29
+ import { isToolContextUnavailableError } from '../../policy/tool-availability-registry.js';
30
+ function summarizeIterationMessages(messages) {
31
+ const totalChars = messages.reduce((sum, msg) => {
32
+ const content = typeof msg.content === 'string'
33
+ ? msg.content
34
+ : JSON.stringify(msg.content ?? '');
35
+ return sum + content.length;
36
+ }, 0);
37
+ return {
38
+ messageCount: messages.length,
39
+ totalChars,
40
+ roles: messages.map((msg, index) => ({
41
+ index,
42
+ role: msg.role,
43
+ contentLength: typeof msg.content === 'string'
44
+ ? msg.content.length
45
+ : JSON.stringify(msg.content ?? '').length,
46
+ hasToolCalls: 'tool_calls' in msg && Array.isArray(msg.tool_calls),
47
+ toolCallCount: 'tool_calls' in msg && Array.isArray(msg.tool_calls)
48
+ ? msg.tool_calls.length
49
+ : 0,
50
+ toolCallId: 'tool_call_id' in msg ? (msg.tool_call_id ?? null) : null,
51
+ })),
52
+ tail: messages.slice(-6).map((msg, index) => {
53
+ const content = typeof msg.content === 'string'
54
+ ? msg.content
55
+ : JSON.stringify(msg.content ?? '');
56
+ return {
57
+ index: messages.length - Math.min(messages.length, 6) + index,
58
+ role: msg.role,
59
+ preview: content.length > 240
60
+ ? `${content.slice(0, 240)}...[truncated]`
61
+ : content,
62
+ hasToolCalls: 'tool_calls' in msg && Array.isArray(msg.tool_calls),
63
+ toolCallNames: 'tool_calls' in msg && Array.isArray(msg.tool_calls)
64
+ ? msg.tool_calls.map((tc) => tc.function?.name || '')
65
+ : [],
66
+ toolCallId: 'tool_call_id' in msg ? (msg.tool_call_id ?? null) : null,
67
+ };
68
+ }),
69
+ };
70
+ }
71
+ export class ToolLoopHandler {
72
+ async execute(ctx, config, parentSpan) {
73
+ const maxIterations = config.maxIterations ?? ctx.config.maxIterations;
74
+ const maxToolCalls = config.maxToolCalls ?? ctx.config.maxToolCalls;
75
+ const heartbeatMs = config.heartbeatIntervalMs ??
76
+ ctx.config.heartbeatIntervalMs ??
77
+ 5000;
78
+ const mode = ctx.config.mode || 'smart';
79
+ const externalTools = mode === 'hard' ? [] : ctx.externalTools;
80
+ const externalToolNames = new Set(externalTools.map((t) => t.name));
81
+ let toolCallCount = 0;
82
+ let messages = ctx.assembledMessages;
83
+ const usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
84
+ const timingLog = [];
85
+ const loopStart = Date.now();
86
+ let currentTools = ctx.activeTools;
87
+ // Inject tool priority instruction when external tools are present
88
+ if (externalTools.length > 0) {
89
+ const systemIdx = messages.findIndex((m) => m.role === 'system');
90
+ if (systemIdx >= 0) {
91
+ const sys = messages[systemIdx];
92
+ messages = [...messages];
93
+ messages[systemIdx] = {
94
+ ...sys,
95
+ content: `${sys.content}\n\nIMPORTANT: You have internal tools and client-provided tools (marked [client-provided] in their description). Always prefer internal tools when they can accomplish the task. Use client-provided tools only when no internal tool can do the job.`,
96
+ };
97
+ }
98
+ }
99
+ // Inject pending internal tool results from previous mixed-call request
100
+ if (ctx.pendingToolResults.has(ctx.sessionId)) {
101
+ const pending = await ctx.pendingToolResults.consume(ctx.sessionId);
102
+ if (pending) {
103
+ messages = [
104
+ ...messages,
105
+ pending.assistantMessage,
106
+ ...pending.results.map((r) => ({
107
+ role: 'tool',
108
+ content: r.text,
109
+ tool_call_id: r.toolCallId,
110
+ })),
111
+ ];
112
+ ctx.options?.sessionLogger?.logStep('pending_tool_results_injected', {
113
+ toolNames: pending.results.map((r) => r.toolName),
114
+ });
115
+ }
116
+ }
117
+ for (let iteration = 0;; iteration++) {
118
+ if (ctx.options?.signal?.aborted) {
119
+ ctx.yield({
120
+ ok: false,
121
+ error: new OrchestratorError('Aborted', 'ABORTED'),
122
+ });
123
+ return false;
124
+ }
125
+ if (iteration >= maxIterations) {
126
+ timingLog.push({ phase: 'total', duration: Date.now() - loopStart });
127
+ ctx.timing.push(...timingLog);
128
+ ctx.yield({
129
+ ok: true,
130
+ value: {
131
+ content: '',
132
+ finishReason: 'length',
133
+ usage: {
134
+ ...usage,
135
+ models: ctx.requestLogger.getSummary().byModel,
136
+ components: ctx.requestLogger.getSummary().byComponent,
137
+ },
138
+ timing: timingLog,
139
+ },
140
+ });
141
+ return true;
142
+ }
143
+ // Refresh MCP tools on each iteration (when enabled)
144
+ const prevSelectedTools = [...currentTools];
145
+ if (iteration > 0 && ctx.config.refreshToolsPerIteration !== false) {
146
+ const refreshSpan = ctx.tracer.startSpan('smart_agent.refresh_tools', {
147
+ parent: parentSpan,
148
+ attributes: { 'llm.iteration': iteration + 1 },
149
+ });
150
+ const prevNames = [...ctx.toolClientMap.keys()];
151
+ ctx.toolClientMap.clear();
152
+ ctx.mcpTools.length = 0;
153
+ const settled = await Promise.allSettled(ctx.mcpClients.map(async (client) => ({
154
+ client,
155
+ result: await client.listTools(ctx.options),
156
+ })));
157
+ for (const entry of settled) {
158
+ if (entry.status === 'fulfilled' && entry.value.result.ok) {
159
+ for (const t of entry.value.result.value) {
160
+ if (!ctx.toolClientMap.has(t.name)) {
161
+ ctx.toolClientMap.set(t.name, entry.value.client);
162
+ ctx.mcpTools.push(t);
163
+ }
164
+ }
165
+ }
166
+ }
167
+ currentTools = [...ctx.mcpTools, ...externalTools];
168
+ ctx.options?.sessionLogger?.logStep('tools_refreshed', {
169
+ iteration: iteration + 1,
170
+ previous: prevNames,
171
+ current: currentTools.map((t) => t.name),
172
+ });
173
+ refreshSpan.end();
174
+ }
175
+ // Per-iteration RAG tool re-selection (when enabled)
176
+ if (iteration > 0 &&
177
+ ctx.config.toolReselectPerIteration &&
178
+ ctx.ragStores?.tools) {
179
+ const reselectSpan = ctx.tracer.startSpan('smart_agent.tool_reselect', {
180
+ parent: parentSpan,
181
+ attributes: { 'llm.iteration': iteration + 1 },
182
+ });
183
+ try {
184
+ // Extract last tool calls
185
+ const lastAssistant = [...messages]
186
+ .reverse()
187
+ .find((m) => m.role === 'assistant');
188
+ const toolCallNames = [];
189
+ if (lastAssistant && 'tool_calls' in lastAssistant) {
190
+ const tcs = lastAssistant.tool_calls;
191
+ if (Array.isArray(tcs)) {
192
+ for (const tc of tcs) {
193
+ const name = tc?.function?.name || '';
194
+ if (name)
195
+ toolCallNames.push(name);
196
+ }
197
+ }
198
+ }
199
+ // Skip for read-only tools — they rarely need different tools on retry
200
+ const readOnlyPrefixes = [
201
+ 'Search',
202
+ 'Read',
203
+ 'Get',
204
+ 'List',
205
+ 'Describe',
206
+ ];
207
+ const allReadOnly = toolCallNames.length > 0 &&
208
+ toolCallNames.every((n) => readOnlyPrefixes.some((p) => n.startsWith(p)));
209
+ if (allReadOnly) {
210
+ // Read-only tools don't need re-selection — restore previous selection
211
+ // to avoid sending all MCP tools after refresh
212
+ currentTools = prevSelectedTools;
213
+ }
214
+ else {
215
+ // Build context-aware query from error/result context
216
+ const lastToolMsg = [...messages]
217
+ .reverse()
218
+ .find((m) => m.role === 'tool');
219
+ const toolResult = typeof lastToolMsg?.content === 'string'
220
+ ? lastToolMsg.content.slice(0, 200)
221
+ : '';
222
+ const isError = toolResult.toLowerCase().includes('error') ||
223
+ toolResult.toLowerCase().includes('already exist') ||
224
+ toolResult.toLowerCase().includes('failed');
225
+ let reSelectQuery;
226
+ if (toolCallNames.length > 0 && isError) {
227
+ const updateHints = toolCallNames
228
+ .filter((n) => n.startsWith('Create'))
229
+ .map((n) => n.replace(/^Create/, 'Update'))
230
+ .join(', ');
231
+ const hints = updateHints ? ` Need ${updateHints}.` : '';
232
+ reSelectQuery = `${toolCallNames.join(', ')} failed: ${toolResult.slice(0, 150)}.${hints} ${ctx.inputText.slice(0, 200)}`;
233
+ }
234
+ else if (toolCallNames.length > 0) {
235
+ reSelectQuery = `After ${toolCallNames.join(', ')}: ${toolResult}\n${ctx.inputText.slice(0, 200)}`;
236
+ }
237
+ else {
238
+ reSelectQuery = ctx.inputText;
239
+ }
240
+ // Query tools RAG
241
+ const { QueryEmbedding, TextOnlyEmbedding } = await import('@mcp-abap-adt/llm-agent');
242
+ const embedding = ctx.embedder
243
+ ? new QueryEmbedding(reSelectQuery, ctx.embedder, ctx.options)
244
+ : new TextOnlyEmbedding(reSelectQuery);
245
+ const ragK = ctx.config.ragQueryK ?? 20;
246
+ const ragStart = Date.now();
247
+ const ragResult = await ctx.ragStores.tools.query(embedding, ragK, ctx.options);
248
+ ctx.requestLogger.logRagQuery({
249
+ store: 'tools',
250
+ query: reSelectQuery.slice(0, 200),
251
+ resultCount: ragResult.ok ? ragResult.value.length : 0,
252
+ durationMs: Date.now() - ragStart,
253
+ });
254
+ if (ragResult.ok && ragResult.value.length > 0) {
255
+ const newToolNames = new Set(ragResult.value
256
+ .map((r) => r.metadata?.id || '')
257
+ .filter((id) => id.startsWith('tool:'))
258
+ .map((id) => id.slice(5).replace(/:.*$/, '')));
259
+ if (newToolNames.size > 0) {
260
+ const newMcpTools = ctx.mcpTools.filter((t) => newToolNames.has(t.name));
261
+ currentTools = [
262
+ ...newMcpTools,
263
+ ...externalTools,
264
+ ];
265
+ ctx.options?.sessionLogger?.logStep('tools_reselected', {
266
+ iteration: iteration + 1,
267
+ query: reSelectQuery.slice(0, 100),
268
+ previousTools: toolCallNames,
269
+ newTools: [...newToolNames],
270
+ });
271
+ }
272
+ }
273
+ }
274
+ }
275
+ finally {
276
+ reselectSpan.end();
277
+ }
278
+ }
279
+ // Filter tools per iteration
280
+ const filteredForIteration = ctx.toolAvailabilityRegistry.filterTools(ctx.sessionId, currentTools);
281
+ currentTools = filteredForIteration.allowed;
282
+ if (filteredForIteration.blocked.length > 0) {
283
+ ctx.options?.sessionLogger?.logStep('active_tools_filtered_in_iteration', { iteration: iteration + 1, blocked: filteredForIteration.blocked });
284
+ }
285
+ let iterPromptTokens = 0;
286
+ let iterCompletionTokens = 0;
287
+ let iterTotalTokens = 0;
288
+ ctx.options?.sessionLogger?.logStep(`llm_request_iter_${iteration + 1}`, {
289
+ messages,
290
+ tools: currentTools,
291
+ });
292
+ const iterationMessageSummary = summarizeIterationMessages(messages);
293
+ const looksLikeFinalPass = messages.some((msg) => msg.role === 'tool' ||
294
+ ('tool_calls' in msg &&
295
+ Array.isArray(msg.tool_calls) &&
296
+ msg.tool_calls.length > 0));
297
+ ctx.options?.sessionLogger?.logStep('llm_request_iter_context_summary', {
298
+ iteration: iteration + 1,
299
+ toolCount: currentTools.length,
300
+ looksLikeFinalPass,
301
+ summary: iterationMessageSummary,
302
+ });
303
+ ctx.logger?.log({
304
+ type: 'warning',
305
+ traceId: ctx.options?.trace?.traceId ?? 'tool-loop',
306
+ message: `tool-loop iteration ${iteration + 1} context summary ${JSON.stringify({
307
+ iteration: iteration + 1,
308
+ toolCount: currentTools.length,
309
+ looksLikeFinalPass,
310
+ summary: iterationMessageSummary,
311
+ })}`,
312
+ });
313
+ const llmSpan = ctx.tracer.startSpan('smart_agent.llm_call', {
314
+ parent: parentSpan,
315
+ attributes: { 'llm.iteration': iteration + 1 },
316
+ });
317
+ ctx.metrics.llmCallCount.add();
318
+ const llmCallStart = Date.now();
319
+ let content = '';
320
+ let finishReason;
321
+ const toolCallsMap = new Map();
322
+ // Track which streaming indices belong to external tools so that
323
+ // argument-only continuation deltas (no name field) are forwarded too.
324
+ const externalToolIndices = new Set();
325
+ // Delegate to ILlmCallStrategy — handles streaming, non-streaming, or fallback
326
+ const llmStream = ctx.llmCallStrategy.call(ctx.mainLlm, messages, currentTools, ctx.options);
327
+ for await (const chunkResult of llmStream) {
328
+ if (!chunkResult.ok) {
329
+ llmSpan.setStatus('error', chunkResult.error.message);
330
+ llmSpan.end();
331
+ ctx.yield({
332
+ ok: false,
333
+ error: new OrchestratorError(chunkResult.error.message, 'LLM_ERROR'),
334
+ });
335
+ return false;
336
+ }
337
+ const chunk = chunkResult.value;
338
+ // Mid-stream retry or fallback reset: discard accumulated state
339
+ if (chunk.reset) {
340
+ content = '';
341
+ toolCallsMap.clear();
342
+ externalToolIndices.clear();
343
+ finishReason = undefined;
344
+ iterPromptTokens = 0;
345
+ iterCompletionTokens = 0;
346
+ iterTotalTokens = 0;
347
+ continue;
348
+ }
349
+ if (chunk.content) {
350
+ content += chunk.content;
351
+ ctx.yield({ ok: true, value: { content: chunk.content } });
352
+ }
353
+ if (chunk.toolCalls) {
354
+ // Register newly seen external tool indices
355
+ for (const tc of chunk.toolCalls) {
356
+ const name = getStreamToolCallName(tc);
357
+ if (name && externalToolNames.has(name)) {
358
+ const delta = toToolCallDelta(tc, 0);
359
+ externalToolIndices.add(delta.index);
360
+ }
361
+ }
362
+ const externalDeltas = chunk.toolCalls.filter((tc) => {
363
+ const delta = toToolCallDelta(tc, 0);
364
+ return externalToolIndices.has(delta.index);
365
+ });
366
+ if (externalDeltas.length > 0) {
367
+ ctx.yield({
368
+ ok: true,
369
+ value: { content: '', toolCalls: externalDeltas },
370
+ });
371
+ }
372
+ for (const [fallbackIndex, rawToolCall,] of chunk.toolCalls.entries()) {
373
+ const tc = toToolCallDelta(rawToolCall, fallbackIndex);
374
+ if (!toolCallsMap.has(tc.index)) {
375
+ toolCallsMap.set(tc.index, {
376
+ id: tc.id || '',
377
+ name: tc.name || '',
378
+ arguments: tc.arguments || '',
379
+ });
380
+ }
381
+ else {
382
+ const ex = toolCallsMap.get(tc.index);
383
+ if (ex) {
384
+ if (tc.id)
385
+ ex.id = tc.id;
386
+ if (tc.name)
387
+ ex.name = tc.name;
388
+ if (tc.arguments)
389
+ ex.arguments += tc.arguments;
390
+ }
391
+ }
392
+ }
393
+ }
394
+ if (chunk.finishReason)
395
+ finishReason = chunk.finishReason;
396
+ if (chunk.usage) {
397
+ usage.promptTokens += chunk.usage.promptTokens;
398
+ usage.completionTokens += chunk.usage.completionTokens;
399
+ usage.totalTokens += chunk.usage.totalTokens;
400
+ iterPromptTokens += chunk.usage.promptTokens;
401
+ iterCompletionTokens += chunk.usage.completionTokens;
402
+ iterTotalTokens += chunk.usage.totalTokens;
403
+ ctx.sessionManager.addTokens(chunk.usage.totalTokens);
404
+ }
405
+ }
406
+ llmSpan.setStatus('ok');
407
+ llmSpan.end();
408
+ const llmCallDuration = Date.now() - llmCallStart;
409
+ ctx.metrics.llmCallLatency.record(llmCallDuration);
410
+ timingLog.push({
411
+ phase: `llm_call_${iteration + 1}`,
412
+ duration: llmCallDuration,
413
+ });
414
+ ctx.requestLogger.logLlmCall({
415
+ component: 'tool-loop',
416
+ model: ctx.mainLlm.model ?? 'unknown',
417
+ promptTokens: iterPromptTokens,
418
+ completionTokens: iterCompletionTokens,
419
+ totalTokens: iterTotalTokens,
420
+ durationMs: llmCallDuration,
421
+ });
422
+ const toolCalls = Array.from(toolCallsMap.values()).map((tc) => {
423
+ let args = {};
424
+ try {
425
+ args = JSON.parse(tc.arguments);
426
+ }
427
+ catch {
428
+ args = {};
429
+ }
430
+ return { id: tc.id, name: tc.name, arguments: args };
431
+ });
432
+ ctx.options?.sessionLogger?.logStep(`llm_response_iter_${iteration + 1}`, { content, toolCalls, finishReason });
433
+ // -- No tool calls: validate and finish --------------------------------
434
+ if (finishReason !== 'tool_calls' || toolCalls.length === 0) {
435
+ const valResult = await ctx.outputValidator.validate(content, { messages, tools: currentTools }, ctx.options);
436
+ if (valResult.ok && !valResult.value.valid) {
437
+ const correction = valResult.value.correctedContent ?? valResult.value.reason;
438
+ messages = [
439
+ ...messages,
440
+ { role: 'assistant', content },
441
+ {
442
+ role: 'user',
443
+ content: `Your previous response was rejected by validation: ${correction}. Please try again.`,
444
+ },
445
+ ];
446
+ continue;
447
+ }
448
+ const summary = ctx.requestLogger.getSummary();
449
+ ctx.options?.sessionLogger?.logStep('final_response', {
450
+ content,
451
+ usage,
452
+ byComponent: summary.byComponent,
453
+ byModel: summary.byModel,
454
+ byCategory: summary.byCategory,
455
+ });
456
+ timingLog.push({ phase: 'total', duration: Date.now() - loopStart });
457
+ ctx.timing.push(...timingLog);
458
+ ctx.yield({
459
+ ok: true,
460
+ value: {
461
+ content: '',
462
+ finishReason: finishReason || 'stop',
463
+ usage: {
464
+ ...usage,
465
+ models: ctx.requestLogger.getSummary().byModel,
466
+ components: ctx.requestLogger.getSummary().byComponent,
467
+ categories: ctx.requestLogger.getSummary().byCategory,
468
+ },
469
+ timing: timingLog,
470
+ },
471
+ });
472
+ return true;
473
+ }
474
+ // -- Classify tool calls -----------------------------------------------
475
+ const internalCalls = toolCalls.filter((tc) => ctx.toolClientMap.has(tc.name));
476
+ const validExternalCalls = toolCalls.filter((tc) => externalToolNames.has(tc.name));
477
+ const blockedToolNames = ctx.toolAvailabilityRegistry.getBlockedToolNames(ctx.sessionId);
478
+ const blockedCalls = toolCalls.filter((tc) => blockedToolNames.has(tc.name));
479
+ const hallucinations = toolCalls.filter((tc) => !blockedToolNames.has(tc.name) &&
480
+ !ctx.toolClientMap.has(tc.name) &&
481
+ !externalToolNames.has(tc.name));
482
+ // -- Handle blocked tools ----------------------------------------------
483
+ if (blockedCalls.length > 0) {
484
+ messages = [
485
+ ...messages,
486
+ {
487
+ role: 'assistant',
488
+ content: content || null,
489
+ tool_calls: blockedCalls.map((tc) => ({
490
+ id: tc.id,
491
+ type: 'function',
492
+ function: {
493
+ name: tc.name,
494
+ arguments: JSON.stringify(tc.arguments),
495
+ },
496
+ })),
497
+ },
498
+ ];
499
+ for (const blocked of blockedCalls) {
500
+ messages = [
501
+ ...messages,
502
+ {
503
+ role: 'tool',
504
+ content: `Error: Tool "${blocked.name}" is temporarily unavailable in this session.`,
505
+ tool_call_id: blocked.id,
506
+ },
507
+ ];
508
+ }
509
+ ctx.options?.sessionLogger?.logStep('blocked_tool_calls_intercepted', {
510
+ toolNames: blockedCalls.map((tc) => tc.name),
511
+ });
512
+ continue;
513
+ }
514
+ // -- Handle hallucinated tools -----------------------------------------
515
+ if (hallucinations.length > 0) {
516
+ messages = [
517
+ ...messages,
518
+ {
519
+ role: 'assistant',
520
+ content: content || null,
521
+ tool_calls: toolCalls.map((tc) => ({
522
+ id: tc.id,
523
+ type: 'function',
524
+ function: {
525
+ name: tc.name,
526
+ arguments: JSON.stringify(tc.arguments),
527
+ },
528
+ })),
529
+ },
530
+ ];
531
+ for (const h of hallucinations) {
532
+ messages = [
533
+ ...messages,
534
+ {
535
+ role: 'tool',
536
+ content: `Error: Tool "${h.name}" not found.`,
537
+ tool_call_id: h.id,
538
+ },
539
+ ];
540
+ }
541
+ continue;
542
+ }
543
+ // -- Handle external tool calls (delegate to consumer) -----------------
544
+ if (validExternalCalls.length > 0) {
545
+ if (internalCalls.length > 0) {
546
+ fireInternalToolsAsync(content, internalCalls, ctx.pendingToolResults, ctx.sessionId, {
547
+ toolClientMap: ctx.toolClientMap,
548
+ toolCache: ctx.toolCache,
549
+ metrics: ctx.metrics,
550
+ options: ctx.options,
551
+ });
552
+ ctx.options?.sessionLogger?.logStep('mixed_tool_calls', {
553
+ internal: internalCalls.map((tc) => tc.name),
554
+ external: validExternalCalls.map((tc) => tc.name),
555
+ });
556
+ }
557
+ timingLog.push({ phase: 'total', duration: Date.now() - loopStart });
558
+ ctx.timing.push(...timingLog);
559
+ ctx.yield({
560
+ ok: true,
561
+ value: {
562
+ content: '',
563
+ finishReason: 'tool_calls',
564
+ usage: {
565
+ ...usage,
566
+ models: ctx.requestLogger.getSummary().byModel,
567
+ components: ctx.requestLogger.getSummary().byComponent,
568
+ },
569
+ timing: timingLog,
570
+ },
571
+ });
572
+ return true;
573
+ }
574
+ // -- Execute internal MCP tool calls -----------------------------------
575
+ if (content || internalCalls.length > 0) {
576
+ messages = [
577
+ ...messages,
578
+ {
579
+ role: 'assistant',
580
+ content: content || null,
581
+ tool_calls: internalCalls.map((tc) => ({
582
+ id: tc.id,
583
+ type: 'function',
584
+ function: {
585
+ name: tc.name,
586
+ arguments: JSON.stringify(tc.arguments),
587
+ },
588
+ })),
589
+ },
590
+ ];
591
+ }
592
+ // Check tool call budget
593
+ const remaining = maxToolCalls !== undefined
594
+ ? maxToolCalls - toolCallCount
595
+ : internalCalls.length;
596
+ if (remaining <= 0) {
597
+ timingLog.push({ phase: 'total', duration: Date.now() - loopStart });
598
+ ctx.timing.push(...timingLog);
599
+ ctx.yield({
600
+ ok: true,
601
+ value: {
602
+ content: '',
603
+ finishReason: 'length',
604
+ usage: {
605
+ ...usage,
606
+ models: ctx.requestLogger.getSummary().byModel,
607
+ components: ctx.requestLogger.getSummary().byComponent,
608
+ },
609
+ timing: timingLog,
610
+ },
611
+ });
612
+ return true;
613
+ }
614
+ const batch = internalCalls.slice(0, remaining);
615
+ // Yield progress messages
616
+ for (const tc of batch) {
617
+ ctx.yield({
618
+ ok: true,
619
+ value: { content: `\n\n[SmartAgent: Executing ${tc.name}...]\n` },
620
+ });
621
+ }
622
+ const toolExecPromises = batch.map(async (tc) => {
623
+ const toolStart = Date.now();
624
+ ctx.options?.sessionLogger?.logStep(`mcp_call_${tc.name}`, {
625
+ arguments: tc.arguments,
626
+ });
627
+ const client = ctx.toolClientMap.get(tc.name);
628
+ if (!client)
629
+ return { tc, text: '', res: null, duration: 0, cached: false };
630
+ const toolSpan = ctx.tracer.startSpan('smart_agent.tool_call', {
631
+ parent: parentSpan,
632
+ attributes: { 'tool.name': tc.name },
633
+ });
634
+ const cachedValue = ctx.toolCache.get(tc.name, tc.arguments);
635
+ const wasCached = !!cachedValue;
636
+ const res = cachedValue
637
+ ? (() => {
638
+ ctx.metrics.toolCacheHitCount.add();
639
+ toolSpan.setAttribute('cache', 'hit');
640
+ return { ok: true, value: cachedValue };
641
+ })()
642
+ : await (async () => {
643
+ const r = await client.callTool(tc.name, tc.arguments, ctx.options);
644
+ if (r.ok)
645
+ ctx.toolCache.set(tc.name, tc.arguments, r.value);
646
+ return r;
647
+ })();
648
+ const text = !res.ok
649
+ ? res.error.message
650
+ : typeof res.value.content === 'string'
651
+ ? res.value.content
652
+ : JSON.stringify(res.value.content);
653
+ toolSpan.setStatus(res.ok ? 'ok' : 'error', res.ok ? undefined : text);
654
+ toolSpan.end();
655
+ return {
656
+ tc,
657
+ text,
658
+ res,
659
+ duration: Date.now() - toolStart,
660
+ cached: wasCached,
661
+ };
662
+ });
663
+ // Race: tool execution vs periodic heartbeat
664
+ const allDone = Promise.all(toolExecPromises);
665
+ const pendingTools = new Set(batch.map((tc) => tc.name));
666
+ const toolStartTime = Date.now();
667
+ let results = [];
668
+ let settled = false;
669
+ for (const [i, p] of toolExecPromises.entries()) {
670
+ p.then(() => pendingTools.delete(batch[i].name));
671
+ }
672
+ while (!settled) {
673
+ const winner = await Promise.race([
674
+ allDone.then((r) => ({ tag: 'done', results: r })),
675
+ new Promise((resolve) => setTimeout(() => resolve({ tag: 'tick' }), heartbeatMs)),
676
+ ]);
677
+ if (winner.tag === 'done') {
678
+ results = winner.results;
679
+ settled = true;
680
+ }
681
+ else {
682
+ for (const tool of pendingTools) {
683
+ ctx.yield({
684
+ ok: true,
685
+ value: {
686
+ content: '',
687
+ heartbeat: { tool, elapsed: Date.now() - toolStartTime },
688
+ },
689
+ });
690
+ }
691
+ }
692
+ }
693
+ // Collect timing
694
+ for (const r of results) {
695
+ timingLog.push({ phase: `tool_${r.tc.name}`, duration: r.duration });
696
+ }
697
+ // Process results
698
+ const toolMessages = [];
699
+ for (const r of results) {
700
+ const { tc, text, res } = r;
701
+ if (!res)
702
+ continue;
703
+ if (!res.ok &&
704
+ isToolContextUnavailableError(text) &&
705
+ !externalToolNames.has(tc.name)) {
706
+ const entry = ctx.toolAvailabilityRegistry.block(ctx.sessionId, tc.name, text);
707
+ currentTools = currentTools.filter((t) => t.name !== tc.name);
708
+ ctx.options?.sessionLogger?.logStep(`tool_blacklisted_${tc.name}`, {
709
+ reason: text,
710
+ blockedUntil: entry.blockedUntil,
711
+ });
712
+ }
713
+ ctx.options?.sessionLogger?.logStep(`mcp_result_${tc.name}`, {
714
+ result: text,
715
+ });
716
+ toolCallCount++;
717
+ ctx.metrics.toolCallCount.add();
718
+ toolMessages.push({
719
+ role: 'tool',
720
+ content: text,
721
+ tool_call_id: tc.id,
722
+ });
723
+ ctx.requestLogger.logToolCall({
724
+ toolName: tc.name,
725
+ success: !!res?.ok,
726
+ durationMs: r.duration,
727
+ cached: r.cached,
728
+ });
729
+ }
730
+ messages = [...messages, ...toolMessages];
731
+ }
732
+ }
733
+ }
734
+ //# sourceMappingURL=tool-loop.js.map