failproofai 1.0.1 → 1.0.2-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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +6 -6
- package/.next/standalone/.next/required-server-files.json +1 -1
- 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 +2 -2
- 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.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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- 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.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
- 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 +5 -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.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 -3
- 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 +4 -4
- 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 +4 -4
- 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.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- 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.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- 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/server-reference-manifest.json +66 -22
- package/.next/standalone/.next/server/app/policies/page.js +5 -4
- 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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
- 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/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 +2 -2
- 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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- 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/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- 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]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
- package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07tbbf3._.js → [root-of-the-server]__0zig5sv._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0lvyuv4._.js → _00e9tnf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _12l4a_k._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.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 +3 -3
- 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 +71 -27
- package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 0eqky-7n7dgdm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 0sa9v5mhc8861.js} +13 -13
- package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
- package/.next/standalone/.next/static/chunks/{3k7mxbp9hiom0.js → 1nuqytbta44af.js} +1 -1
- package/.next/standalone/.next/static/chunks/1sl2xmn9inx3y.js +6 -0
- package/.next/standalone/.next/static/chunks/{2_2lhuojq2xop.js → 2aawsjld9ied_.js} +1 -1
- package/.next/standalone/.next/static/chunks/2gn7ut4svofm2.js +1 -0
- package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2hs7ycvsav7v2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 2i2yt3hzjredm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 35xg6retpe6rp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 3y54nw0i7dsjl.js} +1 -1
- package/.next/standalone/SECURITY.md +5 -4
- package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
- package/.next/standalone/app/actions/pack-actions.ts +153 -0
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
- package/.next/standalone/app/policies/hooks-client.tsx +320 -14
- package/.next/standalone/components/navbar.tsx +2 -28
- package/.next/standalone/components/reach-developers.tsx +8 -1
- package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
- package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
- package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
- package/.next/standalone/docs-old/i18n/README.de.md +1 -0
- package/.next/standalone/docs-old/i18n/README.es.md +1 -0
- package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.he.md +1 -0
- package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.it.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
- package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
- package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
- package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
- package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
- package/.next/standalone/fp-cloud-cli/README.md +259 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
- package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
- package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
- package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
- package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
- package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
- package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
- package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
- package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
- package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
- package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
- package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
- package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
- package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
- package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
- package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
- package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
- package/.next/standalone/package.json +29 -22
- package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
- package/.next/standalone/sdk/python/LICENSE +42 -0
- package/.next/standalone/sdk/python/MANIFEST.in +21 -0
- package/.next/standalone/sdk/python/README.md +466 -0
- package/.next/standalone/sdk/python/docs/README.md +249 -0
- package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
- package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
- package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
- package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
- package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
- package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
- package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
- package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
- package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
- package/.next/standalone/sdk/python/pyproject.toml +92 -0
- package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
- package/.next/standalone/sdk/python/skill/references/events.md +234 -0
- package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
- package/.next/standalone/sdk/python/skill/references/install.md +100 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
- package/.next/standalone/sdk/python/tests/__init__.py +0 -0
- package/.next/standalone/sdk/python/tests/conftest.py +169 -0
- package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
- package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
- package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
- package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
- package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
- package/.next/standalone/sdk/python/tests/test_context.py +410 -0
- package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
- package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
- package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
- package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
- package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
- package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
- package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
- package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
- package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
- package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
- package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
- package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
- package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
- package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
- package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
- package/.next/standalone/sdk/python/uv.lock +6005 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +77 -21
- package/bin/failproofai.mjs +145 -11
- package/dist/cli.mjs +4482 -2205
- package/dist/index.js +5 -5
- package/dist/worker.mjs +2399 -1978
- package/package.json +29 -22
- package/policy-pack/SHA256SUMS +2 -0
- package/policy-pack/failproofai-pack.json +732 -0
- package/policy-pack/failproofai-pack.mjs +2017 -0
- package/scripts/build-policy-pack.mjs +121 -0
- package/scripts/changelog-section.py +77 -0
- package/scripts/prune-standalone.mjs +3 -0
- package/scripts/python-version.py +232 -0
- package/scripts/translate-docs/cli.ts +21 -0
- package/scripts/translate-docs/mintlify-nav.ts +85 -13
- package/scripts/translate-docs/translator.ts +66 -11
- package/src/audit/cache.ts +40 -3
- package/src/audit/cli.ts +1 -1
- package/src/audit/index.ts +1 -1
- package/src/audit/replay.ts +89 -3
- package/src/audit/report.ts +5 -5
- package/src/audit/schedule-cli.ts +49 -53
- package/src/hooks/builtin-policies.ts +217 -579
- package/src/hooks/cloud-enrollment-cli.ts +66 -27
- package/src/hooks/cloud-managed-policies.ts +6 -1
- package/src/hooks/configure-wizard.ts +1 -1
- package/src/hooks/custom-hooks-loader.ts +89 -6
- package/src/hooks/fp-home.ts +92 -4
- package/src/hooks/fp-reset.ts +26 -2
- package/src/hooks/handler.ts +181 -11
- package/src/hooks/harness-cli.ts +79 -25
- package/src/hooks/hook-activity-store.ts +13 -3
- package/src/hooks/install-prompt.ts +25 -3
- package/src/hooks/manager.ts +418 -172
- package/src/hooks/migrations.ts +2 -2
- package/src/hooks/pack-cli.ts +505 -0
- package/src/hooks/pack-failclosed.ts +189 -0
- package/src/hooks/pack-manifest.ts +344 -0
- package/src/hooks/pack-store.ts +930 -0
- package/src/hooks/policy-catalog.ts +535 -0
- package/src/hooks/policy-evaluator.ts +24 -16
- package/src/hooks/policy-presets.ts +4 -2
- package/src/hooks/policy-registry.ts +8 -2
- package/src/hooks/policy-types.ts +26 -0
- package/src/hooks/session-pause-cli.ts +33 -9
- package/src/hooks/tui.ts +570 -0
- package/src/hooks/types.ts +15 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
- package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
- package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
- package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Writing the integration
|
|
2
|
+
|
|
3
|
+
Identity is ambient. Bind it once per run with a context manager and every
|
|
4
|
+
`event.*` call inside — including calls in functions that have never heard of
|
|
5
|
+
Failproof AI — lands on the right session and agent.
|
|
6
|
+
|
|
7
|
+
You do not write the contextvar layer any more; it ships. This page is how to use
|
|
8
|
+
it, and what the two failure modes it exists to prevent look like when you route
|
|
9
|
+
around it.
|
|
10
|
+
|
|
11
|
+
On a supported framework (LangChain/LangGraph, CrewAI, LlamaIndex, Pydantic AI),
|
|
12
|
+
read `frameworks.md` first — `failproofai_sdk.instrument()` does all of this for you.
|
|
13
|
+
|
|
14
|
+
## The three scopes
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import failproofai_sdk
|
|
18
|
+
|
|
19
|
+
failproofai_sdk.configure(environment="production")
|
|
20
|
+
|
|
21
|
+
with failproofai_sdk.agent("planner", goal=question): # agent_start / agent_end
|
|
22
|
+
with failproofai_sdk.tool_call("web_search", input={"q": question}) as t:
|
|
23
|
+
t.output = search(question)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Scope | Emits | Yields |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `failproofai_sdk.session(session_id=None, *, agent_id=None)` | **nothing** — identity only | the session id |
|
|
29
|
+
| `failproofai_sdk.agent(agent_id="main", *, session_id=None, goal=None, parent_id=AUTO, outcome="success", summary=None, **fields)` | `agent_start` on entry, `agent_end` on every exit | an `Identity` |
|
|
30
|
+
| `failproofai_sdk.tool_call(tool_name, *, tool_call_id=None, input=None, **fields)` | `tool_use` / `tool_result`, timed | a handle — set `.output`, read `.id` |
|
|
31
|
+
|
|
32
|
+
Every one of them works as `with` **and** `async with`, with identical semantics
|
|
33
|
+
and byte-identical events. Nothing in a scope awaits, so the async form is the
|
|
34
|
+
same code; use whichever matches the function you are in.
|
|
35
|
+
|
|
36
|
+
Read the current identity anywhere with `failproofai_sdk.current()`, which returns a
|
|
37
|
+
frozen `Identity(session_id, agent_id, parent_id, depth)`. `session_id is None`
|
|
38
|
+
means nothing is bound.
|
|
39
|
+
|
|
40
|
+
**`session()` versus `agent()`.** `session()` binds identity and emits nothing —
|
|
41
|
+
reach for it when the run is brackets you do not own (a request handler that
|
|
42
|
+
delegates to a framework), or when you want one session id to cover several
|
|
43
|
+
`agent()` blocks. `agent()` is what creates the session on the platform, because
|
|
44
|
+
a session is *defined* as something that emitted `agent_start`.
|
|
45
|
+
|
|
46
|
+
**Defaults, all of them chosen to be hard to get wrong:**
|
|
47
|
+
|
|
48
|
+
- `session_id=None` on `agent()`/`session()` inherits an already-bound session,
|
|
49
|
+
and generates a `uuid4().hex` only if there is none. A nested scope stays inside
|
|
50
|
+
one run rather than splitting it in two.
|
|
51
|
+
- `parent_id` defaults to the enclosing agent from the scope stack. Pass
|
|
52
|
+
`parent_id=None` to force a root span, or a string to override.
|
|
53
|
+
- `tool_call_id` defaults to `uuid4().hex` — unique process-wide, which is what
|
|
54
|
+
the correlation map needs (see `events.md`).
|
|
55
|
+
|
|
56
|
+
## What `agent()` does on the way out
|
|
57
|
+
|
|
58
|
+
This is the exit path hand-written instrumentation always gets wrong, so it is
|
|
59
|
+
worth stating exactly:
|
|
60
|
+
|
|
61
|
+
| Exception | Events, in order | `outcome` |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| none | `agent_end` | `"success"` (or your `outcome=`) |
|
|
64
|
+
| any `Exception` | `error`, **then** `agent_end` | `"failed"` |
|
|
65
|
+
| `KeyboardInterrupt` / `SystemExit` | `error`, then `agent_end` | `"failed"` |
|
|
66
|
+
| `CancelledError` / `GeneratorExit` | `agent_end` only | `"cancelled"` |
|
|
67
|
+
|
|
68
|
+
The exception is always re-raised. `error` comes strictly *before* `agent_end`,
|
|
69
|
+
because the agent span closes at `agent_end` and anything after it is attributed
|
|
70
|
+
to nothing. A cancellation is not a failure and must not pollute the Errors
|
|
71
|
+
surface, so it emits no `error` event.
|
|
72
|
+
|
|
73
|
+
`tool_call()` on failure emits `tool_result(error="TypeName: message")` and **no
|
|
74
|
+
`error` event** — a tool failure the agent loop catches is not a run-level error,
|
|
75
|
+
and one that propagates is reported exactly once, by the enclosing `agent()`.
|
|
76
|
+
|
|
77
|
+
## Explicit identity still works
|
|
78
|
+
|
|
79
|
+
`session_id` and `agent_id` are optional keyword arguments on all 15 `event.*`
|
|
80
|
+
methods, not removed ones. Passing them explicitly still works, and still wins
|
|
81
|
+
over whatever is bound:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
failproofai_sdk.event.tool_use(session_id=sid, agent_id="planner",
|
|
85
|
+
tool_name="web_search", tool_call_id="toolu_01")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Omit `session_id` with nothing bound and you get a **`TypeError`** naming both
|
|
89
|
+
fixes. That is deliberate: the alternative is dropping the event silently, and
|
|
90
|
+
this SDK already asks you to debug enough invisible failures. `agent_id` never
|
|
91
|
+
raises — it is a label, and it falls back to `"main"`.
|
|
92
|
+
|
|
93
|
+
## Don't use a module global
|
|
94
|
+
|
|
95
|
+
The shortcut the scopes exist to replace, because it breaks silently:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# WRONG
|
|
99
|
+
_current_session = None # or self.session_id on a shared client
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The moment two runs overlap — two asyncio tasks, two threads, two requests in one
|
|
103
|
+
process — they share this variable. Events from both runs get whichever
|
|
104
|
+
`session_id` was written last. You do not get an error; you get **one session
|
|
105
|
+
containing two runs' events, interleaved**, and another session missing entirely.
|
|
106
|
+
Nothing about the output says the data is wrong.
|
|
107
|
+
|
|
108
|
+
The scopes bind contextvars instead: per-task and per-thread, so overlapping runs
|
|
109
|
+
cannot see each other's identity. The agent stack they keep is immutable for the
|
|
110
|
+
same reason — a mutable stack shared by reference between tasks is the same bug
|
|
111
|
+
wearing a contextvars costume, and it passes every single-threaded test.
|
|
112
|
+
|
|
113
|
+
## Threads will drop your events — `propagate()` is the fix
|
|
114
|
+
|
|
115
|
+
This one is worth reading twice, because it is the failure everybody hits.
|
|
116
|
+
|
|
117
|
+
`contextvars` propagate into asyncio tasks automatically — a task started inside
|
|
118
|
+
an `agent()` block inherits the session. **They do not propagate into new
|
|
119
|
+
threads.** A fresh thread starts with an empty context, so nothing is bound
|
|
120
|
+
there.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# WRONG — the worker has no session
|
|
124
|
+
pool.submit(dispatch, tool_call)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Wrap the callable:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
pool.submit(failproofai_sdk.propagate(dispatch), tool_call)
|
|
131
|
+
pool.map(failproofai_sdk.propagate(work), items)
|
|
132
|
+
threading.Thread(target=failproofai_sdk.propagate(work)).start()
|
|
133
|
+
loop.run_in_executor(None, failproofai_sdk.propagate(work), x)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`propagate(fn)` captures the identity bound *at the moment you call it* and binds
|
|
137
|
+
it inside the worker, then restores what was there. One name covers thread pools,
|
|
138
|
+
bare threads and `run_in_executor`.
|
|
139
|
+
|
|
140
|
+
**Do not reach for `contextvars.copy_context().run` instead.** A `Context` object
|
|
141
|
+
cannot be entered by two threads at once — the second gets `RuntimeError: cannot
|
|
142
|
+
enter context` — so the copy-context form crashes the caller's worker on any
|
|
143
|
+
reuse, which is exactly what `pool.map` and a retried submit do. Mutations made
|
|
144
|
+
inside `ctx.run` also persist in that `Context`, so a reused one leaks the
|
|
145
|
+
previous call's agent stack into the next run.
|
|
146
|
+
|
|
147
|
+
Since events now raise rather than vanish when nothing is bound, an un-propagated
|
|
148
|
+
worker announces itself with a `TypeError` naming `failproofai_sdk.propagate` instead of
|
|
149
|
+
producing a session that is quietly missing half its events.
|
|
150
|
+
|
|
151
|
+
## Where to put the calls
|
|
152
|
+
|
|
153
|
+
**One tool dispatcher.** Most agents route every tool through one function. That
|
|
154
|
+
is one edit site for all tools:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
def dispatch(tool_call):
|
|
158
|
+
with failproofai_sdk.tool_call(tool_call.name, tool_call_id=tool_call.id,
|
|
159
|
+
input=tool_call.input) as t:
|
|
160
|
+
t.output = TOOLS[tool_call.name](**tool_call.input)
|
|
161
|
+
return t.output
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Reuse the framework's own tool-call id — Anthropic and OpenAI both give you one.
|
|
165
|
+
It is already unique, and it makes the events line up with your provider logs.
|
|
166
|
+
|
|
167
|
+
**One LLM wrapper.** Same idea on the model side. Pass a `request_id` on both
|
|
168
|
+
halves so concurrent calls pair correctly, and set `duration_ms` yourself on the
|
|
169
|
+
response — it is not auto-computed for model events:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
def call_model(messages, **kw):
|
|
173
|
+
rid = uuid.uuid4().hex
|
|
174
|
+
started = time.monotonic()
|
|
175
|
+
failproofai_sdk.event.model_request(model=MODEL, messages=messages, request_id=rid,
|
|
176
|
+
tools=kw.get("tools"))
|
|
177
|
+
try:
|
|
178
|
+
resp = client.messages.create(model=MODEL, messages=messages, **kw)
|
|
179
|
+
# BaseException, not Exception. `asyncio.CancelledError` inherits straight
|
|
180
|
+
# from BaseException on every supported Python, so `except Exception` does
|
|
181
|
+
# NOT see a cancelled tool — and a cancelled tool is a `tool_use` with no
|
|
182
|
+
# `tool_result` after it: an orphaned event that also holds its correlation
|
|
183
|
+
# slot until the cap evicts it. Cancellation is the ordinary way an async
|
|
184
|
+
# tool ends when a timeout fires or a caller gives up.
|
|
185
|
+
except BaseException as e:
|
|
186
|
+
failproofai_sdk.event.model_response(
|
|
187
|
+
model=MODEL, request_id=rid, error=f"{type(e).__name__}: {e}",
|
|
188
|
+
duration_ms=round((time.monotonic() - started) * 1000))
|
|
189
|
+
raise
|
|
190
|
+
failproofai_sdk.event.model_response(
|
|
191
|
+
model=resp.model, request_id=rid, stop_reason=resp.stop_reason,
|
|
192
|
+
input_tokens=resp.usage.input_tokens, output_tokens=resp.usage.output_tokens,
|
|
193
|
+
role=resp.role, duration_ms=round((time.monotonic() - started) * 1000))
|
|
194
|
+
return resp
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`duration_ms` must be a whole-millisecond **`int`** — a float raises `ValueError`
|
|
198
|
+
at the call site rather than silently emptying the column.
|
|
199
|
+
|
|
200
|
+
**Sub-agents.** One session, several actors. Nest the scopes and the wiring is
|
|
201
|
+
automatic — the inner `agent()` inherits the session and takes the outer agent as
|
|
202
|
+
its `parent_id`:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
async def researcher(topic):
|
|
206
|
+
async with failproofai_sdk.agent("researcher", goal=topic):
|
|
207
|
+
...
|
|
208
|
+
|
|
209
|
+
async with failproofai_sdk.agent("planner", goal=question):
|
|
210
|
+
await researcher(topic) # session inherited, parent_id="planner"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Yes, this means several `agent_start` events on one `session_id` — that is
|
|
214
|
+
correct and intended.** `SKILL.md` §2 says "emit `agent_start` at the top of the
|
|
215
|
+
run"; read that as *once per agent*, not once per session. One `agent_start` per
|
|
216
|
+
actor is what makes sub-agents appear as distinct, nested spans.
|
|
217
|
+
|
|
218
|
+
If a sub-agent runs in a different function that is called *outside* the parent's
|
|
219
|
+
block, pass `session_id=failproofai_sdk.current().session_id` explicitly rather than
|
|
220
|
+
letting it generate a new one.
|
|
221
|
+
|
|
222
|
+
**A service.** For an HTTP handler or a queue worker you usually already have a
|
|
223
|
+
per-run id — request id, job id, trace id. **Use it as the `session_id`** rather
|
|
224
|
+
than generating one: then a session in Failproof AI and a request in your own logs
|
|
225
|
+
are the same string, and cross-referencing an incident stops being detective
|
|
226
|
+
work.
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
@app.post("/chat")
|
|
230
|
+
async def chat(req: Request, body: ChatBody):
|
|
231
|
+
async with failproofai_sdk.agent("assistant", session_id=req.headers["x-request-id"],
|
|
232
|
+
goal=body.message):
|
|
233
|
+
return await run_agent(body.message)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Frameworks
|
|
237
|
+
|
|
238
|
+
| Framework | What to do |
|
|
239
|
+
|---|---|
|
|
240
|
+
| LangChain / LangGraph | `failproofai_sdk.instrument()` — see `frameworks.md` |
|
|
241
|
+
| CrewAI | `failproofai_sdk.instrument()` |
|
|
242
|
+
| LlamaIndex | `failproofai_sdk.instrument()` |
|
|
243
|
+
| Pydantic AI | `failproofai_sdk.instrument()` |
|
|
244
|
+
| a plain agent loop | `agent()` around the loop, `tool_call()` in the dispatcher |
|
|
245
|
+
| an HTTP agent service | `agent()` in request middleware, keyed on the request id |
|
|
246
|
+
| a queue worker | `agent()` around the job handler, keyed on the job id |
|
|
247
|
+
| anything else with a callback surface | `agent()` at its outermost boundary, `tool_call()` in its tool hook |
|
|
248
|
+
|
|
249
|
+
Adapters and hand-written scopes compose: instrument the framework, wrap the call
|
|
250
|
+
in your own `failproofai_sdk.agent(...)`, and the adapter's events join **that** session
|
|
251
|
+
with your agent as their parent. `frameworks.md` has the details.
|
|
252
|
+
|
|
253
|
+
## Testing your integration
|
|
254
|
+
|
|
255
|
+
Point the SDK somewhere disposable and read what came out:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
# conftest.py
|
|
259
|
+
import json, pathlib, pytest
|
|
260
|
+
|
|
261
|
+
@pytest.fixture
|
|
262
|
+
def events(tmp_path, monkeypatch):
|
|
263
|
+
# `configure(base_dir=...)` below is what actually redirects the spool.
|
|
264
|
+
# Do NOT reach for AGENTEYE_HOME here: the SDK no longer reads it, so it
|
|
265
|
+
# would leave events going to the real ~/.failproofai/custom-agents while
|
|
266
|
+
# this fixture read an empty tmp_path.
|
|
267
|
+
monkeypatch.setenv("FAILPROOFAI_HOME", str(tmp_path))
|
|
268
|
+
import failproofai_sdk
|
|
269
|
+
# flush_interval is huge on purpose: it parks the background thread so it
|
|
270
|
+
# cannot race our explicit flush. Both paths write a file named only to the
|
|
271
|
+
# millisecond, so two flushes in the same millisecond clobber each other and
|
|
272
|
+
# you lose events — a flaky test with a real cause.
|
|
273
|
+
failproofai_sdk.configure(base_dir=tmp_path, environment="test", flush_interval=3600)
|
|
274
|
+
yield lambda: [
|
|
275
|
+
json.loads(line)
|
|
276
|
+
for f in sorted((tmp_path / "events").glob("*.jsonl"))
|
|
277
|
+
for line in f.read_text().splitlines()
|
|
278
|
+
]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
def test_run_emits_a_session(events):
|
|
283
|
+
import failproofai_sdk
|
|
284
|
+
with failproofai_sdk.agent("planner", goal="test"):
|
|
285
|
+
pass
|
|
286
|
+
failproofai_sdk._writer.flush_now() # don't wait for the flush thread
|
|
287
|
+
types = [e["type"] for e in events()]
|
|
288
|
+
assert types == ["agent_start", "agent_end"]
|
|
289
|
+
|
|
290
|
+
def test_failure_is_recorded_as_failed(events):
|
|
291
|
+
import failproofai_sdk, pytest
|
|
292
|
+
with pytest.raises(ValueError):
|
|
293
|
+
with failproofai_sdk.agent("planner"):
|
|
294
|
+
raise ValueError("boom")
|
|
295
|
+
failproofai_sdk._writer.flush_now()
|
|
296
|
+
ev = {e["type"]: e for e in events()}
|
|
297
|
+
assert ev["error"]["error_type"] == "ValueError"
|
|
298
|
+
assert ev["agent_end"]["outcome"] == "failed" # not "failure"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`failproofai_sdk._writer.flush_now()` drains the queue synchronously. Without it, a fast
|
|
302
|
+
test finishes before the flush cycle and reads an empty directory — a
|
|
303
|
+
flaky-looking failure with a real cause. Note the leading underscore: `_writer` is
|
|
304
|
+
not a public API, so pin your SDK version if your tests depend on it.
|
|
305
|
+
|
|
306
|
+
Assert on `type`, `session_id` and `outcome`. Those are the three that break
|
|
307
|
+
silently in production.
|
|
308
|
+
|
|
309
|
+
**Test the overlap, not just the happy path.** A single run passes even when
|
|
310
|
+
identity is a module global; mixing only shows up once two runs overlap, which is
|
|
311
|
+
production and not your laptop:
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
import asyncio
|
|
315
|
+
|
|
316
|
+
def test_two_runs_do_not_mix(events):
|
|
317
|
+
import failproofai_sdk
|
|
318
|
+
|
|
319
|
+
async def run(name):
|
|
320
|
+
async with failproofai_sdk.agent(name, session_id=name):
|
|
321
|
+
await asyncio.sleep(0) # force the interleave
|
|
322
|
+
failproofai_sdk.event.tool_use(tool_name="t", tool_call_id=f"{name}-1")
|
|
323
|
+
|
|
324
|
+
async def both():
|
|
325
|
+
await asyncio.gather(run("a"), run("b"))
|
|
326
|
+
|
|
327
|
+
asyncio.run(both())
|
|
328
|
+
failproofai_sdk._writer.flush_now()
|
|
329
|
+
for e in events():
|
|
330
|
+
assert e["agent_id"] == e["session_id"] # no event crossed over
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Without the `await asyncio.sleep(0)` the tasks do not interleave and the test
|
|
334
|
+
passes against a broken implementation, which makes it worthless.
|
|
335
|
+
|
|
336
|
+
**Add one test with an awkward payload**, because this is the failure that costs
|
|
337
|
+
you everything and it is invisible in normal testing:
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
def test_unserializable_payload_does_not_kill_the_writer(events):
|
|
341
|
+
import failproofai_sdk, datetime
|
|
342
|
+
with failproofai_sdk.agent("planner"):
|
|
343
|
+
with failproofai_sdk.tool_call("clock") as t:
|
|
344
|
+
t.output = {"at": datetime.datetime.now()} # a real tool does this
|
|
345
|
+
failproofai_sdk._writer.flush_now()
|
|
346
|
+
assert [e["type"] for e in events()] == [
|
|
347
|
+
"agent_start", "tool_use", "tool_result", "agent_end"]
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The awkward value is stringified by the SDK writer, and the later `agent_end`
|
|
351
|
+
must still be present. Keep this test because real tool outputs frequently carry
|
|
352
|
+
objects outside JSON's native type set.
|
|
File without changes
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Suite-wide isolation.
|
|
2
|
+
|
|
3
|
+
This SDK is built on module-level singletons, so state leaks between tests in
|
|
4
|
+
four ways, and every one of them has bitten this package.
|
|
5
|
+
|
|
6
|
+
1. **At interpreter exit.** Two tests build an `EventWriter` with a long flush
|
|
7
|
+
interval to inspect its queue and never flush it — the point of those tests.
|
|
8
|
+
But every writer registers in `_writer._live_writers`, and
|
|
9
|
+
`_flush_all_at_exit` flushes ALL of them when the process ends, long after
|
|
10
|
+
pytest has torn its fixtures down. Whatever redirection a test applied is
|
|
11
|
+
already undone by then, so `get_base_dir()` resolves to the real spool again.
|
|
12
|
+
One run of the queue-cap tests deposited over 160,000 synthetic events into a
|
|
13
|
+
live spool, where a configured collector would have shipped them to a real
|
|
14
|
+
dashboard as though an agent had emitted them.
|
|
15
|
+
|
|
16
|
+
A fixture cannot fix that, because the write happens after the last fixture
|
|
17
|
+
is gone. So the redirection is applied at IMPORT, straight into `os.environ`
|
|
18
|
+
rather than through `monkeypatch` — pytest undoes monkeypatch at session end,
|
|
19
|
+
and session end is still earlier than the flush.
|
|
20
|
+
|
|
21
|
+
2. **`_resolver._base_dir` during a test.** Same destination, different clock.
|
|
22
|
+
Every test gets `tmp_path` whether it asked for one or not.
|
|
23
|
+
|
|
24
|
+
3. **`_environment` and the writer's flush interval.** `configure()` mutates
|
|
25
|
+
process-global state and nothing puts it back.
|
|
26
|
+
|
|
27
|
+
4. **The identity contextvars.** A leaked `session_id` in production means
|
|
28
|
+
events attributed to the wrong run, accepted at HTTP 200 — the exact silent
|
|
29
|
+
failure that layer exists to prevent. So the leak is asserted and the leaking
|
|
30
|
+
test *fails*. It is repaired afterwards only so the failure stays readable as
|
|
31
|
+
one test rather than cascading through the rest of the run.
|
|
32
|
+
|
|
33
|
+
5. **The process-wide writer's own background thread.** `_runtime.writer` starts
|
|
34
|
+
at import on a 0.5-second interval, and the spool path is resolved when a
|
|
35
|
+
batch is WRITTEN, not when the event is submitted. So an event queued by one
|
|
36
|
+
test is written wherever `FAILPROOFAI_HOME` points up to half a second later
|
|
37
|
+
— which is a different test's directory, and the events land there with no
|
|
38
|
+
error on either side. It reached CI as a one-leg-in-five failure of
|
|
39
|
+
`test_repeated_flushes_do_not_recreate_or_churn_the_directories`, which
|
|
40
|
+
counts the batch files in its own spool and found seven where it wrote six.
|
|
41
|
+
|
|
42
|
+
Fixed by quiescing that thread for the whole session: nothing in this suite
|
|
43
|
+
depends on it firing (every test that asserts on disk builds its own
|
|
44
|
+
`EventWriter` and calls `flush_now()`), and whatever accumulates is written
|
|
45
|
+
by the exit-time flush into the sandbox from (1), which is then removed.
|
|
46
|
+
|
|
47
|
+
`test_sdk.py` has its own in-file autouse `_reset_environment`; these are
|
|
48
|
+
additive and idempotent with it.
|
|
49
|
+
"""
|
|
50
|
+
import atexit
|
|
51
|
+
import os
|
|
52
|
+
import shutil
|
|
53
|
+
import tempfile
|
|
54
|
+
|
|
55
|
+
import pytest
|
|
56
|
+
|
|
57
|
+
# ── (1) survives to interpreter exit ─────────────────────────────────────────
|
|
58
|
+
#
|
|
59
|
+
# `FAILPROOFAI_HOME`, not `AGENTEYE_HOME`: the latter no longer resolves the
|
|
60
|
+
# spool at all (see `_resolver.get_base_dir`), so setting it here would isolate
|
|
61
|
+
# nothing and the exit-time flush described above would land in the developer's
|
|
62
|
+
# REAL `~/.failproofai/custom-agents`.
|
|
63
|
+
#
|
|
64
|
+
# `setdefault`, so a developer who exports `FAILPROOFAI_HOME` to point at a
|
|
65
|
+
# scratch home of their own keeps it. `test_resolver_umbrella.py` deletes this
|
|
66
|
+
# variable per test through monkeypatch, so the resolution rules themselves are
|
|
67
|
+
# still tested against a clean environment.
|
|
68
|
+
#
|
|
69
|
+
# Deliberately NOT deleted by the per-test fixture below: that fixture pins the
|
|
70
|
+
# spool with `set_base_dir(tmp_path)`, but a test which calls
|
|
71
|
+
# `set_base_dir(None)` mid-run would then fall through to the real home. Keeping
|
|
72
|
+
# this set at all times means the floor under every test is still the sandbox.
|
|
73
|
+
_SANDBOX = tempfile.mkdtemp(prefix="failproofai-sdk-tests-")
|
|
74
|
+
os.environ.setdefault("FAILPROOFAI_HOME", _SANDBOX)
|
|
75
|
+
|
|
76
|
+
# Registered before `failproofai_sdk._writer` is imported, so it lands EARLIER in
|
|
77
|
+
# atexit's LIFO order and therefore runs LAST — after the final flush has written
|
|
78
|
+
# into the sandbox we are about to remove.
|
|
79
|
+
atexit.register(shutil.rmtree, _SANDBOX, True)
|
|
80
|
+
|
|
81
|
+
import failproofai_sdk._environment as _environment # noqa: E402
|
|
82
|
+
import failproofai_sdk._resolver as _resolver # noqa: E402
|
|
83
|
+
from failproofai_sdk import _context, _runtime # noqa: E402
|
|
84
|
+
from failproofai_sdk._events import EventNamespace # noqa: E402
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
#: Long enough that the background loop cannot wake during any plausible run,
|
|
88
|
+
#: finite because `_validated_interval` rejects `inf` (it would raise
|
|
89
|
+
#: OverflowError inside `Event.wait` and kill the thread outright).
|
|
90
|
+
_QUIET_INTERVAL = 86_400.0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class RecordingWriter:
|
|
94
|
+
"""A writer that keeps entries in memory. No disk, no flush timing."""
|
|
95
|
+
|
|
96
|
+
def __init__(self) -> None:
|
|
97
|
+
self.entries: list[dict] = []
|
|
98
|
+
|
|
99
|
+
def submit(self, entry: dict) -> None:
|
|
100
|
+
self.entries.append(entry)
|
|
101
|
+
|
|
102
|
+
def last(self) -> dict:
|
|
103
|
+
return self.entries[-1]
|
|
104
|
+
|
|
105
|
+
def types(self) -> list[str]:
|
|
106
|
+
return [e["type"] for e in self.entries]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.fixture(autouse=True, scope="session")
|
|
110
|
+
def _quiesce_the_process_wide_writer():
|
|
111
|
+
"""(5): stop `_runtime.writer`'s thread flushing into whichever test is running.
|
|
112
|
+
|
|
113
|
+
`set_flush_interval`, not a bare attribute write: the loop is already blocked
|
|
114
|
+
in `_wake.wait(0.5)` and would flush once more on the old interval before
|
|
115
|
+
seeing a new value. `set_flush_interval` sets `_wake`, so the loop re-waits on
|
|
116
|
+
the new interval immediately. Done ONCE at session start, when nothing has
|
|
117
|
+
been submitted yet, so the wake it induces drains an empty queue.
|
|
118
|
+
|
|
119
|
+
Not restored afterwards. The only thing left to run is the exit-time flush,
|
|
120
|
+
and it is better served by an interval that cannot fire underneath it.
|
|
121
|
+
"""
|
|
122
|
+
_runtime.writer.set_flush_interval(_QUIET_INTERVAL)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@pytest.fixture(autouse=True)
|
|
126
|
+
def _sdk_global_state(tmp_path, monkeypatch):
|
|
127
|
+
"""(2) and (3): a private spool per test, and process globals restored."""
|
|
128
|
+
monkeypatch.delenv("AGENTEYE_ENVIRONMENT", raising=False)
|
|
129
|
+
|
|
130
|
+
base_dir = _resolver._base_dir
|
|
131
|
+
environment = _environment._environment
|
|
132
|
+
flush_interval = _runtime.writer._flush_interval
|
|
133
|
+
|
|
134
|
+
_resolver.set_base_dir(tmp_path)
|
|
135
|
+
try:
|
|
136
|
+
yield
|
|
137
|
+
finally:
|
|
138
|
+
_resolver.set_base_dir(base_dir)
|
|
139
|
+
_environment._environment = environment
|
|
140
|
+
_runtime.writer._flush_interval = flush_interval
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.fixture(autouse=True)
|
|
144
|
+
def _no_context_leak():
|
|
145
|
+
"""(4): an unbalanced scope fails the test that left it open."""
|
|
146
|
+
yield
|
|
147
|
+
session_id, stack = _context.snapshot()
|
|
148
|
+
if session_id is not None or stack:
|
|
149
|
+
# Repair before failing: without this every subsequent test in the run
|
|
150
|
+
# also fails and the real culprit is impossible to spot.
|
|
151
|
+
_context.restore((None, ()))
|
|
152
|
+
pytest.fail(
|
|
153
|
+
"failproofai_sdk identity contextvars leaked out of this test: "
|
|
154
|
+
f"session_id={session_id!r}, agent_stack={stack!r}. "
|
|
155
|
+
"A scope was entered without being exited."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.fixture()
|
|
160
|
+
def events(monkeypatch):
|
|
161
|
+
"""Swap the process-wide namespace for a recording one.
|
|
162
|
+
|
|
163
|
+
`_scopes` resolves `_runtime.event` at call time precisely so this works.
|
|
164
|
+
Tests must not emit through the real writer: `test_sdk.py` asserts on the
|
|
165
|
+
exact contents of the events directory.
|
|
166
|
+
"""
|
|
167
|
+
writer = RecordingWriter()
|
|
168
|
+
monkeypatch.setattr(_runtime, "event", EventNamespace(writer))
|
|
169
|
+
return writer
|