failproofai 1.0.10 → 1.0.11-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +6 -6
  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/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.meta +8 -0
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  17. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  19. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.meta +8 -0
  23. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  26. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/invite/route.js +4 -4
  28. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  30. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -4
  36. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -3
  38. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -3
  40. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  44. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/audit/page.js +4 -4
  46. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/index.html +1 -1
  49. package/.next/standalone/.next/server/app/index.meta +8 -0
  50. package/.next/standalone/.next/server/app/index.rsc +14 -14
  51. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  52. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  53. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  54. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  55. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  56. package/.next/standalone/.next/server/app/page.js +4 -4
  57. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  60. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  61. package/.next/standalone/.next/server/app/policies/page.js +8 -8
  62. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  65. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/page.js +5 -5
  67. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  76. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  77. package/.next/standalone/.next/server/app/projects/page.js +5 -5
  78. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +3 -3
  81. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  82. package/.next/standalone/.next/server/app/settings/page.js +9 -9
  83. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  85. package/.next/standalone/.next/server/chunks/{[externals]__0qxhecv._.js → [externals]__0d7i9ea._.js} +2 -2
  86. package/.next/standalone/.next/server/chunks/{[externals]__0sg9-3p._.js → [externals]__0hjaw70._.js} +1 -1
  87. package/.next/standalone/.next/server/chunks/[externals]__1_9ze9b._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/[externals]__1oenfmn._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ga9hs7._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0moxohc._.js +22 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z2ll-a._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +11 -11
  96. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1autwno._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1v25n-6._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
  102. package/.next/standalone/.next/server/chunks/_1fz8m31._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/_1jrv3hg._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/_1nx8fim._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/_1ogwigf._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1q27azx._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0el6rg8._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0ol5pa3._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1rqmk27._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1w7tiq4._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
  116. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
  118. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
  122. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0oqrl71._.js → [root-of-the-server]__1_qv8oh._.js} +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
  129. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/{_16pa9ht._.js → _0lnpwk8._.js} +2 -2
  132. package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _15y8myg._.js} +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/{_042cgl1._.js → _1o95rf0._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/{_1zopuov._.js → _1thr5rf._.js} +2 -2
  136. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
  137. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
  144. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  146. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  147. package/.next/standalone/.next/server/middleware.js +1 -1
  148. package/.next/standalone/.next/server/pages/404.html +1 -1
  149. package/.next/standalone/.next/server/pages/500.html +1 -1
  150. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  151. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  152. package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0pra9i676pym1.js} +1 -1
  153. package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1-lpi0m-hc1kb.js} +1 -1
  154. package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 17yvyyblr_eb5.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 1mylcr99gccyz.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 1wozchtn2etvh.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 23thtelz1a-ur.js} +2 -2
  158. package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 2bc17tohgs4bs.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 2xq-ejo8kti18.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/3-_pth_c9n938.js +1 -0
  161. package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
  162. package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 3q7sndqftqhw9.js} +1 -1
  163. package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
  164. package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
  165. package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
  166. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  167. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  168. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  169. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
  170. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
  171. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
  172. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
  173. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
  174. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
  175. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  176. package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
  177. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  178. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  179. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  180. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
  181. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
  183. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
  184. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
  185. package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
  186. package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
  187. package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
  188. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
  189. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  190. package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
  191. package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
  192. package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
  193. package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
  194. package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
  195. package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
  196. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
  197. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
  198. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  199. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  200. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  201. package/.next/standalone/node_modules/next/package.json +10 -10
  202. package/.next/standalone/package.json +14 -11
  203. package/.next/standalone/sdk/python/README.md +41 -0
  204. package/.next/standalone/sdk/python/docs/README.md +1 -0
  205. package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
  206. package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
  207. package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
  208. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
  209. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
  210. package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
  211. package/.next/standalone/sdk/python/pyproject.toml +2 -1
  212. package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
  213. package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
  214. package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
  215. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
  216. package/.next/standalone/sdk/python/uv.lock +258 -147
  217. package/.next/standalone/sdk/typescript/README.md +43 -0
  218. package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
  219. package/.next/standalone/sdk/typescript/package-lock.json +57 -5
  220. package/.next/standalone/sdk/typescript/package.json +2 -0
  221. package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
  222. package/.next/standalone/sdk/typescript/src/index.ts +2 -0
  223. package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
  224. package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
  225. package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
  226. package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
  227. package/.next/standalone/server.js +1 -1
  228. package/bin/failproofai.mjs +653 -1077
  229. package/dist/cli.mjs +8781 -4050
  230. package/dist/worker.mjs +170 -62
  231. package/hermes-plugin/plugin.yaml +1 -1
  232. package/package.json +14 -11
  233. package/scripts/launch-facts.ts +157 -0
  234. package/scripts/launch-ready.ts +228 -0
  235. package/scripts/launch-screen.ts +202 -0
  236. package/scripts/launch.ts +106 -49
  237. package/scripts/skew-log-filter.ts +38 -0
  238. package/src/audit/cli-login.ts +39 -59
  239. package/src/audit/cli.ts +308 -228
  240. package/src/audit/index.ts +45 -2
  241. package/src/audit/schedule-cli.ts +83 -120
  242. package/src/audit/types.ts +31 -0
  243. package/src/hooks/agent-selection.ts +155 -0
  244. package/src/hooks/backfill-cli.ts +151 -137
  245. package/src/hooks/backfill-request.ts +149 -0
  246. package/src/hooks/cloud-connection.ts +1 -1
  247. package/src/hooks/cloud-enrollment-cli.ts +78 -28
  248. package/src/hooks/cloud-introspect.ts +3 -0
  249. package/src/hooks/collector-config.ts +10 -1
  250. package/src/hooks/config-status.ts +717 -0
  251. package/src/hooks/configure-wizard.ts +967 -988
  252. package/src/hooks/daemon-service.ts +16 -6
  253. package/src/hooks/did-you-mean.ts +185 -0
  254. package/src/hooks/flush-cli.ts +33 -7
  255. package/src/hooks/fp-config.ts +96 -0
  256. package/src/hooks/fp-home.ts +11 -0
  257. package/src/hooks/fp-reset.ts +102 -95
  258. package/src/hooks/handler.ts +4 -0
  259. package/src/hooks/harness-cli.ts +87 -138
  260. package/src/hooks/install-prompt.ts +38 -477
  261. package/src/hooks/integrations.ts +1 -1
  262. package/src/hooks/jev-cli.ts +183 -196
  263. package/src/hooks/manager.ts +554 -358
  264. package/src/hooks/onboarding-attempt.ts +11 -9
  265. package/src/hooks/otel-cli.ts +350 -0
  266. package/src/hooks/otel-output.ts +24 -0
  267. package/src/hooks/otel-writers.ts +215 -0
  268. package/src/hooks/pack-cli.ts +219 -204
  269. package/src/hooks/report-body.ts +50 -0
  270. package/src/hooks/safe-config-write.ts +4 -2
  271. package/src/hooks/session-pause-cli.ts +93 -73
  272. package/src/hooks/tui.ts +849 -389
  273. package/src/hooks/uninstall-cli.ts +136 -149
  274. package/src/hooks/untraced-note.ts +52 -0
  275. package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
  276. package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
  282. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
  283. package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
  290. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
  291. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  292. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
  293. package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
  294. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_buildManifest.js +0 -0
  295. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_clientMiddlewareManifest.js +0 -0
  296. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_ssgManifest.js +0 -0
