failproofai 1.0.7-beta.1 → 1.0.7

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 (429) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +7 -8
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +5 -7
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +11 -13
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +7 -8
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +7 -7
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +6 -7
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -41
  63. package/.next/standalone/.next/server/app/settings/page.js +9 -12
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1j-zsg5._.js → [externals]__1_bftcl._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__19_pzeq._.js → [externals]__1msfs-h._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1bf34x4._.js → [root-of-the-server]__1_r2rbg._.js} +7 -5
  70. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  71. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1c3k-8x._.js} +2 -2
  74. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  75. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -4
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0yxwl6j._.js → [root-of-the-server]__0bd3mje._.js} +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +5 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__0cxe_2_._.js} +3 -3
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +22 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +5 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
  98. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/{_1w_5l7t._.js → _0h_douw._.js} +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/{_214wgrp._.js → _1-i_gzc._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/{_0bn2oo8._.js → _166t73i._.js} +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/{_1q46vxx._.js → _1_qswah._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifp._.js → _1es2j7i._.js} +5 -5
  104. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  107. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_18_d8l1._.js → node_modules_next_dist_0drixxt._.js} +4 -4
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
  113. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  116. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  117. package/.next/standalone/.next/server/pages/404.html +1 -1
  118. package/.next/standalone/.next/server/pages/500.html +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/server-reference-manifest.json +23 -56
  121. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +1 -0
  122. package/.next/standalone/.next/static/chunks/0__8a7m868fvf.js +1 -0
  123. package/.next/standalone/.next/static/chunks/{12tvm75t5ffui.js → 0o6qlkgubtoex.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/13i7-9is-vhys.js +1 -0
  125. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +6 -0
  126. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
  127. package/.next/standalone/.next/static/chunks/{0vmd180qfntfb.js → 2klitrtzpaoe0.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{258668t68du6b.js → 2mdh397ghgnvv.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +2 -0
  130. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +1 -0
  131. package/.next/standalone/.next/static/chunks/{0qrbdkv9qmvli.js → 3rh5o7e16irrm.js} +1 -1
  132. package/.next/standalone/.next/static/chunks/{29fql3nbnfc9q.js → 43ufqrz8qo3h-.js} +1 -1
  133. package/.next/standalone/SECURITY.md +53 -0
  134. package/.next/standalone/app/actions/pack-actions.ts +0 -12
  135. package/.next/standalone/app/policies/hooks-client.tsx +0 -9
  136. package/.next/standalone/app/settings/page.tsx +1 -20
  137. package/.next/standalone/app/settings/settings-client.tsx +1 -27
  138. package/.next/standalone/app/settings/settings.css +0 -79
  139. package/.next/standalone/package.json +9 -9
  140. package/.next/standalone/sdk/python/skill/SKILL.md +60 -14
  141. package/.next/standalone/sdk/python/skill/agents/openai.yaml +2 -1
  142. package/.next/standalone/sdk/python/skill/references/evaluator.md +255 -0
  143. package/.next/standalone/sdk/python/skill/references/events.md +17 -8
  144. package/.next/standalone/sdk/python/skill/references/frameworks.md +3 -0
  145. package/.next/standalone/sdk/python/skill/references/install.md +3 -0
  146. package/.next/standalone/sdk/python/skill/references/integration.md +6 -2
  147. package/.next/standalone/sdk/python/skill/references/typescript.md +568 -0
  148. package/.next/standalone/sdk/typescript/CHANGELOG.md +133 -0
  149. package/.next/standalone/sdk/typescript/LICENSE +42 -0
  150. package/.next/standalone/sdk/typescript/README.md +552 -0
  151. package/.next/standalone/sdk/typescript/eslint.config.mjs +59 -0
  152. package/.next/standalone/sdk/typescript/examples/research-agent.ts +197 -0
  153. package/.next/standalone/sdk/typescript/integration/ai.test.ts +920 -0
  154. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/agent.ts +337 -0
  155. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +261 -0
  156. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +16 -0
  157. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/surfaces.ts +605 -0
  158. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.json +12 -0
  159. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.surfaces.json +4 -0
  160. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/agent.ts +342 -0
  161. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +156 -0
  162. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +16 -0
  163. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/surfaces.ts +628 -0
  164. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.json +12 -0
  165. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.surfaces.json +4 -0
  166. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/agent.ts +346 -0
  167. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +156 -0
  168. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +13 -0
  169. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/surfaces.ts +651 -0
  170. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.json +12 -0
  171. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.surfaces.json +4 -0
  172. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/agent.ts +350 -0
  173. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +153 -0
  174. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +13 -0
  175. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/surfaces.ts +651 -0
  176. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.json +12 -0
  177. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.surfaces.json +4 -0
  178. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/agent.ts +623 -0
  179. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +344 -0
  180. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +19 -0
  181. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/tsconfig.json +12 -0
  182. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent-v1.ts +99 -0
  183. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent.ts +623 -0
  184. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +336 -0
  185. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +15 -0
  186. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/tsconfig.json +12 -0
  187. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/agent.ts +96 -0
  188. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +441 -0
  189. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +19 -0
  190. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/tsconfig.json +12 -0
  191. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.cjs +53 -0
  192. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.mjs +55 -0
  193. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/package.json +18 -0
  194. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/agent.ts +659 -0
  195. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +635 -0
  196. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +15 -0
  197. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/tsconfig.json +12 -0
  198. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/agent.ts +659 -0
  199. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +553 -0
  200. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +15 -0
  201. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/tsconfig.json +12 -0
  202. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/agent.ts +877 -0
  203. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/mcp-server.mjs +66 -0
  204. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +6797 -0
  205. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +24 -0
  206. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/tsconfig.json +12 -0
  207. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/agent.ts +872 -0
  208. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/mcp-server.mjs +66 -0
  209. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +2540 -0
  210. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +15 -0
  211. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/tsconfig.json +12 -0
  212. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/actions.ts +18 -0
  213. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/ai/route.ts +42 -0
  214. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/edge/route.ts +29 -0
  215. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/langgraph/route.ts +21 -0
  216. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/llamaindex/route.ts +11 -0
  217. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/mastra/route.ts +19 -0
  218. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/status/route.ts +7 -0
  219. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/layout.tsx +9 -0
  220. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/page.tsx +15 -0
  221. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/instrumentation.ts +15 -0
  222. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/ai.ts +61 -0
  223. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/langgraph.ts +68 -0
  224. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/llamaindex.ts +98 -0
  225. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/mastra.ts +90 -0
  226. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/next.config.ts +52 -0
  227. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +4343 -0
  228. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +28 -0
  229. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/agent.ts +51 -0
  230. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/deno-npm.ts +76 -0
  231. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +484 -0
  232. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +15 -0
  233. package/.next/standalone/sdk/typescript/integration/fixtures/types/agent.ts +79 -0
  234. package/.next/standalone/sdk/typescript/integration/fixtures/types/package-lock.json +740 -0
  235. package/.next/standalone/sdk/typescript/integration/fixtures/types/package.json +11 -0
  236. package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/agent.ts +197 -0
  237. package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package-lock.json +70 -0
  238. package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package.json +12 -0
  239. package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/tsconfig.json +12 -0
  240. package/.next/standalone/sdk/typescript/integration/global-setup.ts +29 -0
  241. package/.next/standalone/sdk/typescript/integration/harness.ts +451 -0
  242. package/.next/standalone/sdk/typescript/integration/langchain.test.ts +682 -0
  243. package/.next/standalone/sdk/typescript/integration/llamaindex.test.ts +709 -0
  244. package/.next/standalone/sdk/typescript/integration/mastra-coverage.test.ts +386 -0
  245. package/.next/standalone/sdk/typescript/integration/mastra.test.ts +311 -0
  246. package/.next/standalone/sdk/typescript/integration/nextjs.test.ts +341 -0
  247. package/.next/standalone/sdk/typescript/integration/runtime-parity.ts +180 -0
  248. package/.next/standalone/sdk/typescript/integration/runtimes.bun.test.ts +15 -0
  249. package/.next/standalone/sdk/typescript/integration/runtimes.core.test.ts +113 -0
  250. package/.next/standalone/sdk/typescript/integration/runtimes.deno.test.ts +19 -0
  251. package/.next/standalone/sdk/typescript/integration/types.test.ts +141 -0
  252. package/.next/standalone/sdk/typescript/integration/vanilla.test.ts +255 -0
  253. package/.next/standalone/sdk/typescript/package-lock.json +2640 -0
  254. package/.next/standalone/sdk/typescript/package.json +401 -0
  255. package/.next/standalone/sdk/typescript/scripts/finalize-build.mjs +123 -0
  256. package/.next/standalone/sdk/typescript/scripts/release.mjs +177 -0
  257. package/.next/standalone/sdk/typescript/src/clock.ts +58 -0
  258. package/.next/standalone/sdk/typescript/src/context.ts +214 -0
  259. package/.next/standalone/sdk/typescript/src/edge/adapter.ts +18 -0
  260. package/.next/standalone/sdk/typescript/src/edge/ai.ts +116 -0
  261. package/.next/standalone/sdk/typescript/src/edge/index.ts +238 -0
  262. package/.next/standalone/sdk/typescript/src/edge/langchain.ts +18 -0
  263. package/.next/standalone/sdk/typescript/src/edge/llamaindex.ts +12 -0
  264. package/.next/standalone/sdk/typescript/src/edge/mastra.ts +17 -0
  265. package/.next/standalone/sdk/typescript/src/edge/notice.ts +33 -0
  266. package/.next/standalone/sdk/typescript/src/environment.ts +75 -0
  267. package/.next/standalone/sdk/typescript/src/evaluator/authoring.ts +480 -0
  268. package/.next/standalone/sdk/typescript/src/evaluator/cli.ts +96 -0
  269. package/.next/standalone/sdk/typescript/src/evaluator/client.ts +421 -0
  270. package/.next/standalone/sdk/typescript/src/evaluator/expression.ts +1292 -0
  271. package/.next/standalone/sdk/typescript/src/evaluator/index.ts +144 -0
  272. package/.next/standalone/sdk/typescript/src/evaluator/protocol.ts +747 -0
  273. package/.next/standalone/sdk/typescript/src/evaluator/runtime.ts +930 -0
  274. package/.next/standalone/sdk/typescript/src/evaluator/sandbox-worker.ts +171 -0
  275. package/.next/standalone/sdk/typescript/src/evaluator/source-limits.ts +27 -0
  276. package/.next/standalone/sdk/typescript/src/evaluator/source.ts +509 -0
  277. package/.next/standalone/sdk/typescript/src/events.ts +879 -0
  278. package/.next/standalone/sdk/typescript/src/exit.ts +117 -0
  279. package/.next/standalone/sdk/typescript/src/index.ts +186 -0
  280. package/.next/standalone/sdk/typescript/src/integrations/ai.ts +1566 -0
  281. package/.next/standalone/sdk/typescript/src/integrations/compat.ts +322 -0
  282. package/.next/standalone/sdk/typescript/src/integrations/core.ts +1321 -0
  283. package/.next/standalone/sdk/typescript/src/integrations/index.ts +355 -0
  284. package/.next/standalone/sdk/typescript/src/integrations/langchain.ts +2340 -0
  285. package/.next/standalone/sdk/typescript/src/integrations/llamaindex.ts +2111 -0
  286. package/.next/standalone/sdk/typescript/src/integrations/mastra.ts +1802 -0
  287. package/.next/standalone/sdk/typescript/src/logger.ts +98 -0
  288. package/.next/standalone/sdk/typescript/src/next.ts +115 -0
  289. package/.next/standalone/sdk/typescript/src/node-require.ts +446 -0
  290. package/.next/standalone/sdk/typescript/src/redact.ts +305 -0
  291. package/.next/standalone/sdk/typescript/src/resolver.ts +120 -0
  292. package/.next/standalone/sdk/typescript/src/runtime.ts +29 -0
  293. package/.next/standalone/sdk/typescript/src/schema.ts +410 -0
  294. package/.next/standalone/sdk/typescript/src/scopes.ts +701 -0
  295. package/.next/standalone/sdk/typescript/src/shared.ts +33 -0
  296. package/.next/standalone/sdk/typescript/src/version.ts +5 -0
  297. package/.next/standalone/sdk/typescript/src/writer.ts +934 -0
  298. package/.next/standalone/sdk/typescript/test/adapters.test.ts +397 -0
  299. package/.next/standalone/sdk/typescript/test/ai.test.ts +1076 -0
  300. package/.next/standalone/sdk/typescript/test/copies.test.ts +204 -0
  301. package/.next/standalone/sdk/typescript/test/edge.test.ts +183 -0
  302. package/.next/standalone/sdk/typescript/test/evaluator-client.test.ts +234 -0
  303. package/.next/standalone/sdk/typescript/test/evaluator-protocol.test.ts +225 -0
  304. package/.next/standalone/sdk/typescript/test/events.test.ts +193 -0
  305. package/.next/standalone/sdk/typescript/test/expression.test.ts +181 -0
  306. package/.next/standalone/sdk/typescript/test/global-setup.ts +26 -0
  307. package/.next/standalone/sdk/typescript/test/helpers.ts +130 -0
  308. package/.next/standalone/sdk/typescript/test/integrations.test.ts +369 -0
  309. package/.next/standalone/sdk/typescript/test/langchain-copies.test.ts +204 -0
  310. package/.next/standalone/sdk/typescript/test/langchain.test.ts +999 -0
  311. package/.next/standalone/sdk/typescript/test/llamaindex.test.ts +1760 -0
  312. package/.next/standalone/sdk/typescript/test/mastra-coverage.test.ts +501 -0
  313. package/.next/standalone/sdk/typescript/test/mastra-lifecycle.test.ts +479 -0
  314. package/.next/standalone/sdk/typescript/test/mastra.test.ts +285 -0
  315. package/.next/standalone/sdk/typescript/test/next.test.ts +109 -0
  316. package/.next/standalone/sdk/typescript/test/packaging.test.ts +312 -0
  317. package/.next/standalone/sdk/typescript/test/redaction.test.ts +171 -0
  318. package/.next/standalone/sdk/typescript/test/runtimes.test.ts +101 -0
  319. package/.next/standalone/sdk/typescript/test/sandbox.test.ts +189 -0
  320. package/.next/standalone/sdk/typescript/test/scopes.test.ts +271 -0
  321. package/.next/standalone/sdk/typescript/test/setup.ts +19 -0
  322. package/.next/standalone/sdk/typescript/test/skill-snippets.test.ts +73 -0
  323. package/.next/standalone/sdk/typescript/test/spool-contract.test.ts +124 -0
  324. package/.next/standalone/sdk/typescript/test/tracker-bounds.test.ts +191 -0
  325. package/.next/standalone/sdk/typescript/test/wire-format.test.ts +214 -0
  326. package/.next/standalone/sdk/typescript/test/writer.test.ts +407 -0
  327. package/.next/standalone/sdk/typescript/tsconfig.build.json +15 -0
  328. package/.next/standalone/sdk/typescript/tsconfig.cjs.json +19 -0
  329. package/.next/standalone/sdk/typescript/tsconfig.json +28 -0
  330. package/.next/standalone/sdk/typescript/vitest.config.ts +33 -0
  331. package/.next/standalone/sdk/typescript/vitest.integration.config.ts +23 -0
  332. package/.next/standalone/server.js +1 -1
  333. package/bin/failproofai.mjs +2 -115
  334. package/dist/cli.mjs +6538 -13686
  335. package/dist/index.js +1 -19
  336. package/dist/worker.mjs +2085 -8012
  337. package/package.json +9 -9
  338. package/pi-extension/index.ts +0 -11
  339. package/scripts/build-policy-pack.mjs +2 -39
  340. package/src/hooks/builtin-policies.ts +3 -21
  341. package/src/hooks/cloud-enrollment-cli.ts +1 -1
  342. package/src/hooks/cloud-managed-policies.ts +0 -22
  343. package/src/hooks/custom-hooks-loader.ts +7 -45
  344. package/src/hooks/custom-hooks-registry.ts +1 -45
  345. package/src/hooks/first-run-gate.ts +0 -5
  346. package/src/hooks/fp-home.ts +0 -23
  347. package/src/hooks/handler.ts +6 -265
  348. package/src/hooks/hook-activity-store.ts +1 -105
  349. package/src/hooks/hook-telemetry.ts +0 -41
  350. package/src/hooks/loader-utils.ts +0 -6
  351. package/src/hooks/pack-cli.ts +17 -278
  352. package/src/hooks/pack-manifest.ts +7 -479
  353. package/src/hooks/pack-store.ts +10 -156
  354. package/src/hooks/policy-catalog.ts +0 -65
  355. package/src/hooks/policy-evaluator.ts +796 -940
  356. package/src/hooks/policy-registry.ts +0 -25
  357. package/src/hooks/policy-types.ts +0 -126
  358. package/src/hooks/worker-server.ts +26 -119
  359. package/src/index.ts +0 -6
  360. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
  361. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
  362. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +0 -3
  363. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +0 -4
  364. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  365. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +0 -3
  366. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +0 -22
  367. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  368. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +0 -5
  370. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  371. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
  372. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +0 -3
  373. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  374. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  375. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +0 -3
  376. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +0 -4
  378. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  379. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +0 -3
  380. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +0 -3
  381. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +0 -3
  382. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +0 -3
  383. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
  384. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +0 -3
  385. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +0 -3
  386. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0w6mzq5._.js +0 -151
  387. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
  388. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
  389. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
  390. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
  391. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
  392. package/.next/standalone/.next/static/chunks/0cd-_8-c-m1ea.js +0 -6
  393. package/.next/standalone/.next/static/chunks/0uldbut9y2-e8.js +0 -1
  394. package/.next/standalone/.next/static/chunks/1u5zsejmgrir_.js +0 -1
  395. package/.next/standalone/.next/static/chunks/285spx855h_3r.css +0 -2
  396. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +0 -1
  397. package/.next/standalone/.next/static/chunks/2vkvu9-opa_1z.js +0 -1
  398. package/.next/standalone/.next/static/chunks/37lhv7wa3ywt6.js +0 -1
  399. package/.next/standalone/app/actions/get-jev-config.ts +0 -409
  400. package/.next/standalone/app/actions/update-jev-config.ts +0 -420
  401. package/.next/standalone/app/components/jev-notices.tsx +0 -96
  402. package/.next/standalone/app/settings/jev-panel.tsx +0 -469
  403. package/src/hooks/effective-reviewers.ts +0 -79
  404. package/src/hooks/jev-activity.ts +0 -385
  405. package/src/hooks/jev-cli.ts +0 -1193
  406. package/src/hooks/policy-authority.ts +0 -333
  407. package/src/hooks/policy-reviewability.ts +0 -229
  408. package/src/hooks/semantic/combine.ts +0 -541
  409. package/src/hooks/semantic/compile.ts +0 -176
  410. package/src/hooks/semantic/decide.ts +0 -392
  411. package/src/hooks/semantic/envelope.ts +0 -1296
  412. package/src/hooks/semantic/evaluator.ts +0 -547
  413. package/src/hooks/semantic/facts.ts +0 -292
  414. package/src/hooks/semantic/intent.ts +0 -1190
  415. package/src/hooks/semantic/jev-client.ts +0 -643
  416. package/src/hooks/semantic/jev-config.ts +0 -594
  417. package/src/hooks/semantic/jev-review.ts +0 -374
  418. package/src/hooks/semantic/jev-stats.ts +0 -289
  419. package/src/hooks/semantic/jev-throttle.ts +0 -421
  420. package/src/hooks/semantic/pack-policies.ts +0 -251
  421. package/src/hooks/semantic/policies.ts +0 -596
  422. package/src/hooks/semantic/precondition-names.ts +0 -60
  423. package/src/hooks/semantic/preconditions.ts +0 -58
  424. package/src/hooks/semantic/redact.ts +0 -2910
  425. package/src/hooks/semantic/types.ts +0 -145
  426. package/src/hooks/semver-precedence.ts +0 -128
  427. /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_buildManifest.js +0 -0
  428. /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_clientMiddlewareManifest.js +0 -0
  429. /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_ssgManifest.js +0 -0
@@ -0,0 +1,2111 @@
1
+ /**
2
+ * LlamaIndex.TS (`llamaindex`, `@llamaindex/core`, `@llamaindex/workflow`).
3
+ *
4
+ * The TypeScript counterpart of the Python SDK's `integrations/llama_index.py`,
5
+ * and it must draw the same tree for the same program:
6
+ *
7
+ * | LlamaIndex.TS | FailproofAI |
8
+ * |---------------------------------------|-----------------------------------------------|
9
+ * | `AgentWorkflow` run (`agent()`) | session + `agent_start`/`agent_end` |
10
+ * | nested run (inside a tool, a scope) | nested `agent_start`/`agent_end` |
11
+ * | `multiAgent()` handoff | nested agent per agent holding the turn |
12
+ * | workflow step | `hook_triggered`/`hook_completed`, `trigger_event="workflow_step"` |
13
+ * | legacy `LLMAgent` / `AgentRunner` task| `agent_start`/`agent_end` across ALL its steps |
14
+ * | `createWorkflow()` workflow (core ≥1.1)| `agent_start`/`agent_end` (`"Workflow"`), steps as hooks |
15
+ * | LLM chat | `model_request`/`model_response` on `request_id` |
16
+ * | tool call | `tool_use`/`tool_result`, the model's call id |
17
+ * | retrieval | `tool_use`/`tool_result` named after the retriever's class, output summarised |
18
+ * | top-level chat engine / query engine / retriever / `llm.chat()` / tool | its own root run, named after its class |
19
+ *
20
+ * `agent_id` is the agent's `name` (`"Agent"` when unnamed, as in Python), the
21
+ * class name for a multi-agent workflow or a legacy runner — never an id.
22
+ *
23
+ * ## Two extension points, because LlamaIndex.TS has two halves
24
+ *
25
+ * **The callback bus** (`Settings.callbackManager`, from `@llamaindex/core/global`)
26
+ * carries everything below the agent: `llm-start`/`llm-stream`/`llm-end`,
27
+ * `llm-tool-call`/`llm-tool-result`, `retrieve-*`, `query-*`, and the legacy
28
+ * runner's `agent-start`/`agent-end`. Subscribing is the whole integration for
29
+ * that half, and uninstrumenting is unsubscribing.
30
+ *
31
+ * **The workflow runtime** (`@llamaindex/workflow` ≥1.1, on
32
+ * `@llamaindex/workflow-core`) emits NOTHING on that bus: an `agent().run()`
33
+ * has no run boundary and no step events there at all. What it does have is the
34
+ * middleware surface its own first-party middleware is built on — a context's
35
+ * `__internal__call_context` (wraps every step handler invocation; this is how
36
+ * `withTraceEvents` works) and `__internal__call_send_event` (sees every event
37
+ * a step sends; this is how `withState` works). So `AgentWorkflow.prototype.runStream`
38
+ * — which `run()` also goes through — is wrapped to open the run and to attach
39
+ * those two subscriptions to the context it creates. That is a prototype patch,
40
+ * so it records agents built before `instrument()` too.
41
+ *
42
+ * Three smaller hooks fill what neither half says, each on one object and each
43
+ * undone by `uninstrument()`:
44
+ *
45
+ * * **Invocation boundaries.** A chat engine's `chat()` dispatches nothing of
46
+ * its own, so its retrieval and its model call used to become two root runs
47
+ * in two sessions. Every `@wrapEventCaller` method runs as
48
+ * `storage.run(new EventCaller(...), fn)` on ONE module-private storage in
49
+ * `@llamaindex/core/global`; an own `run` on that storage (found through the
50
+ * exported `getEventCaller()`, see `eventCallerStorage`) sees each
51
+ * invocation start and return. A top-level invocation is then one run named
52
+ * after its class that ends when it returns, and an invocation that THROWS
53
+ * is the failure signal `wrapLLMEvent` and the legacy runner lack.
54
+ * * **Retriever names.** `retrieve-start` carries only the query; the patched
55
+ * `BaseRetriever.prototype.retrieve` binds the retriever for it.
56
+ * * **Plain workflows.** workflow-core ≥1.1 runs every step handler as
57
+ * `AsyncContext.Variable#run(handlerContext, …)`, a class it exports; its
58
+ * prototype `run` sees every step of every context. See `plainStep` for why
59
+ * such a run ends when its context goes idle.
60
+ *
61
+ * ## Correlation, which LlamaIndex.TS does not give us
62
+ *
63
+ * The bus events carry an id that pairs start with end and nothing that says
64
+ * which run they belong to. Two signals do, and we use both:
65
+ *
66
+ * * our own `AsyncLocalStorage` frame, bound around every workflow step we
67
+ * wrap. The bus dispatches in a `queueMicrotask`, which Node runs in the
68
+ * dispatcher's async context, so a handler sees the step that caused it;
69
+ * * LlamaIndex's own `EventCaller` (`event.reason`), set by `@wrapEventCaller`
70
+ * — on `AgentRunner.chat`, `BaseQueryEngine.query` and every first-party
71
+ * provider's `chat`. `withEventCaller` binds a FRESH `EventCaller` per
72
+ * invocation in LlamaIndex's own `AsyncLocalStorage`, chained through
73
+ * `.parent` to the invocation it ran inside. A legacy task or a query run is
74
+ * registered under the `EventCaller` of the invocation that opened it, and
75
+ * an event belongs to it when that exact object is on the event's chain.
76
+ *
77
+ * The invocation, NOT the object that owns it. One query engine (or one
78
+ * `LLMAgent`) built at startup and serving every request is the normal
79
+ * deployment, so the owner is the same object for every concurrent call; keyed
80
+ * by owner, request B's `query-start` found A's run, treated itself as nested
81
+ * and recorded nothing of its own. The `EventCaller` is per call and already
82
+ * flows through the async context, which is why it is used rather than a
83
+ * prototype patch of `query`/`chat`: `@wrapEventCaller` binds the method onto
84
+ * each INSTANCE at construction (`this.query = (...) => withEventCaller(...)`),
85
+ * so a prototype patch would miss every engine built before `instrument()`.
86
+ *
87
+ * Only a bus that carries no `EventCaller` (a build without one; the unit
88
+ * tests' stand-ins) falls back to matching the owner objects in
89
+ * `computedCallers`, and there a run owned by the object STARTING a new run is
90
+ * never taken as its parent: without the chain a concurrent sibling on a shared
91
+ * object is indistinguishable from re-entry, and the sibling is the common case.
92
+ *
93
+ * When both our frame and a caller match, the deeper run wins. When neither
94
+ * does, the call is a root run of its own — the LangChain/Python precedent for a
95
+ * bare model call — and nests under an enclosing `failproofai.session()`/
96
+ * `agent()` scope if there is one.
97
+ *
98
+ * ## Known gaps, each one the framework's and each one documented, not faked
99
+ *
100
+ * * A model call has no failure signal of its own: `wrapLLMEvent` has no error
101
+ * path (no `llm-end` when `chat()` throws). A provider whose `chat` is
102
+ * `@wrapEventCaller` (every first-party one) fails with its invocation, and
103
+ * inside a workflow the failed step closes it. A STREAM that fails while it
104
+ * is being read, outside a workflow, has returned already: that leaf stays
105
+ * open until the reaper (`staleAfter`) or `uninstrument()`.
106
+ * * A provider error mid-stream inside `agent().run()` escapes LlamaIndex's
107
+ * workflow runtime as an UNHANDLED rejection and `run()` never settles —
108
+ * with or without this SDK. The failed step still closes the run, failed.
109
+ * * `callTool` dispatches no `llm-tool-result` when a tool throws. In a
110
+ * workflow the runtime's own tool-result event closes it with the error; in
111
+ * a legacy agent the next model call does, from the tool-result message.
112
+ * * No embedding events exist on the TS bus, so `embeddings: true` has
113
+ * nothing to record.
114
+ * * Streamed calls carry token usage only when the provider sends it:
115
+ * `@llamaindex/openai` requests it only with
116
+ * `additionalChatOptions: { stream_options: { include_usage: true } }`.
117
+ * * A plain `createWorkflow()` workflow is a run only on workflow-core ≥1.1
118
+ * resolvable from the application (not the floor's `@llama-flow/core`, not
119
+ * an unhoisted pnpm layout): elsewhere its model calls are loose root runs.
120
+ * Its run ends when the context goes idle, so a workflow that waits for an
121
+ * event from outside records each burst as its own run, and it is named
122
+ * `"Workflow"` — the runtime has no name to give it (wrap it in
123
+ * `failproofai.agent("name", …)` to name the parent).
124
+ * * No human-in-the-loop pairs: the TS runtime has no waiting-for-event signal.
125
+ */
126
+
127
+ import { AsyncLocalStorage } from "node:async_hooks";
128
+ import { randomUUID } from "node:crypto";
129
+ import { performance } from "node:perf_hooks";
130
+
131
+ import { logger } from "../logger.js";
132
+ import * as compat from "./compat.js";
133
+ import * as core from "./core.js";
134
+ import type { Adapter } from "./core.js";
135
+
136
+ const NAME = "llamaindex";
137
+ const PACKAGE = "llamaindex";
138
+ const CORE_PACKAGE = "@llamaindex/core";
139
+ const WORKFLOW_PACKAGE = "@llamaindex/workflow";
140
+ const ASYNC_CONTEXT_MODULE = "@llamaindex/workflow-core/async-context";
141
+ const INSTALL = "npm install llamaindex";
142
+
143
+ /**
144
+ * 0.11.4 is a CAPABILITY floor: it is the first `llamaindex` whose agent API
145
+ * (`agent()` / `multiAgent()`) runs on the `@llamaindex/workflow` 1.1 runtime
146
+ * this adapter wraps. 0.9–0.11.3 ship workflow 1.0, a different class-based
147
+ * runtime with none of the surfaces above — their workflow agents would record
148
+ * no run and no steps, only loose model and tool calls.
149
+ */
150
+ export const MIN_VERSION = "0.11.4";
151
+ export const BELOW_VERSION = "1.0.0";
152
+ const WORKFLOW_MIN = "1.1.0";
153
+ const WORKFLOW_BELOW = "2.0.0";
154
+
155
+ const MAX_NODES_IN_SUMMARY = 5;
156
+ /**
157
+ * Open runs and leaves are bounded, oldest evicted first: orphans are normal (a
158
+ * stream nobody consumed, a legacy task whose step threw and so never sent
159
+ * `agent-end`) and a long-lived server must not keep every one of them.
160
+ */
161
+ const MAX_OPEN = 10_000;
162
+
163
+ // Token key aliases, widest first. LlamaIndex normalises nothing, so this is
164
+ // the union of what the provider packages actually put in `raw` — the Python
165
+ // adapter's list, plus the camelCase spellings TS providers use.
166
+ const INPUT_TOKEN_KEYS = [
167
+ "prompt_tokens",
168
+ "input_tokens",
169
+ "inputTokens",
170
+ "promptTokens",
171
+ "prompt_token_count",
172
+ "promptTokenCount",
173
+ ] as const;
174
+ const OUTPUT_TOKEN_KEYS = [
175
+ "completion_tokens",
176
+ "output_tokens",
177
+ "outputTokens",
178
+ "completionTokens",
179
+ "candidates_token_count",
180
+ "candidatesTokenCount",
181
+ ] as const;
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // Pure helpers — no framework import in any of these
185
+ // ---------------------------------------------------------------------------
186
+
187
+ type Json = Record<string, unknown>;
188
+
189
+ function isObject(value: unknown): value is Json {
190
+ return typeof value === "object" && value !== null;
191
+ }
192
+
193
+ /** A property read that cannot throw (getters on framework classes can). */
194
+ function read(target: unknown, key: string): unknown {
195
+ if (!isObject(target) && typeof target !== "function") return undefined;
196
+ try {
197
+ return (target as Json)[key];
198
+ } catch {
199
+ return undefined;
200
+ }
201
+ }
202
+
203
+ function nonEmpty(value: unknown): string | undefined {
204
+ return typeof value === "string" && value !== "" ? value : undefined;
205
+ }
206
+
207
+ /**
208
+ * A correlation id out of a payload field whose type the framework does not
209
+ * promise. An object would render as `[object Object]` and correlate with every
210
+ * other one; a fresh id leaves the pair merely unpaired instead.
211
+ */
212
+ function asId(...candidates: unknown[]): string {
213
+ for (const candidate of candidates) {
214
+ if (typeof candidate === "string" && candidate !== "") return candidate;
215
+ if (typeof candidate === "number" && Number.isFinite(candidate)) return String(candidate);
216
+ }
217
+ return randomUUID();
218
+ }
219
+
220
+ function firstInt(source: Json, keys: readonly string[]): number | undefined {
221
+ for (const key of keys) {
222
+ const value = source[key];
223
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
224
+ }
225
+ return undefined;
226
+ }
227
+
228
+ export interface Usage {
229
+ usage?: Json;
230
+ inputTokens?: number;
231
+ outputTokens?: number;
232
+ }
233
+
234
+ /**
235
+ * Token usage from a `ChatResponse`, streaming or not.
236
+ *
237
+ * Conservative on purpose, like the Python adapter: the token numbers are set
238
+ * ONLY when a key we recognise is present, while the raw usage object always
239
+ * ships as `usage` so a provider that names its counters something new still
240
+ * reports something the server can fall back to.
241
+ *
242
+ * A streamed response is the case that used to lose everything: `wrapLLMEvent`
243
+ * hands `llm-end` a `raw` that is the ARRAY of chunks, and providers put the
244
+ * usage on one chunk — OpenAI on the last, content-less one — so reading
245
+ * `raw.usage` found nothing on every streamed call, which is every
246
+ * `FunctionAgent` call.
247
+ */
248
+ export function usageOf(response: unknown): Usage {
249
+ const raw = read(response, "raw");
250
+ const candidates: unknown[] = [];
251
+ const fromChunk = (chunk: unknown): void => {
252
+ const chunkRaw = read(chunk, "raw");
253
+ candidates.push(
254
+ read(chunkRaw, "usage"),
255
+ read(chunkRaw, "usage_metadata"),
256
+ read(chunkRaw, "usageMetadata"),
257
+ read(read(chunk, "options"), "usage"),
258
+ );
259
+ };
260
+ if (Array.isArray(raw)) {
261
+ // Newest chunk first: providers that report running totals end with the final one.
262
+ for (let i = raw.length - 1; i >= 0; i -= 1) fromChunk(raw[i]);
263
+ } else {
264
+ candidates.push(read(raw, "usage"), read(raw, "usage_metadata"), read(raw, "usageMetadata"));
265
+ }
266
+ candidates.push(read(read(read(response, "message"), "options"), "usage"), read(response, "usage"));
267
+ for (const candidate of candidates) {
268
+ if (!isObject(candidate) || Array.isArray(candidate) || Object.keys(candidate).length === 0) continue;
269
+ return {
270
+ usage: candidate,
271
+ inputTokens: firstInt(candidate, INPUT_TOKEN_KEYS),
272
+ outputTokens: firstInt(candidate, OUTPUT_TOKEN_KEYS),
273
+ };
274
+ }
275
+ return {};
276
+ }
277
+
278
+ function stopReasonOf(response: unknown): string | undefined {
279
+ const raw = read(response, "raw");
280
+ const sources = Array.isArray(raw) ? [...raw].reverse().map((chunk) => read(chunk, "raw")) : [raw];
281
+ for (const source of sources) {
282
+ const choice = (read(source, "choices") as unknown[] | undefined)?.[0];
283
+ const value =
284
+ nonEmpty(read(choice, "finish_reason")) ??
285
+ nonEmpty(read(source, "stop_reason")) ??
286
+ nonEmpty(read(source, "finishReason"));
287
+ if (value) return value;
288
+ }
289
+ return undefined;
290
+ }
291
+
292
+ /** A retrieval result small enough to store: count, scores, a prefix of the top few. */
293
+ export function summarizeNodes(nodes: unknown): { num_nodes: number; top: Json[] } {
294
+ const items = Array.isArray(nodes) ? nodes : [];
295
+ const top = items.slice(0, MAX_NODES_IN_SUMMARY).map((item) => {
296
+ const node = read(item, "node") ?? item;
297
+ let text: unknown;
298
+ const getContent = read(node, "getContent");
299
+ if (typeof getContent === "function") {
300
+ try {
301
+ text = (getContent as () => unknown).call(node);
302
+ } catch {
303
+ text = undefined;
304
+ }
305
+ }
306
+ text ??= read(node, "text");
307
+ const score = read(item, "score");
308
+ return {
309
+ id: nonEmpty(read(node, "id_")) ?? nonEmpty(read(node, "id")),
310
+ score: typeof score === "number" ? score : undefined,
311
+ text: core.truncate(typeof text === "string" ? text : "", 200),
312
+ };
313
+ });
314
+ return { num_nodes: items.length, top };
315
+ }
316
+
317
+ function messagesOf(messages: unknown): Json[] | undefined {
318
+ if (!Array.isArray(messages)) return undefined;
319
+ return messages.map((message) => ({
320
+ role: nonEmpty(read(message, "role")) ?? "user",
321
+ content: read(message, "content"),
322
+ }));
323
+ }
324
+
325
+ /** The text of a query or a `QueryBundle`. */
326
+ function queryText(query: unknown): unknown {
327
+ if (typeof query === "string") return query;
328
+ return read(query, "query") ?? read(query, "queryStr") ?? query;
329
+ }
330
+
331
+ /** The text of whatever a run returned — `EngineResponse`, a message, a string. */
332
+ function textOf(value: unknown): string | undefined {
333
+ if (typeof value === "string") return value;
334
+ const content = read(read(value, "message"), "content") ?? read(value, "response") ?? read(value, "content");
335
+ if (typeof content === "string") return content;
336
+ if (Array.isArray(content)) {
337
+ const parts = content.map((part) => read(part, "text")).filter((part) => typeof part === "string");
338
+ if (parts.length > 0) return parts.join("");
339
+ }
340
+ return undefined;
341
+ }
342
+
343
+ function errorText(error: unknown): string {
344
+ if (error instanceof Error) return `${error.name || "Error"}: ${error.message}`;
345
+ return String(error);
346
+ }
347
+
348
+ function className(value: unknown): string | undefined {
349
+ const name = read(read(value, "constructor"), "name");
350
+ return typeof name === "string" && name !== "" && name !== "Object" && name !== "Function"
351
+ ? name
352
+ : undefined;
353
+ }
354
+
355
+ /** `event.reason.computedCallers` — the objects inside whose `@wrapEventCaller` calls this ran. */
356
+ function callersOf(event: unknown): unknown[] {
357
+ const callers = read(read(event, "reason"), "computedCallers");
358
+ return Array.isArray(callers) ? callers : [];
359
+ }
360
+
361
+ /**
362
+ * The `EventCaller` chain of an event's `reason`, innermost first — or `null`
363
+ * when the reason is not an `EventCaller` (no `caller` field), in which case
364
+ * only the owner objects in `computedCallers` are known.
365
+ */
366
+ function invocationChain(reason: unknown): object[] | null {
367
+ if (!isObject(reason)) return null;
368
+ try {
369
+ if (!("caller" in reason)) return null;
370
+ } catch {
371
+ return null;
372
+ }
373
+ const chain: object[] = [];
374
+ const seen = new Set<unknown>();
375
+ let node: unknown = reason;
376
+ while (isObject(node) && !seen.has(node)) {
377
+ seen.add(node);
378
+ chain.push(node);
379
+ node = read(node, "parent");
380
+ }
381
+ return chain;
382
+ }
383
+
384
+ /** Where an event came from: LlamaIndex's invocation chain, and the owner objects on it. */
385
+ interface Origin {
386
+ chain: object[] | null;
387
+ callers: unknown[];
388
+ }
389
+
390
+ function originOf(event: unknown): Origin {
391
+ return { chain: invocationChain(read(event, "reason")), callers: callersOf(event) };
392
+ }
393
+
394
+ /**
395
+ * The model name for a bus event.
396
+ *
397
+ * `llm-start` carries only `{id, messages}`: `wrapLLMEvent` never passes the
398
+ * model. Every first-party provider decorates `chat` with `@wrapEventCaller`
399
+ * too, so the LLM instance itself is the nearest caller, and its
400
+ * `metadata.model` is the name. A legacy runner above it has the LLM as `.llm`.
401
+ */
402
+ function modelFromCallers(event: unknown): string | undefined {
403
+ for (const caller of callersOf(event)) {
404
+ const model = nonEmpty(read(read(caller, "metadata"), "model"));
405
+ if (model) return model;
406
+ const viaLlm = nonEmpty(read(read(read(caller, "llm"), "metadata"), "model"));
407
+ if (viaLlm) return viaLlm;
408
+ }
409
+ return undefined;
410
+ }
411
+
412
+ function detail(event: unknown): Json {
413
+ // A `CustomEvent`, so the payload is on `.detail` — with a fallback to the
414
+ // event itself, for a build that dispatched the payload directly.
415
+ const payload = read(event, "detail") ?? event;
416
+ return isObject(payload) ? payload : {};
417
+ }
418
+
419
+ function numberOption(value: unknown, fallback: number): number {
420
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
421
+ }
422
+
423
+ // ---------------------------------------------------------------------------
424
+ // State
425
+ // ---------------------------------------------------------------------------
426
+
427
+ /** One agent span we opened: a workflow run, a sub-agent, a legacy task, a bare call. */
428
+ interface Run {
429
+ key: string;
430
+ agentId: string;
431
+ depth: number;
432
+ /** The run this one is nested in, when it is a multi-agent sub-agent. */
433
+ root: Run | null;
434
+ leaves: Set<string>;
435
+ usedToolIds: Map<string, number>;
436
+ /** Sub-agent currently holding the turn (multi-agent workflows only). */
437
+ sub: Run | null;
438
+ subSeq: number;
439
+ /** The object whose `@wrapEventCaller` calls belong to this run, if any. */
440
+ owner: object | null;
441
+ /** LlamaIndex's `EventCaller` for the ONE invocation that opened this run, if any. */
442
+ caller: object | null;
443
+ /** Workflow step keys linked to this run and not yet ended. */
444
+ steps: Set<string>;
445
+ /** Legacy runs have no end signal until their last step; bare runs end with their leaf. */
446
+ bare: boolean;
447
+ /**
448
+ * Ends when the LlamaIndex invocation in `caller` returns — a chat engine, a
449
+ * bare model call — because nothing on the bus marks its end. (Query and
450
+ * legacy runs have their own end event and take only a FAILURE from there.)
451
+ */
452
+ byInvocation: boolean;
453
+ /** Plain-workflow runs: step handlers still running, and the last step's output text. */
454
+ inFlight: number;
455
+ output?: string;
456
+ /** A run whose end arrived while a streamed leaf was still open. */
457
+ ending: { outcome: string; summary?: string } | null;
458
+ lastContent?: string;
459
+ model?: string;
460
+ ended: boolean;
461
+ /** `performance.now()` of the last event that touched this run; what the reaper reads. */
462
+ lastActivity: number;
463
+ /** Drops this run from whichever index found it (legacy tasks, queries). */
464
+ forget?: () => void;
465
+ }
466
+
467
+ interface Leaf {
468
+ key: string;
469
+ kind: "model" | "tool" | "retrieval";
470
+ run: Run;
471
+ parentKey: string;
472
+ name: string;
473
+ callId: string;
474
+ rawId?: string;
475
+ /** Who opened a tool leaf: the callback bus, or the workflow runtime's own event. */
476
+ source?: "bus" | "workflow";
477
+ started: number;
478
+ model?: string;
479
+ firstChunk?: number;
480
+ }
481
+
482
+ /** What the adapter's `AsyncLocalStorage` carries through a workflow step. */
483
+ interface Frame {
484
+ key: string;
485
+ run: Run;
486
+ model?: string;
487
+ }
488
+
489
+ interface Located {
490
+ parentKey: string;
491
+ run: Run;
492
+ }
493
+
494
+ export interface LlamaIndexOptions {
495
+ captureMessages: boolean;
496
+ steps: boolean;
497
+ embeddings: boolean;
498
+ staleAfter: number;
499
+ reaperInterval: number;
500
+ captureLimit?: number;
501
+ }
502
+
503
+ export function parseOptions(options: Record<string, unknown>): LlamaIndexOptions {
504
+ return {
505
+ captureMessages: options.captureMessages !== false,
506
+ steps: options.steps !== false,
507
+ embeddings: options.embeddings === true,
508
+ staleAfter: numberOption(options.staleAfter, 600),
509
+ reaperInterval: numberOption(options.reaperInterval, 30),
510
+ captureLimit: typeof options.captureLimit === "number" ? options.captureLimit : undefined,
511
+ };
512
+ }
513
+
514
+ interface Bus {
515
+ on: (event: string, handler: (event: unknown) => void) => unknown;
516
+ off?: (event: string, handler: (event: unknown) => void) => unknown;
517
+ }
518
+
519
+ /** One loaded copy of `@llamaindex/core/global` (or of the umbrella re-exporting it). */
520
+ export interface GlobalModule {
521
+ Settings?: { callbackManager?: unknown };
522
+ getEventCaller?: () => unknown;
523
+ }
524
+
525
+ /** One loaded copy of `@llamaindex/core/retriever`. */
526
+ export interface RetrieverModule {
527
+ BaseRetriever?: { prototype: object };
528
+ }
529
+
530
+ /** One loaded copy of `@llamaindex/workflow-core/async-context` (workflow-core ≥1.1). */
531
+ export interface AsyncContextModule {
532
+ AsyncContext?: { Variable?: { prototype: object } };
533
+ }
534
+
535
+ /** One loaded copy of `@llamaindex/workflow`. */
536
+ export interface WorkflowModule {
537
+ AgentWorkflow?: { prototype: object; name?: string };
538
+ stopAgentEvent?: { include: (event: unknown) => boolean };
539
+ agentToolCallEvent?: { include: (event: unknown) => boolean };
540
+ agentToolCallResultEvent?: { include: (event: unknown) => boolean };
541
+ }
542
+
543
+ interface Subscribable {
544
+ subscribe: (callback: (...args: never[]) => unknown) => unknown;
545
+ }
546
+
547
+ interface HandlerContext {
548
+ handler: (...args: unknown[]) => unknown;
549
+ [key: string]: unknown;
550
+ }
551
+
552
+ class State {
553
+ readonly options: LlamaIndexOptions;
554
+ readonly tracker: core.RunTracker;
555
+ readonly frames = new AsyncLocalStorage<Frame>();
556
+ private readonly runs = new Map<string, Run>();
557
+ private readonly leaves = new Map<string, Leaf>();
558
+ private readonly owners = new WeakMap<object, Run[]>();
559
+ /** Runs by the `EventCaller` of the invocation that opened them. */
560
+ private readonly invocations = new WeakMap<object, Run>();
561
+ /** Legacy task: first step id -> run. */
562
+ private readonly tasks = new Map<string, Run>();
563
+ /** Root query-engine runs, by `query-start` id. */
564
+ private readonly queries = new Map<string, Run>();
565
+ /** The retriever whose `retrieve()` is running, for the retrieval's name. */
566
+ readonly retrievers = new AsyncLocalStorage<object>();
567
+ /** `EventCaller`s whose invocation we watch start and end (see `invocation`). */
568
+ private readonly observed = new WeakSet<object>();
569
+ /** `EventCaller`s whose invocation has already returned or thrown. */
570
+ private readonly returned = new WeakSet<object>();
571
+ /** Model leaves opened directly inside an invocation (not in a workflow step), by its `EventCaller`. */
572
+ private readonly invocationLeaves = new WeakMap<object, Set<string>>();
573
+ /** Errors a leaf already carries, so the run they end does not report them again. */
574
+ private readonly carried = new WeakSet<object>();
575
+ /** Plain-workflow runs, by the workflow-core root handler context of their context. */
576
+ private readonly plainRuns = new WeakMap<object, Run>();
577
+ /** Step handlers of `AgentWorkflow`s, which are recorded as agent runs instead. */
578
+ private readonly agentHandlers = new WeakSet<object>();
579
+ private readonly globals: GlobalModule[];
580
+ private reaper: ReturnType<typeof setInterval> | null = null;
581
+ private seq = 0;
582
+ active = true;
583
+
584
+ constructor(options: LlamaIndexOptions, globals: GlobalModule[], frameworkPackage: string) {
585
+ this.options = options;
586
+ this.globals = globals;
587
+ this.tracker = new core.RunTracker(NAME, {
588
+ baseFields: core.frameworkFields(NAME, frameworkPackage),
589
+ fieldLimit: options.captureLimit,
590
+ });
591
+ }
592
+
593
+ /** The one gate every payload goes through — `captureMessages: false` drops them all. */
594
+ capture<T>(value: T): T | undefined {
595
+ return this.options.captureMessages ? value : undefined;
596
+ }
597
+
598
+ private nextKey(prefix: string): string {
599
+ this.seq += 1;
600
+ return `${prefix}#${this.seq}`;
601
+ }
602
+
603
+ // -- where does this event belong -----------------------------------------
604
+
605
+ private live(run: Run | undefined | null): Run | null {
606
+ return run && !run.ended ? run : null;
607
+ }
608
+
609
+ /** The innermost run opened by an invocation on this chain — exact, per call. */
610
+ private invocationRun(chain: object[]): Run | null {
611
+ for (const node of chain) {
612
+ const run = this.live(this.invocations.get(node));
613
+ if (run) return run;
614
+ }
615
+ return null;
616
+ }
617
+
618
+ /**
619
+ * Fallback for a bus without `EventCaller`s: the newest live run of an owner
620
+ * object on the caller list. `starting` is the owner of a run being opened
621
+ * now; its own runs are skipped, because without the chain they are far more
622
+ * likely concurrent siblings on a shared object than this call's parent.
623
+ */
624
+ private ownerRun(callers: unknown[], starting?: unknown): Run | null {
625
+ for (const caller of callers) {
626
+ if (!isObject(caller) || caller === starting) continue;
627
+ const stack = this.owners.get(caller);
628
+ const run = this.live(stack?.[stack.length - 1]);
629
+ if (run) return run;
630
+ }
631
+ return null;
632
+ }
633
+
634
+ /**
635
+ * The run an event belongs to: our step frame or LlamaIndex's invocation
636
+ * chain, whichever is deeper. `origin` defaults to the chain bound right now,
637
+ * for callers (a workflow starting) that have no event to read it from.
638
+ * `starting` is the owner of a run about to be opened (see `ownerRun`).
639
+ */
640
+ locate(origin?: Origin, starting?: unknown): Located | null {
641
+ const from = origin ?? this.boundOrigin();
642
+ const frame = this.frames.getStore();
643
+ const fromFrame = frame && this.live(frame.run) ? { parentKey: frame.key, run: frame.run } : null;
644
+ // With an `EventCaller` chain, ONLY the chain decides: a run whose owner is
645
+ // on the caller list but whose invocation is not on the chain belongs to a
646
+ // different call — a concurrent request on a shared engine — never this one.
647
+ const owner = from.chain ? this.invocationRun(from.chain) : this.ownerRun(from.callers, starting);
648
+ const fromOwner = owner ? { parentKey: (owner.sub ?? owner).key, run: owner.sub ?? owner } : null;
649
+ if (fromFrame && fromOwner) return fromOwner.run.depth > fromFrame.run.depth ? fromOwner : fromFrame;
650
+ return fromFrame ?? fromOwner;
651
+ }
652
+
653
+ private boundOrigin(): Origin {
654
+ for (const module of this.globals) {
655
+ try {
656
+ const caller = module.getEventCaller?.();
657
+ if (!isObject(caller)) continue;
658
+ const callers = read(caller, "computedCallers");
659
+ return { chain: invocationChain(caller), callers: Array.isArray(callers) ? callers : [] };
660
+ } catch {
661
+ // An older build without `getEventCaller`; the frame is still consulted.
662
+ }
663
+ }
664
+ return { chain: null, callers: [] };
665
+ }
666
+
667
+ // -- runs ---------------------------------------------------------------
668
+
669
+ openRun(
670
+ prefix: string,
671
+ agentId: string,
672
+ options: {
673
+ parent?: Located | null;
674
+ owner?: object | null;
675
+ caller?: object | null;
676
+ bare?: boolean;
677
+ byInvocation?: boolean;
678
+ goal?: unknown;
679
+ root?: Run | null;
680
+ fields?: Json;
681
+ },
682
+ ): Run {
683
+ while (this.runs.size >= MAX_OPEN) {
684
+ const oldest = this.runs.values().next();
685
+ if (oldest.done) break;
686
+ this.finishRun(oldest.value, "cancelled", undefined, "evicted");
687
+ }
688
+ const key = this.nextKey(prefix);
689
+ const identity = this.tracker.startAgent(key, {
690
+ agentId,
691
+ parentKey: options.parent?.parentKey,
692
+ goal: this.options.captureMessages && typeof options.goal === "string" ? options.goal : undefined,
693
+ ...core.fwFields({ run_id: key, ...(options.fields ?? {}) }),
694
+ });
695
+ const run: Run = {
696
+ key,
697
+ agentId: identity.agentId ?? agentId,
698
+ depth: identity.depth,
699
+ root: options.root ?? null,
700
+ leaves: new Set(),
701
+ usedToolIds: new Map(),
702
+ sub: null,
703
+ subSeq: 0,
704
+ owner: options.owner ?? null,
705
+ caller: options.caller ?? null,
706
+ steps: new Set(),
707
+ bare: options.bare ?? false,
708
+ byInvocation: options.byInvocation ?? false,
709
+ inFlight: 0,
710
+ ending: null,
711
+ ended: false,
712
+ lastActivity: performance.now(),
713
+ };
714
+ this.runs.set(key, run);
715
+ if (run.owner) {
716
+ const stack = this.owners.get(run.owner) ?? [];
717
+ stack.push(run);
718
+ this.owners.set(run.owner, stack);
719
+ }
720
+ if (run.caller) this.invocations.set(run.caller, run);
721
+ return run;
722
+ }
723
+
724
+ /**
725
+ * End a run. A success whose streamed model call is still being consumed is
726
+ * DEFERRED until that leaf closes, so the tokens are not lost; anything else
727
+ * force-closes what is open first, because an `agent_end` with an open leaf
728
+ * under it leaves the session `ongoing` forever.
729
+ */
730
+ finishRun(run: Run, outcome: string, summary?: string, reason = "run_ended"): void {
731
+ if (run.ended) return;
732
+ if (run.sub) this.finishRun(run.sub, outcome, undefined, reason);
733
+ if (outcome === "success" && run.leaves.size > 0 && reason === "run_ended") {
734
+ run.ending = { outcome, summary };
735
+ return;
736
+ }
737
+ // Ended BEFORE its leaves are force-closed: closing the last leaf of a bare
738
+ // run would otherwise settle it as a success in the middle of this.
739
+ run.ended = true;
740
+ for (const key of [...run.leaves]) {
741
+ const leaf = this.leaves.get(key);
742
+ if (leaf) this.closeLeaf(leaf, { closedBy: reason });
743
+ }
744
+ this.runs.delete(run.key);
745
+ run.forget?.();
746
+ if (run.owner) {
747
+ const stack = this.owners.get(run.owner);
748
+ const index = stack?.lastIndexOf(run) ?? -1;
749
+ if (stack && index !== -1) stack.splice(index, 1);
750
+ if (stack?.length === 0) this.owners.delete(run.owner);
751
+ }
752
+ if (run.caller && this.invocations.get(run.caller) === run) this.invocations.delete(run.caller);
753
+ if (run.root && run.root.sub === run) run.root.sub = null;
754
+ const text = summary ?? (outcome === "success" ? run.lastContent : undefined);
755
+ this.tracker.endAgent(run.key, {
756
+ outcome,
757
+ summary: this.options.captureMessages || outcome !== "success" ? text : undefined,
758
+ ...core.fwFields({ run_id: run.key }),
759
+ });
760
+ // Every tracker link this run made goes with it — its own and any step
761
+ // still in flight. A leaked link is worse than memory: at the tracker's
762
+ // FIFO cap, the next eviction takes a LIVE run's link and its events drop.
763
+ for (const step of run.steps) this.tracker.unlink(step);
764
+ run.steps.clear();
765
+ this.tracker.unlink(run.key);
766
+ }
767
+
768
+ /**
769
+ * A deferred or bare run whose last leaf just closed. A bare run IS its one
770
+ * call, so it ends the way that call did: a failure, a reaped orphan
771
+ * (`cancelled` — we never learned how it ended), or a success.
772
+ */
773
+ private settle(run: Run, outcome: string): void {
774
+ if (run.ended || run.leaves.size > 0) return;
775
+ if (run.ending) this.finishRun(run, run.ending.outcome, run.ending.summary);
776
+ else if (run.bare) this.finishRun(run, outcome, undefined, outcome === "success" ? "run_ended" : "leaf");
777
+ }
778
+
779
+ // -- leaves -------------------------------------------------------------
780
+
781
+ private openLeaf(leaf: Leaf): void {
782
+ while (this.leaves.size >= MAX_OPEN) {
783
+ const oldest = this.leaves.values().next();
784
+ if (oldest.done) break;
785
+ this.closeLeaf(oldest.value, { closedBy: "evicted" });
786
+ }
787
+ this.leaves.set(leaf.key, leaf);
788
+ leaf.run.leaves.add(leaf.key);
789
+ leaf.run.lastActivity = performance.now();
790
+ }
791
+
792
+ closeLeaf(leaf: Leaf, result: { output?: unknown; error?: string; response?: unknown; closedBy?: string }): void {
793
+ if (!this.leaves.delete(leaf.key)) return;
794
+ leaf.run.leaves.delete(leaf.key);
795
+ leaf.run.lastActivity = performance.now();
796
+ const extras = core.fwFields({ run_id: leaf.run.key, closed_by: result.closedBy });
797
+ if (leaf.kind === "model") {
798
+ const response = result.response;
799
+ const usage = usageOf(response);
800
+ const content = read(read(response, "message"), "content");
801
+ if (typeof content === "string" && content !== "") leaf.run.lastContent = content;
802
+ const now = performance.now();
803
+ this.tracker.emit("modelResponse", leaf.key, {
804
+ parentKey: leaf.parentKey,
805
+ model: leaf.model,
806
+ requestId: leaf.callId,
807
+ role: response === undefined ? undefined : "assistant",
808
+ content: this.capture(content),
809
+ stopReason: stopReasonOf(response),
810
+ inputTokens: usage.inputTokens,
811
+ outputTokens: usage.outputTokens,
812
+ usage: usage.usage,
813
+ error: result.error,
814
+ // Always an int: `modelResponse` does not measure its own duration.
815
+ duration_ms: core.ms(now - leaf.started),
816
+ ...extras,
817
+ ...core.fwFields({
818
+ ttft_ms: leaf.firstChunk === undefined ? undefined : core.ms(leaf.firstChunk - leaf.started),
819
+ chunks: Array.isArray(read(response, "raw")) ? (read(response, "raw") as unknown[]).length : undefined,
820
+ }),
821
+ });
822
+ } else {
823
+ this.tracker.emit("toolResult", leaf.key, {
824
+ parentKey: leaf.parentKey,
825
+ toolName: leaf.name,
826
+ toolCallId: leaf.callId,
827
+ output: result.error === undefined ? this.capture(result.output) : undefined,
828
+ error: result.error,
829
+ ...extras,
830
+ });
831
+ }
832
+ this.tracker.unlink(leaf.key);
833
+ this.settle(leaf.run, result.error !== undefined ? "failed" : result.closedBy ? "cancelled" : "success");
834
+ }
835
+
836
+ // -- the bus --------------------------------------------------------------
837
+
838
+ /** True when the outermost invocation on the chain is one we can see end. */
839
+ private observedOuter(origin: Origin): boolean {
840
+ const outer = origin.chain?.[origin.chain.length - 1];
841
+ return outer !== undefined && this.observed.has(outer) && !this.returned.has(outer);
842
+ }
843
+
844
+ /** Where a bus event goes; opens a root run when it belongs to nothing. */
845
+ private placeOrOpen(event: unknown, prefix: string, agentId: () => string, origin = originOf(event)): Located {
846
+ const located = this.locate(origin);
847
+ if (located) return located;
848
+ const run = this.openRoot(origin, prefix, agentId);
849
+ return { parentKey: run.key, run };
850
+ }
851
+
852
+ /**
853
+ * The root run for an event nothing encloses: Python's rule, "any other
854
+ * top-level instrumented call opens the session and becomes its root agent,
855
+ * named after its class".
856
+ *
857
+ * The top-level call is the OUTERMOST invocation on the event's `EventCaller`
858
+ * chain — a `ContextChatEngine.chat`, not the retriever or the model call it
859
+ * made — so everything one chat does lands in one run instead of one session
860
+ * per retrieval and per model call. The run ends when that invocation
861
+ * returns, which only `invocation()` sees; an invocation it did not see start
862
+ * (no hook on this build, or begun before `instrument()`) falls back to a
863
+ * bare run that ends with its one leaf, as every root run used to.
864
+ */
865
+ private openRoot(origin: Origin, prefix: string, agentId: () => string): Run {
866
+ const outer = origin.chain?.[origin.chain.length - 1];
867
+ if (outer && this.observedOuter(origin)) {
868
+ return this.openRun(prefix, className(read(outer, "caller")) ?? agentId(), {
869
+ parent: null,
870
+ caller: outer,
871
+ byInvocation: true,
872
+ });
873
+ }
874
+ return this.openRun(prefix, agentId(), { parent: null, bare: true });
875
+ }
876
+
877
+ /**
878
+ * Run one LlamaIndex invocation — the callback `withEventCaller` binds an
879
+ * `EventCaller` around — and observe how it ends. Called from the hook on
880
+ * LlamaIndex's own event-caller storage (`hookInvocations`).
881
+ *
882
+ * A native promise is REPLACED by one that settles identically, never merely
883
+ * observed: attaching a rejection handler to the caller's own promise would
884
+ * mark it handled, and a rejection the application never handled would stop
885
+ * being reported. Anything else is returned untouched and only a synchronous
886
+ * throw is seen.
887
+ */
888
+ invocation(caller: object, fn: () => unknown): unknown {
889
+ this.observed.add(caller);
890
+ let result: unknown;
891
+ try {
892
+ result = fn();
893
+ } catch (error) {
894
+ this.returnedFrom(caller, undefined, error, true);
895
+ throw error;
896
+ }
897
+ if (result instanceof Promise && result.constructor === Promise) {
898
+ return result.then(
899
+ (value: unknown) => {
900
+ this.returnedFrom(caller, value);
901
+ return value;
902
+ },
903
+ (error: unknown) => {
904
+ this.returnedFrom(caller, undefined, error, true);
905
+ throw error;
906
+ },
907
+ );
908
+ }
909
+ if (!(isObject(result) && typeof (result as { then?: unknown }).then === "function")) {
910
+ this.returnedFrom(caller, result);
911
+ }
912
+ return result;
913
+ }
914
+
915
+ private returnedFrom(caller: object, value: unknown, error?: unknown, failed = false): void {
916
+ core.callSafely(
917
+ () => {
918
+ if (this.active) this.invocationEnded(caller, value, error, failed);
919
+ },
920
+ [],
921
+ `${NAME}.invocationEnded`,
922
+ );
923
+ }
924
+
925
+ /**
926
+ * An invocation returned or threw. A failure is the ONLY failure signal a
927
+ * model call, a query or a legacy task has — `wrapLLMEvent` has no error path
928
+ * and a failed step dispatches no `agent-end` — so it closes what that call
929
+ * left open. A success ends only a run that has no end event of its own.
930
+ */
931
+ private invocationEnded(caller: object, value: unknown, error: unknown, failed: boolean): void {
932
+ this.returned.add(caller);
933
+ const text = failed ? errorText(error) : undefined;
934
+ if (failed) {
935
+ for (const key of this.invocationLeaves.get(caller) ?? []) {
936
+ const leaf = this.leaves.get(key);
937
+ if (!leaf) continue;
938
+ this.closeLeaf(leaf, { error: text });
939
+ if (isObject(error)) this.carried.add(error);
940
+ }
941
+ }
942
+ this.invocationLeaves.delete(caller);
943
+ const run = this.live(this.invocations.get(caller));
944
+ if (!run) return;
945
+ if (failed) {
946
+ for (const key of [...run.leaves]) {
947
+ const leaf = this.leaves.get(key);
948
+ if (!leaf) continue;
949
+ this.closeLeaf(leaf, { error: text });
950
+ if (isObject(error)) this.carried.add(error);
951
+ }
952
+ // One report per failure: a leaf that already carries it is the report.
953
+ if (!(isObject(error) && this.carried.has(error))) this.reportError(run, error);
954
+ this.finishRun(run, "failed", text);
955
+ } else if (run.byInvocation) {
956
+ this.finishRun(run, "success", this.options.captureMessages ? textOf(value) : undefined);
957
+ }
958
+ }
959
+
960
+ llmStart(event: unknown): void {
961
+ const payload = detail(event);
962
+ const id = asId(payload.id);
963
+ const frame = this.frames.getStore();
964
+ const origin = originOf(event);
965
+ const place = this.placeOrOpen(event, "llm", () => className(callersOf(event)[0]) ?? "llm", origin);
966
+ this.closeAnsweredTools(place.run, payload.messages);
967
+ const model = modelFromCallers(event) ?? (frame && frame.run === place.run ? frame.model : undefined) ?? place.run.model;
968
+ const leaf: Leaf = {
969
+ key: `llm:${id}`,
970
+ kind: "model",
971
+ run: place.run,
972
+ parentKey: place.parentKey,
973
+ name: model ?? "llm",
974
+ callId: id,
975
+ started: performance.now(),
976
+ model,
977
+ };
978
+ this.openLeaf(leaf);
979
+ // A model call that throws has no `llm-end`; its invocation throwing is the
980
+ // only signal. Inside a workflow step the step's failure already closes it.
981
+ const invocation = origin.chain?.[0];
982
+ if (invocation && !frame) {
983
+ const keys = this.invocationLeaves.get(invocation) ?? new Set<string>();
984
+ keys.add(leaf.key);
985
+ this.invocationLeaves.set(invocation, keys);
986
+ }
987
+ this.tracker.emit("modelRequest", leaf.key, {
988
+ parentKey: leaf.parentKey,
989
+ model,
990
+ requestId: id,
991
+ messages: this.capture(messagesOf(payload.messages)),
992
+ ...core.fwFields({ run_id: place.run.key }),
993
+ });
994
+ }
995
+
996
+ llmStream(event: unknown): void {
997
+ const leaf = this.leaves.get(`llm:${asId(detail(event).id)}`);
998
+ if (leaf && leaf.firstChunk === undefined) leaf.firstChunk = performance.now();
999
+ }
1000
+
1001
+ llmEnd(event: unknown): void {
1002
+ const payload = detail(event);
1003
+ const leaf = this.leaves.get(`llm:${asId(payload.id)}`);
1004
+ if (leaf) this.closeLeaf(leaf, { response: payload.response });
1005
+ }
1006
+
1007
+ /**
1008
+ * Close tool leaves the model has already been shown the result of.
1009
+ *
1010
+ * `callTool` dispatches no `llm-tool-result` when a tool throws, so without
1011
+ * this a failing tool in a legacy agent stays open for the life of the run.
1012
+ * The next model call carries the result as a `toolResult` message — with
1013
+ * `isError` — which is the framework's own record of how the tool ended.
1014
+ */
1015
+ private closeAnsweredTools(run: Run, messages: unknown): void {
1016
+ if (run.leaves.size === 0 || !Array.isArray(messages)) return;
1017
+ const answers = new Map<string, Json>();
1018
+ for (const message of messages) {
1019
+ const result = read(read(message, "options"), "toolResult");
1020
+ const id = nonEmpty(read(result, "id"));
1021
+ if (id && isObject(result)) answers.set(id, result);
1022
+ }
1023
+ for (const key of [...run.leaves]) {
1024
+ const leaf = this.leaves.get(key);
1025
+ if (!leaf || leaf.kind !== "tool" || !leaf.rawId) continue;
1026
+ const answer = answers.get(leaf.rawId);
1027
+ if (!answer) continue;
1028
+ this.closeLeaf(leaf, answer.isError === true ? { error: cleanToolError(answer.result) } : { output: answer.result });
1029
+ }
1030
+ }
1031
+
1032
+ private toolCallId(run: Run, rawId: string | undefined): string {
1033
+ if (!rawId) return randomUUID();
1034
+ const seen = run.usedToolIds.get(rawId) ?? 0;
1035
+ run.usedToolIds.set(rawId, seen + 1);
1036
+ // A repeat of the same provider id within a run would pair wrongly.
1037
+ return seen === 0 ? rawId : `${rawId}#${seen}`;
1038
+ }
1039
+
1040
+ toolCall(event: unknown): void {
1041
+ const call = read(detail(event), "toolCall");
1042
+ const name = nonEmpty(read(call, "name")) ?? "tool";
1043
+ const rawId = nonEmpty(read(call, "id"));
1044
+ const located = this.locate(originOf(event));
1045
+ // Inside a workflow the runtime's own `agentToolCallEvent` opened this call
1046
+ // already, synchronously and before the tool ran; this is the same call.
1047
+ if (located && this.findTool(rawId, located.run, "workflow")) return;
1048
+ const place = located ?? this.placeOrOpen(event, "tool", () => name);
1049
+ this.openTool(place, name, rawId, read(call, "input"), "bus");
1050
+ }
1051
+
1052
+ private openTool(place: Located, name: string, rawId: string | undefined, input: unknown, source: Leaf["source"]): void {
1053
+ const leaf: Leaf = {
1054
+ key: this.nextKey("tool"),
1055
+ kind: "tool",
1056
+ run: place.run,
1057
+ parentKey: place.parentKey,
1058
+ name,
1059
+ callId: this.toolCallId(place.run, rawId),
1060
+ rawId,
1061
+ source,
1062
+ started: performance.now(),
1063
+ };
1064
+ this.openLeaf(leaf);
1065
+ this.tracker.emit("toolUse", leaf.key, {
1066
+ parentKey: leaf.parentKey,
1067
+ toolName: name,
1068
+ toolCallId: leaf.callId,
1069
+ input: this.capture(isObject(input) && !Array.isArray(input) ? input : input === undefined ? undefined : { input }),
1070
+ ...core.fwFields({ run_id: place.run.key, tool_id: rawId !== leaf.callId ? rawId : undefined }),
1071
+ });
1072
+ }
1073
+
1074
+ /** The open tool leaf for a provider tool call id, in the run this event belongs to first. */
1075
+ private findTool(rawId: string | undefined, run: Run | null, source?: Leaf["source"]): Leaf | undefined {
1076
+ if (!rawId) return undefined;
1077
+ const search = (keys: Iterable<string>): Leaf | undefined => {
1078
+ for (const key of keys) {
1079
+ const leaf = this.leaves.get(key);
1080
+ if (leaf?.kind === "tool" && leaf.rawId === rawId && (!source || leaf.source === source)) return leaf;
1081
+ }
1082
+ return undefined;
1083
+ };
1084
+ if (source) return run ? search(run.leaves) : undefined;
1085
+ return (run ? search(run.leaves) : undefined) ?? search(this.leaves.keys());
1086
+ }
1087
+
1088
+ toolResult(event: unknown): void {
1089
+ const payload = detail(event);
1090
+ const leaf = this.findTool(nonEmpty(read(read(payload, "toolCall"), "id")), this.locate(originOf(event))?.run ?? null);
1091
+ if (!leaf) return;
1092
+ const result = read(payload, "toolResult");
1093
+ const failed = read(result, "isError") === true;
1094
+ this.closeLeaf(leaf, failed ? { error: cleanToolError(read(result, "output")) } : { output: read(result, "output") });
1095
+ }
1096
+
1097
+ retrieveStart(event: unknown): void {
1098
+ const payload = detail(event);
1099
+ const id = asId(payload.id);
1100
+ // The retriever's class, as Python names it (`VectorIndexRetriever`). The
1101
+ // event does not carry the retriever; the patched `retrieve()` it was
1102
+ // dispatched from does, and the dispatch runs in that call's async context.
1103
+ const name = className(this.retrievers.getStore()) ?? "retriever";
1104
+ const place = this.placeOrOpen(event, "retrieve", () => name);
1105
+ const leaf: Leaf = {
1106
+ key: `retrieve:${id}`,
1107
+ kind: "retrieval",
1108
+ run: place.run,
1109
+ parentKey: place.parentKey,
1110
+ name,
1111
+ callId: id,
1112
+ started: performance.now(),
1113
+ };
1114
+ this.openLeaf(leaf);
1115
+ this.tracker.emit("toolUse", leaf.key, {
1116
+ parentKey: leaf.parentKey,
1117
+ toolName: leaf.name,
1118
+ toolCallId: id,
1119
+ input: this.capture({ query: queryText(payload.query) }),
1120
+ ...core.fwFields({ run_id: place.run.key, kind: "retrieval" }),
1121
+ });
1122
+ }
1123
+
1124
+ retrieveEnd(event: unknown): void {
1125
+ const payload = detail(event);
1126
+ const leaf = this.leaves.get(`retrieve:${asId(payload.id)}`);
1127
+ if (leaf) this.closeLeaf(leaf, { output: summarizeNodes(payload.nodes) });
1128
+ }
1129
+
1130
+ /**
1131
+ * A query engine call is a root run when nothing encloses it — Python's
1132
+ * top-level `query_engine.query()` — and nothing at all inside a run, where
1133
+ * its retrievals and model calls are what is worth seeing.
1134
+ */
1135
+ queryStart(event: unknown): void {
1136
+ const origin = originOf(event);
1137
+ const owner = origin.callers[0];
1138
+ if (this.locate(origin, owner)) return;
1139
+ // Called from inside ANOTHER invocation nothing encloses — a chat engine
1140
+ // whose first act is a query: that invocation is the root, and the query
1141
+ // is inside it (recording nothing of its own, as inside any run).
1142
+ if (origin.chain && origin.chain.length > 1 && this.observedOuter(origin)) {
1143
+ this.openRoot(origin, "invocation", () => className(owner) ?? "query_engine");
1144
+ return;
1145
+ }
1146
+ const payload = detail(event);
1147
+ const run = this.openRun(`query:${asId(payload.id)}`, className(owner) ?? "query_engine", {
1148
+ owner: isObject(owner) ? owner : null,
1149
+ caller: origin.chain?.[0] ?? null,
1150
+ goal: queryText(payload.query),
1151
+ });
1152
+ const id = asId(payload.id);
1153
+ this.queries.set(id, run);
1154
+ run.forget = () => this.queries.delete(id);
1155
+ }
1156
+
1157
+ queryEnd(event: unknown): void {
1158
+ const payload = detail(event);
1159
+ const id = asId(payload.id);
1160
+ const run = this.queries.get(id);
1161
+ if (!run) return;
1162
+ this.finishRun(run, "success", this.options.captureMessages ? textOf(payload.response) : undefined);
1163
+ }
1164
+
1165
+ /**
1166
+ * Legacy `AgentRunner` (`LLMAgent`, `OpenAIAgent`, `ReActAgent`, …).
1167
+ *
1168
+ * `agent-start` fires for EVERY step and `agent-end` only after the last one,
1169
+ * both carrying the step. Pairing them by step id — what this adapter used to
1170
+ * do — opened a span per step and closed one, so every multi-step run left
1171
+ * one agent open forever and split into two sessions. A task is identified by
1172
+ * its FIRST step instead, reached by walking `prevStep`.
1173
+ */
1174
+ agentStart(event: unknown): void {
1175
+ const step = read(detail(event), "startStep");
1176
+ if (!isObject(step)) return;
1177
+ const first = firstStep(step);
1178
+ const firstId = asId(read(first, "id"));
1179
+ const running = this.tasks.get(firstId);
1180
+ if (running) {
1181
+ running.lastActivity = performance.now();
1182
+ return;
1183
+ }
1184
+ const origin = originOf(event);
1185
+ const owner = origin.callers[0];
1186
+ let parent = this.locate(origin, owner);
1187
+ if (!parent && origin.chain && origin.chain.length > 1 && this.observedOuter(origin)) {
1188
+ // A legacy agent called from inside another un-run invocation nests
1189
+ // under it, the way a sub-agent nests under a workflow.
1190
+ const root = this.openRoot(origin, "invocation", () => "invocation");
1191
+ parent = { parentKey: root.key, run: root };
1192
+ }
1193
+ const run = this.openRun(`task:${firstId}`, className(owner) ?? "AgentRunner", {
1194
+ parent,
1195
+ owner: isObject(owner) ? owner : null,
1196
+ caller: origin.chain?.[0] ?? null,
1197
+ goal: textOf((read(read(read(step, "context"), "store"), "messages") as unknown[] | undefined)?.at(-1)),
1198
+ });
1199
+ run.model = nonEmpty(read(read(read(read(step, "context"), "llm"), "metadata"), "model"));
1200
+ this.tasks.set(firstId, run);
1201
+ run.forget = () => this.tasks.delete(firstId);
1202
+ }
1203
+
1204
+ agentEnd(event: unknown): void {
1205
+ const step = read(detail(event), "endStep");
1206
+ if (!isObject(step)) return;
1207
+ const firstId = asId(read(firstStep(step), "id"));
1208
+ const run = this.tasks.get(firstId);
1209
+ if (run) this.finishRun(run, "success");
1210
+ }
1211
+
1212
+ // -- workflows ------------------------------------------------------------
1213
+
1214
+ /**
1215
+ * Open the run for an `AgentWorkflow.runStream()` call and return the hook to
1216
+ * attach to the context it is about to create.
1217
+ */
1218
+ beginWorkflow(
1219
+ workflow: Json,
1220
+ userInput: unknown,
1221
+ module: WorkflowModule,
1222
+ ): { run: Run; attach: (context: unknown) => void } {
1223
+ // Its steps are recorded through its context (`attachContext`), never
1224
+ // again as a plain workflow's.
1225
+ for (const handler of handlerNames(workflow).keys()) {
1226
+ if (isObject(handler) || typeof handler === "function") this.agentHandlers.add(handler);
1227
+ }
1228
+ const agents = read(workflow, "agents");
1229
+ const size = agents instanceof Map ? agents.size : 1;
1230
+ const rootName = nonEmpty(read(workflow, "rootAgentName"));
1231
+ const agentId = size <= 1 && rootName ? rootName : (className(workflow) ?? "AgentWorkflow");
1232
+ const run = this.openRun("workflow", agentId, {
1233
+ parent: this.locate(),
1234
+ goal: typeof userInput === "string" ? userInput : textOf(userInput),
1235
+ fields: { workflow: className(workflow), agent_name: size <= 1 ? rootName : undefined },
1236
+ });
1237
+ return {
1238
+ run,
1239
+ attach: (context: unknown) => {
1240
+ this.attachContext(run, workflow, context, module);
1241
+ },
1242
+ };
1243
+ }
1244
+
1245
+ failWorkflow(run: Run | null, error: unknown): void {
1246
+ if (!run) return;
1247
+ // Nothing below the run saw this failure, so the run reports it — once.
1248
+ this.reportError(run, error);
1249
+ this.finishRun(run, "failed", errorText(error));
1250
+ }
1251
+
1252
+ /**
1253
+ * An `error` event, for a failure no leaf or hook already carries. The
1254
+ * Python adapter's rule: a second report of one failure double-counts on the
1255
+ * session's error total, so only the innermost place that saw it reports it.
1256
+ */
1257
+ private reportError(run: Run, error: unknown): void {
1258
+ this.tracker.emit("error", run.key, {
1259
+ errorType: error instanceof Error ? error.name || "Error" : typeof error,
1260
+ message: error instanceof Error ? error.message || error.name : String(error),
1261
+ traceback: error instanceof Error ? error.stack : undefined,
1262
+ ...core.fwFields({ run_id: run.key }),
1263
+ });
1264
+ }
1265
+
1266
+ private attachContext(run: Run, workflow: Json, context: unknown, module: WorkflowModule): void {
1267
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- the subscriber is a named function (its name is its degradation site)
1268
+ const state = this;
1269
+ const callContext = read(context, "__internal__call_context") as Subscribable | undefined;
1270
+ const sendEvent = read(context, "__internal__call_send_event") as Subscribable | undefined;
1271
+ if (typeof sendEvent?.subscribe === "function") {
1272
+ sendEvent.subscribe(
1273
+ core.safe(NAME, function workflowEvent(this: void, sent: unknown): void {
1274
+ state.workflowEvent(run, sent, module);
1275
+ }),
1276
+ );
1277
+ }
1278
+ if (typeof callContext?.subscribe !== "function") {
1279
+ compat.warn(
1280
+ "this @llamaindex/workflow context has no __internal__call_context, so workflow steps " +
1281
+ "are not recorded (the run, its model calls and its tool calls still are).",
1282
+ `${NAME}:call_context`,
1283
+ );
1284
+ return;
1285
+ }
1286
+ const names = handlerNames(workflow);
1287
+ callContext.subscribe(((handlerContext: HandlerContext, next: (context: HandlerContext) => void) => {
1288
+ // `next` MUST be called exactly once whatever happens here: a subscriber
1289
+ // that throws before it would silently stop the customer's workflow.
1290
+ let wrapped: HandlerContext = handlerContext;
1291
+ try {
1292
+ const original = handlerContext.handler;
1293
+ if (typeof original === "function") {
1294
+ const name = names.get(original) ?? nonEmpty(original.name) ?? "step";
1295
+ const inputs = handlerContext.inputs;
1296
+ const input = Array.isArray(inputs) ? (inputs[0] as unknown) : undefined;
1297
+ wrapped = { ...handlerContext, handler: this.wrapStep(run, workflow, name, original, input) };
1298
+ }
1299
+ } catch (error) {
1300
+ core.callSafely(
1301
+ () => {
1302
+ throw error;
1303
+ },
1304
+ [],
1305
+ `${NAME}.wrapStep`,
1306
+ );
1307
+ }
1308
+ next(wrapped);
1309
+ }));
1310
+ }
1311
+
1312
+ workflowEvent(run: Run, sent: unknown, module: WorkflowModule): void {
1313
+ // A context outlives uninstall(); its subscriptions cannot be removed.
1314
+ if (!this.active) return;
1315
+ if (module.stopAgentEvent?.include(sent)) {
1316
+ const data = read(sent, "data");
1317
+ const result = read(data, "result");
1318
+ this.finishRun(run, "success", this.options.captureMessages ? (textOf(result) ?? textOf(read(data, "message"))) : undefined);
1319
+ return;
1320
+ }
1321
+ if (module.agentToolCallEvent?.include(sent)) {
1322
+ // The runtime announces every tool call before running it — the only
1323
+ // signal on releases whose `AgentWorkflow` calls tools directly rather
1324
+ // than through `callTool` (workflow 1.1.5 dispatches nothing on the bus).
1325
+ const data = read(sent, "data");
1326
+ const owner = run.sub ?? run;
1327
+ const frame = this.frames.getStore();
1328
+ const place = frame && frame.run === owner ? { parentKey: frame.key, run: owner } : { parentKey: owner.key, run: owner };
1329
+ const name = nonEmpty(read(data, "toolName")) ?? "tool";
1330
+ this.openTool(place, name, nonEmpty(read(data, "toolId")), read(data, "toolKwargs"), "workflow");
1331
+ return;
1332
+ }
1333
+ if (module.agentToolCallResultEvent?.include(sent)) {
1334
+ // The runtime's own record of how a tool ended. It is the ONLY signal for
1335
+ // a tool that threw: `callTool` dispatches no `llm-tool-result` then.
1336
+ const data = read(sent, "data");
1337
+ const leaf = this.findTool(nonEmpty(read(data, "toolId")), run.sub ?? run);
1338
+ if (!leaf) return;
1339
+ const output = read(data, "toolOutput");
1340
+ if (read(output, "isError") === true) {
1341
+ this.closeLeaf(leaf, { error: cleanToolError(read(output, "result")) });
1342
+ } else {
1343
+ this.closeLeaf(leaf, { output: read(data, "raw") ?? read(output, "result") });
1344
+ }
1345
+ }
1346
+ }
1347
+
1348
+ private wrapStep(
1349
+ run: Run,
1350
+ workflow: Json,
1351
+ name: string,
1352
+ original: (...args: unknown[]) => unknown,
1353
+ input: unknown,
1354
+ ): (...args: unknown[]) => unknown {
1355
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- the wrapper needs its own `this`
1356
+ const state = this;
1357
+ return function failproofaiStep(this: unknown, ...args: unknown[]): unknown {
1358
+ if (!state.active) return original.apply(this, args);
1359
+ const step = core.callSafely(() => state.stepStart(run, workflow, name, input), [], `${NAME}.stepStart`);
1360
+ if (!step) return original.apply(this, args);
1361
+ const finish = (value: unknown, error?: unknown): void => {
1362
+ core.callSafely(() => {
1363
+ state.stepEnd(step, value, error);
1364
+ }, [], `${NAME}.stepEnd`);
1365
+ };
1366
+ let result: unknown;
1367
+ try {
1368
+ result = state.frames.run(step.frame, () => original.apply(this, args));
1369
+ } catch (error) {
1370
+ finish(undefined, error);
1371
+ throw error;
1372
+ }
1373
+ if (isObject(result) && typeof (result as { then?: unknown }).then === "function") {
1374
+ return (result as unknown as PromiseLike<unknown>).then(
1375
+ (value) => {
1376
+ finish(value);
1377
+ return value;
1378
+ },
1379
+ (error: unknown) => {
1380
+ finish(undefined, error);
1381
+ throw error;
1382
+ },
1383
+ );
1384
+ }
1385
+ finish(result);
1386
+ return result;
1387
+ };
1388
+ }
1389
+
1390
+ /**
1391
+ * One step handler of a plain `createWorkflow()` workflow, seen as
1392
+ * workflow-core binds its handler context (`hookAsyncContext`).
1393
+ *
1394
+ * A plain workflow has no run boundary of its own: the application creates
1395
+ * the context, sends it events and stops reading whenever it likes. So the
1396
+ * run is the context's BURST of activity — it opens with the first step
1397
+ * handler of a context and ends once no step of it is running and none was
1398
+ * started by the last one's output (checked a macrotask later, after the
1399
+ * runtime has dispatched that output). A workflow that then waits for an
1400
+ * event from outside (human-in-the-loop) records the next burst as a new run.
1401
+ */
1402
+ plainStep(handlerContext: Json, proceed: () => unknown): unknown {
1403
+ const original = handlerContext.handler;
1404
+ if (typeof original !== "function" || this.agentHandlers.has(original)) return proceed();
1405
+ const root = rootContext(handlerContext);
1406
+ const inputs = handlerContext.inputs;
1407
+ const input = Array.isArray(inputs) ? (inputs[0] as unknown) : undefined;
1408
+ let run = this.live(this.plainRuns.get(root));
1409
+ if (!run) {
1410
+ const data = read(input, "data");
1411
+ run = this.openRun("workflow", "Workflow", {
1412
+ parent: this.locate(),
1413
+ goal: typeof data === "string" ? data : textOf(data),
1414
+ fields: { workflow: "Workflow" },
1415
+ });
1416
+ this.plainRuns.set(root, run);
1417
+ }
1418
+ const label = eventLabel(input);
1419
+ const name = nonEmpty(read(original, "name")) ?? (label ? `handle:${label}` : "step");
1420
+ const step = this.stepStart(run, {}, name, input, true);
1421
+ if (!step) return proceed();
1422
+ const owner = run;
1423
+ owner.inFlight += 1;
1424
+ let done = false;
1425
+ const end = (value: unknown, error?: unknown): void => {
1426
+ if (done) return;
1427
+ done = true;
1428
+ owner.inFlight -= 1;
1429
+ core.callSafely(
1430
+ () => {
1431
+ const data = read(value, "data");
1432
+ if (error === undefined && typeof data === "string") owner.output = data;
1433
+ this.stepEnd(step, value, error);
1434
+ if (owner.inFlight === 0 && !owner.ended) this.whenQuiet(owner);
1435
+ },
1436
+ [],
1437
+ `${NAME}.plainStepEnd`,
1438
+ );
1439
+ };
1440
+ // The handler object is this invocation's own, so its handler is replaced
1441
+ // in place — the same thing middleware does — rather than wrapped around.
1442
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- the wrapper needs its own `this`
1443
+ const state = this;
1444
+ handlerContext.handler = function failproofaiStep(this: unknown, ...args: unknown[]): unknown {
1445
+ let result: unknown;
1446
+ try {
1447
+ result = state.frames.run(step.frame, () => (original as (...a: unknown[]) => unknown).apply(this, args));
1448
+ } catch (error) {
1449
+ end(undefined, error);
1450
+ throw error;
1451
+ }
1452
+ if (isObject(result) && typeof (result as { then?: unknown }).then === "function") {
1453
+ return (result as unknown as PromiseLike<unknown>).then(
1454
+ (value) => {
1455
+ end(value);
1456
+ return value;
1457
+ },
1458
+ (error: unknown) => {
1459
+ end(undefined, error);
1460
+ throw error;
1461
+ },
1462
+ );
1463
+ }
1464
+ end(result);
1465
+ return result;
1466
+ };
1467
+ return proceed();
1468
+ }
1469
+
1470
+ /**
1471
+ * End a plain-workflow run if it is still idle once the runtime has
1472
+ * dispatched the last step's output. Two microtask hops: the step's end runs
1473
+ * in a reaction to the handler's promise, the runtime's `sendEvent` in the
1474
+ * reaction to the promise returned in its place — queued one hop later, and
1475
+ * starting any next handler synchronously. Any later and the run would end
1476
+ * after the caller that awaited it (and after an `agent()` scope around it).
1477
+ */
1478
+ private whenQuiet(run: Run): void {
1479
+ queueMicrotask(() => {
1480
+ queueMicrotask(() => {
1481
+ core.callSafely(
1482
+ () => {
1483
+ if (this.active && !run.ended && run.inFlight === 0) {
1484
+ this.finishRun(run, "success", this.options.captureMessages ? run.output : undefined);
1485
+ }
1486
+ },
1487
+ [],
1488
+ `${NAME}.whenQuiet`,
1489
+ );
1490
+ });
1491
+ });
1492
+ }
1493
+
1494
+ private stepStart(run: Run, workflow: Json, name: string, event: unknown, plain = false): Step | null {
1495
+ if (run.ended) return null;
1496
+ const data = read(event, "data");
1497
+ // Only an AgentWorkflow's events name the agent holding the turn; a plain
1498
+ // workflow's data is the application's own, whatever its field names.
1499
+ const agentName = plain
1500
+ ? undefined
1501
+ : (nonEmpty(read(data, "currentAgentName")) ?? nonEmpty(read(data, "agentName")));
1502
+ const owner = plain ? run : this.subAgent(run, agentName);
1503
+ run.lastActivity = owner.lastActivity = performance.now();
1504
+ const key = this.nextKey(`${run.key}:step`);
1505
+ this.tracker.link(key, owner.key);
1506
+ owner.steps.add(key);
1507
+ const agents = read(workflow, "agents");
1508
+ const agent = agents instanceof Map ? agents.get(agentName ?? read(workflow, "rootAgentName")) : undefined;
1509
+ const model = nonEmpty(read(read(read(agent, "llm"), "metadata"), "model"));
1510
+ if (this.options.steps) {
1511
+ this.tracker.emit("hookTriggered", key, {
1512
+ parentKey: owner.key,
1513
+ hookName: name,
1514
+ hookId: key,
1515
+ triggerEvent: "workflow_step",
1516
+ input: this.capture(eventData(data)),
1517
+ ...core.fwFields({ run_id: run.key, step: name, input_event: eventLabel(event), agent_name: agentName }),
1518
+ });
1519
+ }
1520
+ return { key, name, run, owner, frame: { key, run: owner, model } };
1521
+ }
1522
+
1523
+ private stepEnd(step: Step, value: unknown, error?: unknown): void {
1524
+ step.run.lastActivity = step.owner.lastActivity = performance.now();
1525
+ const failed = error !== undefined;
1526
+ let reported = false;
1527
+ if (failed) {
1528
+ // Whatever this step had open failed with it — a model call that threw
1529
+ // has no `llm-end` at all.
1530
+ for (const key of [...step.owner.leaves]) {
1531
+ const leaf = this.leaves.get(key);
1532
+ if (leaf && leaf.parentKey === step.key) {
1533
+ this.closeLeaf(leaf, { error: errorText(error) });
1534
+ reported = true;
1535
+ }
1536
+ }
1537
+ }
1538
+ if (this.options.steps) {
1539
+ this.tracker.emit("hookCompleted", step.key, {
1540
+ parentKey: step.owner.key,
1541
+ hookName: step.name,
1542
+ hookId: step.key,
1543
+ outcome: failed ? "failed" : "success",
1544
+ output: failed ? undefined : this.capture(eventData(read(value, "data") ?? value)),
1545
+ error: failed ? errorText(error) : undefined,
1546
+ ...core.fwFields({ run_id: step.run.key, step: step.name, output_event: eventLabel(value) }),
1547
+ });
1548
+ }
1549
+ // An AgentWorkflow is a single chain of steps: a step that throws ends the
1550
+ // run, and the runtime never settles `run()` to tell us so. Under
1551
+ // `steps: false` there is no failed hook, so unless a leaf carried it the
1552
+ // run reports it — as the Python adapter does.
1553
+ if (failed) {
1554
+ if (!reported && !this.options.steps) this.reportError(step.owner, error);
1555
+ this.finishRun(step.run, "failed", errorText(error));
1556
+ }
1557
+ // Last: the step's leaves and its hook resolve their session through it.
1558
+ step.owner.steps.delete(step.key);
1559
+ this.tracker.unlink(step.key);
1560
+ }
1561
+
1562
+ /**
1563
+ * The nested agent a multi-agent step belongs to, or the run itself.
1564
+ *
1565
+ * The Python adapter's rule: each distinct agent name opens a nested agent
1566
+ * under the workflow and a handoff closes the previous one. The name is
1567
+ * sticky — tool steps carry `agentName`, not `currentAgentName`, and a step
1568
+ * with neither keeps whichever agent holds the turn.
1569
+ */
1570
+ private subAgent(run: Run, name: string | undefined): Run {
1571
+ if (!name || name === run.agentId || (run.sub && run.sub.agentId === name)) return run.sub ?? run;
1572
+ if (run.sub) this.finishRun(run.sub, "success");
1573
+ run.subSeq += 1;
1574
+ const sub = this.openRun(`${run.key}:sub${run.subSeq}`, name, {
1575
+ parent: { parentKey: run.key, run },
1576
+ root: run,
1577
+ fields: { agent_name: name, workflow: run.agentId },
1578
+ });
1579
+ run.sub = sub;
1580
+ return sub;
1581
+ }
1582
+
1583
+ // -- teardown -------------------------------------------------------------
1584
+
1585
+ /**
1586
+ * Close what nobody is going to close. Returns how many leaves and runs.
1587
+ *
1588
+ * Leaves first — a model call that threw has no `llm-end` — then runs that
1589
+ * have been silent for `staleAfter`: LlamaIndex.TS never signals a legacy
1590
+ * task whose step threw (no `agent-end`) or a `runStream()` nobody drained,
1591
+ * and a run left open is a session the dashboard shows as `ongoing` forever.
1592
+ * Python reaps only leaves because its span handler sees every drop; here
1593
+ * the run-level signal genuinely does not exist.
1594
+ */
1595
+ sweep(now = performance.now()): number {
1596
+ const cutoff = now - this.options.staleAfter * 1000;
1597
+ let closed = 0;
1598
+ for (const leaf of [...this.leaves.values()]) {
1599
+ if (leaf.started > cutoff) continue;
1600
+ this.closeLeaf(leaf, { closedBy: "stale" });
1601
+ closed += 1;
1602
+ }
1603
+ for (const run of [...this.runs.values()].reverse()) {
1604
+ if (run.ended || run.root !== null || run.leaves.size > 0 || run.lastActivity > cutoff) continue;
1605
+ this.finishRun(run, "cancelled", undefined, "stale");
1606
+ closed += 1;
1607
+ }
1608
+ return closed;
1609
+ }
1610
+
1611
+ startReaper(): void {
1612
+ if (this.options.reaperInterval <= 0 || this.reaper !== null) return;
1613
+ this.reaper = setInterval(() => {
1614
+ core.callSafely(() => this.sweep(), [], `${NAME}.reaper`);
1615
+ }, this.options.reaperInterval * 1000);
1616
+ this.reaper.unref();
1617
+ }
1618
+
1619
+ /** What is still held, for tests: every per-run table, and the tracker. */
1620
+ residue(): { runs: number; leaves: number; tasks: number; queries: number; tracker: core.RunTracker } {
1621
+ return {
1622
+ runs: this.runs.size,
1623
+ leaves: this.leaves.size,
1624
+ tasks: this.tasks.size,
1625
+ queries: this.queries.size,
1626
+ tracker: this.tracker,
1627
+ };
1628
+ }
1629
+
1630
+ shutdown(): void {
1631
+ this.active = false;
1632
+ if (this.reaper !== null) clearInterval(this.reaper);
1633
+ this.reaper = null;
1634
+ // Newest first, so a sub-agent closes before the workflow that opened it.
1635
+ for (const run of [...this.runs.values()].reverse()) this.finishRun(run, "cancelled", undefined, "uninstrument");
1636
+ this.leaves.clear();
1637
+ this.tasks.clear();
1638
+ this.queries.clear();
1639
+ this.tracker.reset();
1640
+ }
1641
+ }
1642
+
1643
+ interface Step {
1644
+ key: string;
1645
+ name: string;
1646
+ run: Run;
1647
+ owner: Run;
1648
+ frame: Frame;
1649
+ }
1650
+
1651
+ function firstStep(step: Json): Json {
1652
+ let current = step;
1653
+ const seen = new Set<unknown>();
1654
+ for (;;) {
1655
+ const previous = read(current, "prevStep");
1656
+ if (!isObject(previous) || seen.has(previous)) return current;
1657
+ seen.add(previous);
1658
+ current = previous;
1659
+ }
1660
+ }
1661
+
1662
+ /** A workflow-core handler context's root: one per `createContext()`, so one per context. */
1663
+ function rootContext(handlerContext: Json): object {
1664
+ const root = read(handlerContext, "root");
1665
+ if (isObject(root)) return root;
1666
+ let current: Json = handlerContext;
1667
+ const seen = new Set<unknown>();
1668
+ for (;;) {
1669
+ const previous = read(current, "prev");
1670
+ if (!isObject(previous) || seen.has(previous)) return current;
1671
+ seen.add(previous);
1672
+ current = previous;
1673
+ }
1674
+ }
1675
+
1676
+ /** Step handler functions are instance arrow fields, so they are named by the field. */
1677
+ function handlerNames(workflow: Json): Map<unknown, string> {
1678
+ const names = new Map<unknown, string>();
1679
+ try {
1680
+ for (const key of Object.keys(workflow)) {
1681
+ const value = read(workflow, key);
1682
+ if (typeof value === "function") names.set(value, key);
1683
+ }
1684
+ } catch {
1685
+ // A frozen or exotic object; unnamed steps fall back to the function name.
1686
+ }
1687
+ return names;
1688
+ }
1689
+
1690
+ /** A workflow event's label: workflow-core tags each event with `Symbol.toStringTag`. */
1691
+ function eventLabel(event: unknown): string | undefined {
1692
+ if (!isObject(event)) return undefined;
1693
+ try {
1694
+ const tag = /^\[object (.+)\]$/.exec(Object.prototype.toString.call(event))?.[1];
1695
+ return tag !== undefined && tag !== "Object" && !tag.startsWith("WorkflowEvent") ? tag : undefined;
1696
+ } catch {
1697
+ return undefined;
1698
+ }
1699
+ }
1700
+
1701
+ /** A workflow event's payload, minus the run's whole state object. */
1702
+ function eventData(data: unknown): unknown {
1703
+ if (!isObject(data) || Array.isArray(data)) return data;
1704
+ const rest = { ...data };
1705
+ delete rest.state;
1706
+ return rest;
1707
+ }
1708
+
1709
+ /**
1710
+ * `AgentWorkflow` stores a thrown tool as `Error: ${new Error(String(output))}`,
1711
+ * where `output` is already `prettifyError`'s rendering — three nested
1712
+ * prefixes for one failure. Keep the innermost, as `<name>: <message>`.
1713
+ *
1714
+ * `prettifyError` has two spellings: `Error: <message>` on older releases and
1715
+ * `Error(<name>): <message>` on llamaindex 0.12, which recorded verbatim read
1716
+ * "Error: Error(Error): unknown region: latam".
1717
+ */
1718
+ function cleanToolError(result: unknown): string {
1719
+ let text = typeof result === "string" ? result : String(result);
1720
+ while (/^Error: (\w*Error: |Error\(\w*\): )/.test(text)) text = text.slice("Error: ".length);
1721
+ const named = /^Error\((\w*)\): ([\s\S]*)$/.exec(text);
1722
+ if (named) text = `${named[1] || "Error"}: ${named[2]}`;
1723
+ return text;
1724
+ }
1725
+
1726
+ // ---------------------------------------------------------------------------
1727
+ // Attachment
1728
+ // ---------------------------------------------------------------------------
1729
+
1730
+ interface Installed {
1731
+ state: State;
1732
+ unsubscribe: Array<() => void>;
1733
+ patcher: core.Patcher;
1734
+ }
1735
+
1736
+ let installed: Installed | null = null;
1737
+
1738
+ const BUS_EVENTS: Array<[string, keyof State]> = [
1739
+ ["llm-start", "llmStart"],
1740
+ ["llm-stream", "llmStream"],
1741
+ ["llm-end", "llmEnd"],
1742
+ ["llm-tool-call", "toolCall"],
1743
+ ["llm-tool-result", "toolResult"],
1744
+ ["retrieve-start", "retrieveStart"],
1745
+ ["retrieve-end", "retrieveEnd"],
1746
+ ["query-start", "queryStart"],
1747
+ ["query-end", "queryEnd"],
1748
+ ["agent-start", "agentStart"],
1749
+ ["agent-end", "agentEnd"],
1750
+ ];
1751
+
1752
+ /**
1753
+ * Subscribe to every copy of the bus and patch every copy of `AgentWorkflow`.
1754
+ *
1755
+ * Separate from `install()` so a test can hand it stand-ins for the framework
1756
+ * modules; `install()` is only about FINDING the right copies.
1757
+ *
1758
+ * THROWS while an earlier attach is still installed, before touching anything.
1759
+ * Overwriting it would orphan that install's bus subscriptions and prototype
1760
+ * patch — `uninstall()` only reaches the latest — so they would record for the
1761
+ * life of the process, every event twice. A throw rather than a no-op because a
1762
+ * no-op would hand back a handle for an install that did not happen, with
1763
+ * options that were never applied. `instrument()` never reaches this: it skips
1764
+ * an adapter that is already active.
1765
+ *
1766
+ * @internal Not part of the public API.
1767
+ */
1768
+ export function attach(
1769
+ rawOptions: Record<string, unknown>,
1770
+ modules: {
1771
+ globals: GlobalModule[];
1772
+ workflows: WorkflowModule[];
1773
+ retrievers?: RetrieverModule[];
1774
+ asyncContexts?: AsyncContextModule[];
1775
+ frameworkPackage?: string;
1776
+ },
1777
+ ): { sweep: (now?: number) => number; residue: () => ReturnType<State["residue"]> } {
1778
+ if (installed !== null) {
1779
+ throw new Error(
1780
+ "the llamaindex adapter is already installed; uninstrument(\"llamaindex\") (or " +
1781
+ "adapter.uninstall()) before attaching again.",
1782
+ );
1783
+ }
1784
+ const options = parseOptions(rawOptions);
1785
+ const state = new State(options, modules.globals, modules.frameworkPackage ?? PACKAGE);
1786
+ const current: Installed = { state, unsubscribe: [], patcher: new core.Patcher() };
1787
+ installed = current;
1788
+
1789
+ const buses = new Set<Bus>();
1790
+ for (const module of modules.globals) {
1791
+ const bus = module.Settings?.callbackManager as Bus | undefined;
1792
+ if (bus && typeof bus.on === "function") buses.add(bus);
1793
+ }
1794
+ if (buses.size === 0) {
1795
+ throw new Error(
1796
+ "Settings.callbackManager is missing or has no `on` — this build of LlamaIndex does not " +
1797
+ "expose the callback bus this adapter subscribes to.",
1798
+ );
1799
+ }
1800
+ for (const bus of buses) {
1801
+ if (typeof bus.off !== "function") {
1802
+ compat.warn(
1803
+ "this build of LlamaIndex has no `callbackManager.off`, so uninstrument() cannot detach " +
1804
+ "the handlers. They stay subscribed for the life of the process and emit nothing.",
1805
+ `${NAME}:off`,
1806
+ );
1807
+ }
1808
+ for (const [event, method] of BUS_EVENTS) {
1809
+ // Named per event BEFORE `safe()` reads the name: the name is the
1810
+ // degradation site, and one shared site would let a handler that keeps
1811
+ // failing on, say, `retrieve-end` switch off `llm-start` with it.
1812
+ const named = {
1813
+ [method](raw: unknown): void {
1814
+ if (!state.active) return;
1815
+ (state[method] as (event: unknown) => void).call(state, raw);
1816
+ },
1817
+ }[method]!;
1818
+ const handler = core.safe(NAME, named);
1819
+ bus.on(event, handler);
1820
+ current.unsubscribe.push(() => {
1821
+ bus.off?.(event, handler);
1822
+ });
1823
+ }
1824
+ }
1825
+ if (options.embeddings) {
1826
+ logger.debug(
1827
+ "llamaindex: embeddings=true has nothing to record — LlamaIndex.TS dispatches no embedding events.",
1828
+ );
1829
+ }
1830
+
1831
+ const storages = new Set<AsyncLocalStorage<unknown>>();
1832
+ for (const module of modules.globals) {
1833
+ const storage = eventCallerStorage(module);
1834
+ if (storage) storages.add(storage);
1835
+ }
1836
+ for (const storage of storages) hookInvocations(current, storage);
1837
+ compat.probe(NAME, "EventCaller storage", () => storages.size > 0 || modules.globals.every((m) => !m.getEventCaller));
1838
+ for (const module of modules.retrievers ?? []) {
1839
+ patchRetriever(current, module);
1840
+ }
1841
+ for (const module of modules.workflows) {
1842
+ patchWorkflow(current, module);
1843
+ }
1844
+ for (const module of modules.asyncContexts ?? []) {
1845
+ hookAsyncContext(current, module);
1846
+ }
1847
+ state.startReaper();
1848
+ logger.debug(`llamaindex adapter subscribed on ${buses.size} bus(es), ${current.patcher.size} patch(es)`);
1849
+ return { sweep: (now?: number) => state.sweep(now), residue: () => state.residue() };
1850
+ }
1851
+
1852
+ /**
1853
+ * LlamaIndex's own `AsyncLocalStorage` of `EventCaller`s — module-private in
1854
+ * `@llamaindex/core/global`, so found by watching which storage one call of
1855
+ * the exported `getEventCaller()` reads. The prototype is swapped back before
1856
+ * this returns: the window is one synchronous call, with no other code in it.
1857
+ *
1858
+ * @internal Exported for the unit tests.
1859
+ */
1860
+ export function eventCallerStorage(module: GlobalModule): AsyncLocalStorage<unknown> | null {
1861
+ const getEventCaller = module.getEventCaller;
1862
+ if (typeof getEventCaller !== "function") return null;
1863
+ const proto = AsyncLocalStorage.prototype;
1864
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- restored as the same unbound function
1865
+ const original = proto.getStore;
1866
+ let found: unknown = null;
1867
+ proto.getStore = function getStore(this: AsyncLocalStorage<unknown>): unknown {
1868
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- recording WHICH storage is read is the point
1869
+ found ??= this;
1870
+ return original.call(this);
1871
+ };
1872
+ try {
1873
+ getEventCaller();
1874
+ } catch {
1875
+ // An exotic build; no storage, so no invocation boundaries.
1876
+ } finally {
1877
+ proto.getStore = original;
1878
+ }
1879
+ return found instanceof AsyncLocalStorage ? (found as AsyncLocalStorage<unknown>) : null;
1880
+ }
1881
+
1882
+ /**
1883
+ * See every LlamaIndex invocation start and end: `withEventCaller` runs each
1884
+ * `@wrapEventCaller` method (a chat engine's `chat`, a query engine's `query`,
1885
+ * a provider's `chat`, `AgentRunner.chat`) as `storage.run(new EventCaller(...), fn)`.
1886
+ * An own `run` on that ONE storage object wraps `fn`; no other storage in the
1887
+ * process is touched, and uninstall restores it.
1888
+ *
1889
+ * This is the run boundary the callback bus lacks: without it a chat engine
1890
+ * has no start or end event at all, so its retrieval and its model call were
1891
+ * two unrelated root runs in two sessions.
1892
+ */
1893
+ function hookInvocations(current: Installed, storage: AsyncLocalStorage<unknown>): void {
1894
+ const state = current.state;
1895
+ // Whatever `run` this storage has now (normally the prototype's), called with the storage as `this`.
1896
+ const original = Reflect.get(storage, "run") as (this: unknown, ...args: unknown[]) => unknown;
1897
+ const replacement = function run(this: unknown, store: unknown, callback: unknown, ...args: unknown[]): unknown {
1898
+ if (!state.active || typeof callback !== "function" || !isObject(store) || !("caller" in store)) {
1899
+ return original.call(this, store, callback, ...args);
1900
+ }
1901
+ const fn = callback as (...a: unknown[]) => unknown;
1902
+ return original.call(
1903
+ this,
1904
+ store,
1905
+ function invocation(this: unknown, ...inner: unknown[]): unknown {
1906
+ return state.invocation(store, () => fn.apply(this, inner));
1907
+ },
1908
+ ...args,
1909
+ );
1910
+ };
1911
+ current.patcher.patch(storage, "run", replacement);
1912
+ }
1913
+
1914
+ /**
1915
+ * `BaseRetriever.prototype.retrieve` — a plain prototype method, so this also
1916
+ * covers retrievers built before `instrument()` — binds the retriever for the
1917
+ * `retrieve-start` it dispatches, which carries only the query.
1918
+ */
1919
+ function patchRetriever(current: Installed, module: RetrieverModule): void {
1920
+ const proto = module.BaseRetriever?.prototype as Record<string, unknown> | undefined;
1921
+ const ok = compat.probe(NAME, "BaseRetriever.retrieve", () => typeof proto?.retrieve === "function");
1922
+ if (!ok || !proto) return;
1923
+ const original = proto.retrieve as (...args: unknown[]) => unknown;
1924
+ const state = current.state;
1925
+ const replacement = function retrieve(this: object, ...args: unknown[]): unknown {
1926
+ if (!state.active || !isObject(this)) return original.apply(this, args);
1927
+ return state.retrievers.run(this, () => original.apply(this, args));
1928
+ };
1929
+ current.patcher.patch(proto, "retrieve", replacement);
1930
+ }
1931
+
1932
+ /**
1933
+ * Plain `createWorkflow()` workflows. workflow-core runs every step handler as
1934
+ * `handlerContextAsyncLocalStorage.run(handlerContext, …)`, and from 1.1 that
1935
+ * storage is an `AsyncContext.Variable` — a class exported from
1936
+ * `@llamaindex/workflow-core/async-context`, so its prototype `run` sees every
1937
+ * handler of every context, including workflows built before `instrument()`.
1938
+ * (Before 1.1, and on `@llama-flow/core`, it is a closure: nothing to hook.)
1939
+ * Only a value shaped like a handler context is acted on; every other use of
1940
+ * the class passes straight through.
1941
+ */
1942
+ function hookAsyncContext(current: Installed, module: AsyncContextModule): void {
1943
+ const proto = module.AsyncContext?.Variable?.prototype as Record<string, unknown> | undefined;
1944
+ if (typeof proto?.run !== "function") {
1945
+ logger.debug("llamaindex: this workflow-core has no AsyncContext.Variable; plain workflows are not runs.");
1946
+ return;
1947
+ }
1948
+ const original = proto.run as (value: unknown, fn: () => unknown) => unknown;
1949
+ const state = current.state;
1950
+ const replacement = function run(this: unknown, value: unknown, fn: () => unknown): unknown {
1951
+ if (!state.active || !isHandlerContext(value)) return original.call(this, value, fn);
1952
+ let proceeded = false;
1953
+ const proceed = (): unknown => {
1954
+ proceeded = true;
1955
+ return original.call(this, value, fn);
1956
+ };
1957
+ try {
1958
+ return state.plainStep(value, proceed);
1959
+ } catch (error) {
1960
+ // A failure of ours before the handler ran must not stop the workflow;
1961
+ // one from the handler (after `proceed`) is the application's own.
1962
+ if (proceeded) throw error;
1963
+ core.callSafely(
1964
+ () => {
1965
+ throw error;
1966
+ },
1967
+ [],
1968
+ `${NAME}.plainStep`,
1969
+ );
1970
+ return original.call(this, value, fn);
1971
+ }
1972
+ };
1973
+ current.patcher.patch(proto, "run", replacement);
1974
+ }
1975
+
1976
+ /** workflow-core's per-invocation handler context, by shape. */
1977
+ function isHandlerContext(value: unknown): value is Json {
1978
+ return (
1979
+ isObject(value) &&
1980
+ typeof read(value, "handler") === "function" &&
1981
+ Array.isArray(read(value, "inputs")) &&
1982
+ read(value, "next") instanceof Set &&
1983
+ "prev" in value
1984
+ );
1985
+ }
1986
+
1987
+ function patchWorkflow(current: Installed, module: WorkflowModule): void {
1988
+ const proto = module.AgentWorkflow?.prototype as Record<string, unknown> | undefined;
1989
+ const ok = compat.probe(NAME, "AgentWorkflow.runStream", () => typeof proto?.runStream === "function");
1990
+ if (!ok || !proto) return;
1991
+ const original = proto.runStream as (...args: unknown[]) => unknown;
1992
+ const state = current.state;
1993
+ const replacement = function runStream(this: Json, ...args: unknown[]): unknown {
1994
+ if (!state.active) return original.apply(this, args);
1995
+ const wf = read(this, "workflow") as Json | undefined;
1996
+ const createContext = read(wf, "createContext");
1997
+ const own = wf ? Object.getOwnPropertyDescriptor(wf, "createContext") : undefined;
1998
+ // Without the context there is no end signal, so an opened run would stay
1999
+ // open until the reaper: record the calls inside it as loose runs instead.
2000
+ const observable = compat.probe(
2001
+ NAME,
2002
+ "AgentWorkflow.workflow.createContext",
2003
+ () => typeof createContext === "function" && own?.writable === true,
2004
+ );
2005
+ if (!observable) return original.apply(this, args);
2006
+ let attach: ((context: unknown) => void) | undefined;
2007
+ let run: Run | null = null;
2008
+ core.callSafely(
2009
+ () => {
2010
+ ({ run, attach } = state.beginWorkflow(this, args[0], module));
2011
+ },
2012
+ [],
2013
+ `${NAME}.beginWorkflow`,
2014
+ );
2015
+ const intercept = attach !== undefined;
2016
+ if (intercept) {
2017
+ // `runStream` creates the context and sends the start event in one
2018
+ // synchronous call, and the first step runs inside that send. So the
2019
+ // subscriptions go on the context the moment it exists, for exactly the
2020
+ // duration of this call.
2021
+ wf!.createContext = function createContextOnce(this: unknown, ...inner: unknown[]): unknown {
2022
+ const context = (createContext as (...a: unknown[]) => unknown).apply(this, inner);
2023
+ core.callSafely(() => attach!(context), [], `${NAME}.attachContext`);
2024
+ return context;
2025
+ };
2026
+ }
2027
+ try {
2028
+ return original.apply(this, args);
2029
+ } catch (error) {
2030
+ core.callSafely(() => state.failWorkflow(run, error), [], `${NAME}.failWorkflow`);
2031
+ throw error;
2032
+ } finally {
2033
+ if (intercept && own) Object.defineProperty(wf!, "createContext", own);
2034
+ }
2035
+ };
2036
+ current.patcher.patch(proto, "runStream", replacement);
2037
+ }
2038
+
2039
+ // ---------------------------------------------------------------------------
2040
+ // The adapter
2041
+ // ---------------------------------------------------------------------------
2042
+
2043
+ async function loadCopies(specifier: string): Promise<unknown[] | null> {
2044
+ try {
2045
+ return await compat.requireModuleCopies(specifier, INSTALL);
2046
+ } catch {
2047
+ return null;
2048
+ }
2049
+ }
2050
+
2051
+ export const adapter: Adapter = {
2052
+ name: NAME,
2053
+
2054
+ async install(options: Record<string, unknown> = {}): Promise<void> {
2055
+ compat.checkVersion(NAME, PACKAGE, {
2056
+ minimum: MIN_VERSION,
2057
+ below: BELOW_VERSION,
2058
+ reason: "the first release whose agent() runs on the @llamaindex/workflow 1.1 runtime",
2059
+ });
2060
+
2061
+ // `@llamaindex/core/global` is where the bus singleton lives, and it is the
2062
+ // one module every LlamaIndex install has — an app on `@llamaindex/core` +
2063
+ // `@llamaindex/workflow` alone never installs the umbrella. The umbrella
2064
+ // re-exports the same `Settings`, so it is only the fallback for a layout
2065
+ // (pnpm, strict) where the app cannot resolve the scoped package itself.
2066
+ let globals = (await loadCopies("@llamaindex/core/global")) as GlobalModule[] | null;
2067
+ let frameworkPackage = compat.versionString(PACKAGE) !== null ? PACKAGE : CORE_PACKAGE;
2068
+ let retrievers = (await loadCopies("@llamaindex/core/retriever")) as RetrieverModule[] | null;
2069
+ if (!globals?.some((module) => module.Settings)) {
2070
+ globals = (await compat.requireModuleCopies(PACKAGE, INSTALL)) as GlobalModule[];
2071
+ frameworkPackage = PACKAGE;
2072
+ // The umbrella re-exports `BaseRetriever` from the same copy of core.
2073
+ retrievers = globals as RetrieverModule[];
2074
+ }
2075
+
2076
+ // The workflow package is optional: a legacy-agent or query-engine app does
2077
+ // not have it, and that is not a reason to record nothing.
2078
+ let workflows: WorkflowModule[] = [];
2079
+ if (compat.versionString(WORKFLOW_PACKAGE) !== null) {
2080
+ compat.checkVersion(NAME, WORKFLOW_PACKAGE, {
2081
+ minimum: WORKFLOW_MIN,
2082
+ below: WORKFLOW_BELOW,
2083
+ reason: "1.1 moved agent workflows onto @llamaindex/workflow-core",
2084
+ });
2085
+ workflows = ((await loadCopies(WORKFLOW_PACKAGE)) ?? []) as WorkflowModule[];
2086
+ }
2087
+ // Plain workflows: workflow-core ≥1.1 only (see `hookAsyncContext`). Resolved
2088
+ // from the application, so a layout that does not hoist it (pnpm) leaves
2089
+ // plain workflows unrecorded as runs — their model calls still are.
2090
+ const asyncContexts = ((await loadCopies(ASYNC_CONTEXT_MODULE)) ?? []) as AsyncContextModule[];
2091
+ attach(options, { globals, workflows, retrievers: retrievers ?? [], asyncContexts, frameworkPackage });
2092
+ },
2093
+
2094
+ uninstall(): void {
2095
+ const current = installed;
2096
+ installed = null;
2097
+ if (current === null) return;
2098
+ for (const unsubscribe of current.unsubscribe) {
2099
+ try {
2100
+ unsubscribe();
2101
+ } catch {
2102
+ // Detaching is best-effort; `state.active = false` below is what
2103
+ // actually stops events being recorded.
2104
+ }
2105
+ }
2106
+ current.patcher.restoreAll();
2107
+ core.callSafely(() => {
2108
+ current.state.shutdown();
2109
+ }, [], `${NAME}.shutdown`);
2110
+ },
2111
+ };