@amodalai/runtime 0.1.26 → 0.2.0

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 (347) hide show
  1. package/dist/src/__fixtures__/README.md +84 -0
  2. package/dist/src/__fixtures__/smoke-agent/amodal.json +11 -0
  3. package/dist/src/__fixtures__/smoke-agent/automations/test-auto.md +5 -0
  4. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/access.json +11 -0
  5. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/spec.json +4 -0
  6. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/surface.md +9 -0
  7. package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/access.json +3 -0
  8. package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/spec.json +8 -0
  9. package/dist/src/__fixtures__/smoke-agent/evals/basic-eval.md +12 -0
  10. package/dist/src/__fixtures__/smoke-agent/knowledge/test-knowledge.md +3 -0
  11. package/dist/src/__fixtures__/smoke-agent/skills/test-skill/SKILL.md +11 -0
  12. package/dist/src/__fixtures__/smoke-agent/stores/test-items.json +11 -0
  13. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.d.ts +18 -0
  14. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js +22 -0
  15. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js.map +1 -0
  16. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/tool.json +17 -0
  17. package/dist/src/__fixtures__/smoke.test.js +718 -0
  18. package/dist/src/__fixtures__/smoke.test.js.map +1 -0
  19. package/dist/src/__tests__/test-providers.d.ts +40 -0
  20. package/dist/src/__tests__/test-providers.js +61 -0
  21. package/dist/src/__tests__/test-providers.js.map +1 -0
  22. package/dist/src/agent/local-server.d.ts +3 -3
  23. package/dist/src/agent/local-server.js +213 -122
  24. package/dist/src/agent/local-server.js.map +1 -1
  25. package/dist/src/agent/loop-types.d.ts +175 -0
  26. package/dist/src/agent/loop-types.js +20 -0
  27. package/dist/src/agent/loop-types.js.map +1 -0
  28. package/dist/src/agent/loop.d.ts +31 -0
  29. package/dist/src/agent/loop.js +139 -0
  30. package/dist/src/agent/loop.js.map +1 -0
  31. package/dist/src/agent/loop.test.js +1030 -0
  32. package/dist/src/agent/loop.test.js.map +1 -0
  33. package/dist/src/agent/mcp-config.d.ts +28 -0
  34. package/dist/src/agent/mcp-config.js +57 -0
  35. package/dist/src/agent/mcp-config.js.map +1 -0
  36. package/dist/src/agent/page-builder.js +6 -1
  37. package/dist/src/agent/page-builder.js.map +1 -1
  38. package/dist/src/agent/proactive/proactive-runner.d.ts +24 -8
  39. package/dist/src/agent/proactive/proactive-runner.js +30 -32
  40. package/dist/src/agent/proactive/proactive-runner.js.map +1 -1
  41. package/dist/src/agent/proactive/proactive-runner.test.d.ts +1 -1
  42. package/dist/src/agent/proactive/proactive-runner.test.js +75 -87
  43. package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
  44. package/dist/src/agent/routes/admin-chat.d.ts +15 -3
  45. package/dist/src/agent/routes/admin-chat.js +63 -18
  46. package/dist/src/agent/routes/admin-chat.js.map +1 -1
  47. package/dist/src/agent/routes/automations.js +5 -6
  48. package/dist/src/agent/routes/automations.js.map +1 -1
  49. package/dist/src/agent/routes/evals.d.ts +3 -2
  50. package/dist/src/agent/routes/evals.js +25 -12
  51. package/dist/src/agent/routes/evals.js.map +1 -1
  52. package/dist/src/agent/routes/files.js +7 -9
  53. package/dist/src/agent/routes/files.js.map +1 -1
  54. package/dist/src/agent/routes/inspect.d.ts +6 -2
  55. package/dist/src/agent/routes/inspect.js +31 -17
  56. package/dist/src/agent/routes/inspect.js.map +1 -1
  57. package/dist/src/agent/routes/inspect.test.js +18 -42
  58. package/dist/src/agent/routes/inspect.test.js.map +1 -1
  59. package/dist/src/agent/routes/stores.js +9 -12
  60. package/dist/src/agent/routes/stores.js.map +1 -1
  61. package/dist/src/agent/routes/task.d.ts +15 -3
  62. package/dist/src/agent/routes/task.js +16 -7
  63. package/dist/src/agent/routes/task.js.map +1 -1
  64. package/dist/src/agent/routes/task.test.d.ts +1 -1
  65. package/dist/src/agent/routes/task.test.js +70 -53
  66. package/dist/src/agent/routes/task.test.js.map +1 -1
  67. package/dist/src/agent/routes/webhooks.js +12 -3
  68. package/dist/src/agent/routes/webhooks.js.map +1 -1
  69. package/dist/src/agent/session-store.d.ts +11 -2
  70. package/dist/src/agent/session-store.js +1 -1
  71. package/dist/src/agent/session-store.js.map +1 -1
  72. package/dist/src/agent/snapshot-server.d.ts +2 -22
  73. package/dist/src/agent/snapshot-server.js +50 -27
  74. package/dist/src/agent/snapshot-server.js.map +1 -1
  75. package/dist/src/agent/states/compacting.d.ts +14 -0
  76. package/dist/src/agent/states/compacting.js +258 -0
  77. package/dist/src/agent/states/compacting.js.map +1 -0
  78. package/dist/src/agent/states/confirming.d.ts +10 -0
  79. package/dist/src/agent/states/confirming.js +76 -0
  80. package/dist/src/agent/states/confirming.js.map +1 -0
  81. package/dist/src/agent/states/dispatching.d.ts +18 -0
  82. package/dist/src/agent/states/dispatching.js +241 -0
  83. package/dist/src/agent/states/dispatching.js.map +1 -0
  84. package/dist/src/agent/states/executing.d.ts +21 -0
  85. package/dist/src/agent/states/executing.js +308 -0
  86. package/dist/src/agent/states/executing.js.map +1 -0
  87. package/dist/src/agent/states/streaming.d.ts +10 -0
  88. package/dist/src/agent/states/streaming.js +155 -0
  89. package/dist/src/agent/states/streaming.js.map +1 -0
  90. package/dist/src/agent/states/thinking.d.ts +13 -0
  91. package/dist/src/agent/states/thinking.js +233 -0
  92. package/dist/src/agent/states/thinking.js.map +1 -0
  93. package/dist/src/agent/token-estimate.d.ts +17 -0
  94. package/dist/src/agent/token-estimate.js +13 -0
  95. package/dist/src/agent/token-estimate.js.map +1 -0
  96. package/dist/src/agent/tool-executor-local.js +9 -18
  97. package/dist/src/agent/tool-executor-local.js.map +1 -1
  98. package/dist/src/agent/tool-executor-local.test.js +3 -5
  99. package/dist/src/agent/tool-executor-local.test.js.map +1 -1
  100. package/dist/src/api/create-agent.d.ts +15 -0
  101. package/dist/src/api/create-agent.js +137 -0
  102. package/dist/src/api/create-agent.js.map +1 -0
  103. package/dist/src/api/types.d.ts +68 -0
  104. package/dist/src/api/types.js +7 -0
  105. package/dist/src/api/types.js.map +1 -0
  106. package/dist/src/context/compiler.d.ts +13 -0
  107. package/dist/src/context/compiler.js +358 -0
  108. package/dist/src/context/compiler.js.map +1 -0
  109. package/dist/src/context/compiler.test.js +532 -0
  110. package/dist/src/context/compiler.test.js.map +1 -0
  111. package/dist/src/context/types.d.ts +110 -0
  112. package/dist/src/context/types.js +7 -0
  113. package/dist/src/context/types.js.map +1 -0
  114. package/dist/src/index.d.ts +33 -6
  115. package/dist/src/index.js +35 -21
  116. package/dist/src/index.js.map +1 -1
  117. package/dist/src/providers/create-provider.d.ts +23 -0
  118. package/dist/src/providers/create-provider.js +185 -0
  119. package/dist/src/providers/create-provider.js.map +1 -0
  120. package/dist/src/{agent/stores-e2e.test.d.ts → providers/create-provider.test.d.ts} +1 -1
  121. package/dist/src/providers/create-provider.test.js +95 -0
  122. package/dist/src/providers/create-provider.test.js.map +1 -0
  123. package/dist/src/providers/failover.d.ts +38 -0
  124. package/dist/src/providers/failover.js +147 -0
  125. package/dist/src/providers/failover.js.map +1 -0
  126. package/dist/src/providers/failover.test.d.ts +6 -0
  127. package/dist/src/providers/failover.test.js +169 -0
  128. package/dist/src/providers/failover.test.js.map +1 -0
  129. package/dist/src/providers/types.d.ts +110 -0
  130. package/dist/src/providers/types.js +7 -0
  131. package/dist/src/providers/types.js.map +1 -0
  132. package/dist/src/routes/ai-stream.d.ts +13 -10
  133. package/dist/src/routes/ai-stream.js +76 -41
  134. package/dist/src/routes/ai-stream.js.map +1 -1
  135. package/dist/src/routes/chat-new.test.d.ts +6 -0
  136. package/dist/src/routes/chat-new.test.js +107 -0
  137. package/dist/src/routes/chat-new.test.js.map +1 -0
  138. package/dist/src/routes/chat-stream-new.test.d.ts +6 -0
  139. package/dist/src/routes/chat-stream-new.test.js +135 -0
  140. package/dist/src/routes/chat-stream-new.test.js.map +1 -0
  141. package/dist/src/routes/chat-stream.d.ts +14 -4
  142. package/dist/src/routes/chat-stream.js +47 -29
  143. package/dist/src/routes/chat-stream.js.map +1 -1
  144. package/dist/src/routes/chat.d.ts +13 -4
  145. package/dist/src/routes/chat.js +60 -23
  146. package/dist/src/routes/chat.js.map +1 -1
  147. package/dist/src/routes/health.d.ts +3 -2
  148. package/dist/src/routes/health.js.map +1 -1
  149. package/dist/src/routes/route-helpers.d.ts +50 -0
  150. package/dist/src/routes/route-helpers.js +80 -0
  151. package/dist/src/routes/route-helpers.js.map +1 -0
  152. package/dist/src/routes/session-resolver.d.ts +72 -0
  153. package/dist/src/routes/session-resolver.js +123 -0
  154. package/dist/src/routes/session-resolver.js.map +1 -0
  155. package/dist/src/routes/session-resolver.test.d.ts +6 -0
  156. package/dist/src/routes/session-resolver.test.js +206 -0
  157. package/dist/src/routes/session-resolver.test.js.map +1 -0
  158. package/dist/src/routes/webhooks.d.ts +3 -1
  159. package/dist/src/routes/webhooks.js +12 -4
  160. package/dist/src/routes/webhooks.js.map +1 -1
  161. package/dist/src/security/permission-checker.d.ts +80 -0
  162. package/dist/src/security/permission-checker.js +75 -0
  163. package/dist/src/security/permission-checker.js.map +1 -0
  164. package/dist/src/security/permission-checker.test.d.ts +6 -0
  165. package/dist/src/security/permission-checker.test.js +208 -0
  166. package/dist/src/security/permission-checker.test.js.map +1 -0
  167. package/dist/src/server.d.ts +12 -11
  168. package/dist/src/server.js +44 -46
  169. package/dist/src/server.js.map +1 -1
  170. package/dist/src/server.test.d.ts +1 -1
  171. package/dist/src/server.test.js +6 -144
  172. package/dist/src/server.test.js.map +1 -1
  173. package/dist/src/session/manager.d.ts +98 -0
  174. package/dist/src/session/manager.js +364 -0
  175. package/dist/src/session/manager.js.map +1 -0
  176. package/dist/src/session/manager.test.d.ts +6 -0
  177. package/dist/src/session/manager.test.js +315 -0
  178. package/dist/src/session/manager.test.js.map +1 -0
  179. package/dist/src/session/session-builder.d.ts +71 -0
  180. package/dist/src/session/session-builder.js +364 -0
  181. package/dist/src/session/session-builder.js.map +1 -0
  182. package/dist/src/session/session-builder.test.d.ts +6 -0
  183. package/dist/src/session/session-builder.test.js +352 -0
  184. package/dist/src/session/session-builder.test.js.map +1 -0
  185. package/dist/src/session/store.d.ts +57 -0
  186. package/dist/src/session/store.js +167 -0
  187. package/dist/src/session/store.js.map +1 -0
  188. package/dist/src/session/store.test.d.ts +6 -0
  189. package/dist/src/session/store.test.js +145 -0
  190. package/dist/src/session/store.test.js.map +1 -0
  191. package/dist/src/session/stream-hooks.d.ts +39 -0
  192. package/dist/src/session/stream-hooks.js +7 -0
  193. package/dist/src/session/stream-hooks.js.map +1 -0
  194. package/dist/src/session/tool-context-factory.d.ts +60 -0
  195. package/dist/src/session/tool-context-factory.js +190 -0
  196. package/dist/src/session/tool-context-factory.js.map +1 -0
  197. package/dist/src/session/tool-context-factory.test.d.ts +6 -0
  198. package/dist/src/session/tool-context-factory.test.js +287 -0
  199. package/dist/src/session/tool-context-factory.test.js.map +1 -0
  200. package/dist/src/session/types.d.ts +188 -0
  201. package/dist/src/session/types.js +7 -0
  202. package/dist/src/session/types.js.map +1 -0
  203. package/dist/src/stores/drizzle-store-backend.d.ts +49 -0
  204. package/dist/src/stores/drizzle-store-backend.js +306 -0
  205. package/dist/src/stores/drizzle-store-backend.js.map +1 -0
  206. package/dist/src/stores/drizzle-store-backend.test.d.ts +6 -0
  207. package/dist/src/stores/drizzle-store-backend.test.js +215 -0
  208. package/dist/src/stores/drizzle-store-backend.test.js.map +1 -0
  209. package/dist/src/stores/index.d.ts +4 -0
  210. package/dist/src/stores/index.js +2 -0
  211. package/dist/src/stores/index.js.map +1 -1
  212. package/dist/src/stores/pglite-store-backend.d.ts +16 -19
  213. package/dist/src/stores/pglite-store-backend.js +85 -239
  214. package/dist/src/stores/pglite-store-backend.js.map +1 -1
  215. package/dist/src/stores/postgres-store-backend.d.ts +30 -0
  216. package/dist/src/stores/postgres-store-backend.js +100 -0
  217. package/dist/src/stores/postgres-store-backend.js.map +1 -0
  218. package/dist/src/stores/schema.d.ts +491 -0
  219. package/dist/src/stores/schema.js +57 -0
  220. package/dist/src/stores/schema.js.map +1 -0
  221. package/dist/src/tools/admin-file-tools.d.ts +13 -0
  222. package/dist/src/tools/admin-file-tools.js +200 -0
  223. package/dist/src/tools/admin-file-tools.js.map +1 -0
  224. package/dist/src/tools/admin-file-tools.test.d.ts +6 -0
  225. package/dist/src/tools/admin-file-tools.test.js +152 -0
  226. package/dist/src/tools/admin-file-tools.test.js.map +1 -0
  227. package/dist/src/tools/custom-tool-adapter.d.ts +41 -0
  228. package/dist/src/tools/custom-tool-adapter.js +190 -0
  229. package/dist/src/tools/custom-tool-adapter.js.map +1 -0
  230. package/dist/src/tools/custom-tool-adapter.test.d.ts +6 -0
  231. package/dist/src/tools/custom-tool-adapter.test.js +244 -0
  232. package/dist/src/tools/custom-tool-adapter.test.js.map +1 -0
  233. package/dist/src/tools/dispatch-tool.d.ts +52 -0
  234. package/dist/src/tools/dispatch-tool.js +71 -0
  235. package/dist/src/tools/dispatch-tool.js.map +1 -0
  236. package/dist/src/tools/dispatch-tool.test.d.ts +6 -0
  237. package/dist/src/tools/dispatch-tool.test.js +75 -0
  238. package/dist/src/tools/dispatch-tool.test.js.map +1 -0
  239. package/dist/src/tools/mcp-tool-adapter.d.ts +18 -0
  240. package/dist/src/tools/mcp-tool-adapter.js +135 -0
  241. package/dist/src/tools/mcp-tool-adapter.js.map +1 -0
  242. package/dist/src/tools/mcp-tool-adapter.test.d.ts +6 -0
  243. package/dist/src/tools/mcp-tool-adapter.test.js +227 -0
  244. package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -0
  245. package/dist/src/tools/registry.d.ts +25 -0
  246. package/dist/src/tools/registry.js +72 -0
  247. package/dist/src/tools/registry.js.map +1 -0
  248. package/dist/src/tools/registry.test.d.ts +6 -0
  249. package/dist/src/tools/registry.test.js +121 -0
  250. package/dist/src/tools/registry.test.js.map +1 -0
  251. package/dist/src/tools/request-tool.d.ts +42 -0
  252. package/dist/src/tools/request-tool.js +190 -0
  253. package/dist/src/tools/request-tool.js.map +1 -0
  254. package/dist/src/tools/request-tool.test.d.ts +6 -0
  255. package/dist/src/tools/request-tool.test.js +254 -0
  256. package/dist/src/tools/request-tool.test.js.map +1 -0
  257. package/dist/src/tools/store-tools.d.ts +29 -0
  258. package/dist/src/tools/store-tools.js +224 -0
  259. package/dist/src/tools/store-tools.js.map +1 -0
  260. package/dist/src/tools/store-tools.test.d.ts +6 -0
  261. package/dist/src/tools/store-tools.test.js +216 -0
  262. package/dist/src/tools/store-tools.test.js.map +1 -0
  263. package/dist/src/tools/types.d.ts +111 -0
  264. package/dist/src/tools/types.js +7 -0
  265. package/dist/src/tools/types.js.map +1 -0
  266. package/dist/src/types.d.ts +20 -12
  267. package/dist/src/types.js +3 -2
  268. package/dist/src/types.js.map +1 -1
  269. package/dist/tsconfig.tsbuildinfo +1 -1
  270. package/package.json +13 -4
  271. package/dist/src/__tests__/sse-contract.test.js +0 -464
  272. package/dist/src/__tests__/sse-contract.test.js.map +0 -1
  273. package/dist/src/__tests__/tools.test.js +0 -583
  274. package/dist/src/__tests__/tools.test.js.map +0 -1
  275. package/dist/src/agent/agent-runner.d.ts +0 -33
  276. package/dist/src/agent/agent-runner.js +0 -1040
  277. package/dist/src/agent/agent-runner.js.map +0 -1
  278. package/dist/src/agent/custom-tools-e2e.test.d.ts +0 -6
  279. package/dist/src/agent/custom-tools-e2e.test.js +0 -566
  280. package/dist/src/agent/custom-tools-e2e.test.js.map +0 -1
  281. package/dist/src/agent/request-helper.d.ts +0 -16
  282. package/dist/src/agent/request-helper.js +0 -96
  283. package/dist/src/agent/request-helper.js.map +0 -1
  284. package/dist/src/agent/stores-e2e.test.js +0 -433
  285. package/dist/src/agent/stores-e2e.test.js.map +0 -1
  286. package/dist/src/agent/tool-context-builder.d.ts +0 -11
  287. package/dist/src/agent/tool-context-builder.js +0 -102
  288. package/dist/src/agent/tool-context-builder.js.map +0 -1
  289. package/dist/src/agent/tool-context-builder.test.d.ts +0 -6
  290. package/dist/src/agent/tool-context-builder.test.js +0 -152
  291. package/dist/src/agent/tool-context-builder.test.js.map +0 -1
  292. package/dist/src/agent/write-repo-file.test.js +0 -270
  293. package/dist/src/agent/write-repo-file.test.js.map +0 -1
  294. package/dist/src/cron/heartbeat-runner.d.ts +0 -21
  295. package/dist/src/cron/heartbeat-runner.js +0 -79
  296. package/dist/src/cron/heartbeat-runner.js.map +0 -1
  297. package/dist/src/cron/heartbeat-runner.test.d.ts +0 -6
  298. package/dist/src/cron/heartbeat-runner.test.js +0 -120
  299. package/dist/src/cron/heartbeat-runner.test.js.map +0 -1
  300. package/dist/src/cron/heartbeat-scheduler.d.ts +0 -26
  301. package/dist/src/cron/heartbeat-scheduler.js +0 -55
  302. package/dist/src/cron/heartbeat-scheduler.js.map +0 -1
  303. package/dist/src/cron/heartbeat-scheduler.test.d.ts +0 -6
  304. package/dist/src/cron/heartbeat-scheduler.test.js +0 -61
  305. package/dist/src/cron/heartbeat-scheduler.test.js.map +0 -1
  306. package/dist/src/routes/ai-stream.test.d.ts +0 -6
  307. package/dist/src/routes/ai-stream.test.js +0 -586
  308. package/dist/src/routes/ai-stream.test.js.map +0 -1
  309. package/dist/src/routes/ask-user-response.d.ts +0 -30
  310. package/dist/src/routes/ask-user-response.js +0 -61
  311. package/dist/src/routes/ask-user-response.js.map +0 -1
  312. package/dist/src/routes/ask-user-response.test.d.ts +0 -6
  313. package/dist/src/routes/ask-user-response.test.js +0 -88
  314. package/dist/src/routes/ask-user-response.test.js.map +0 -1
  315. package/dist/src/routes/chat-stream.test.d.ts +0 -6
  316. package/dist/src/routes/chat-stream.test.js +0 -155
  317. package/dist/src/routes/chat-stream.test.js.map +0 -1
  318. package/dist/src/routes/chat.test.d.ts +0 -6
  319. package/dist/src/routes/chat.test.js +0 -99
  320. package/dist/src/routes/chat.test.js.map +0 -1
  321. package/dist/src/routes/widget-actions.d.ts +0 -49
  322. package/dist/src/routes/widget-actions.js +0 -78
  323. package/dist/src/routes/widget-actions.js.map +0 -1
  324. package/dist/src/session/custom-tool-adapter.d.ts +0 -74
  325. package/dist/src/session/custom-tool-adapter.js +0 -180
  326. package/dist/src/session/custom-tool-adapter.js.map +0 -1
  327. package/dist/src/session/history-converter.d.ts +0 -21
  328. package/dist/src/session/history-converter.js +0 -59
  329. package/dist/src/session/history-converter.js.map +0 -1
  330. package/dist/src/session/history-converter.test.d.ts +0 -6
  331. package/dist/src/session/history-converter.test.js +0 -130
  332. package/dist/src/session/history-converter.test.js.map +0 -1
  333. package/dist/src/session/session-manager.d.ts +0 -219
  334. package/dist/src/session/session-manager.js +0 -915
  335. package/dist/src/session/session-manager.js.map +0 -1
  336. package/dist/src/session/session-manager.test.d.ts +0 -6
  337. package/dist/src/session/session-manager.test.js +0 -455
  338. package/dist/src/session/session-manager.test.js.map +0 -1
  339. package/dist/src/session/session-runner.d.ts +0 -45
  340. package/dist/src/session/session-runner.js +0 -719
  341. package/dist/src/session/session-runner.js.map +0 -1
  342. package/dist/src/session/session-runner.test.d.ts +0 -6
  343. package/dist/src/session/session-runner.test.js +0 -834
  344. package/dist/src/session/session-runner.test.js.map +0 -1
  345. /package/dist/src/{__tests__/sse-contract.test.d.ts → __fixtures__/smoke.test.d.ts} +0 -0
  346. /package/dist/src/{__tests__/tools.test.d.ts → agent/loop.test.d.ts} +0 -0
  347. /package/dist/src/{agent/write-repo-file.test.d.ts → context/compiler.test.d.ts} +0 -0