@@ -74,6 +74,47 @@ standard library. See `skill/references/frameworks.md` for the per-framework
74
74
  mapping, and `docs/` for a per-framework integration guide with runnable
75
75
  examples beside it.
76
76
 
77
+ ## OpenTelemetry (opt in)
78
+
79
+ Already emitting OpenTelemetry spans? Install `opentelemetry-sdk` separately,
80
+ then attach FailproofAI's exporter without changing your existing adapters:
81
+
82
+ ```bash
83
+ pip install opentelemetry-sdk
84
+ ```
85
+
86
+ ```python
87
+ import failproofai_sdk
88
+ from opentelemetry.sdk.trace import TracerProvider
89
+
90
+ provider = TracerProvider()
91
+ failproofai_sdk.instrument("otel", provider=provider)
92
+ tracer = provider.get_tracer("my-agent")
93
+ with tracer.start_as_current_span(
94
+ "agent", attributes={"gen_ai.operation.name": "invoke_agent"}
95
+ ):
96
+ pass
97
+ provider.force_flush()
98
+ ```
99
+
100
+ Without a `provider`, `instrument("otel")` uses the registered provider or creates
101
+ one if none exists. You can also pass `failproofai_sdk.OtelSpanExporter()` to your
102
+ own `SimpleSpanProcessor` or `BatchSpanProcessor`. `uninstrument("otel")` disables
103
+ only our processor and leaves your provider and other exporters unchanged.
104
+
105
+ The exporter writes durable OTLP/JSON requests to
106
+ `$FAILPROOFAI_HOME/state/spool-otlp`, otherwise `~/.failproofai/state/spool-otlp`.
107
+ It never sends HTTP or reads a Cloud key. Connect the daemon with
108
+ `failproofai config` and ask an admin to enable Settings → OpenTelemetry.
109
+ SDK delivery needs no local TCP listener. `spool_dir` explicitly overrides the
110
+ destination for tests or managed deployments; `configure(base_dir=...)` continues
111
+ to control only the original event adapters.
112
+
113
+ A bare `instrument()` never enables OTEL. OpenTelemetry is imported only when
114
+ requested, and the SDK still declares zero hard runtime dependencies. Your
115
+ instrumentation controls prompt/content attributes; inspect them before exporting
116
+ sensitive data. Flush your provider before short-lived processes exit.
117
+
77
118
  ## Scopes
78
119
 
79
120
  The same identity layer, for code the adapters do not cover. `session_id` and
