failproofai 1.0.10 → 1.0.11-beta.1
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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.meta +8 -0
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.meta +8 -0
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +4 -4
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -3
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -3
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +4 -4
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.meta +8 -0
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +4 -4
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +8 -8
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +5 -5
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +5 -5
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js +9 -9
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0qxhecv._.js → [externals]__0d7i9ea._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[externals]__0sg9-3p._.js → [externals]__0hjaw70._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1_9ze9b._.js +3 -0
- package/.next/standalone/.next/server/chunks/[externals]__1oenfmn._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ga9hs7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0moxohc._.js +22 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z2ll-a._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +11 -11
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1autwno._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1v25n-6._.js +3 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
- package/.next/standalone/.next/server/chunks/_1fz8m31._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1jrv3hg._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1nx8fim._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ogwigf._.js +1 -1
- package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1q27azx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
- 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
- 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
- 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
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0oqrl71._.js → [root-of-the-server]__042u-jn._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _1ffxwxz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_042cgl1._.js → _1o95rf0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1zopuov._.js → _1thr5rf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_16pa9ht._.js → _1v1o2ya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/middleware.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/08tmc_7n-rba1.js +1 -0
- package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 09tlrmucn6oq3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 0i69k917myh7b.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0u_nhsujkactp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 0zp51hzx62i-u.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 1fz7q0gtnmeod.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 1j75le8wln-26.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1z4km7tw0_50h.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 239ay_80-xr8z.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 2c9pc5xco6rnu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
- package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
- package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
- package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
- package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
- package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
- package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
- package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
- package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
- package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
- package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
- package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
- package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -10
- package/.next/standalone/package.json +14 -11
- package/.next/standalone/sdk/python/README.md +41 -0
- package/.next/standalone/sdk/python/docs/README.md +1 -0
- package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
- package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
- package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
- package/.next/standalone/sdk/python/pyproject.toml +2 -1
- package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
- package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
- package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
- package/.next/standalone/sdk/python/uv.lock +258 -147
- package/.next/standalone/sdk/typescript/README.md +43 -0
- package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
- package/.next/standalone/sdk/typescript/package-lock.json +57 -5
- package/.next/standalone/sdk/typescript/package.json +2 -0
- package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
- package/.next/standalone/sdk/typescript/src/index.ts +2 -0
- package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
- package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
- package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
- package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +653 -1077
- package/dist/cli.mjs +8174 -3930
- package/dist/worker.mjs +170 -62
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +14 -11
- package/scripts/launch-facts.ts +157 -0
- package/scripts/launch-ready.ts +228 -0
- package/scripts/launch-screen.ts +202 -0
- package/scripts/launch.ts +106 -49
- package/scripts/skew-log-filter.ts +42 -0
- package/src/audit/cli-login.ts +39 -59
- package/src/audit/cli.ts +308 -228
- package/src/audit/index.ts +45 -2
- package/src/audit/schedule-cli.ts +83 -120
- package/src/audit/types.ts +31 -0
- package/src/hooks/agent-selection.ts +155 -0
- package/src/hooks/backfill-cli.ts +151 -137
- package/src/hooks/backfill-request.ts +149 -0
- package/src/hooks/cloud-connection.ts +1 -1
- package/src/hooks/cloud-enrollment-cli.ts +78 -28
- package/src/hooks/cloud-introspect.ts +3 -0
- package/src/hooks/collector-config.ts +10 -1
- package/src/hooks/config-status.ts +606 -0
- package/src/hooks/configure-wizard.ts +915 -989
- package/src/hooks/daemon-service.ts +16 -6
- package/src/hooks/did-you-mean.ts +185 -0
- package/src/hooks/flush-cli.ts +33 -7
- package/src/hooks/fp-config.ts +96 -0
- package/src/hooks/fp-home.ts +11 -0
- package/src/hooks/fp-reset.ts +102 -95
- package/src/hooks/handler.ts +4 -0
- package/src/hooks/harness-cli.ts +87 -138
- package/src/hooks/install-prompt.ts +38 -477
- package/src/hooks/integrations.ts +1 -1
- package/src/hooks/jev-cli.ts +183 -196
- package/src/hooks/manager.ts +554 -358
- package/src/hooks/onboarding-attempt.ts +11 -9
- package/src/hooks/otel-cli.ts +344 -0
- package/src/hooks/otel-output.ts +24 -0
- package/src/hooks/otel-writers.ts +225 -0
- package/src/hooks/pack-cli.ts +219 -204
- package/src/hooks/report-body.ts +50 -0
- package/src/hooks/safe-config-write.ts +4 -2
- package/src/hooks/session-pause-cli.ts +93 -73
- package/src/hooks/tui.ts +849 -389
- package/src/hooks/uninstall-cli.ts +136 -149
- package/src/hooks/untraced-note.ts +52 -0
- package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
- package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|