failproofai 1.0.7-beta.1 → 1.0.7-beta.2

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 (324) 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 +3 -3
  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.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +15 -15
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  51. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +7 -7
  54. package/.next/standalone/.next/server/app/settings/page.js +2 -2
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  58. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0yxwl6j._.js → [root-of-the-server]__0l44ual._.js} +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/{_214wgrp._.js → _1mel6y1._.js} +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/{_0bn2oo8._.js → _1v-jvrv._.js} +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  89. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  90. package/.next/standalone/.next/server/pages/404.html +1 -1
  91. package/.next/standalone/.next/server/pages/500.html +1 -1
  92. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  93. package/.next/standalone/.next/server/server-reference-manifest.json +22 -22
  94. package/.next/standalone/.next/static/chunks/043j99m8ykg__.css +2 -0
  95. package/.next/standalone/.next/static/chunks/0fqd7m_u81mi5.js +1 -0
  96. package/.next/standalone/.next/static/chunks/129ag2bw93bdh.js +1 -0
  97. package/.next/standalone/.next/static/chunks/{29fql3nbnfc9q.js → 1qd741hzlmjbo.js} +1 -1
  98. package/.next/standalone/.next/static/chunks/{0vmd180qfntfb.js → 2_pltstd8-xgs.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/{1u5zsejmgrir_.js → 2c8j9l6j_b1ci.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +1 -0
  101. package/.next/standalone/.next/static/chunks/{258668t68du6b.js → 3brze37td_wnc.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/3otmypm6j_xfo.js +6 -0
  103. package/.next/standalone/.next/static/chunks/{12tvm75t5ffui.js → 3ugmd_7dyn0id.js} +1 -1
  104. package/.next/standalone/.next/static/chunks/3yxro_r2_o9ad.js +69 -0
  105. package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
  106. package/.next/standalone/app/actions/get-jev-config.ts +38 -23
  107. package/.next/standalone/app/actions/update-jev-config.ts +1 -1
  108. package/.next/standalone/app/settings/jev-panel.tsx +33 -29
  109. package/.next/standalone/package.json +10 -10
  110. package/.next/standalone/sdk/python/skill/SKILL.md +60 -14
  111. package/.next/standalone/sdk/python/skill/agents/openai.yaml +2 -1
  112. package/.next/standalone/sdk/python/skill/references/evaluator.md +255 -0
  113. package/.next/standalone/sdk/python/skill/references/events.md +17 -8
  114. package/.next/standalone/sdk/python/skill/references/frameworks.md +3 -0
  115. package/.next/standalone/sdk/python/skill/references/install.md +3 -0
  116. package/.next/standalone/sdk/python/skill/references/integration.md +6 -2
  117. package/.next/standalone/sdk/python/skill/references/typescript.md +568 -0
  118. package/.next/standalone/sdk/typescript/CHANGELOG.md +119 -0
  119. package/.next/standalone/sdk/typescript/LICENSE +42 -0
  120. package/.next/standalone/sdk/typescript/README.md +552 -0
  121. package/.next/standalone/sdk/typescript/eslint.config.mjs +59 -0
  122. package/.next/standalone/sdk/typescript/examples/research-agent.ts +197 -0
  123. package/.next/standalone/sdk/typescript/integration/ai.test.ts +920 -0
  124. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/agent.ts +337 -0
  125. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +281 -0
  126. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +13 -0
  127. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/surfaces.ts +605 -0
  128. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.json +12 -0
  129. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.surfaces.json +4 -0
  130. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/agent.ts +342 -0
  131. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +168 -0
  132. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +13 -0
  133. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/surfaces.ts +628 -0
  134. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.json +12 -0
  135. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.surfaces.json +4 -0
  136. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/agent.ts +346 -0
  137. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +156 -0
  138. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +13 -0
  139. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/surfaces.ts +651 -0
  140. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.json +12 -0
  141. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.surfaces.json +4 -0
  142. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/agent.ts +350 -0
  143. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +153 -0
  144. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +13 -0
  145. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/surfaces.ts +651 -0
  146. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.json +12 -0
  147. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.surfaces.json +4 -0
  148. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/agent.ts +623 -0
  149. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +463 -0
  150. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +15 -0
  151. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/tsconfig.json +12 -0
  152. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent-v1.ts +99 -0
  153. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent.ts +623 -0
  154. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +336 -0
  155. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +15 -0
  156. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/tsconfig.json +12 -0
  157. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/agent.ts +96 -0
  158. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +579 -0
  159. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +15 -0
  160. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/tsconfig.json +12 -0
  161. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.cjs +53 -0
  162. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.mjs +55 -0
  163. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/package.json +18 -0
  164. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/agent.ts +659 -0
  165. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +635 -0
  166. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +15 -0
  167. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/tsconfig.json +12 -0
  168. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/agent.ts +659 -0
  169. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +553 -0
  170. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +15 -0
  171. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/tsconfig.json +12 -0
  172. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/agent.ts +877 -0
  173. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/mcp-server.mjs +66 -0
  174. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +6417 -0
  175. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +15 -0
  176. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/tsconfig.json +12 -0
  177. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/agent.ts +872 -0
  178. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/mcp-server.mjs +66 -0
  179. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +2540 -0
  180. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +15 -0
  181. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/tsconfig.json +12 -0
  182. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/actions.ts +18 -0
  183. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/ai/route.ts +42 -0
  184. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/edge/route.ts +29 -0
  185. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/langgraph/route.ts +21 -0
  186. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/llamaindex/route.ts +11 -0
  187. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/mastra/route.ts +19 -0
  188. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/status/route.ts +7 -0
  189. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/layout.tsx +9 -0
  190. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/page.tsx +15 -0
  191. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/instrumentation.ts +15 -0
  192. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/ai.ts +61 -0
  193. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/langgraph.ts +68 -0
  194. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/llamaindex.ts +98 -0
  195. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/mastra.ts +90 -0
  196. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/next.config.ts +52 -0
  197. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +4343 -0
  198. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +28 -0
  199. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/agent.ts +51 -0
  200. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/deno-npm.ts +76 -0
  201. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +484 -0
  202. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +15 -0
  203. package/.next/standalone/sdk/typescript/integration/fixtures/types/agent.ts +79 -0
  204. package/.next/standalone/sdk/typescript/integration/fixtures/types/package-lock.json +740 -0
  205. package/.next/standalone/sdk/typescript/integration/fixtures/types/package.json +11 -0
  206. package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/agent.ts +197 -0
  207. package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package-lock.json +70 -0
  208. package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package.json +12 -0
  209. package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/tsconfig.json +12 -0
  210. package/.next/standalone/sdk/typescript/integration/global-setup.ts +29 -0
  211. package/.next/standalone/sdk/typescript/integration/harness.ts +451 -0
  212. package/.next/standalone/sdk/typescript/integration/langchain.test.ts +682 -0
  213. package/.next/standalone/sdk/typescript/integration/llamaindex.test.ts +709 -0
  214. package/.next/standalone/sdk/typescript/integration/mastra-coverage.test.ts +386 -0
  215. package/.next/standalone/sdk/typescript/integration/mastra.test.ts +311 -0
  216. package/.next/standalone/sdk/typescript/integration/nextjs.test.ts +341 -0
  217. package/.next/standalone/sdk/typescript/integration/runtime-parity.ts +180 -0
  218. package/.next/standalone/sdk/typescript/integration/runtimes.bun.test.ts +15 -0
  219. package/.next/standalone/sdk/typescript/integration/runtimes.core.test.ts +113 -0
  220. package/.next/standalone/sdk/typescript/integration/runtimes.deno.test.ts +19 -0
  221. package/.next/standalone/sdk/typescript/integration/types.test.ts +141 -0
  222. package/.next/standalone/sdk/typescript/integration/vanilla.test.ts +255 -0
  223. package/.next/standalone/sdk/typescript/package-lock.json +2640 -0
  224. package/.next/standalone/sdk/typescript/package.json +401 -0
  225. package/.next/standalone/sdk/typescript/scripts/finalize-build.mjs +123 -0
  226. package/.next/standalone/sdk/typescript/scripts/release.mjs +177 -0
  227. package/.next/standalone/sdk/typescript/src/clock.ts +58 -0
  228. package/.next/standalone/sdk/typescript/src/context.ts +214 -0
  229. package/.next/standalone/sdk/typescript/src/edge/adapter.ts +18 -0
  230. package/.next/standalone/sdk/typescript/src/edge/ai.ts +116 -0
  231. package/.next/standalone/sdk/typescript/src/edge/index.ts +238 -0
  232. package/.next/standalone/sdk/typescript/src/edge/langchain.ts +18 -0
  233. package/.next/standalone/sdk/typescript/src/edge/llamaindex.ts +12 -0
  234. package/.next/standalone/sdk/typescript/src/edge/mastra.ts +17 -0
  235. package/.next/standalone/sdk/typescript/src/edge/notice.ts +33 -0
  236. package/.next/standalone/sdk/typescript/src/environment.ts +75 -0
  237. package/.next/standalone/sdk/typescript/src/evaluator/authoring.ts +480 -0
  238. package/.next/standalone/sdk/typescript/src/evaluator/cli.ts +96 -0
  239. package/.next/standalone/sdk/typescript/src/evaluator/client.ts +421 -0
  240. package/.next/standalone/sdk/typescript/src/evaluator/expression.ts +1292 -0
  241. package/.next/standalone/sdk/typescript/src/evaluator/index.ts +144 -0
  242. package/.next/standalone/sdk/typescript/src/evaluator/protocol.ts +747 -0
  243. package/.next/standalone/sdk/typescript/src/evaluator/runtime.ts +930 -0
  244. package/.next/standalone/sdk/typescript/src/evaluator/sandbox-worker.ts +171 -0
  245. package/.next/standalone/sdk/typescript/src/evaluator/source-limits.ts +27 -0
  246. package/.next/standalone/sdk/typescript/src/evaluator/source.ts +509 -0
  247. package/.next/standalone/sdk/typescript/src/events.ts +879 -0
  248. package/.next/standalone/sdk/typescript/src/exit.ts +117 -0
  249. package/.next/standalone/sdk/typescript/src/index.ts +186 -0
  250. package/.next/standalone/sdk/typescript/src/integrations/ai.ts +1566 -0
  251. package/.next/standalone/sdk/typescript/src/integrations/compat.ts +322 -0
  252. package/.next/standalone/sdk/typescript/src/integrations/core.ts +1321 -0
  253. package/.next/standalone/sdk/typescript/src/integrations/index.ts +355 -0
  254. package/.next/standalone/sdk/typescript/src/integrations/langchain.ts +2340 -0
  255. package/.next/standalone/sdk/typescript/src/integrations/llamaindex.ts +2111 -0
  256. package/.next/standalone/sdk/typescript/src/integrations/mastra.ts +1802 -0
  257. package/.next/standalone/sdk/typescript/src/logger.ts +98 -0
  258. package/.next/standalone/sdk/typescript/src/next.ts +115 -0
  259. package/.next/standalone/sdk/typescript/src/node-require.ts +446 -0
  260. package/.next/standalone/sdk/typescript/src/redact.ts +305 -0
  261. package/.next/standalone/sdk/typescript/src/resolver.ts +120 -0
  262. package/.next/standalone/sdk/typescript/src/runtime.ts +29 -0
  263. package/.next/standalone/sdk/typescript/src/schema.ts +410 -0
  264. package/.next/standalone/sdk/typescript/src/scopes.ts +701 -0
  265. package/.next/standalone/sdk/typescript/src/shared.ts +33 -0
  266. package/.next/standalone/sdk/typescript/src/version.ts +5 -0
  267. package/.next/standalone/sdk/typescript/src/writer.ts +934 -0
  268. package/.next/standalone/sdk/typescript/test/adapters.test.ts +397 -0
  269. package/.next/standalone/sdk/typescript/test/ai.test.ts +1076 -0
  270. package/.next/standalone/sdk/typescript/test/copies.test.ts +204 -0
  271. package/.next/standalone/sdk/typescript/test/edge.test.ts +183 -0
  272. package/.next/standalone/sdk/typescript/test/evaluator-client.test.ts +234 -0
  273. package/.next/standalone/sdk/typescript/test/evaluator-protocol.test.ts +225 -0
  274. package/.next/standalone/sdk/typescript/test/events.test.ts +193 -0
  275. package/.next/standalone/sdk/typescript/test/expression.test.ts +181 -0
  276. package/.next/standalone/sdk/typescript/test/global-setup.ts +26 -0
  277. package/.next/standalone/sdk/typescript/test/helpers.ts +130 -0
  278. package/.next/standalone/sdk/typescript/test/integrations.test.ts +369 -0
  279. package/.next/standalone/sdk/typescript/test/langchain-copies.test.ts +204 -0
  280. package/.next/standalone/sdk/typescript/test/langchain.test.ts +999 -0
  281. package/.next/standalone/sdk/typescript/test/llamaindex.test.ts +1760 -0
  282. package/.next/standalone/sdk/typescript/test/mastra-coverage.test.ts +501 -0
  283. package/.next/standalone/sdk/typescript/test/mastra-lifecycle.test.ts +479 -0
  284. package/.next/standalone/sdk/typescript/test/mastra.test.ts +285 -0
  285. package/.next/standalone/sdk/typescript/test/next.test.ts +109 -0
  286. package/.next/standalone/sdk/typescript/test/packaging.test.ts +312 -0
  287. package/.next/standalone/sdk/typescript/test/redaction.test.ts +171 -0
  288. package/.next/standalone/sdk/typescript/test/runtimes.test.ts +101 -0
  289. package/.next/standalone/sdk/typescript/test/sandbox.test.ts +189 -0
  290. package/.next/standalone/sdk/typescript/test/scopes.test.ts +271 -0
  291. package/.next/standalone/sdk/typescript/test/setup.ts +19 -0
  292. package/.next/standalone/sdk/typescript/test/skill-snippets.test.ts +73 -0
  293. package/.next/standalone/sdk/typescript/test/spool-contract.test.ts +124 -0
  294. package/.next/standalone/sdk/typescript/test/tracker-bounds.test.ts +191 -0
  295. package/.next/standalone/sdk/typescript/test/wire-format.test.ts +214 -0
  296. package/.next/standalone/sdk/typescript/test/writer.test.ts +407 -0
  297. package/.next/standalone/sdk/typescript/tsconfig.build.json +15 -0
  298. package/.next/standalone/sdk/typescript/tsconfig.cjs.json +19 -0
  299. package/.next/standalone/sdk/typescript/tsconfig.json +28 -0
  300. package/.next/standalone/sdk/typescript/vitest.config.ts +33 -0
  301. package/.next/standalone/sdk/typescript/vitest.integration.config.ts +23 -0
  302. package/.next/standalone/server.js +1 -1
  303. package/README.md +2 -2
  304. package/dist/cli.mjs +208 -27
  305. package/dist/worker.mjs +120 -14
  306. package/package.json +10 -10
  307. package/scripts/build-policy-pack.mjs +14 -0
  308. package/src/audit/features.ts +3 -2
  309. package/src/hooks/builtin-policies.ts +156 -5
  310. package/src/hooks/jev-cli.ts +57 -1
  311. package/src/hooks/manager.ts +1 -1
  312. package/src/hooks/pack-cli.ts +150 -18
  313. package/src/hooks/pack-store.ts +1 -1
  314. package/src/hooks/policy-catalog.ts +148 -9
  315. package/src/hooks/types.ts +1 -1
  316. package/.next/standalone/.next/static/chunks/0cd-_8-c-m1ea.js +0 -6
  317. package/.next/standalone/.next/static/chunks/0qrbdkv9qmvli.js +0 -69
  318. package/.next/standalone/.next/static/chunks/0uldbut9y2-e8.js +0 -1
  319. package/.next/standalone/.next/static/chunks/285spx855h_3r.css +0 -2
  320. package/.next/standalone/.next/static/chunks/2vkvu9-opa_1z.js +0 -1
  321. package/.next/standalone/.next/static/chunks/37lhv7wa3ywt6.js +0 -1
  322. /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_buildManifest.js +0 -0
  323. /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_clientMiddlewareManifest.js +0 -0
  324. /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_ssgManifest.js +0 -0
@@ -0,0 +1,1076 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
7
+
8
+ import * as core from "../src/integrations/core.js";
9
+ import {
10
+ FailproofSpan,
11
+ _internals,
12
+ adapter,
13
+ integration,
14
+ middleware,
15
+ responseContent,
16
+ stopReasonOf,
17
+ toolCallsOf,
18
+ tracer,
19
+ usageTokens,
20
+ } from "../src/integrations/ai.js";
21
+ import { setLogger } from "../src/logger.js";
22
+ import { runtime } from "../src/runtime.js";
23
+ import { agent, session } from "../src/scopes.js";
24
+ import { flushed, useSpool } from "./helpers.js";
25
+ import type { Spool } from "./helpers.js";
26
+
27
+ /**
28
+ * The Vercel AI SDK adapter's logic, against the shapes each `ai` major hands
29
+ * it. The real-framework proof is `integration/ai.test.ts`; this file pins the
30
+ * translation rules it depends on, without installing four copies of `ai`.
31
+ */
32
+
33
+ let spool: Spool;
34
+ const warnings: string[] = [];
35
+
36
+ beforeEach(() => {
37
+ spool = useSpool();
38
+ core.resetFailures();
39
+ warnings.length = 0;
40
+ setLogger({
41
+ debug: () => undefined,
42
+ info: () => undefined,
43
+ warn: (message: string) => warnings.push(message),
44
+ error: (message: string) => warnings.push(message),
45
+ });
46
+ });
47
+ afterEach(async () => {
48
+ adapter.uninstall();
49
+ await spool.cleanup();
50
+ setLogger(null);
51
+ });
52
+
53
+ const types = (events: Array<Record<string, unknown>>) => events.map((e) => e.type);
54
+ const count = (events: Array<Record<string, unknown>>, type: string) => events.filter((e) => e.type === type).length;
55
+
56
+ interface Span {
57
+ setAttributes: (a: Record<string, unknown>) => unknown;
58
+ recordException: (e: unknown) => void;
59
+ setStatus: (s: { code: number; message?: string }) => unknown;
60
+ end: () => void;
61
+ }
62
+
63
+ describe("reading every major's spelling", () => {
64
+ it("reads tokens from v4, v5, v6+ and v7-normalised usage", () => {
65
+ expect(usageTokens({ promptTokens: 5, completionTokens: 7 })).toEqual({ inputTokens: 5, outputTokens: 7 });
66
+ expect(usageTokens({ inputTokens: 11, outputTokens: 7, totalTokens: 18 })).toEqual({ inputTokens: 11, outputTokens: 7 });
67
+ expect(
68
+ usageTokens({ inputTokens: { total: 23, noCache: 23 }, outputTokens: { total: 9, text: 9 } }),
69
+ ).toEqual({ inputTokens: 23, outputTokens: 9 });
70
+ expect(usageTokens({ tokens: 4 })).toEqual({ inputTokens: 4, outputTokens: undefined });
71
+ expect(usageTokens(undefined)).toEqual({});
72
+ // A count that is not a count is dropped, never shipped as NaN.
73
+ expect(usageTokens({ inputTokens: { total: undefined }, outputTokens: "x" })).toEqual({
74
+ inputTokens: undefined,
75
+ outputTokens: undefined,
76
+ });
77
+ });
78
+
79
+ it("reduces every finish-reason shape to a string", () => {
80
+ expect(stopReasonOf("stop")).toBe("stop");
81
+ expect(stopReasonOf({ unified: "tool-calls", raw: "tool_use" })).toBe("tool-calls");
82
+ expect(stopReasonOf(["length"])).toBe("length");
83
+ expect(stopReasonOf("")).toBeUndefined();
84
+ expect(stopReasonOf({})).toBeUndefined();
85
+ });
86
+
87
+ it("uses the tool calls as content when a step produced no text", () => {
88
+ const call = { type: "tool-call", toolCallId: "c1", toolName: "weather", input: '{"city":"Paris"}' };
89
+ expect(responseContent({ content: [call] })).toEqual([
90
+ { toolCallId: "c1", toolName: "weather", input: { city: "Paris" } },
91
+ ]);
92
+ expect(responseContent({ content: [{ type: "text", text: "hi" }, call] })).toBe("hi");
93
+ // v4: `text` is "" on a tool-call step — "" is not content.
94
+ expect(
95
+ responseContent({ text: "", toolCalls: [{ toolCallId: "c1", toolName: "weather", args: "{}" }] }),
96
+ ).toEqual([{ toolCallId: "c1", toolName: "weather", input: {} }]);
97
+ expect(responseContent({ text: "" })).toBeUndefined();
98
+ });
99
+ });
100
+
101
+ describe("the tracer (ai v4–v6)", () => {
102
+ it("never ends a span for the caller: a stream span stays open until the SDK ends it", async () => {
103
+ const t = tracer();
104
+ let stream: Span | undefined;
105
+ await session({ sessionId: "s1" }, async () => {
106
+ // `streamText` opens its root span with endWhenDone: false and returns
107
+ // before a single token has been produced.
108
+ await t.startActiveSpan(
109
+ "ai.streamText",
110
+ { attributes: { "ai.operationId": "ai.streamText", "ai.telemetry.functionId": "streamer" } },
111
+ async (span: Span) => {
112
+ stream = span;
113
+ },
114
+ );
115
+ });
116
+ expect(types(await flushed(spool))).toEqual(["agent_start"]);
117
+ stream!.end();
118
+ expect(types(await flushed(spool))).toEqual(["agent_start", "agent_end"]);
119
+ });
120
+
121
+ it("stamps the model request when the step starts, before the tool it causes", async () => {
122
+ const t = tracer();
123
+ await session({ sessionId: "s1" }, () =>
124
+ t.startActiveSpan("ai.streamText", { attributes: { "ai.operationId": "ai.streamText" } }, async (root: Span) => {
125
+ let step: Span | undefined;
126
+ t.startActiveSpan(
127
+ "ai.streamText.doStream",
128
+ { attributes: { "ai.operationId": "ai.streamText.doStream", "ai.model.id": "m" } },
129
+ (span: Span) => {
130
+ step = span;
131
+ },
132
+ );
133
+ // The tool runs while the model's stream is still open.
134
+ t.startActiveSpan(
135
+ "ai.toolCall",
136
+ { attributes: { "ai.operationId": "ai.toolCall", "ai.toolCall.name": "weather", "ai.toolCall.id": "c1" } },
137
+ (span: Span) => span.end(),
138
+ );
139
+ step!.setAttributes({
140
+ "ai.response.finishReason": "tool-calls",
141
+ "ai.response.text": "",
142
+ "ai.response.toolCalls": JSON.stringify([{ toolCallId: "c1", toolName: "weather", input: "{}" }]),
143
+ });
144
+ step!.end();
145
+ root.end();
146
+ }),
147
+ );
148
+ const events = await flushed(spool);
149
+ expect(types(events)).toEqual([
150
+ "agent_start",
151
+ "model_request",
152
+ "tool_use",
153
+ "tool_result",
154
+ "model_response",
155
+ "agent_end",
156
+ ]);
157
+ expect(events.every((e) => e.agent_id === "ai.streamText")).toBe(true);
158
+ const response = events.find((e) => e.type === "model_response")!;
159
+ expect(response.stop_reason).toBe("tool-calls");
160
+ // The call's input is parsed, as on every other path — not a JSON string in JSON.
161
+ expect(response.content).toEqual([{ toolCallId: "c1", toolName: "weather", input: {} }]);
162
+ });
163
+
164
+ it("records a failed model step once, on its model_response", async () => {
165
+ const t = tracer();
166
+ await session({ sessionId: "s1" }, async () => {
167
+ await expect(
168
+ t.startActiveSpan("ai.generateText", { attributes: { "ai.operationId": "ai.generateText" } }, async (root: Span) => {
169
+ try {
170
+ await t.startActiveSpan(
171
+ "ai.generateText.doGenerate",
172
+ { attributes: { "ai.operationId": "ai.generateText.doGenerate", "ai.model.id": "m" } },
173
+ async (span: Span) => {
174
+ const error = new Error("model exploded");
175
+ span.recordException(error);
176
+ span.setStatus({ code: 2, message: error.message });
177
+ span.end();
178
+ throw error;
179
+ },
180
+ );
181
+ } catch (error) {
182
+ root.recordException(error);
183
+ root.setStatus({ code: 2 });
184
+ root.end();
185
+ throw error;
186
+ }
187
+ }),
188
+ ).rejects.toThrow("model exploded");
189
+ });
190
+ const events = await flushed(spool);
191
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
192
+ const response = events.find((e) => e.type === "model_response")!;
193
+ expect(response.error).toBe("Error: model exploded");
194
+ expect(response.stop_reason).toBe("error");
195
+ expect(events.at(-1)!.outcome).toBe("failed");
196
+ });
197
+
198
+ it("nests an operation under an enclosing agent() scope", async () => {
199
+ const t = tracer();
200
+ await session({ sessionId: "s1" }, () =>
201
+ agent("planner", () =>
202
+ t.startActiveSpan("ai.generateText", { attributes: { "ai.operationId": "ai.generateText" } }, (span: Span) =>
203
+ span.end(),
204
+ ),
205
+ ),
206
+ );
207
+ const events = await flushed(spool);
208
+ const start = events.find((e) => e.type === "agent_start" && e.agent_id === "ai.generateText")!;
209
+ expect(start.parent_id).toBe("planner");
210
+ expect(start.session_id).toBe("s1");
211
+ });
212
+ });
213
+
214
+ describe("the telemetry integration (ai v7)", () => {
215
+ const usage = (i: number, o: number) => ({ inputTokens: i, outputTokens: o, totalTokens: i + o });
216
+
217
+ it("turns v7's lifecycle callbacks into one agent, model pairs and tool pairs", async () => {
218
+ await session({ sessionId: "s1" }, async () => {
219
+ const callId = "call-abc";
220
+ const base = { callId, functionId: "weather-agent", provider: "p", modelId: "m" };
221
+ integration.onStart({ ...base, operationId: "ai.generateText" });
222
+ integration.onLanguageModelCallStart({ ...base, messages: [{ role: "user", content: "hi" }] });
223
+ await integration.executeLanguageModelCall({ callId, execute: async () => "ok" });
224
+ integration.onLanguageModelCallEnd({
225
+ ...base,
226
+ finishReason: "tool-calls",
227
+ usage: usage(11, 7),
228
+ content: [{ type: "tool-call", toolCallId: "tc1", toolName: "weather", input: { city: "Paris" } }],
229
+ performance: { responseTimeMs: 12.6 },
230
+ });
231
+ const toolCall = { type: "tool-call", toolCallId: "tc1", toolName: "weather", input: { city: "Paris" } };
232
+ integration.onToolExecutionStart({ ...base, toolCall });
233
+ integration.onToolExecutionEnd({ ...base, toolCall, toolOutput: { type: "tool-result", output: { c: 20 } } });
234
+ integration.onLanguageModelCallStart({ ...base, messages: [] });
235
+ integration.onLanguageModelCallEnd({ ...base, finishReason: "stop", usage: usage(23, 9), content: [{ type: "text", text: "sunny" }] });
236
+ integration.onEnd({ ...base, finishReason: "stop" });
237
+ });
238
+ const events = await flushed(spool);
239
+ expect(types(events)).toEqual([
240
+ "agent_start",
241
+ "model_request",
242
+ "model_response",
243
+ "tool_use",
244
+ "tool_result",
245
+ "model_request",
246
+ "model_response",
247
+ "agent_end",
248
+ ]);
249
+ expect(events.every((e) => e.agent_id === "weather-agent" && e.session_id === "s1")).toBe(true);
250
+ const responses = events.filter((e) => e.type === "model_response");
251
+ expect(responses.map((e) => [e.input_tokens, e.output_tokens, e.stop_reason])).toEqual([
252
+ [11, 7, "tool-calls"],
253
+ [23, 9, "stop"],
254
+ ]);
255
+ expect(responses[0]!.duration_ms).toBe(13);
256
+ const requests = events.filter((e) => e.type === "model_request");
257
+ expect(responses.map((e) => e.request_id)).toEqual(requests.map((e) => e.request_id));
258
+ expect(events.find((e) => e.type === "tool_use")!.tool_call_id).toBe("tc1");
259
+ expect(events.find((e) => e.type === "tool_result")!.output).toEqual({ c: 20 });
260
+ expect(events.at(-1)!.outcome).toBe("success");
261
+ });
262
+
263
+ it("ignores ai v6's integration events, which carry no callId (v6 records through the tracer)", async () => {
264
+ await session({ sessionId: "s1" }, () => {
265
+ integration.onStart({ functionId: "x", model: { modelId: "m" } });
266
+ integration.onToolExecutionStart({ toolCall: { toolCallId: "t", toolName: "w" } });
267
+ integration.onEnd({ finishReason: "stop" });
268
+ });
269
+ expect(await flushed(spool)).toEqual([]);
270
+ });
271
+
272
+ it("records a failing provider call once, on its model_response", async () => {
273
+ await session({ sessionId: "s1" }, async () => {
274
+ const base = { callId: "c2", functionId: "f", modelId: "m" };
275
+ integration.onStart({ ...base, operationId: "ai.generateText" });
276
+ integration.onLanguageModelCallStart(base);
277
+ await expect(
278
+ integration.executeLanguageModelCall({
279
+ callId: "c2",
280
+ execute: async () => {
281
+ throw new Error("model exploded");
282
+ },
283
+ }),
284
+ ).rejects.toThrow("model exploded");
285
+ integration.onError({ callId: "c2", error: new Error("model exploded") });
286
+ });
287
+ const events = await flushed(spool);
288
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
289
+ expect(events[2]!.error).toBe("Error: model exploded");
290
+ expect(events[2]!.stop_reason).toBe("error");
291
+ expect(events[3]!.outcome).toBe("failed");
292
+ });
293
+
294
+ it("emits a single error when the operation itself failed and no leaf carried it", async () => {
295
+ await session({ sessionId: "s1" }, () => {
296
+ integration.onStart({ callId: "c3", operationId: "ai.streamObject", functionId: "extract" });
297
+ integration.onEnd({ callId: "c3", error: new Error("schema mismatch") });
298
+ });
299
+ const events = await flushed(spool);
300
+ expect(types(events)).toEqual(["agent_start", "error", "agent_end"]);
301
+ expect(events[2]!.outcome).toBe("failed");
302
+ });
303
+
304
+ it("records a failing tool on its tool_result", async () => {
305
+ await session({ sessionId: "s1" }, () => {
306
+ const toolCall = { toolCallId: "t1", toolName: "weather", input: {} };
307
+ integration.onStart({ callId: "c4", operationId: "ai.generateText", functionId: "f" });
308
+ integration.onToolExecutionStart({ callId: "c4", toolCall });
309
+ integration.onToolExecutionEnd({ callId: "c4", toolCall, toolOutput: { type: "tool-error", error: new Error("down") } });
310
+ integration.onEnd({ callId: "c4" });
311
+ });
312
+ const events = await flushed(spool);
313
+ expect(events.find((e) => e.type === "tool_result")!.error).toBe("Error: down");
314
+ expect(types(events)).not.toContain("error");
315
+ });
316
+
317
+ it("nests an operation started inside a tool under the operation that called it", async () => {
318
+ await session({ sessionId: "s1" }, async () => {
319
+ integration.onStart({ callId: "outer", operationId: "ai.generateText", functionId: "supervisor" });
320
+ await integration.executeTool({
321
+ callId: "outer",
322
+ execute: async () => {
323
+ integration.onStart({ callId: "inner", operationId: "ai.generateText", functionId: "researcher" });
324
+ integration.onEnd({ callId: "inner" });
325
+ },
326
+ });
327
+ integration.onEnd({ callId: "outer" });
328
+ });
329
+ const events = await flushed(spool);
330
+ const inner = events.find((e) => e.type === "agent_start" && e.agent_id === "researcher")!;
331
+ expect(inner.parent_id).toBe("supervisor");
332
+ });
333
+
334
+ it("is registered process-wide by instrument() exactly once, and removed by uninstrument()", async () => {
335
+ const g = globalThis as { AI_SDK_TELEMETRY_INTEGRATIONS?: unknown[] };
336
+ const before = g.AI_SDK_TELEMETRY_INTEGRATIONS;
337
+ try {
338
+ g.AI_SDK_TELEMETRY_INTEGRATIONS = [{ someone: "else" }];
339
+ await adapter.install({});
340
+ await adapter.install({});
341
+ expect(g.AI_SDK_TELEMETRY_INTEGRATIONS.filter((i) => i === integration)).toHaveLength(1);
342
+ adapter.uninstall();
343
+ expect(g.AI_SDK_TELEMETRY_INTEGRATIONS).toEqual([{ someone: "else" }]);
344
+ } finally {
345
+ g.AI_SDK_TELEMETRY_INTEGRATIONS = before;
346
+ }
347
+ });
348
+ });
349
+
350
+ describe("the middleware", () => {
351
+ const generate = (result: Record<string, unknown>) => ({
352
+ params: { prompt: [{ role: "user", content: "hi" }] },
353
+ model: { modelId: "gpt-x", provider: "p" },
354
+ doGenerate: async () => result,
355
+ });
356
+
357
+ it("makes a call outside any scope its own run, in a session of its own", async () => {
358
+ await middleware().wrapGenerate(generate({ content: [{ type: "text", text: "hi" }], finishReason: "stop" }));
359
+ const events = await flushed(spool);
360
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
361
+ expect(events.every((e) => e.agent_id === "gpt-x")).toBe(true);
362
+ expect(new Set(events.map((e) => e.session_id)).size).toBe(1);
363
+ expect(warnings).toEqual([]);
364
+ });
365
+
366
+ it("records a call inside agent() as a step of that agent", async () => {
367
+ await session({ sessionId: "s1" }, () =>
368
+ agent("planner", () =>
369
+ middleware().wrapGenerate(
370
+ generate({
371
+ content: [{ type: "text", text: "ok" }],
372
+ finishReason: { unified: "stop", raw: "end_turn" },
373
+ usage: { inputTokens: { total: 3 }, outputTokens: { total: 4 } },
374
+ }),
375
+ ),
376
+ ),
377
+ );
378
+ const events = await flushed(spool);
379
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
380
+ expect(events.every((e) => e.agent_id === "planner")).toBe(true);
381
+ const response = events[2]!;
382
+ expect([response.input_tokens, response.output_tokens, response.stop_reason]).toEqual([3, 4, "stop"]);
383
+ });
384
+
385
+ it("records a failing model on its model_response, and rethrows", async () => {
386
+ await expect(
387
+ middleware().wrapGenerate({
388
+ params: {},
389
+ model: { modelId: "gpt-x" },
390
+ doGenerate: async () => {
391
+ throw new Error("rate limited");
392
+ },
393
+ }),
394
+ ).rejects.toThrow("rate limited");
395
+ const events = await flushed(spool);
396
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
397
+ expect(events[2]!.error).toBe("Error: rate limited");
398
+ expect(events[3]!.outcome).toBe("failed");
399
+ });
400
+
401
+ it("defers to the tracer inside a model span, so each call is recorded once", async () => {
402
+ const t = tracer();
403
+ await session({ sessionId: "s1" }, () =>
404
+ t.startActiveSpan(
405
+ "ai.generateText.doGenerate",
406
+ { attributes: { "ai.operationId": "ai.generateText.doGenerate" } },
407
+ async (span: Span) => {
408
+ await middleware().wrapGenerate(generate({ text: "x" }));
409
+ span.end();
410
+ },
411
+ ),
412
+ );
413
+ const events = await flushed(spool);
414
+ expect(types(events)).toEqual(["model_request", "model_response"]);
415
+ });
416
+
417
+ it("defers to the v7 integration inside executeLanguageModelCall", async () => {
418
+ await integration.executeLanguageModelCall({
419
+ execute: () => middleware().wrapGenerate(generate({ text: "x" })),
420
+ });
421
+ expect(await flushed(spool)).toEqual([]);
422
+ });
423
+
424
+ it("reads a v4 stream's textDelta parts", async () => {
425
+ const result = await middleware().wrapStream({
426
+ params: {},
427
+ model: { modelId: "gpt-x" },
428
+ doStream: async () => ({
429
+ stream: new ReadableStream({
430
+ start(controller) {
431
+ controller.enqueue({ type: "text-delta", textDelta: "he" });
432
+ controller.enqueue({ type: "text-delta", textDelta: "y" });
433
+ controller.enqueue({ type: "finish", finishReason: "stop", usage: { promptTokens: 1, completionTokens: 2 } });
434
+ controller.close();
435
+ },
436
+ }),
437
+ }),
438
+ });
439
+ for await (const _ of result.stream as unknown as AsyncIterable<unknown>) void _;
440
+ const response = (await flushed(spool)).find((e) => e.type === "model_response")!;
441
+ expect([response.content, response.input_tokens, response.output_tokens, response.stop_reason]).toEqual([
442
+ "hey",
443
+ 1,
444
+ 2,
445
+ "stop",
446
+ ]);
447
+ });
448
+ });
449
+
450
+ describe("the middleware on a stream that does not finish", () => {
451
+ const endless = (onCancel: (reason: unknown) => void) =>
452
+ new ReadableStream({
453
+ pull(controller) {
454
+ controller.enqueue({ type: "text-delta", delta: "x" });
455
+ },
456
+ cancel(reason) {
457
+ onCancel(reason);
458
+ },
459
+ });
460
+
461
+ it("closes a streamed call the consumer cancels, as cancelled, and cancels the provider stream", async () => {
462
+ let sourceCancelled: unknown;
463
+ const result = await middleware().wrapStream({
464
+ params: {},
465
+ model: { modelId: "gpt-x" },
466
+ doStream: async () => ({ stream: endless((reason) => (sourceCancelled = reason)) }),
467
+ });
468
+ const reader = (result.stream as ReadableStream<unknown>).getReader();
469
+ await reader.read();
470
+ await reader.read();
471
+ await reader.cancel("client disconnected");
472
+ const events = await flushed(spool);
473
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
474
+ const response = events[2]!;
475
+ expect(response.stop_reason).toBe("cancelled");
476
+ expect(response.error).toBeUndefined();
477
+ expect(response.content).toMatch(/^x+$/);
478
+ expect(response.request_id).toBe(events[1]!.request_id);
479
+ expect(events[3]!.outcome).toBe("cancelled");
480
+ expect(sourceCancelled).toBe("client disconnected");
481
+ expect(count(events, "error")).toBe(0);
482
+ });
483
+
484
+ it("closes a streamed call whose stream errors, with the error, and ends its run failed", async () => {
485
+ const result = await middleware().wrapStream({
486
+ params: {},
487
+ model: { modelId: "gpt-x" },
488
+ doStream: async () => ({
489
+ stream: new ReadableStream({
490
+ async pull(controller) {
491
+ controller.enqueue({ type: "text-delta", delta: "y" });
492
+ await Promise.resolve();
493
+ controller.error(new Error("ECONNRESET"));
494
+ },
495
+ }),
496
+ }),
497
+ });
498
+ const reader = (result.stream as ReadableStream<unknown>).getReader();
499
+ await expect(
500
+ (async () => {
501
+ while (!(await reader.read()).done) {
502
+ /* drain */
503
+ }
504
+ })(),
505
+ ).rejects.toThrow("ECONNRESET");
506
+ const events = await flushed(spool);
507
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
508
+ expect(events[2]!.stop_reason).toBe("error");
509
+ expect(events[2]!.error).toBe("Error: ECONNRESET");
510
+ expect(events[3]!.outcome).toBe("failed");
511
+ // Recorded once, on the model_response — not again as an `error` event.
512
+ expect(count(events, "error")).toBe(0);
513
+ });
514
+
515
+ it("closes a cancelled call inside agent() without ending the enclosing agent", async () => {
516
+ await session({ sessionId: "s1" }, () =>
517
+ agent("planner", async () => {
518
+ const result = await middleware().wrapStream({
519
+ params: {},
520
+ model: { modelId: "gpt-x" },
521
+ doStream: async () => ({ stream: endless(() => undefined) }),
522
+ });
523
+ const reader = (result.stream as ReadableStream<unknown>).getReader();
524
+ await reader.read();
525
+ await reader.cancel();
526
+ }),
527
+ );
528
+ const events = await flushed(spool);
529
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
530
+ expect(events.every((e) => e.agent_id === "planner")).toBe(true);
531
+ expect(events[2]!.stop_reason).toBe("cancelled");
532
+ // The only agent_end is the planner's own, from its scope.
533
+ expect(events[3]!.outcome).toBe("success");
534
+ });
535
+ });
536
+
537
+ describe("instrument('ai') and the process-wide OpenTelemetry slot", () => {
538
+ /**
539
+ * A throwaway application with `ai` at `version` and a stand-in for
540
+ * `@opentelemetry/api` that keeps the real one's global-registration rule:
541
+ * the first provider wins and every later `setGlobalTracerProvider` returns
542
+ * false. (The real API against real `ai` releases is proven in
543
+ * `integration/ai.test.ts`.)
544
+ */
545
+ const apps: string[] = [];
546
+ const makeApp = (version: string): string => {
547
+ const app = mkdtempSync(join(tmpdir(), "failproofai-ai-otel-"));
548
+ apps.push(app);
549
+ const write = (path: string, text: string): void => {
550
+ mkdirSync(join(path, ".."), { recursive: true });
551
+ writeFileSync(path, text);
552
+ };
553
+ write(join(app, "node_modules", "ai", "package.json"), JSON.stringify({ name: "ai", version, main: "index.js" }));
554
+ write(join(app, "node_modules", "ai", "index.js"), "module.exports = {};\n");
555
+ write(
556
+ join(app, "node_modules", "@opentelemetry", "api", "package.json"),
557
+ JSON.stringify({ name: "@opentelemetry/api", version: "1.9.0", main: "index.js" }),
558
+ );
559
+ write(
560
+ join(app, "node_modules", "@opentelemetry", "api", "index.js"),
561
+ [
562
+ "let delegate = null;",
563
+ "class NoopTracerProvider { getTracer() { return {}; } }",
564
+ "class ProxyTracerProvider { getDelegate() { return delegate ?? new NoopTracerProvider(); } }",
565
+ "const proxy = new ProxyTracerProvider();",
566
+ "module.exports = {",
567
+ " ProxyTracerProvider,",
568
+ " trace: {",
569
+ " setGlobalTracerProvider(p) { if (delegate) return false; delegate = p; return true; },",
570
+ " getTracerProvider() { return proxy; },",
571
+ " disable() { delegate = null; },",
572
+ " },",
573
+ "};",
574
+ ].join("\n"),
575
+ );
576
+ return app;
577
+ };
578
+ interface FakeOtel {
579
+ trace: {
580
+ setGlobalTracerProvider(p: unknown): boolean;
581
+ getTracerProvider(): { getDelegate(): unknown };
582
+ };
583
+ }
584
+ const otelOf = (app: string): FakeOtel => createRequire(join(app, "main.js"))("@opentelemetry/api") as FakeOtel;
585
+ const inApp = async (app: string, fn: () => unknown): Promise<void> => {
586
+ const cwd = process.cwd();
587
+ process.chdir(app);
588
+ try {
589
+ await fn();
590
+ } finally {
591
+ process.chdir(cwd);
592
+ }
593
+ };
594
+ afterEach(() => {
595
+ for (const app of apps.splice(0)) rmSync(app, { recursive: true, force: true });
596
+ });
597
+
598
+ it.each(["4.3.19", "5.0.0", "6.0.288"])(
599
+ "on ai %s does not take the global slot by default, so the customer's own provider still registers",
600
+ async (version) => {
601
+ const app = makeApp(version);
602
+ await inApp(app, () => adapter.install({}));
603
+ const theirs = { getTracer: () => ({ theirs: true }) };
604
+ expect(otelOf(app).trace.setGlobalTracerProvider(theirs)).toBe(true);
605
+ expect(otelOf(app).trace.getTracerProvider().getDelegate()).toBe(theirs);
606
+ // …and says, once, what instrument("ai") does and does not cover here.
607
+ await inApp(app, () => adapter.install({}));
608
+ const advice = warnings.filter((w) => w.includes("registerGlobalTracer"));
609
+ expect(advice).toHaveLength(1);
610
+ expect(advice[0]).toContain("telemetry()");
611
+ // …naming an import that exists. The root package has no `ai` export, so
612
+ // `failproofai.ai.telemetry()` sent the reader to `undefined`.
613
+ expect(advice[0]).toContain('from "@failproofai/sdk/ai"');
614
+ expect(advice[0]).not.toContain("failproofai.ai.");
615
+ },
616
+ );
617
+
618
+ it("registers the global tracer on ai 4–6 only when asked to", async () => {
619
+ const app = makeApp("6.0.288");
620
+ await inApp(app, () => adapter.install({ registerGlobalTracer: true }));
621
+ const delegate = otelOf(app).trace.getTracerProvider().getDelegate() as { getTracer?: () => unknown };
622
+ expect((delegate.getTracer?.() as object | undefined)?.constructor.name).toBe("FailproofTracer");
623
+ expect(warnings.filter((w) => w.includes("registerGlobalTracer"))).toEqual([]);
624
+ // uninstrument() gives the slot back.
625
+ adapter.uninstall();
626
+ expect(otelOf(app).trace.setGlobalTracerProvider({ getTracer: () => ({}) })).toBe(true);
627
+ });
628
+
629
+ it("stays quiet on ai 4–6 when told registerGlobalTracer: false", async () => {
630
+ const app = makeApp("5.0.0");
631
+ await inApp(app, () => adapter.install({ registerGlobalTracer: false }));
632
+ expect(warnings).toEqual([]);
633
+ expect(otelOf(app).trace.setGlobalTracerProvider({ getTracer: () => ({}) })).toBe(true);
634
+ });
635
+
636
+ it("on ai 7 neither touches OpenTelemetry nor warns", async () => {
637
+ const app = makeApp("7.0.111");
638
+ await inApp(app, () => adapter.install({}));
639
+ expect(warnings).toEqual([]);
640
+ expect(otelOf(app).trace.setGlobalTracerProvider({ getTracer: () => ({}) })).toBe(true);
641
+ });
642
+ });
643
+
644
+ describe("bookkeeping", () => {
645
+ interface TrackerMaps {
646
+ links: Map<unknown, unknown>;
647
+ runs: Map<unknown, unknown>;
648
+ }
649
+ const maps = (): TrackerMaps => _internals.tracker() as unknown as TrackerMaps;
650
+
651
+ it("leaves no residue after 20k completed operations, and never evicts a live run's links", async () => {
652
+ const captured: Array<Record<string, unknown>> = [];
653
+ const original = runtime.event;
654
+ runtime.event = new Proxy(
655
+ {},
656
+ {
657
+ get: (_, method) => (options: Record<string, unknown>) =>
658
+ captured.push({ method: String(method), ...options }),
659
+ },
660
+ ) as typeof runtime.event;
661
+ try {
662
+ const t = tracer();
663
+ const mw = middleware();
664
+
665
+ // A run that stays open throughout: a root operation with an
666
+ // intermediate span under it, whose child only arrives at the very end.
667
+ let liveRoot: FailproofSpan | undefined;
668
+ let liveStep: FailproofSpan | undefined;
669
+ await session({ sessionId: "live" }, () =>
670
+ t.startActiveSpan(
671
+ "ai.generateText",
672
+ { attributes: { "ai.operationId": "ai.generateText", "ai.telemetry.functionId": "live-agent" } },
673
+ (root) => {
674
+ liveRoot = root;
675
+ t.startActiveSpan("ai.step", (step) => {
676
+ liveStep = step;
677
+ });
678
+ },
679
+ ),
680
+ );
681
+
682
+ const stream = (mode: "finish" | "cancel" | "error") => async () => ({
683
+ stream: new ReadableStream({
684
+ pull(controller) {
685
+ controller.enqueue({ type: "text-delta", delta: "x" });
686
+ if (mode === "finish") {
687
+ controller.enqueue({ type: "finish", finishReason: "stop" });
688
+ controller.close();
689
+ } else if (mode === "error") {
690
+ controller.error(new Error("reset"));
691
+ }
692
+ },
693
+ }),
694
+ });
695
+ const drain = async (s: unknown, cancel: boolean): Promise<void> => {
696
+ const reader = (s as ReadableStream<unknown>).getReader();
697
+ try {
698
+ if (cancel) {
699
+ await reader.read();
700
+ await reader.cancel();
701
+ return;
702
+ }
703
+ while (!(await reader.read()).done) {
704
+ /* drain */
705
+ }
706
+ } catch {
707
+ // the errored stream
708
+ }
709
+ };
710
+
711
+ const N = 20_000;
712
+ for (let i = 0; i < N; i += 1) {
713
+ // ai v4–v6: the tracer, every span kind.
714
+ t.startActiveSpan("ai.generateText", { attributes: { "ai.operationId": "ai.generateText" } }, (root) => {
715
+ t.startActiveSpan(
716
+ "ai.generateText.doGenerate",
717
+ { attributes: { "ai.operationId": "ai.generateText.doGenerate" } },
718
+ (span) => span.end(),
719
+ );
720
+ t.startActiveSpan(
721
+ "ai.toolCall",
722
+ { attributes: { "ai.operationId": "ai.toolCall", "ai.toolCall.id": `t${String(i)}` } },
723
+ (span) => span.end(),
724
+ );
725
+ t.startActiveSpan("ai.other", (span) => span.end());
726
+ if (i % 2 === 0) root.recordException(new Error("x"));
727
+ root.end();
728
+ });
729
+ // ai v7: the integration — success, error and abort paths.
730
+ const callId = `c${String(i)}`;
731
+ const toolCall = { toolCallId: `tc${String(i)}`, toolName: "w", input: {} };
732
+ integration.onStart({ callId, operationId: "ai.generateText", functionId: "f" });
733
+ integration.onLanguageModelCallStart({ callId });
734
+ integration.onLanguageModelCallEnd({ callId, finishReason: "tool-calls" });
735
+ integration.onToolExecutionStart({ callId, toolCall });
736
+ if (i % 3 === 0) {
737
+ integration.onToolExecutionEnd({ callId, toolCall, toolOutput: { type: "tool-result", output: 1 } });
738
+ integration.onEnd({ callId });
739
+ } else if (i % 3 === 1) {
740
+ integration.onLanguageModelCallStart({ callId });
741
+ integration.onError({ callId, error: new Error("boom") });
742
+ } else {
743
+ integration.onAbort({ callId });
744
+ }
745
+ // The middleware: generate, and a stream that finishes, is cancelled, or errors.
746
+ await mw.wrapGenerate({ params: {}, model: { modelId: "m" }, doGenerate: async () => ({ text: "x" }) });
747
+ const mode = (["finish", "cancel", "error"] as const)[i % 3]!;
748
+ const result = await mw.wrapStream({ params: {}, model: { modelId: "m" }, doStream: stream(mode) });
749
+ await drain(result.stream, mode === "cancel");
750
+ }
751
+
752
+ // Only the live run is left: its agent, and its intermediate span's link.
753
+ expect(maps().runs.size).toBe(1);
754
+ expect(maps().links.size).toBe(1);
755
+ expect(_internals.openCalls()).toBe(0);
756
+
757
+ // That link survived 20k runs through the table: the live run's late
758
+ // child still resolves to it.
759
+ captured.length = 0;
760
+ const step = new FailproofSpan("ai.generateText.doGenerate", liveStep, {
761
+ "ai.operationId": "ai.generateText.doGenerate",
762
+ });
763
+ step.end();
764
+ liveStep!.end();
765
+ liveRoot!.end();
766
+ expect(captured.map((e) => [e.method, e.agentId, e.sessionId])).toEqual([
767
+ ["modelRequest", "live-agent", "live"],
768
+ ["modelResponse", "live-agent", "live"],
769
+ ["agentEnd", "live-agent", "live"],
770
+ ]);
771
+ expect(maps().runs.size).toBe(0);
772
+ expect(maps().links.size).toBe(0);
773
+ } finally {
774
+ runtime.event = original;
775
+ }
776
+ }, 120_000);
777
+ });
778
+
779
+ describe("tool calls in a model_response", () => {
780
+ it("reads every major's ai.response.toolCalls into one shape, input parsed", () => {
781
+ // v4: toolCallType + args as a JSON string.
782
+ expect(toolCallsOf(JSON.stringify([{ toolCallType: "function", toolCallId: "a", toolName: "w", args: '{"city":"Paris"}' }]))).toEqual([
783
+ { toolCallId: "a", toolName: "w", input: { city: "Paris" } },
784
+ ]);
785
+ // v5/v6 generateText: input as a JSON string.
786
+ expect(toolCallsOf(JSON.stringify([{ toolCallId: "b", toolName: "w", input: '{"city":"Rome"}' }]))).toEqual([
787
+ { toolCallId: "b", toolName: "w", input: { city: "Rome" } },
788
+ ]);
789
+ // v5/v6 streamText: a typed part with input already an object.
790
+ expect(toolCallsOf(JSON.stringify([{ type: "tool-call", toolCallId: "c", toolName: "w", input: { city: "Oslo" } }]))).toEqual([
791
+ { toolCallId: "c", toolName: "w", input: { city: "Oslo" } },
792
+ ]);
793
+ expect(toolCallsOf(undefined)).toBeUndefined();
794
+ expect(toolCallsOf("[]")).toBeUndefined();
795
+ expect(toolCallsOf("not json")).toBeUndefined();
796
+ });
797
+ });
798
+
799
+ describe("the tracer when a stream does not finish (ai v4–v6)", () => {
800
+ type Tracer = ReturnType<typeof tracer>;
801
+ const streamRoot = (t: Tracer, functionId: string, body: (root: Span) => void): void => {
802
+ t.startActiveSpan(
803
+ "ai.streamText",
804
+ { attributes: { "ai.operationId": "ai.streamText", "ai.telemetry.functionId": functionId } },
805
+ (root: Span) => body(root),
806
+ );
807
+ };
808
+ const openStep = (t: Tracer): Span => {
809
+ let step: Span | undefined;
810
+ t.startActiveSpan(
811
+ "ai.streamText.doStream",
812
+ { attributes: { "ai.operationId": "ai.streamText.doStream", "ai.model.id": "m" } },
813
+ (span: Span) => {
814
+ step = span;
815
+ },
816
+ );
817
+ return step!;
818
+ };
819
+
820
+ it("closes the model call an aborted stream left open, and ends the agent cancelled", async () => {
821
+ const t = tracer();
822
+ let step: Span | undefined;
823
+ await session({ sessionId: "s1" }, () => {
824
+ streamRoot(t, "counter", (root) => {
825
+ step = openStep(t);
826
+ // v5/v6 on abort: the root ends from the result stream's flush; the
827
+ // model step's span is never ended.
828
+ root.end();
829
+ });
830
+ });
831
+ // A late end of the abandoned step is not news.
832
+ step!.setAttributes({ "ai.response.finishReason": "stop" });
833
+ step!.end();
834
+ const events = await flushed(spool);
835
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
836
+ const response = events.find((e) => e.type === "model_response")!;
837
+ expect(response.stop_reason).toBe("cancelled");
838
+ expect(response.model).toBe("m");
839
+ expect(response.request_id).toBe(events.find((e) => e.type === "model_request")!.request_id);
840
+ expect(events.at(-1)!.outcome).toBe("cancelled");
841
+ expect(count(events, "error")).toBe(0);
842
+ expect(_internals.tracker()!.stats()).toEqual({ runs: 0, links: 0 });
843
+ });
844
+
845
+ it("closes a tool a cut-off operation left running, as cancelled", async () => {
846
+ const t = tracer();
847
+ await session({ sessionId: "s1" }, () => {
848
+ streamRoot(t, "agent", (root) => {
849
+ t.startActiveSpan(
850
+ "ai.toolCall",
851
+ { attributes: { "ai.operationId": "ai.toolCall", "ai.toolCall.name": "weather", "ai.toolCall.id": "tc1" } },
852
+ () => undefined,
853
+ );
854
+ root.end();
855
+ });
856
+ });
857
+ const events = await flushed(spool);
858
+ const result = events.find((e) => e.type === "tool_result")!;
859
+ expect(result.tool_call_id).toBe("tc1");
860
+ expect(result.error).toMatch(/cancelled/);
861
+ expect(events.at(-1)!.outcome).toBe("cancelled");
862
+ });
863
+
864
+ it("ends a streamText that finished no step as cancelled, and a completed one as success", async () => {
865
+ const t = tracer();
866
+ await session({ sessionId: "s1" }, () => {
867
+ streamRoot(t, "aborted-between-steps", (root) => root.end());
868
+ streamRoot(t, "finished", (root) => {
869
+ root.setAttributes({ "ai.response.finishReason": "stop" });
870
+ root.end();
871
+ });
872
+ t.startActiveSpan("ai.generateText", { attributes: { "ai.operationId": "ai.generateText" } }, (root: Span) => root.end());
873
+ });
874
+ const ends = (await flushed(spool)).filter((e) => e.type === "agent_end");
875
+ expect(ends.map((e) => [e.agent_id, e.outcome])).toEqual([
876
+ ["aborted-between-steps", "cancelled"],
877
+ ["finished", "success"],
878
+ ["ai.generateText", "success"],
879
+ ]);
880
+ });
881
+
882
+ it("ends an operation nothing will ever end — its root span collected — as cancelled and abandoned", async () => {
883
+ const { setFlagsFromString } = await import("node:v8");
884
+ const { runInNewContext } = await import("node:vm");
885
+ setFlagsFromString("--expose-gc");
886
+ const gc = runInNewContext("gc") as () => void;
887
+
888
+ // A client that disconnected: the SDK's result stream never flushes, so
889
+ // neither span is ended, and every reference to them is then dropped.
890
+ const start = (): void => {
891
+ const t = tracer();
892
+ streamRoot(t, "disconnected", () => {
893
+ openStep(t);
894
+ });
895
+ };
896
+ await session({ sessionId: "s1" }, () => {
897
+ start();
898
+ });
899
+ for (let i = 0; i < 20 && _internals.tracker()!.stats().runs > 0; i += 1) {
900
+ gc();
901
+ await new Promise((resolve) => setTimeout(resolve, 20));
902
+ }
903
+ const events = await flushed(spool);
904
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
905
+ expect(events[2]!.stop_reason).toBe("cancelled");
906
+ expect(events.at(-1)!.outcome).toBe("cancelled");
907
+ expect(events.at(-1)!.fw_abandoned).toBe(true);
908
+ expect(events.every((e) => e.session_id === "s1" && e.agent_id === "disconnected")).toBe(true);
909
+ expect(_internals.tracker()!.stats()).toEqual({ runs: 0, links: 0 });
910
+ });
911
+ });
912
+
913
+ describe("embeddings", () => {
914
+ type Tracer = ReturnType<typeof tracer>;
915
+ const embedSpans = (t: Tracer, operation = "ai.embed", functionId?: string): void => {
916
+ t.startActiveSpan(
917
+ operation,
918
+ { attributes: { "ai.operationId": operation, ...(functionId ? { "ai.telemetry.functionId": functionId } : {}) } },
919
+ (root: Span) => {
920
+ t.startActiveSpan(
921
+ `${operation}.doEmbed`,
922
+ { attributes: { "ai.operationId": `${operation}.doEmbed`, "ai.model.id": "embedder" } },
923
+ (span: Span) => {
924
+ span.setAttributes({ "ai.usage.tokens": 3 });
925
+ span.end();
926
+ },
927
+ );
928
+ root.end();
929
+ },
930
+ );
931
+ };
932
+ const lines = (events: Array<Record<string, unknown>>) => events.map((e) => `${String(e.agent_id)} ${String(e.type)}`);
933
+
934
+ it("tracer: a bare embed() is its own run, named by functionId", async () => {
935
+ embedSpans(tracer(), "ai.embed", "indexer");
936
+ const events = await flushed(spool);
937
+ expect(lines(events)).toEqual([
938
+ "indexer agent_start",
939
+ "indexer model_request",
940
+ "indexer model_response",
941
+ "indexer agent_end",
942
+ ]);
943
+ expect(events[2]!.input_tokens).toBe(3);
944
+ });
945
+
946
+ it("tracer: an embed() inside agent() is a model call of that agent, not a nested agent", async () => {
947
+ const t = tracer();
948
+ await session({ sessionId: "s1" }, () =>
949
+ agent("rag", () => {
950
+ embedSpans(t);
951
+ embedSpans(t, "ai.embedMany");
952
+ }),
953
+ );
954
+ const events = await flushed(spool);
955
+ expect(lines(events)).toEqual([
956
+ "rag agent_start",
957
+ "rag model_request",
958
+ "rag model_response",
959
+ "rag model_request",
960
+ "rag model_response",
961
+ "rag agent_end",
962
+ ]);
963
+ expect(_internals.tracker()!.stats()).toEqual({ runs: 0, links: 0 });
964
+ });
965
+
966
+ it("tracer: an embed() inside a tool is a model call of the operation that ran the tool", async () => {
967
+ const t = tracer();
968
+ await session({ sessionId: "s1" }, () => {
969
+ t.startActiveSpan(
970
+ "ai.generateText",
971
+ { attributes: { "ai.operationId": "ai.generateText", "ai.telemetry.functionId": "weather-agent" } },
972
+ (root: Span) => {
973
+ t.startActiveSpan(
974
+ "ai.toolCall",
975
+ { attributes: { "ai.operationId": "ai.toolCall", "ai.toolCall.name": "lookup", "ai.toolCall.id": "tc1" } },
976
+ (tool: Span) => {
977
+ embedSpans(t);
978
+ tool.end();
979
+ },
980
+ );
981
+ root.end();
982
+ },
983
+ );
984
+ });
985
+ const events = await flushed(spool);
986
+ expect(lines(events)).toEqual([
987
+ "weather-agent agent_start",
988
+ "weather-agent tool_use",
989
+ "weather-agent model_request",
990
+ "weather-agent model_response",
991
+ "weather-agent tool_result",
992
+ "weather-agent agent_end",
993
+ ]);
994
+ expect(events.at(-1)!.outcome).toBe("success");
995
+ });
996
+
997
+ it("tracer: a failing enclosed embed() reports its error under the enclosing agent", async () => {
998
+ const t = tracer();
999
+ await session({ sessionId: "s1" }, () =>
1000
+ agent("rag", () => {
1001
+ t.startActiveSpan("ai.embed", { attributes: { "ai.operationId": "ai.embed" } }, (root: Span) => {
1002
+ root.recordException(new Error("too many values"));
1003
+ root.end();
1004
+ });
1005
+ }),
1006
+ );
1007
+ expect(lines(await flushed(spool))).toEqual(["rag agent_start", "rag error", "rag agent_end"]);
1008
+ });
1009
+
1010
+ it("v7: bare, inside agent(), and inside a tool — the same three answers", async () => {
1011
+ const embed = (callId: string, functionId?: string): void => {
1012
+ integration.onStart({ callId, operationId: "ai.embed", ...(functionId ? { functionId } : {}) });
1013
+ integration.onEmbedStart({ callId, modelId: "embedder", values: ["a"] });
1014
+ integration.onEmbedEnd({ callId, modelId: "embedder", usage: { tokens: 3 } });
1015
+ integration.onEnd({ callId });
1016
+ };
1017
+ embed("bare", "indexer");
1018
+ await session({ sessionId: "s1" }, async () => {
1019
+ await agent("rag", () => {
1020
+ embed("scoped");
1021
+ });
1022
+ integration.onStart({ callId: "outer", operationId: "ai.generateText", functionId: "weather-agent" });
1023
+ await integration.executeTool({
1024
+ callId: "outer",
1025
+ execute: async () => {
1026
+ embed("in-tool");
1027
+ },
1028
+ });
1029
+ integration.onEnd({ callId: "outer" });
1030
+ });
1031
+ const events = await flushed(spool);
1032
+ expect(lines(events)).toEqual([
1033
+ "indexer agent_start",
1034
+ "indexer model_request",
1035
+ "indexer model_response",
1036
+ "indexer agent_end",
1037
+ "rag agent_start",
1038
+ "rag model_request",
1039
+ "rag model_response",
1040
+ "rag agent_end",
1041
+ "weather-agent agent_start",
1042
+ "weather-agent model_request",
1043
+ "weather-agent model_response",
1044
+ "weather-agent agent_end",
1045
+ ]);
1046
+ expect(events.filter((e) => e.type === "model_response").map((e) => e.input_tokens)).toEqual([3, 3, 3]);
1047
+ expect(_internals.openCalls()).toBe(0);
1048
+ expect(_internals.tracker()!.stats()).toEqual({ runs: 0, links: 0 });
1049
+ });
1050
+ });
1051
+
1052
+ describe("the telemetry integration on abort and failure (ai v7)", () => {
1053
+ it("closes the interrupted model call as cancelled, keeping its model", async () => {
1054
+ await session({ sessionId: "s1" }, () => {
1055
+ integration.onStart({ callId: "a1", operationId: "ai.streamText", functionId: "counter" });
1056
+ integration.onLanguageModelCallStart({ callId: "a1", modelId: "m" });
1057
+ integration.onAbort({ callId: "a1" });
1058
+ });
1059
+ const events = await flushed(spool);
1060
+ expect(types(events)).toEqual(["agent_start", "model_request", "model_response", "agent_end"]);
1061
+ expect(events[2]!.stop_reason).toBe("cancelled");
1062
+ expect(events[2]!.model).toBe("m");
1063
+ expect(events[3]!.outcome).toBe("cancelled");
1064
+ });
1065
+
1066
+ it("keeps the model on a call closed by a failure", async () => {
1067
+ await session({ sessionId: "s1" }, () => {
1068
+ integration.onStart({ callId: "e1", operationId: "ai.generateText", functionId: "f" });
1069
+ integration.onLanguageModelCallStart({ callId: "e1", modelId: "m" });
1070
+ integration.onError({ callId: "e1", error: new Error("boom") });
1071
+ });
1072
+ const response = (await flushed(spool)).find((e) => e.type === "model_response")!;
1073
+ expect(response.stop_reason).toBe("error");
1074
+ expect(response.model).toBe("m");
1075
+ });
1076
+ });