@@ -28,6 +28,7 @@ That is the whole integration.
28
28
  | CrewAI | [crewai/](crewai/) | [crewai/examples/](crewai/examples/) |
29
29
  | LlamaIndex | [llama_index/](llama_index/) | [llama_index/examples/](llama_index/examples/) |
30
30
  | Pydantic AI | [pydantic_ai/](pydantic_ai/) | [pydantic_ai/examples/](pydantic_ai/examples/) |
31
+ | OpenTelemetry (opt in) | [otel/](otel/) | [otel/examples/](otel/examples/) |
31
32
  | **no framework** (your own agent) | [manual/](manual/) | [manual/examples/](manual/examples/) |
32
33
 
33
34
  Using something else — AutoGen, Haystack, Semantic Kernel, your own loop? Read
@@ -0,0 +1,36 @@
1
+ # OpenTelemetry spans
2
+
3
+ Use `failproofai_sdk.instrument("otel", provider=provider)` to attach the optional
4
+ OTLP/JSON exporter to your OpenTelemetry `TracerProvider`. Install
5
+ `opentelemetry-sdk` in the application, not as a FailproofAI runtime dependency.
6
+ `pip install 'failproofai-sdk[otel]'` is the optional convenience extra.
7
+
8
+ ```python
9
+ import failproofai_sdk
10
+ from opentelemetry.sdk.trace import TracerProvider
11
+
12
+ provider = TracerProvider()
13
+ failproofai_sdk.instrument("otel", provider=provider)
14
+ with provider.get_tracer("my-agent").start_as_current_span(
15
+ "agent", attributes={"gen_ai.operation.name": "invoke_agent"}
16
+ ):
17
+ pass
18
+ provider.force_flush()
19
+ provider.shutdown()
20
+ ```
21
+
22
+ The exporter publishes requests into the relay's spool. Run a connected
23
+ `failproofaid` on the same machine; no auth header or exporter network setup is
24
+ needed. An org admin must enable OpenTelemetry ingest in Cloud. Export related
25
+ spans together and include the root agent span.
26
+
27
+ You can also pass `failproofai_sdk.OtelSpanExporter()` to your existing span
28
+ processor. Existing processors continue unchanged. `uninstrument("otel")` stops
29
+ new exports from the processor installed by FailproofAI and leaves the provider
30
+ registered. Prompt and response attributes are sent as supplied by your app.
31
+
32
+ See the [SDK README](../../README.md#opentelemetry-opt-in) for spool overrides and
33
+ the zero-dependency contract.
34
+
35
+ Run [examples/quickstart.py](examples/quickstart.py) for one span, or
36
+ [examples/agent_trace.py](examples/agent_trace.py) for an agent/model/tool trace.
@@ -0,0 +1,30 @@
1
+ """Export one related agent/model/tool trace through the local spool.
2
+
3
+ pip install 'failproofai-sdk[otel]'
4
+ python docs/otel/examples/agent_trace.py
5
+ """
6
+ import failproofai_sdk
7
+ from opentelemetry.sdk.trace import TracerProvider
8
+
9
+
10
+ def main():
11
+ provider = TracerProvider()
12
+ failproofai_sdk.instrument("otel", provider=provider)
13
+ tracer = provider.get_tracer("planner")
14
+ with tracer.start_as_current_span(
15
+ "planner", attributes={"gen_ai.operation.name": "invoke_agent"}
16
+ ):
17
+ with tracer.start_as_current_span(
18
+ "model", attributes={"gen_ai.operation.name": "chat"}
19
+ ):
20
+ pass
21
+ with tracer.start_as_current_span(
22
+ "search", attributes={"gen_ai.operation.name": "execute_tool"}
23
+ ):
24
+ pass
25
+ provider.force_flush()
26
+ provider.shutdown()
27
+
28
+
29
+ if __name__ == "__main__":
30
+ main()
@@ -0,0 +1,22 @@
1
+ """An optional OTEL span, durably exported with no HTTP setup.
2
+
3
+ pip install 'failproofai-sdk[otel]'
4
+ python docs/otel/examples/quickstart.py
5
+ """
6
+ import failproofai_sdk
7
+ from opentelemetry.sdk.trace import TracerProvider
8
+
9
+
10
+ def main():
11
+ provider = TracerProvider()
12
+ failproofai_sdk.instrument("otel", provider=provider)
13
+ with provider.get_tracer("my-agent").start_as_current_span(
14
+ "agent", attributes={"gen_ai.operation.name": "invoke_agent"}
15
+ ):
16
+ pass
17
+ provider.force_flush()
18
+ provider.shutdown()
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
@@ -38,6 +38,7 @@ __all__ = [
38
38
  "propagate",
39
39
  "instrument",
40
40
  "uninstrument",
41
+ "OtelSpanExporter",
41
42
  "_writer",
42
43
  ]
43
44
 
@@ -123,6 +124,13 @@ def uninstrument(framework: str | None = None):
123
124
  return _impl(framework)
124
125
 
125
126
 
127
+ def __getattr__(name):
128
+ if name == "OtelSpanExporter":
129
+ from failproofai_sdk.integrations.otel import OtelSpanExporter
130
+ return OtelSpanExporter
131
+ raise AttributeError(name)
132
+
133
+
126
134
  # MUST be last: any `import failproofai_sdk.<sub>` binds the *module* onto this
127
135
  # package as `failproofai_sdk._writer`. Rebinding it here to the instance is what
128
136
  # keeps the published `failproofai_sdk._writer.flush_now()` recipe working — and
@@ -58,6 +58,7 @@ _REGISTRY: dict[str, str] = {
58
58
  "crewai": "failproofai_sdk.integrations.crewai",
59
59
  "llama_index": "failproofai_sdk.integrations.llama_index",
60
60
  "pydantic_ai": "failproofai_sdk.integrations.pydantic_ai",
61
+ "otel": "failproofai_sdk.integrations.otel",
61
62
  }
62
63
 
63
64
  # Spellings people actually type. LangGraph is served by the LangChain adapter
@@ -79,6 +80,8 @@ _DETECT: dict[str, tuple[str, ...]] = {
79
80
  "crewai": ("crewai",),
80
81
  "llama_index": ("llama_index", "llama_index.core"),
81
82
  "pydantic_ai": ("pydantic_ai",),
83
+ # OTEL is explicit-only: installing it must not duplicate existing adapters.
84
+ "otel": (),
82
85
  }
83
86
 
84
87
  # Guards _ACTIVE and every install/uninstall. `instrument()` is called from
@@ -0,0 +1,189 @@
1
+ """Explicit OTLP/JSON exporter; disk only. OpenTelemetry is an optional runtime import."""
2
+ import base64
3
+ import json
4
+ import logging
5
+ import math
6
+ import os
7
+ import threading
8
+ import uuid
9
+ from pathlib import Path
10
+
11
+ _MAX_BYTES = 8 * 1024 * 1024
12
+ _METADATA = b'{"path":"/v1/traces","content_type":"application/json","encoding":null}\n'
13
+
14
+
15
+ def _value(value):
16
+ if isinstance(value, bool):
17
+ return {"boolValue": value}
18
+ if isinstance(value, str):
19
+ return {"stringValue": value}
20
+ if isinstance(value, int):
21
+ return {"intValue": str(value)}
22
+ if isinstance(value, float):
23
+ return {"doubleValue": value if math.isfinite(value) else str(value)}
24
+ if isinstance(value, bytes):
25
+ return {"bytesValue": base64.b64encode(value).decode("ascii")}
26
+ if isinstance(value, (list, tuple)):
27
+ return {"arrayValue": {"values": [_value(v) for v in value]}}
28
+ if hasattr(value, "items"):
29
+ return {"kvlistValue": {"values": _attributes(value)}}
30
+ return {}
31
+
32
+
33
+ def _attributes(attributes=None):
34
+ return [{"key": str(k), "value": _value(v)} for k, v in (attributes or {}).items() if v is not None]
35
+
36
+
37
+ def _context(context):
38
+ result = {"traceId": f"{context.trace_id:032x}", "spanId": f"{context.span_id:016x}",
39
+ "flags": int(context.trace_flags)}
40
+ if context.trace_state:
41
+ result["traceState"] = context.trace_state.to_header()
42
+ return result
43
+
44
+
45
+ def _resource_span(span):
46
+ data = _context(span.context)
47
+ if span.parent:
48
+ data["parentSpanId"] = f"{span.parent.span_id:016x}"
49
+ data.update({
50
+ "name": span.name, "kind": span.kind.value + 1,
51
+ "startTimeUnixNano": str(span.start_time), "endTimeUnixNano": str(span.end_time),
52
+ "attributes": _attributes(span.attributes),
53
+ "status": {"code": span.status.status_code.value, **({"message": span.status.description} if span.status.description else {})},
54
+ "droppedAttributesCount": span.dropped_attributes,
55
+ "droppedEventsCount": span.dropped_events, "droppedLinksCount": span.dropped_links,
56
+ "events": [{"name": event.name, "timeUnixNano": str(event.timestamp),
57
+ "attributes": _attributes(event.attributes),
58
+ "droppedAttributesCount": event.dropped_attributes} for event in span.events],
59
+ "links": [{**_context(link.context), "attributes": _attributes(link.attributes),
60
+ "droppedAttributesCount": link.dropped_attributes} for link in span.links],
61
+ })
62
+ scope = span.instrumentation_scope
63
+ return {
64
+ "resource": {"attributes": _attributes(span.resource.attributes)},
65
+ **({"schemaUrl": span.resource.schema_url} if span.resource.schema_url else {}),
66
+ "scopeSpans": [{
67
+ "scope": {"name": scope.name if scope else "", **({"version": scope.version} if scope and scope.version else {})},
68
+ **({"schemaUrl": scope.schema_url} if scope and scope.schema_url else {}),
69
+ "spans": [data],
70
+ }],
71
+ }
72
+
73
+
74
+ class OtelSpanExporter:
75
+ """Exports standard spans durably to the daemon's raw OTLP spool, never HTTP."""
76
+
77
+ def __init__(self, *, spool_dir=None):
78
+ self._directory = Path(spool_dir) if spool_dir is not None else None
79
+ self._stopped = False
80
+ self._lock = threading.Lock()
81
+
82
+ def export(self, spans):
83
+ from opentelemetry.sdk.trace.export import SpanExportResult
84
+ with self._lock:
85
+ if self._stopped:
86
+ return SpanExportResult.FAILURE
87
+ try:
88
+ directory = self._directory or Path(os.environ.get("FAILPROOFAI_HOME", Path.home() / ".failproofai")) / "state" / "spool-otlp"
89
+ items, chunks, size = [], [], 20
90
+ for span in spans:
91
+ encoded = json.dumps(_resource_span(span), ensure_ascii=True, separators=(",", ":")).encode("utf8")
92
+ if len(encoded) + 20 > _MAX_BYTES:
93
+ raise ValueError("OTEL span exceeds the 8 MiB relay limit")
94
+ if size + len(encoded) + 1 > _MAX_BYTES:
95
+ chunks.append(b'{"resourceSpans":[' + b",".join(items) + b"]}")
96
+ items, size = [], 20
97
+ items.append(encoded)
98
+ size += len(encoded) + 1
99
+ if items:
100
+ chunks.append(b'{"resourceSpans":[' + b",".join(items) + b"]}")
101
+ for chunk in chunks:
102
+ self._publish(directory, chunk)
103
+ return SpanExportResult.SUCCESS
104
+ except Exception:
105
+ logging.getLogger(__name__).warning("OTEL export failed; request was not published", exc_info=True)
106
+ return SpanExportResult.FAILURE
107
+
108
+ def _publish(self, directory, body):
109
+ directory.mkdir(parents=True, exist_ok=True, mode=0o700)
110
+ name = f"otlp-sdk-{uuid.uuid4()}"
111
+ temporary = directory / f".{name}.tmp"
112
+ try:
113
+ descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
114
+ with os.fdopen(descriptor, "wb") as stream:
115
+ stream.write(_METADATA + body)
116
+ stream.flush()
117
+ os.fsync(stream.fileno())
118
+ os.replace(temporary, directory / f"{name}.jsonl")
119
+ if os.name != "nt":
120
+ descriptor = os.open(directory, os.O_RDONLY)
121
+ try:
122
+ os.fsync(descriptor)
123
+ finally:
124
+ os.close(descriptor)
125
+ finally:
126
+ temporary.unlink(missing_ok=True)
127
+
128
+ def shutdown(self):
129
+ with self._lock:
130
+ self._stopped = True
131
+
132
+ def force_flush(self, timeout_millis=30000):
133
+ return True
134
+
135
+
136
+ class _Gate:
137
+ def __init__(self, processor):
138
+ self.processor = processor
139
+ self.enabled = True
140
+
141
+ def on_start(self, span, parent_context=None):
142
+ if self.enabled:
143
+ self.processor.on_start(span, parent_context=parent_context)
144
+
145
+ def on_end(self, span):
146
+ if self.enabled:
147
+ self.processor.on_end(span)
148
+
149
+ def _on_ending(self, span):
150
+ if self.enabled:
151
+ callback = getattr(self.processor, "_on_ending", None)
152
+ if callback:
153
+ callback(span)
154
+
155
+ def shutdown(self):
156
+ self.processor.shutdown()
157
+
158
+ def force_flush(self, timeout_millis=30000):
159
+ return self.processor.force_flush(timeout_millis) if self.enabled else True
160
+
161
+
162
+ class _Adapter:
163
+ name = "otel"
164
+ module = "opentelemetry"
165
+
166
+ def __init__(self):
167
+ self.gate = None
168
+
169
+ def install(self, **options):
170
+ from opentelemetry import trace
171
+ from opentelemetry.sdk.trace import TracerProvider
172
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
173
+ provider = options.get("provider") or trace.get_tracer_provider()
174
+ if not hasattr(provider, "add_span_processor"):
175
+ if options.get("provider") is not None:
176
+ raise ValueError("OTEL provider must support add_span_processor()")
177
+ provider = TracerProvider()
178
+ trace.set_tracer_provider(provider)
179
+ self.gate = _Gate(BatchSpanProcessor(OtelSpanExporter(spool_dir=options.get("spool_dir"))))
180
+ provider.add_span_processor(self.gate)
181
+
182
+ def uninstall(self):
183
+ if self.gate:
184
+ self.gate.enabled = False
185
+ self.gate.shutdown()
186
+ self.gate = None
187
+
188
+
189
+ adapter = _Adapter()
@@ -50,7 +50,7 @@ Changelog = "https://github.com/FailproofAI/failproofai/blob/main/sdk/python/CHA
50
50
  # `tomli` is `tomllib`'s backport: 3.10 has no stdlib TOML parser, and that test
51
51
  # reads this file to prove the promise, so on 3.10 it needs one rather than
52
52
  # skipping the check on the oldest interpreter we advertise.
53
- dev = ["pytest>=7", "tomli>=2; python_version<'3.11'", "pytest-asyncio>=1.3,<2"]
53
+ dev = ["pytest>=7", "tomli>=2; python_version<'3.11'", "pytest-asyncio>=1.3,<2", "opentelemetry-sdk>=1.39,<2"]
54
54
 
55
55
  # Convenience aliases that pull the FRAMEWORK in. The adapter code always ships
56
56
  # in the base wheel and lazy-imports, so these gate nothing on our side — most
@@ -72,6 +72,7 @@ crewai = [
72
72
  ]
73
73
  llamaindex = ["llama-index-core>=0.14.23,<0.15"]
74
74
  pydantic-ai = ["pydantic-ai-slim>=2.0,<3"] # 2.0 removed Agent(instrument=...)
75
+ otel = ["opentelemetry-sdk>=1.39,<2"] # optional SpanExporter / processor APIs
75
76
 
76
77
  [tool.setuptools.dynamic]
77
78
  version = { attr = "failproofai_sdk._version.__version__" }
@@ -40,6 +40,7 @@ ADAPTER_DIRS = {
40
40
  "crewai": "crewai",
41
41
  "llama_index": "llama_index",
42
42
  "pydantic_ai": "pydantic_ai",
43
+ "otel": "otel",
43
44
  }
44
45
 
45
46
  #: directory -> the failproofai-sdk extra its guide must name.
@@ -48,6 +49,7 @@ DIR_EXTRA = {
48
49
  "crewai": "crewai",
49
50
  "llama_index": "llamaindex",
50
51
  "pydantic_ai": "pydantic-ai",
52
+ "otel": "otel",
51
53
  }
52
54
 
53
55
  #: directory -> third-party modules its examples may import. `manual/` maps to
@@ -59,6 +61,7 @@ DIR_IMPORTS = {
59
61
  "llama_index": {"llama_index"},
60
62
  "pydantic_ai": {"pydantic_ai", "pydantic"},
61
63
  "manual": {"openai"},
64
+ "otel": {"opentelemetry"},
62
65
  }
63
66
 
64
67
  #: stdlib and local helpers every example may import. `_shared` is the trace
@@ -191,7 +194,12 @@ def test_a_framework_guide_names_its_own_extra(directory, extra):
191
194
  def test_a_framework_guide_shows_instrument_and_a_session(directory):
192
195
  text = (DOCS / directory / "README.md").read_text(encoding="utf-8")
193
196
  assert "failproofai_sdk.instrument(" in text, f"{directory} never shows instrument()"
194
- assert "failproofai_sdk.session()" in text, f"{directory} never shows a session"
197
+ if directory == "otel":
198
+ # OTEL supplies trace identity itself, not the SDK event scopes.
199
+ assert 'instrument("otel"' in text
200
+ assert "start_as_current_span(" in text
201
+ else:
202
+ assert "failproofai_sdk.session()" in text, f"{directory} never shows a session"
195
203
 
196
204
 
197
205
  @pytest.mark.parametrize("guide", _guides(), ids=lambda p: p.parent.name)
@@ -285,7 +293,11 @@ def test_an_example_does_not_thread_identity_by_hand(path):
285
293
  @pytest.mark.parametrize("path", EXAMPLES, ids=EXAMPLE_IDS)
286
294
  def test_an_example_opens_a_session(path):
287
295
  source = path.read_text(encoding="utf-8")
288
- assert "failproofai_sdk.session()" in source, f"{path.name} never opens a session"
296
+ if path.parents[1].name == "otel":
297
+ assert "start_as_current_span(" in source
298
+ assert '"gen_ai.operation.name": "invoke_agent"' in source
299
+ else:
300
+ assert "failproofai_sdk.session()" in source, f"{path.name} never opens a session"
289
301
 
290
302
 
291
303
  @pytest.mark.parametrize(
@@ -295,7 +307,8 @@ def test_an_example_opens_a_session(path):
295
307
  )
296
308
  def test_a_framework_example_instruments(path):
297
309
  source = path.read_text(encoding="utf-8")
298
- assert "failproofai_sdk.instrument()" in source, f"{path.name} never instruments"
310
+ call = 'failproofai_sdk.instrument("otel",' if path.parents[1].name == "otel" else "failproofai_sdk.instrument()"
311
+ assert call in source, f"{path.name} never instruments"
299
312
 
300
313
 
301
314
  @pytest.mark.parametrize(
@@ -106,7 +106,7 @@ def fake(monkeypatch):
106
106
  def test_the_four_adapters_are_pre_registered():
107
107
  # Registered before their modules exist so that adding an adapter is one new
108
108
  # file, never an edit to the registry (which four agents would conflict on).
109
- assert integrations.available() == ("crewai", "langchain", "llama_index", "pydantic_ai")
109
+ assert integrations.available() == ("crewai", "langchain", "llama_index", "otel", "pydantic_ai")
110
110
 
111
111
 
112
112
  @pytest.mark.parametrize(
@@ -0,0 +1,147 @@
1
+ import json
2
+ import subprocess
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+ from opentelemetry import trace
8
+ from opentelemetry.sdk.trace import TracerProvider
9
+ from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExportResult
10
+ from opentelemetry.trace import Link, SpanKind, Status, StatusCode
11
+
12
+ import failproofai_sdk
13
+ from failproofai_sdk.integrations import _core
14
+
15
+
16
+ def test_module_and_exporter_import_without_opentelemetry():
17
+ package_root = Path(failproofai_sdk.__file__).resolve().parent.parent
18
+ result = subprocess.run(
19
+ [sys.executable, "-I", "-S", "-c", f"""
20
+ import importlib.abc
21
+ import sys
22
+ sys.path.insert(0, {str(package_root)!r})
23
+ class AbsentOpenTelemetry(importlib.abc.MetaPathFinder):
24
+ def find_spec(self, fullname, path=None, target=None):
25
+ if fullname == "opentelemetry" or fullname.startswith("opentelemetry."):
26
+ raise ModuleNotFoundError("OpenTelemetry is not installed")
27
+ sys.meta_path.insert(0, AbsentOpenTelemetry())
28
+ from failproofai_sdk.integrations.otel import OtelSpanExporter
29
+ import failproofai_sdk
30
+ assert failproofai_sdk.OtelSpanExporter is OtelSpanExporter
31
+ exporter = OtelSpanExporter()
32
+ assert exporter.force_flush()
33
+ exporter.shutdown()
34
+ assert not any(name.startswith("opentelemetry") for name in sys.modules)
35
+ print("Imported OTEL module and exporter without OpenTelemetry")
36
+ """],
37
+ capture_output=True, text=True, timeout=15,
38
+ )
39
+ assert result.returncode == 0, result.stderr
40
+ assert "without OpenTelemetry" in result.stdout
41
+
42
+
43
+ @pytest.fixture(autouse=True)
44
+ def enabled_otel(monkeypatch):
45
+ # The CrewAI test module disables its own vendor telemetry at collection.
46
+ # These tests explicitly exercise OTEL, without changing that other suite.
47
+ monkeypatch.delenv("OTEL_SDK_DISABLED", raising=False)
48
+
49
+
50
+ def batches(home):
51
+ result = []
52
+ for file in (home / "state/spool-otlp").glob("*.jsonl"):
53
+ header, body = file.read_bytes().split(b"\n", 1)
54
+ assert json.loads(header) == {"path": "/v1/traces", "content_type": "application/json", "encoding": None}
55
+ assert file.stat().st_mode & 0o777 == 0o600
56
+ result.extend(json.loads(body)["resourceSpans"])
57
+ return result
58
+
59
+
60
+ def test_real_spans_preserve_context_attributes_events_and_precise_time(tmp_path, monkeypatch):
61
+ monkeypatch.setenv("FAILPROOFAI_HOME", str(tmp_path))
62
+ provider = TracerProvider()
63
+ provider.add_span_processor(SimpleSpanProcessor(failproofai_sdk.OtelSpanExporter()))
64
+ tracer = provider.get_tracer("scope", "1.0")
65
+ parent = tracer.start_span("agent", start_time=1780000000000000123)
66
+ child = tracer.start_span("chat", kind=SpanKind.CLIENT,
67
+ context=trace.set_span_in_context(parent),
68
+ start_time=1780000000000000456,
69
+ attributes={"gen_ai.operation.name": "chat", "count": 42, "enabled": True, "values": ("a", "b")},
70
+ links=[Link(parent.get_span_context(), attributes={"linked": True})])
71
+ child.add_event("prompt", {"text": "hi"}, timestamp=1780000000000000789)
72
+ child.set_status(Status(StatusCode.ERROR, "failed"))
73
+ child.end(end_time=1780000001000000012)
74
+ parent.end(end_time=1780000001000000013)
75
+ assert provider.force_flush()
76
+ output = next(span for resource in batches(tmp_path) for scope in resource["scopeSpans"]
77
+ for span in scope["spans"] if span["name"] == "chat")
78
+ assert output["traceId"] == f"{child.get_span_context().trace_id:032x}"
79
+ assert output["parentSpanId"] == f"{parent.get_span_context().span_id:016x}"
80
+ assert output["kind"] == 3
81
+ assert output["startTimeUnixNano"] == "1780000000000000456"
82
+ assert output["endTimeUnixNano"] == "1780000001000000012"
83
+ assert output["status"] == {"code": 2, "message": "failed"}
84
+ assert {"key": "count", "value": {"intValue": "42"}} in output["attributes"]
85
+ assert output["events"][0]["timeUnixNano"] == "1780000000000000789"
86
+ assert output["links"][0]["spanId"] == f"{parent.get_span_context().span_id:016x}"
87
+ assert not list((tmp_path / "state/spool-otlp").glob("*.tmp"))
88
+ provider.shutdown()
89
+
90
+
91
+ def test_exporter_reports_failures_and_shutdown(tmp_path):
92
+ file = tmp_path / "not-a-directory"
93
+ file.write_text("x")
94
+ exporter = failproofai_sdk.OtelSpanExporter(spool_dir=file)
95
+ provider = TracerProvider()
96
+ span = provider.get_tracer("app").start_span("test")
97
+ span.end()
98
+ assert exporter.export([span]) == SpanExportResult.FAILURE
99
+ exporter.shutdown()
100
+ assert exporter.export([]) == SpanExportResult.FAILURE
101
+ assert exporter.force_flush()
102
+ provider.shutdown()
103
+
104
+
105
+ def test_batches_split_at_relay_limit(tmp_path, monkeypatch):
106
+ from opentelemetry.sdk.trace import SpanLimits
107
+ monkeypatch.setenv("FAILPROOFAI_HOME", str(tmp_path))
108
+ provider = TracerProvider(span_limits=SpanLimits(max_attribute_length=10_000_000))
109
+ span = provider.get_tracer("app").start_span("large", attributes={"content": "x" * (4 * 1024 * 1024)})
110
+ span.end()
111
+ assert failproofai_sdk.OtelSpanExporter().export([span, span]) == SpanExportResult.SUCCESS
112
+ assert len(list((tmp_path / "state/spool-otlp").glob("*.jsonl"))) == 2
113
+ assert len(batches(tmp_path)) == 2
114
+ provider.shutdown()
115
+
116
+
117
+ def test_instrument_is_explicit_gated_and_keeps_other_processors(tmp_path, monkeypatch):
118
+ monkeypatch.setenv("FAILPROOFAI_HOME", str(tmp_path))
119
+ _core.set_strict(True)
120
+ provider = TracerProvider()
121
+ other = []
122
+
123
+ class Existing:
124
+ def on_start(self, *_args, **_kwargs): pass
125
+ def on_end(self, span): other.append(span.name)
126
+ def shutdown(self): pass
127
+ def force_flush(self, *_args): return True
128
+ def _on_ending(self, span): pass
129
+
130
+ provider.add_span_processor(Existing())
131
+ try:
132
+ assert "otel" not in failproofai_sdk.integrations._detected()
133
+ assert failproofai_sdk.instrument("otel", provider=provider) == ("otel",)
134
+ assert failproofai_sdk.instrument("otel", provider=provider) == ()
135
+ tracer = provider.get_tracer("app")
136
+ tracer.start_span("before").end()
137
+ assert provider.force_flush()
138
+ assert len(batches(tmp_path)) == 1
139
+ assert failproofai_sdk.uninstrument("otel") == ("otel",)
140
+ tracer.start_span("after").end()
141
+ assert provider.force_flush()
142
+ assert len(batches(tmp_path)) == 1
143
+ assert other == ["before", "after"]
144
+ finally:
145
+ failproofai_sdk.uninstrument("otel")
146
+ _core.set_strict(None)
147
+ provider.shutdown()
@@ -61,13 +61,14 @@ ADAPTER_IMPORTS = {
61
61
  "crewai.py": {"crewai"},
62
62
  "llama_index.py": {"llama_index", "llama_index_instrumentation", "pydantic"},
63
63
  "pydantic_ai.py": {"pydantic_ai"},
64
+ "otel.py": {"opentelemetry"},
64
65
  }
65
66
 
66
67
  #: Framework top-level names that must never appear in `sys.modules` after a bare
67
68
  #: `import failproofai_sdk`.
68
69
  FRAMEWORK_ROOTS = {
69
70
  "langchain", "langchain_core", "langgraph", "crewai",
70
- "llama_index", "llama_index_instrumentation", "pydantic", "pydantic_ai",
71
+ "llama_index", "llama_index_instrumentation", "pydantic", "pydantic_ai", "opentelemetry",
71
72
  }
72
73
 
73
74
 
@@ -142,6 +143,7 @@ def test_no_runtime_dependencies_are_declared():
142
143
  #: convenience library drifting in — anything here is one `--extra dev` away from a
143
144
  #: user's environment, and none of it is covered by the zero-dependency promise.
144
145
  ALLOWED_DEV_DEPENDENCIES = {
146
+ "opentelemetry-sdk": "real optional OTEL SpanExporter and processor contract tests; never a base dependency",
145
147
  "pytest": "the test runner",
146
148
  "pytest-asyncio": "the adapters are half-async; their scopes are exercised "
147
149
  "under `async with`, which needs an async test runner",
@@ -159,6 +161,7 @@ FRAMEWORK_EXTRAS = {
159
161
  "crewai": {"crewai", "onnxruntime"},
160
162
  "llamaindex": {"llama-index-core"},
161
163
  "pydantic-ai": {"pydantic-ai-slim"},
164
+ "otel": {"opentelemetry-sdk"},
162
165
  }
163
166
 
164
167