@@ -1,1040 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Amodal Labs, Inc.
4
- * SPDX-License-Identifier: MIT
5
- */
6
- import { EXPLORE_TOOL_NAME, EXPLORE_TOOL_SCHEMA, validateExploreRequest, resolveExploreModel, FailoverProvider, storeToJsonSchema, storeToToolName, findStoreByToolName, } from '@amodalai/core';
7
- import path from 'node:path';
8
- import { readFile, writeFile, mkdir, unlink } from 'node:fs/promises';
9
- import { resolveKey } from '../stores/key-resolver.js';
10
- import { SSEEventType } from '../types.js';
11
- import { makeApiRequest } from './request-helper.js';
12
- import { buildToolContext } from './tool-context-builder.js';
13
- import { LocalToolExecutor } from './tool-executor-local.js';
14
- const MAX_TURNS = 15;
15
- /**
16
- * Runs an agent turn as a ReAct loop, yielding SSE events.
17
- *
18
- * Uses FailoverProvider for multi-provider support with retry + fallback.
19
- */
20
- export async function* runAgentTurn(session, message, signal) {
21
- // Append user message
22
- session.conversationHistory.push({ role: 'user', content: message });
23
- const modelConfig = session.runtime.repo.config.models.main;
24
- let provider;
25
- try {
26
- provider = new FailoverProvider(modelConfig);
27
- }
28
- catch (err) {
29
- const errMsg = err instanceof Error ? err.message : String(err);
30
- yield {
31
- type: SSEEventType.Error,
32
- message: `Provider initialization failed: ${errMsg}`,
33
- timestamp: ts(),
34
- };
35
- yield { type: SSEEventType.Done, timestamp: ts() };
36
- return;
37
- }
38
- const tools = buildTools(session);
39
- const systemPrompt = buildSystemPrompt(session);
40
- let turns = 0;
41
- let totalInputTokens = 0;
42
- let totalOutputTokens = 0;
43
- let totalCacheReadTokens = 0;
44
- let totalCacheCreationTokens = 0;
45
- while (turns < MAX_TURNS) {
46
- if (signal.aborted) {
47
- yield { type: SSEEventType.Error, message: 'Request aborted', timestamp: ts() };
48
- yield { type: SSEEventType.Done, timestamp: ts() };
49
- return;
50
- }
51
- turns++;
52
- try {
53
- const chatRequest = {
54
- model: modelConfig.model,
55
- systemPrompt,
56
- messages: session.conversationHistory,
57
- tools,
58
- maxTokens: 4096,
59
- signal,
60
- };
61
- // Use streaming when available for real-time text delivery
62
- if (provider.chatStream) {
63
- const { content, hasToolUse, toolResults, usage: streamUsage } = yield* processStream(provider.chatStream(chatRequest), session, signal);
64
- // Store assistant message
65
- session.conversationHistory.push({ role: 'assistant', content });
66
- if (streamUsage) {
67
- totalInputTokens += streamUsage.inputTokens;
68
- totalOutputTokens += streamUsage.outputTokens;
69
- totalCacheReadTokens += streamUsage.cacheReadInputTokens ?? 0;
70
- totalCacheCreationTokens += streamUsage.cacheCreationInputTokens ?? 0;
71
- }
72
- if (hasToolUse && toolResults.length > 0) {
73
- session.conversationHistory.push(...toolResults);
74
- continue;
75
- }
76
- break;
77
- }
78
- // Non-streaming fallback
79
- const response = await provider.chat(chatRequest);
80
- let hasToolUse = false;
81
- const toolResults = [];
82
- for (const block of response.content) {
83
- if (block.type === 'text') {
84
- const processed = processTextOutput(session, block.text);
85
- yield { type: SSEEventType.TextDelta, content: processed, timestamp: ts() };
86
- }
87
- else if (block.type === 'tool_use') {
88
- hasToolUse = true;
89
- yield {
90
- type: SSEEventType.ToolCallStart,
91
- tool_name: block.name,
92
- tool_id: block.id,
93
- parameters: block.input,
94
- timestamp: ts(),
95
- };
96
- // Emit ExploreStart before execution
97
- if (block.name === EXPLORE_TOOL_NAME) {
98
- yield {
99
- type: SSEEventType.ExploreStart,
100
- query: String(block.input['query'] ?? ''),
101
- timestamp: ts(),
102
- };
103
- }
104
- const startMs = Date.now();
105
- const execResult = await executeTool(session, block.name, block.input, block.id, signal);
106
- const durationMs = Date.now() - startMs;
107
- // Emit tool logs (e.g., store writes, progress messages)
108
- if (execResult.toolLogs) {
109
- for (const evt of execResult.toolLogs) {
110
- yield evt;
111
- }
112
- }
113
- // Emit subagent events from explore
114
- if (execResult.subagentEvents) {
115
- for (const evt of execResult.subagentEvents) {
116
- yield evt;
117
- }
118
- }
119
- // Emit ExploreEnd after execution
120
- if (block.name === EXPLORE_TOOL_NAME && execResult.exploreResult) {
121
- yield {
122
- type: SSEEventType.ExploreEnd,
123
- summary: execResult.exploreResult.summary,
124
- tokens_used: execResult.exploreResult.tokensUsed,
125
- timestamp: ts(),
126
- };
127
- }
128
- yield {
129
- type: SSEEventType.ToolCallResult,
130
- tool_id: block.id,
131
- status: execResult.result.error ? 'error' : 'success',
132
- result: execResult.result.output,
133
- parameters: block.input,
134
- error: execResult.result.error,
135
- duration_ms: durationMs,
136
- timestamp: ts(),
137
- };
138
- toolResults.push({
139
- role: 'tool_result',
140
- toolCallId: block.id,
141
- content: execResult.result.error ?? execResult.result.output ?? '',
142
- isError: !!execResult.result.error,
143
- });
144
- }
145
- }
146
- session.conversationHistory.push({ role: 'assistant', content: response.content });
147
- if (response.usage) {
148
- totalInputTokens += response.usage.inputTokens;
149
- totalOutputTokens += response.usage.outputTokens;
150
- totalCacheReadTokens += response.usage.cacheReadInputTokens ?? 0;
151
- totalCacheCreationTokens += response.usage.cacheCreationInputTokens ?? 0;
152
- }
153
- if (hasToolUse && toolResults.length > 0) {
154
- session.conversationHistory.push(...toolResults);
155
- continue;
156
- }
157
- break;
158
- }
159
- catch (err) {
160
- const errMsg = err instanceof Error ? err.message : String(err);
161
- yield { type: SSEEventType.Error, message: `LLM error: ${errMsg}`, timestamp: ts() };
162
- break;
163
- }
164
- }
165
- if (turns >= MAX_TURNS) {
166
- yield {
167
- type: SSEEventType.Error,
168
- message: `Agent loop exceeded max turns (${MAX_TURNS})`,
169
- timestamp: ts(),
170
- };
171
- }
172
- const doneEvent = {
173
- type: SSEEventType.Done,
174
- timestamp: ts(),
175
- usage: totalInputTokens > 0 || totalCacheReadTokens > 0 ? {
176
- input_tokens: totalInputTokens,
177
- output_tokens: totalOutputTokens,
178
- cached_tokens: totalCacheReadTokens,
179
- cache_creation_tokens: totalCacheCreationTokens,
180
- total_tokens: totalInputTokens + totalOutputTokens + totalCacheReadTokens + totalCacheCreationTokens,
181
- } : undefined,
182
- };
183
- yield doneEvent;
184
- }
185
- // ---------------------------------------------------------------------------
186
- // Helpers
187
- // ---------------------------------------------------------------------------
188
- function ts() {
189
- return new Date().toISOString();
190
- }
191
- function buildSystemPrompt(session) {
192
- const parts = [session.runtime.compiledContext.systemPrompt];
193
- const planReminder = session.planModeManager.getPlanningReminder();
194
- if (planReminder) {
195
- parts.push(planReminder);
196
- }
197
- const approvedPlan = session.planModeManager.getApprovedPlanContext();
198
- if (approvedPlan) {
199
- parts.push(approvedPlan);
200
- }
201
- return parts.join('\n\n');
202
- }
203
- function buildTools(session) {
204
- const tools = [];
205
- // Request tool (for connected systems)
206
- tools.push({
207
- name: 'request',
208
- description: 'Make HTTP requests to connected systems. Specify connection name, method, endpoint, and optional params/data.',
209
- parameters: {
210
- type: 'object',
211
- properties: {
212
- connection: { type: 'string', description: 'Connection name' },
213
- method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] },
214
- endpoint: { type: 'string', description: 'API endpoint path' },
215
- params: { type: 'object', description: 'Query parameters' },
216
- data: { description: 'Request body' },
217
- intent: { type: 'string', enum: ['read', 'write', 'confirmed_write'] },
218
- },
219
- required: ['connection', 'method', 'endpoint', 'intent'],
220
- },
221
- });
222
- // Explore tool
223
- tools.push({
224
- name: EXPLORE_TOOL_NAME,
225
- description: EXPLORE_TOOL_SCHEMA.description,
226
- parameters: {
227
- type: 'object',
228
- properties: {
229
- query: { type: 'string', description: 'What to investigate' },
230
- endpoint_hints: {
231
- type: 'array',
232
- items: { type: 'string' },
233
- description: 'Optional endpoint paths to prioritize',
234
- },
235
- model: {
236
- type: 'string',
237
- description: 'Optional: "simple" for lightweight, "default" for standard, "advanced" for primary model, or "provider:model" for specific',
238
- },
239
- },
240
- required: ['query'],
241
- },
242
- });
243
- // Plan mode tools
244
- tools.push({
245
- name: 'enter_plan_mode',
246
- description: 'Enter planning mode. Write operations will be blocked until a plan is approved.',
247
- parameters: {
248
- type: 'object',
249
- properties: {
250
- reason: { type: 'string', description: 'Why planning mode is needed' },
251
- },
252
- },
253
- });
254
- tools.push({
255
- name: 'exit_plan_mode',
256
- description: 'Exit planning mode.',
257
- parameters: {
258
- type: 'object',
259
- properties: {},
260
- },
261
- });
262
- // Custom tools from tools/ directory
263
- for (const tool of session.runtime.repo.tools) {
264
- // Skip tools marked as hidden from the LLM
265
- if (tool.confirm === 'never') {
266
- continue;
267
- }
268
- tools.push({
269
- name: tool.name,
270
- description: tool.description,
271
- parameters: tool.parameters,
272
- });
273
- }
274
- // MCP tools from connected MCP servers
275
- if (session.mcpManager) {
276
- for (const mcpTool of session.mcpManager.getDiscoveredTools()) {
277
- tools.push({
278
- name: mcpTool.name,
279
- description: mcpTool.description,
280
- parameters: mcpTool.parameters,
281
- });
282
- }
283
- }
284
- // Store write tools (one per store — schema as parameters for structured output)
285
- for (const store of session.runtime.repo.stores) {
286
- tools.push({
287
- name: storeToToolName(store.name),
288
- description: `Store a ${store.entity.name} to the ${store.name} collection.`,
289
- parameters: storeToJsonSchema(store),
290
- });
291
- }
292
- // Store query tool (single tool for reading from any store)
293
- if (session.runtime.repo.stores.length > 0) {
294
- tools.push({
295
- name: 'query_store',
296
- description: 'Query documents from a store collection. Use "key" for a single document or "filter" for a list.',
297
- parameters: {
298
- type: 'object',
299
- properties: {
300
- store: {
301
- type: 'string',
302
- enum: session.runtime.repo.stores.map((s) => s.name),
303
- description: 'The store to query',
304
- },
305
- key: { type: 'string', description: 'Get a specific document by key' },
306
- filter: { type: 'object', description: 'Filter by field values (equality match)' },
307
- sort: { type: 'string', description: 'Sort field, prefix with - for descending' },
308
- limit: { type: 'number', description: 'Max documents to return (default: 20)' },
309
- },
310
- required: ['store'],
311
- },
312
- });
313
- }
314
- // Shell execution tool (opt-in via config.sandbox.shellExec)
315
- if (session.runtime.repo.config.sandbox?.shellExec) {
316
- tools.push({
317
- name: 'shell_exec',
318
- description: 'Execute a shell command. Use for data transformation, computation, scripting, or anything that benefits from code execution.',
319
- parameters: {
320
- type: 'object',
321
- properties: {
322
- command: { type: 'string', description: 'The shell command to execute' },
323
- },
324
- required: ['command'],
325
- },
326
- });
327
- }
328
- // Admin repo file tools (local repos only)
329
- if (session.appId === 'admin' && session.runtime.repo.source === 'local') {
330
- tools.push({
331
- name: 'read_repo_file',
332
- description: 'Read a file from the agent repo. Path is relative to repo root. Allowed directories: skills/, knowledge/, connections/, stores/, pages/, automations/, evals/, agents/, tools/.',
333
- parameters: {
334
- type: 'object',
335
- properties: {
336
- path: { type: 'string', description: 'File path relative to repo root (e.g. "knowledge/formatting-rules.md")' },
337
- },
338
- required: ['path'],
339
- },
340
- });
341
- tools.push({
342
- name: 'write_repo_file',
343
- description: 'Create or update a file in the agent repo. Use this to add skills, knowledge, pages, automations, tools, store schemas, evals, connection docs, or agent overrides. Path is relative to repo root. Allowed directories: skills/, knowledge/, connections/, stores/, pages/, automations/, evals/, agents/, tools/.',
344
- parameters: {
345
- type: 'object',
346
- properties: {
347
- path: { type: 'string', description: 'File path relative to repo root (e.g. "knowledge/formatting-rules.md")' },
348
- content: { type: 'string', description: 'Full file content to write' },
349
- },
350
- required: ['path', 'content'],
351
- },
352
- });
353
- tools.push({
354
- name: 'delete_repo_file',
355
- description: 'Delete a file from the agent repo. Always confirm with the user before deleting. Path is relative to repo root. Same directory restrictions as write_repo_file.',
356
- parameters: {
357
- type: 'object',
358
- properties: {
359
- path: { type: 'string', description: 'File path relative to repo root (e.g. "evals/old-test.md")' },
360
- },
361
- required: ['path'],
362
- },
363
- });
364
- }
365
- return tools;
366
- }
367
- async function executeTool(session, toolName, args, toolId, signal) {
368
- switch (toolName) {
369
- case 'request':
370
- return { result: await executeRequestTool(session, args, signal) };
371
- case EXPLORE_TOOL_NAME:
372
- return executeExploreTool(session, args, toolId, signal);
373
- case 'enter_plan_mode':
374
- return { result: await executePlanModeEnter(session, args) };
375
- case 'exit_plan_mode':
376
- return { result: await executePlanModeExit(session) };
377
- case 'shell_exec':
378
- return { result: await executeShellExecTool(session, args, signal) };
379
- case 'read_repo_file':
380
- return { result: await executeReadRepoFile(session, args) };
381
- case 'write_repo_file':
382
- return { result: await executeWriteRepoFile(session, args) };
383
- case 'delete_repo_file':
384
- return { result: await executeDeleteRepoFile(session, args) };
385
- case 'query_store':
386
- return { result: await executeQueryStore(session, args) };
387
- default: {
388
- // Check store write tools (store_* prefix)
389
- if (toolName.startsWith('store_')) {
390
- const store = findStoreByToolName(session.runtime.repo.stores, toolName);
391
- if (store) {
392
- return { result: await executeStorePut(session, store, args) };
393
- }
394
- }
395
- // Check custom tools
396
- const customTool = session.runtime.repo.tools.find((t) => t.name === toolName);
397
- if (customTool) {
398
- return executeCustomTool(session, customTool, args, signal);
399
- }
400
- // Check MCP tools (namespaced as serverName__toolName)
401
- if (session.mcpManager?.isMcpTool(toolName)) {
402
- return { result: await executeMcpTool(session, toolName, args) };
403
- }
404
- // Build helpful error for unknown tools
405
- if (toolName.includes('__')) {
406
- const [serverName] = toolName.split('__', 2);
407
- const mcpTools = session.mcpManager?.getDiscoveredTools() ?? [];
408
- const fromServer = mcpTools.filter((t) => t.serverName === serverName);
409
- if (fromServer.length > 0) {
410
- const names = fromServer.map((t) => t.name).slice(0, 10).join(', ');
411
- return { result: { error: `MCP tool "${toolName}" not found on server "${serverName}". Available: ${names}` } };
412
- }
413
- const servers = [...new Set(mcpTools.map((t) => t.serverName))];
414
- return { result: { error: `MCP server "${serverName}" not found. Available MCP servers: ${servers.join(', ') || '(none)'}. MCP tools use the format: serverName__toolName` } };
415
- }
416
- return { result: { error: `Unknown tool: "${toolName}". Available tools: request, explore, enter_plan_mode, exit_plan_mode` } };
417
- }
418
- }
419
- }
420
- async function executeRequestTool(session, args, signal) {
421
- const connectionName = String(args['connection'] ?? '');
422
- const method = String(args['method'] ?? 'GET');
423
- const endpoint = String(args['endpoint'] ?? '');
424
- const intent = String(args['intent'] ?? 'read');
425
- const params = args['params'];
426
- const data = args['data'];
427
- // Action gate for writes
428
- if (intent === 'write' || intent === 'confirmed_write') {
429
- if (session.planModeManager.isActive()) {
430
- return { error: 'Write operations are blocked in plan mode. Present your plan for approval first.' };
431
- }
432
- const gateResult = session.runtime.actionGate.evaluate(endpoint, connectionName);
433
- if (gateResult['decision'] === 'never') {
434
- return { error: `Write to ${endpoint} is blocked: ${gateResult['reason'] ?? 'policy'}` };
435
- }
436
- if (gateResult['decision'] === 'confirm' && intent !== 'confirmed_write') {
437
- return { error: `Write to ${endpoint} requires confirmation. Re-call with intent: "confirmed_write".` };
438
- }
439
- }
440
- return makeApiRequest(session, connectionName, method, endpoint, params, data, signal);
441
- }
442
- // Lazy-initialized executor (shared across all custom tool calls)
443
- let localToolExecutor = null;
444
- async function executeCustomTool(session, tool, args, signal) {
445
- // Confirmation gating for tools that require it
446
- if (tool.confirm === true || tool.confirm === 'review') {
447
- if (session.planModeManager.isActive()) {
448
- return { result: { error: 'Custom tool writes are blocked in plan mode. Present your plan for approval first.' } };
449
- }
450
- }
451
- const toolLogs = [];
452
- const onLog = (toolName, message) => {
453
- toolLogs.push({ type: SSEEventType.ToolLog, tool_name: toolName, message, timestamp: new Date().toISOString() });
454
- };
455
- const ctx = buildToolContext(session, tool, signal, onLog);
456
- if (!localToolExecutor) {
457
- localToolExecutor = new LocalToolExecutor();
458
- }
459
- const executor = session.toolExecutor ?? localToolExecutor;
460
- try {
461
- const result = await executor.execute(tool, args, ctx);
462
- return { result: { output: JSON.stringify(result) }, toolLogs };
463
- }
464
- catch (err) {
465
- const message = err instanceof Error ? err.message : String(err);
466
- return { result: { error: message }, toolLogs };
467
- }
468
- }
469
- async function executeMcpTool(session, toolName, args) {
470
- if (!session.mcpManager) {
471
- return { error: 'MCP is not configured' };
472
- }
473
- try {
474
- const result = await session.mcpManager.callTool(toolName, args);
475
- if (result.isError) {
476
- const errorText = result.content
477
- .filter((c) => c.type === 'text' && c.text)
478
- .map((c) => c.text)
479
- .join('\n');
480
- return { error: errorText || 'MCP tool returned an error' };
481
- }
482
- const output = result.content
483
- .map((c) => {
484
- if (c.type === 'text' && c.text)
485
- return c.text;
486
- if (c.type === 'image' && c.data)
487
- return `[image: ${c.mimeType ?? 'unknown'}]`;
488
- return `[${c.type}]`;
489
- })
490
- .join('\n');
491
- return { output };
492
- }
493
- catch (err) {
494
- const message = err instanceof Error ? err.message : String(err);
495
- if (message.includes('ECONNREFUSED') || message.includes('ENOTFOUND') || message.includes('unreachable')) {
496
- return { error: `MCP server for "${toolName}" is unreachable: ${message}. The MCP server may have crashed or the URL may be wrong.` };
497
- }
498
- if (message.includes('401') || message.includes('403') || message.includes('missing_token') || message.includes('invalid_token')) {
499
- return { error: `MCP authentication failed for "${toolName}": ${message}. Check that the auth headers and credentials are configured correctly in amodal.json.` };
500
- }
501
- return { error: `MCP tool call failed for "${toolName}": ${message}` };
502
- }
503
- }
504
- async function executeShellExecTool(session, args, signal) {
505
- const command = String(args['command'] ?? '');
506
- if (!command) {
507
- return { error: 'Missing required parameter: command' };
508
- }
509
- const shellExecutor = session.shellExecutor;
510
- if (!shellExecutor) {
511
- return { error: 'Shell execution is not available' };
512
- }
513
- const maxTimeout = session.runtime.repo.config.sandbox?.maxTimeout ?? 30000;
514
- try {
515
- const result = await shellExecutor.exec(command, maxTimeout, signal);
516
- const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
517
- return { output: `Exit code: ${result.exitCode}\n${output}` };
518
- }
519
- catch (err) {
520
- if (signal.aborted) {
521
- return { error: 'Shell execution aborted' };
522
- }
523
- return { error: err instanceof Error ? err.message : String(err) };
524
- }
525
- }
526
- // ---------------------------------------------------------------------------
527
- // Store tools
528
- // ---------------------------------------------------------------------------
529
- async function executeStorePut(session, store, args) {
530
- if (!session.storeBackend) {
531
- return { error: 'Store backend is not configured' };
532
- }
533
- // Block writes in plan mode
534
- if (session.planModeManager.isActive()) {
535
- return { error: 'Store writes are blocked in plan mode. Present your plan for approval first.' };
536
- }
537
- // Resolve key from template
538
- let key;
539
- try {
540
- key = resolveKey(store.entity.key, args);
541
- }
542
- catch (err) {
543
- return { error: err instanceof Error ? err.message : String(err) };
544
- }
545
- try {
546
- const result = await session.storeBackend.put(session.appId, store.name, key, args, {});
547
- return { output: JSON.stringify(result) };
548
- }
549
- catch (err) {
550
- return { error: err instanceof Error ? err.message : String(err) };
551
- }
552
- }
553
- async function executeQueryStore(session, args) {
554
- if (!session.storeBackend) {
555
- return { error: 'Store backend is not configured' };
556
- }
557
- const storeName = String(args['store'] ?? '');
558
- if (!storeName) {
559
- return { error: 'Missing required parameter: store' };
560
- }
561
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- tool args from LLM
562
- const key = args['key'];
563
- try {
564
- if (key) {
565
- // Single document lookup
566
- const doc = await session.storeBackend.get(session.appId, storeName, key);
567
- if (!doc) {
568
- return { output: JSON.stringify({ found: false, key }) };
569
- }
570
- return { output: JSON.stringify({ found: true, ...doc }) };
571
- }
572
- // List with optional filtering
573
- const result = await session.storeBackend.list(session.appId, storeName, {
574
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
575
- filter: args['filter'],
576
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- tool args from LLM
577
- sort: args['sort'],
578
- limit: typeof args['limit'] === 'number' ? args['limit'] : 20,
579
- });
580
- return { output: JSON.stringify(result) };
581
- }
582
- catch (err) {
583
- return { error: err instanceof Error ? err.message : String(err) };
584
- }
585
- }
586
- // ---------------------------------------------------------------------------
587
- // Explore tool
588
- // ---------------------------------------------------------------------------
589
- async function executeExploreTool(session, args, parentToolId, signal) {
590
- const query = String(args['query'] ?? '');
591
- const endpointHints = Array.isArray(args['endpoint_hints'])
592
- ? args['endpoint_hints'].filter((h) => typeof h === 'string')
593
- : undefined;
594
- const modelOverride = typeof args['model'] === 'string' ? args['model'] : undefined;
595
- const validationError = validateExploreRequest({ query, endpointHints, parentDepth: 0 }, session.exploreConfig);
596
- if (validationError) {
597
- return { result: { error: validationError } };
598
- }
599
- // Resolve model override against available models
600
- const effectiveModel = resolveExploreModel(session.exploreConfig, modelOverride);
601
- const { result, events, tokensUsed } = await runExploreAgent(session, query, endpointHints, 0, parentToolId, signal, effectiveModel);
602
- return {
603
- result,
604
- subagentEvents: events,
605
- exploreResult: { summary: result.output ?? '', tokensUsed },
606
- };
607
- }
608
- // ---------------------------------------------------------------------------
609
- // Explore sub-agent
610
- // ---------------------------------------------------------------------------
611
- async function runExploreAgent(session, query, endpointHints, parentDepth, parentToolId, signal, modelOverride) {
612
- const config = session.exploreConfig;
613
- const events = [];
614
- let tokensUsed = 0;
615
- const effectiveModel = modelOverride ?? config.model;
616
- let provider;
617
- try {
618
- provider = new FailoverProvider(effectiveModel);
619
- }
620
- catch (err) {
621
- const errMsg = err instanceof Error ? err.message : String(err);
622
- events.push({
623
- type: SSEEventType.SubagentEvent,
624
- parent_tool_id: parentToolId,
625
- agent_name: 'explore',
626
- event_type: 'error',
627
- error: `Provider init failed: ${errMsg}`,
628
- timestamp: ts(),
629
- });
630
- return { result: { error: `Explore provider failed: ${errMsg}` }, events, tokensUsed };
631
- }
632
- // Build sub-agent tools: always request (read-only)
633
- const subTools = [{
634
- name: 'request',
635
- description: 'Make HTTP requests to connected systems (read-only).',
636
- parameters: {
637
- type: 'object',
638
- properties: {
639
- connection: { type: 'string', description: 'Connection name' },
640
- method: { type: 'string', enum: ['GET'] },
641
- endpoint: { type: 'string', description: 'API endpoint path' },
642
- params: { type: 'object', description: 'Query parameters' },
643
- intent: { type: 'string', enum: ['read'] },
644
- },
645
- required: ['connection', 'method', 'endpoint', 'intent'],
646
- },
647
- }];
648
- // Allow nested explore if depth permits
649
- if (parentDepth + 1 < config.maxDepth) {
650
- subTools.push({
651
- name: EXPLORE_TOOL_NAME,
652
- description: EXPLORE_TOOL_SCHEMA.description,
653
- parameters: {
654
- type: 'object',
655
- properties: {
656
- query: { type: 'string', description: 'What to investigate' },
657
- endpoint_hints: {
658
- type: 'array',
659
- items: { type: 'string' },
660
- description: 'Optional endpoint paths to prioritize',
661
- },
662
- model: {
663
- type: 'string',
664
- description: 'Optional: "simple", "default", "complex", or "provider:model"',
665
- },
666
- },
667
- required: ['query'],
668
- },
669
- });
670
- }
671
- // Build initial message
672
- const hintsStr = endpointHints?.length ? `\nEndpoint hints: ${endpointHints.join(', ')}` : '';
673
- const conversation = [
674
- { role: 'user', content: `Investigate: ${query}${hintsStr}` },
675
- ];
676
- let summaryParts = [];
677
- for (let turn = 0; turn < config.maxTurns; turn++) {
678
- if (signal.aborted) {
679
- events.push({
680
- type: SSEEventType.SubagentEvent,
681
- parent_tool_id: parentToolId,
682
- agent_name: 'explore',
683
- event_type: 'error',
684
- error: 'Aborted',
685
- timestamp: ts(),
686
- });
687
- return { result: { error: 'Explore sub-agent aborted' }, events, tokensUsed };
688
- }
689
- let response;
690
- try {
691
- response = await provider.chat({
692
- model: effectiveModel.model,
693
- systemPrompt: config.systemPrompt,
694
- messages: conversation,
695
- tools: subTools,
696
- maxTokens: 4096,
697
- signal,
698
- });
699
- }
700
- catch (err) {
701
- const errMsg = err instanceof Error ? err.message : String(err);
702
- events.push({
703
- type: SSEEventType.SubagentEvent,
704
- parent_tool_id: parentToolId,
705
- agent_name: 'explore',
706
- event_type: 'error',
707
- error: errMsg,
708
- timestamp: ts(),
709
- });
710
- return { result: { error: `Explore LLM error: ${errMsg}` }, events, tokensUsed };
711
- }
712
- if (response.usage) {
713
- tokensUsed += response.usage.inputTokens + response.usage.outputTokens;
714
- }
715
- let hasToolUse = false;
716
- const toolResults = [];
717
- for (const block of response.content) {
718
- if (block.type === 'text') {
719
- summaryParts.push(block.text);
720
- events.push({
721
- type: SSEEventType.SubagentEvent,
722
- parent_tool_id: parentToolId,
723
- agent_name: 'explore',
724
- event_type: 'thought',
725
- text: block.text,
726
- timestamp: ts(),
727
- });
728
- }
729
- else if (block.type === 'tool_use') {
730
- hasToolUse = true;
731
- events.push({
732
- type: SSEEventType.SubagentEvent,
733
- parent_tool_id: parentToolId,
734
- agent_name: 'explore',
735
- event_type: 'tool_call_start',
736
- tool_name: block.name,
737
- tool_args: block.input,
738
- timestamp: ts(),
739
- });
740
- let toolResult;
741
- if (block.name === 'request') {
742
- toolResult = await executeSubAgentRequest(session, block.input, signal);
743
- }
744
- else if (block.name === EXPLORE_TOOL_NAME) {
745
- // Nested explore
746
- const nestedQuery = String(block.input['query'] ?? '');
747
- const nestedHints = Array.isArray(block.input['endpoint_hints'])
748
- ? block.input['endpoint_hints'].filter((h) => typeof h === 'string')
749
- : undefined;
750
- const nestedModelParam = typeof block.input['model'] === 'string' ? block.input['model'] : undefined;
751
- const nestedModel = resolveExploreModel(config, nestedModelParam);
752
- const nested = await runExploreAgent(session, nestedQuery, nestedHints, parentDepth + 1, parentToolId, signal, nestedModel);
753
- events.push(...nested.events);
754
- tokensUsed += nested.tokensUsed;
755
- toolResult = nested.result;
756
- }
757
- else {
758
- toolResult = { error: `Unknown tool in explore: ${block.name}` };
759
- }
760
- events.push({
761
- type: SSEEventType.SubagentEvent,
762
- parent_tool_id: parentToolId,
763
- agent_name: 'explore',
764
- event_type: 'tool_call_end',
765
- tool_name: block.name,
766
- result: toolResult.error ?? toolResult.output,
767
- timestamp: ts(),
768
- });
769
- toolResults.push({
770
- role: 'tool_result',
771
- toolCallId: block.id,
772
- content: toolResult.error ?? toolResult.output ?? '',
773
- isError: !!toolResult.error,
774
- });
775
- }
776
- }
777
- conversation.push({ role: 'assistant', content: response.content });
778
- if (hasToolUse && toolResults.length > 0) {
779
- conversation.push(...toolResults);
780
- summaryParts = []; // Reset — final text is the real summary
781
- continue;
782
- }
783
- // No tool use — sub-agent is done
784
- break;
785
- }
786
- const summary = summaryParts.join('\n').trim() || 'No findings.';
787
- events.push({
788
- type: SSEEventType.SubagentEvent,
789
- parent_tool_id: parentToolId,
790
- agent_name: 'explore',
791
- event_type: 'complete',
792
- result: summary,
793
- timestamp: ts(),
794
- });
795
- return { result: { output: summary }, events, tokensUsed };
796
- }
797
- async function executeSubAgentRequest(session, args, signal) {
798
- // Force read-only intent
799
- const readOnlyArgs = { ...args, intent: 'read', method: 'GET' };
800
- return executeRequestTool(session, readOnlyArgs, signal);
801
- }
802
- function executePlanModeEnter(session, args) {
803
- const reason = typeof args['reason'] === 'string' ? args['reason'] : undefined;
804
- session.planModeManager.enter(reason);
805
- return Promise.resolve({ output: 'Plan mode activated. Present your plan for approval.' });
806
- }
807
- function executePlanModeExit(session) {
808
- session.planModeManager.exit();
809
- return Promise.resolve({ output: 'Plan mode deactivated.' });
810
- }
811
- /**
812
- * Process a streaming LLM response, yielding SSE events and accumulating
813
- * the complete response for conversation history.
814
- */
815
- async function* processStream(stream, session, signal) {
816
- const content = [];
817
- const toolResults = [];
818
- let hasToolUse = false;
819
- let turnUsage;
820
- // Accumulate text and tool call data from stream events
821
- let currentText = '';
822
- const toolInputBuffers = new Map();
823
- for await (const event of stream) {
824
- switch (event.type) {
825
- case 'text_delta': {
826
- currentText += event.text;
827
- const processed = processTextOutput(session, event.text);
828
- yield { type: SSEEventType.TextDelta, content: processed, timestamp: ts() };
829
- break;
830
- }
831
- case 'tool_use_start':
832
- hasToolUse = true;
833
- toolInputBuffers.set(event.id, { name: event.name, json: '' });
834
- yield {
835
- type: SSEEventType.ToolCallStart,
836
- tool_name: event.name,
837
- tool_id: event.id,
838
- parameters: {},
839
- timestamp: ts(),
840
- };
841
- break;
842
- case 'tool_use_delta': {
843
- const buf = toolInputBuffers.get(event.id);
844
- if (buf) {
845
- buf.json += event.inputDelta;
846
- }
847
- break;
848
- }
849
- case 'tool_use_end': {
850
- // Flush any accumulated text
851
- if (currentText) {
852
- content.push({ type: 'text', text: currentText });
853
- currentText = '';
854
- }
855
- const toolName = toolInputBuffers.get(event.id)?.name ?? '';
856
- content.push({ type: 'tool_use', id: event.id, name: toolName, input: event.input });
857
- toolInputBuffers.delete(event.id);
858
- // Emit ExploreStart before execution
859
- if (toolName === EXPLORE_TOOL_NAME) {
860
- yield {
861
- type: SSEEventType.ExploreStart,
862
- query: String(event.input['query'] ?? ''),
863
- timestamp: ts(),
864
- };
865
- }
866
- // Execute the tool
867
- const startMs = Date.now();
868
- const execResult = await executeTool(session, toolName, event.input, event.id, signal);
869
- const durationMs = Date.now() - startMs;
870
- // Emit subagent events from explore
871
- if (execResult.subagentEvents) {
872
- for (const evt of execResult.subagentEvents) {
873
- yield evt;
874
- }
875
- }
876
- // Emit ExploreEnd after execution
877
- if (toolName === EXPLORE_TOOL_NAME && execResult.exploreResult) {
878
- yield {
879
- type: SSEEventType.ExploreEnd,
880
- summary: execResult.exploreResult.summary,
881
- tokens_used: execResult.exploreResult.tokensUsed,
882
- timestamp: ts(),
883
- };
884
- }
885
- yield {
886
- type: SSEEventType.ToolCallResult,
887
- tool_id: event.id,
888
- status: execResult.result.error ? 'error' : 'success',
889
- result: execResult.result.output,
890
- parameters: event.input,
891
- error: execResult.result.error,
892
- duration_ms: durationMs,
893
- timestamp: ts(),
894
- };
895
- toolResults.push({
896
- role: 'tool_result',
897
- toolCallId: event.id,
898
- content: execResult.result.error ?? execResult.result.output ?? '',
899
- isError: !!execResult.result.error,
900
- });
901
- break;
902
- }
903
- case 'message_end':
904
- // Flush any remaining text
905
- if (currentText) {
906
- content.push({ type: 'text', text: currentText });
907
- currentText = '';
908
- }
909
- if (event.usage) {
910
- turnUsage = event.usage;
911
- }
912
- break;
913
- default:
914
- break;
915
- }
916
- }
917
- // Flush any remaining text not flushed by message_end
918
- if (currentText) {
919
- content.push({ type: 'text', text: currentText });
920
- }
921
- return { content, hasToolUse, toolResults, usage: turnUsage };
922
- }
923
- // ---------------------------------------------------------------------------
924
- // Admin repo file tools
925
- // ---------------------------------------------------------------------------
926
- /** Allowed directory prefixes for admin file operations. */
927
- const ALLOWED_REPO_DIRS = [
928
- 'skills/',
929
- 'knowledge/',
930
- 'connections/',
931
- 'stores/',
932
- 'pages/',
933
- 'automations/',
934
- 'evals/',
935
- 'agents/',
936
- 'tools/',
937
- ];
938
- /** Files that must never be written by the admin agent. */
939
- const BLOCKED_FILENAMES = [
940
- '.env',
941
- 'amodal.json',
942
- 'package.json',
943
- 'pnpm-lock.yaml',
944
- 'tsconfig.json',
945
- ];
946
- /** @internal Exported for testing */
947
- export function isAllowedRepoPath(relPath) {
948
- const basename = path.basename(relPath);
949
- if (BLOCKED_FILENAMES.includes(basename))
950
- return false;
951
- return ALLOWED_REPO_DIRS.some((dir) => relPath.startsWith(dir));
952
- }
953
- /** @internal Exported for testing */
954
- export function validateRepoFilePath(session, rawPath) {
955
- if (!rawPath || rawPath.startsWith('/')) {
956
- return { error: 'Path must be relative to the repo root (no leading /)' };
957
- }
958
- if (rawPath.includes('..')) {
959
- return { error: 'Path traversal (..) is not allowed' };
960
- }
961
- if (session.appId !== 'admin' || session.runtime.repo.source !== 'local') {
962
- return { error: 'write_repo_file/read_repo_file is only available in local admin sessions' };
963
- }
964
- const normalized = path.normalize(rawPath);
965
- if (!isAllowedRepoPath(normalized)) {
966
- return { error: `Path "${normalized}" is not in an allowed directory. Allowed: ${ALLOWED_REPO_DIRS.join(', ')}. Blocked files: ${BLOCKED_FILENAMES.join(', ')}` };
967
- }
968
- const resolved = path.resolve(session.runtime.repo.origin, normalized);
969
- if (!resolved.startsWith(session.runtime.repo.origin)) {
970
- return { error: 'Resolved path escapes the repo directory' };
971
- }
972
- return { resolved, relative: normalized };
973
- }
974
- /** @internal Exported for testing */
975
- export async function executeReadRepoFile(session, args) {
976
- const rawPath = String(args['path'] ?? '');
977
- const validation = validateRepoFilePath(session, rawPath);
978
- if ('error' in validation) {
979
- return { error: validation.error };
980
- }
981
- try {
982
- const content = await readFile(validation.resolved, 'utf-8');
983
- return { output: content };
984
- }
985
- catch (err) {
986
- if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
987
- return { error: `File not found: ${validation.relative}` };
988
- }
989
- return { error: err instanceof Error ? err.message : String(err) };
990
- }
991
- }
992
- /** @internal Exported for testing */
993
- export async function executeWriteRepoFile(session, args) {
994
- const rawPath = String(args['path'] ?? '');
995
- const content = String(args['content'] ?? '');
996
- const validation = validateRepoFilePath(session, rawPath);
997
- if ('error' in validation) {
998
- return { error: validation.error };
999
- }
1000
- if (!content) {
1001
- return { error: 'Content must not be empty' };
1002
- }
1003
- try {
1004
- await mkdir(path.dirname(validation.resolved), { recursive: true });
1005
- await writeFile(validation.resolved, content, 'utf-8');
1006
- return { output: `Wrote ${validation.relative} (${String(content.length)} bytes)` };
1007
- }
1008
- catch (err) {
1009
- return { error: err instanceof Error ? err.message : String(err) };
1010
- }
1011
- }
1012
- /** @internal Exported for testing */
1013
- export async function executeDeleteRepoFile(session, args) {
1014
- const rawPath = String(args['path'] ?? '');
1015
- const validation = validateRepoFilePath(session, rawPath);
1016
- if ('error' in validation) {
1017
- return { error: validation.error };
1018
- }
1019
- try {
1020
- await unlink(validation.resolved);
1021
- return { output: `Deleted ${validation.relative}` };
1022
- }
1023
- catch (err) {
1024
- if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
1025
- return { error: `File not found: ${validation.relative}` };
1026
- }
1027
- return { error: err instanceof Error ? err.message : String(err) };
1028
- }
1029
- }
1030
- function processTextOutput(session, text) {
1031
- const result = session.runtime.outputPipeline.process(text);
1032
- session.runtime.telemetry.logGuard({
1033
- output: result.output,
1034
- modified: result.modified,
1035
- blocked: result.blocked,
1036
- findings: result.findings,
1037
- });
1038
- return result.output;
1039
- }
1040
- //# sourceMappingURL=agent-runner.js.map