failproofai 1.0.7-beta.1 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- 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/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.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 +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.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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +7 -8
- 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.nft.json +1 -1
- 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.nft.json +1 -1
- 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 +5 -7
- 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 +6 -6
- 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 +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +11 -13
- 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 +7 -8
- 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 +7 -7
- 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 +6 -7
- 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 +8 -41
- package/.next/standalone/.next/server/app/settings/page.js +9 -12
- 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]__1j-zsg5._.js → [externals]__1_bftcl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__19_pzeq._.js → [externals]__1msfs-h._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1bf34x4._.js → [root-of-the-server]__1_r2rbg._.js} +7 -5
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- 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/{_1q5i8mb._.js → _1c3k-8x._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.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_0iu54mz._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
- 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]__0yxwl6j._.js → [root-of-the-server]__0bd3mje._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__0cxe_2_._.js} +3 -3
- 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]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1w_5l7t._.js → _0h_douw._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_214wgrp._.js → _1-i_gzc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_0bn2oo8._.js → _166t73i._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1q46vxx._.js → _1_qswah._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifp._.js → _1es2j7i._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_18_d8l1._.js → node_modules_next_dist_0drixxt._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.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/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- 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 -56
- package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +1 -0
- package/.next/standalone/.next/static/chunks/0__8a7m868fvf.js +1 -0
- package/.next/standalone/.next/static/chunks/{12tvm75t5ffui.js → 0o6qlkgubtoex.js} +1 -1
- package/.next/standalone/.next/static/chunks/13i7-9is-vhys.js +1 -0
- package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +6 -0
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
- package/.next/standalone/.next/static/chunks/{0vmd180qfntfb.js → 2klitrtzpaoe0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{258668t68du6b.js → 2mdh397ghgnvv.js} +1 -1
- package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +2 -0
- package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +1 -0
- package/.next/standalone/.next/static/chunks/{0qrbdkv9qmvli.js → 3rh5o7e16irrm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{29fql3nbnfc9q.js → 43ufqrz8qo3h-.js} +1 -1
- package/.next/standalone/SECURITY.md +53 -0
- package/.next/standalone/app/actions/pack-actions.ts +0 -12
- package/.next/standalone/app/policies/hooks-client.tsx +0 -9
- package/.next/standalone/app/settings/page.tsx +1 -20
- package/.next/standalone/app/settings/settings-client.tsx +1 -27
- package/.next/standalone/app/settings/settings.css +0 -79
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/python/skill/SKILL.md +60 -14
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +2 -1
- package/.next/standalone/sdk/python/skill/references/evaluator.md +255 -0
- package/.next/standalone/sdk/python/skill/references/events.md +17 -8
- package/.next/standalone/sdk/python/skill/references/frameworks.md +3 -0
- package/.next/standalone/sdk/python/skill/references/install.md +3 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +6 -2
- package/.next/standalone/sdk/python/skill/references/typescript.md +568 -0
- package/.next/standalone/sdk/typescript/CHANGELOG.md +133 -0
- package/.next/standalone/sdk/typescript/LICENSE +42 -0
- package/.next/standalone/sdk/typescript/README.md +552 -0
- package/.next/standalone/sdk/typescript/eslint.config.mjs +59 -0
- package/.next/standalone/sdk/typescript/examples/research-agent.ts +197 -0
- package/.next/standalone/sdk/typescript/integration/ai.test.ts +920 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/agent.ts +337 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +261 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +16 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/surfaces.ts +605 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/agent.ts +342 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +156 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +16 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/surfaces.ts +628 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/agent.ts +346 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +156 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +13 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/surfaces.ts +651 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/agent.ts +350 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +153 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +13 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/surfaces.ts +651 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/agent.ts +623 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +344 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +19 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent-v1.ts +99 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent.ts +623 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +336 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/agent.ts +96 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +441 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +19 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.cjs +53 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.mjs +55 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/package.json +18 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/agent.ts +659 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +635 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/agent.ts +659 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +553 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/agent.ts +877 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/mcp-server.mjs +66 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +6797 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +24 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/agent.ts +872 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/mcp-server.mjs +66 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +2540 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/actions.ts +18 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/ai/route.ts +42 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/edge/route.ts +29 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/langgraph/route.ts +21 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/llamaindex/route.ts +11 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/mastra/route.ts +19 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/status/route.ts +7 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/layout.tsx +9 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/page.tsx +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/instrumentation.ts +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/ai.ts +61 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/langgraph.ts +68 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/llamaindex.ts +98 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/mastra.ts +90 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/next.config.ts +52 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +4343 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +28 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/agent.ts +51 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/deno-npm.ts +76 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +484 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/agent.ts +79 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/package-lock.json +740 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/package.json +11 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/agent.ts +197 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package-lock.json +70 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/global-setup.ts +29 -0
- package/.next/standalone/sdk/typescript/integration/harness.ts +451 -0
- package/.next/standalone/sdk/typescript/integration/langchain.test.ts +682 -0
- package/.next/standalone/sdk/typescript/integration/llamaindex.test.ts +709 -0
- package/.next/standalone/sdk/typescript/integration/mastra-coverage.test.ts +386 -0
- package/.next/standalone/sdk/typescript/integration/mastra.test.ts +311 -0
- package/.next/standalone/sdk/typescript/integration/nextjs.test.ts +341 -0
- package/.next/standalone/sdk/typescript/integration/runtime-parity.ts +180 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.bun.test.ts +15 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.core.test.ts +113 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.deno.test.ts +19 -0
- package/.next/standalone/sdk/typescript/integration/types.test.ts +141 -0
- package/.next/standalone/sdk/typescript/integration/vanilla.test.ts +255 -0
- package/.next/standalone/sdk/typescript/package-lock.json +2640 -0
- package/.next/standalone/sdk/typescript/package.json +401 -0
- package/.next/standalone/sdk/typescript/scripts/finalize-build.mjs +123 -0
- package/.next/standalone/sdk/typescript/scripts/release.mjs +177 -0
- package/.next/standalone/sdk/typescript/src/clock.ts +58 -0
- package/.next/standalone/sdk/typescript/src/context.ts +214 -0
- package/.next/standalone/sdk/typescript/src/edge/adapter.ts +18 -0
- package/.next/standalone/sdk/typescript/src/edge/ai.ts +116 -0
- package/.next/standalone/sdk/typescript/src/edge/index.ts +238 -0
- package/.next/standalone/sdk/typescript/src/edge/langchain.ts +18 -0
- package/.next/standalone/sdk/typescript/src/edge/llamaindex.ts +12 -0
- package/.next/standalone/sdk/typescript/src/edge/mastra.ts +17 -0
- package/.next/standalone/sdk/typescript/src/edge/notice.ts +33 -0
- package/.next/standalone/sdk/typescript/src/environment.ts +75 -0
- package/.next/standalone/sdk/typescript/src/evaluator/authoring.ts +480 -0
- package/.next/standalone/sdk/typescript/src/evaluator/cli.ts +96 -0
- package/.next/standalone/sdk/typescript/src/evaluator/client.ts +421 -0
- package/.next/standalone/sdk/typescript/src/evaluator/expression.ts +1292 -0
- package/.next/standalone/sdk/typescript/src/evaluator/index.ts +144 -0
- package/.next/standalone/sdk/typescript/src/evaluator/protocol.ts +747 -0
- package/.next/standalone/sdk/typescript/src/evaluator/runtime.ts +930 -0
- package/.next/standalone/sdk/typescript/src/evaluator/sandbox-worker.ts +171 -0
- package/.next/standalone/sdk/typescript/src/evaluator/source-limits.ts +27 -0
- package/.next/standalone/sdk/typescript/src/evaluator/source.ts +509 -0
- package/.next/standalone/sdk/typescript/src/events.ts +879 -0
- package/.next/standalone/sdk/typescript/src/exit.ts +117 -0
- package/.next/standalone/sdk/typescript/src/index.ts +186 -0
- package/.next/standalone/sdk/typescript/src/integrations/ai.ts +1566 -0
- package/.next/standalone/sdk/typescript/src/integrations/compat.ts +322 -0
- package/.next/standalone/sdk/typescript/src/integrations/core.ts +1321 -0
- package/.next/standalone/sdk/typescript/src/integrations/index.ts +355 -0
- package/.next/standalone/sdk/typescript/src/integrations/langchain.ts +2340 -0
- package/.next/standalone/sdk/typescript/src/integrations/llamaindex.ts +2111 -0
- package/.next/standalone/sdk/typescript/src/integrations/mastra.ts +1802 -0
- package/.next/standalone/sdk/typescript/src/logger.ts +98 -0
- package/.next/standalone/sdk/typescript/src/next.ts +115 -0
- package/.next/standalone/sdk/typescript/src/node-require.ts +446 -0
- package/.next/standalone/sdk/typescript/src/redact.ts +305 -0
- package/.next/standalone/sdk/typescript/src/resolver.ts +120 -0
- package/.next/standalone/sdk/typescript/src/runtime.ts +29 -0
- package/.next/standalone/sdk/typescript/src/schema.ts +410 -0
- package/.next/standalone/sdk/typescript/src/scopes.ts +701 -0
- package/.next/standalone/sdk/typescript/src/shared.ts +33 -0
- package/.next/standalone/sdk/typescript/src/version.ts +5 -0
- package/.next/standalone/sdk/typescript/src/writer.ts +934 -0
- package/.next/standalone/sdk/typescript/test/adapters.test.ts +397 -0
- package/.next/standalone/sdk/typescript/test/ai.test.ts +1076 -0
- package/.next/standalone/sdk/typescript/test/copies.test.ts +204 -0
- package/.next/standalone/sdk/typescript/test/edge.test.ts +183 -0
- package/.next/standalone/sdk/typescript/test/evaluator-client.test.ts +234 -0
- package/.next/standalone/sdk/typescript/test/evaluator-protocol.test.ts +225 -0
- package/.next/standalone/sdk/typescript/test/events.test.ts +193 -0
- package/.next/standalone/sdk/typescript/test/expression.test.ts +181 -0
- package/.next/standalone/sdk/typescript/test/global-setup.ts +26 -0
- package/.next/standalone/sdk/typescript/test/helpers.ts +130 -0
- package/.next/standalone/sdk/typescript/test/integrations.test.ts +369 -0
- package/.next/standalone/sdk/typescript/test/langchain-copies.test.ts +204 -0
- package/.next/standalone/sdk/typescript/test/langchain.test.ts +999 -0
- package/.next/standalone/sdk/typescript/test/llamaindex.test.ts +1760 -0
- package/.next/standalone/sdk/typescript/test/mastra-coverage.test.ts +501 -0
- package/.next/standalone/sdk/typescript/test/mastra-lifecycle.test.ts +479 -0
- package/.next/standalone/sdk/typescript/test/mastra.test.ts +285 -0
- package/.next/standalone/sdk/typescript/test/next.test.ts +109 -0
- package/.next/standalone/sdk/typescript/test/packaging.test.ts +312 -0
- package/.next/standalone/sdk/typescript/test/redaction.test.ts +171 -0
- package/.next/standalone/sdk/typescript/test/runtimes.test.ts +101 -0
- package/.next/standalone/sdk/typescript/test/sandbox.test.ts +189 -0
- package/.next/standalone/sdk/typescript/test/scopes.test.ts +271 -0
- package/.next/standalone/sdk/typescript/test/setup.ts +19 -0
- package/.next/standalone/sdk/typescript/test/skill-snippets.test.ts +73 -0
- package/.next/standalone/sdk/typescript/test/spool-contract.test.ts +124 -0
- package/.next/standalone/sdk/typescript/test/tracker-bounds.test.ts +191 -0
- package/.next/standalone/sdk/typescript/test/wire-format.test.ts +214 -0
- package/.next/standalone/sdk/typescript/test/writer.test.ts +407 -0
- package/.next/standalone/sdk/typescript/tsconfig.build.json +15 -0
- package/.next/standalone/sdk/typescript/tsconfig.cjs.json +19 -0
- package/.next/standalone/sdk/typescript/tsconfig.json +28 -0
- package/.next/standalone/sdk/typescript/vitest.config.ts +33 -0
- package/.next/standalone/sdk/typescript/vitest.integration.config.ts +23 -0
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +2 -115
- package/dist/cli.mjs +6538 -13686
- package/dist/index.js +1 -19
- package/dist/worker.mjs +2085 -8012
- package/package.json +9 -9
- package/pi-extension/index.ts +0 -11
- package/scripts/build-policy-pack.mjs +2 -39
- package/src/hooks/builtin-policies.ts +3 -21
- package/src/hooks/cloud-enrollment-cli.ts +1 -1
- package/src/hooks/cloud-managed-policies.ts +0 -22
- package/src/hooks/custom-hooks-loader.ts +7 -45
- package/src/hooks/custom-hooks-registry.ts +1 -45
- package/src/hooks/first-run-gate.ts +0 -5
- package/src/hooks/fp-home.ts +0 -23
- package/src/hooks/handler.ts +6 -265
- package/src/hooks/hook-activity-store.ts +1 -105
- package/src/hooks/hook-telemetry.ts +0 -41
- package/src/hooks/loader-utils.ts +0 -6
- package/src/hooks/pack-cli.ts +17 -278
- package/src/hooks/pack-manifest.ts +7 -479
- package/src/hooks/pack-store.ts +10 -156
- package/src/hooks/policy-catalog.ts +0 -65
- package/src/hooks/policy-evaluator.ts +796 -940
- package/src/hooks/policy-registry.ts +0 -25
- package/src/hooks/policy-types.ts +0 -126
- package/src/hooks/worker-server.ts +26 -119
- package/src/index.ts +0 -6
- package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +0 -4
- 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]__059yza8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +0 -22
- 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]__0qcb0mg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +0 -5
- 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]__0s_yomn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +0 -3
- 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]__13d-wb6._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.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/[root-of-the-server]__1qiz0e4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0w6mzq5._.js +0 -151
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
- package/.next/standalone/.next/static/chunks/0cd-_8-c-m1ea.js +0 -6
- package/.next/standalone/.next/static/chunks/0uldbut9y2-e8.js +0 -1
- package/.next/standalone/.next/static/chunks/1u5zsejmgrir_.js +0 -1
- package/.next/standalone/.next/static/chunks/285spx855h_3r.css +0 -2
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +0 -1
- package/.next/standalone/.next/static/chunks/2vkvu9-opa_1z.js +0 -1
- package/.next/standalone/.next/static/chunks/37lhv7wa3ywt6.js +0 -1
- package/.next/standalone/app/actions/get-jev-config.ts +0 -409
- package/.next/standalone/app/actions/update-jev-config.ts +0 -420
- package/.next/standalone/app/components/jev-notices.tsx +0 -96
- package/.next/standalone/app/settings/jev-panel.tsx +0 -469
- package/src/hooks/effective-reviewers.ts +0 -79
- package/src/hooks/jev-activity.ts +0 -385
- package/src/hooks/jev-cli.ts +0 -1193
- package/src/hooks/policy-authority.ts +0 -333
- package/src/hooks/policy-reviewability.ts +0 -229
- package/src/hooks/semantic/combine.ts +0 -541
- package/src/hooks/semantic/compile.ts +0 -176
- package/src/hooks/semantic/decide.ts +0 -392
- package/src/hooks/semantic/envelope.ts +0 -1296
- package/src/hooks/semantic/evaluator.ts +0 -547
- package/src/hooks/semantic/facts.ts +0 -292
- package/src/hooks/semantic/intent.ts +0 -1190
- package/src/hooks/semantic/jev-client.ts +0 -643
- package/src/hooks/semantic/jev-config.ts +0 -594
- package/src/hooks/semantic/jev-review.ts +0 -374
- package/src/hooks/semantic/jev-stats.ts +0 -289
- package/src/hooks/semantic/jev-throttle.ts +0 -421
- package/src/hooks/semantic/pack-policies.ts +0 -251
- package/src/hooks/semantic/policies.ts +0 -596
- package/src/hooks/semantic/precondition-names.ts +0 -60
- package/src/hooks/semantic/preconditions.ts +0 -58
- package/src/hooks/semantic/redact.ts +0 -2910
- package/src/hooks/semantic/types.ts +0 -145
- package/src/hooks/semver-precedence.ts +0 -128
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → PgeWCHmyVbjRznv2VO7KF}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FORMATS,
|
|
5
|
+
count,
|
|
6
|
+
describeTrace,
|
|
7
|
+
ofType,
|
|
8
|
+
runAgent,
|
|
9
|
+
traceViolations,
|
|
10
|
+
typecheck,
|
|
11
|
+
type Event,
|
|
12
|
+
} from "./harness.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The Vercel AI SDK (`ai`), against real releases of every supported major.
|
|
16
|
+
*
|
|
17
|
+
* The mapping (the adapter's module comment has the reasoning):
|
|
18
|
+
*
|
|
19
|
+
* * one `generateText` / `streamText` / `generateObject` / `streamObject`
|
|
20
|
+
* call is ONE agent, named by its `functionId` — never a span or call id;
|
|
21
|
+
* * each model step is a `model_request` / `model_response` pair on
|
|
22
|
+
* `request_id`, with integer token counts and a STRING stop reason;
|
|
23
|
+
* * each tool is a `tool_use` / `tool_result` pair carrying the MODEL's own
|
|
24
|
+
* tool call id;
|
|
25
|
+
* * a bare model call through `wrapModel` is its own run, named after the
|
|
26
|
+
* model — the LangChain precedent for a bare chat-model call — unless an
|
|
27
|
+
* enclosing `failproofai.agent()` already owns it;
|
|
28
|
+
* * a failure is recorded once, where it happened — no stack of `error`
|
|
29
|
+
* events.
|
|
30
|
+
*
|
|
31
|
+
* Four fixtures because the AI SDK changed its extension points at every
|
|
32
|
+
* major: v4 speaks LanguageModelV1 (`promptTokens`), v5 V2, v6 V3 (usage and
|
|
33
|
+
* finish reason became objects), and v7 dropped the OpenTelemetry `tracer`
|
|
34
|
+
* option for its own `Telemetry` integration interface.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const ALL = ["ai-4", "ai-5", "ai-6", "ai-7"] as const;
|
|
38
|
+
const only = process.env.FAILPROOFAI_IT_FIXTURES?.split(",").filter(Boolean);
|
|
39
|
+
const FIXTURES = ALL.filter((name) => !only || only.includes(name));
|
|
40
|
+
|
|
41
|
+
/** One line per event: `agent_id type [tool]`. */
|
|
42
|
+
const shape = (events: Event[]): string[] =>
|
|
43
|
+
events.map((e) => [e.agent_id, e.type, (e.tool_name ?? "") as string].join(" ").trim());
|
|
44
|
+
|
|
45
|
+
const LOOP = (agent: string): string[] => [
|
|
46
|
+
`${agent} agent_start`,
|
|
47
|
+
`${agent} model_request`,
|
|
48
|
+
`${agent} model_response`,
|
|
49
|
+
`${agent} tool_use weather`,
|
|
50
|
+
`${agent} tool_result weather`,
|
|
51
|
+
`${agent} model_request`,
|
|
52
|
+
`${agent} model_response`,
|
|
53
|
+
`${agent} agent_end`,
|
|
54
|
+
];
|
|
55
|
+
const GEN = LOOP("weather-agent");
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A stream on v4–v6. The SDK runs a tool the moment its `tool-call` part
|
|
59
|
+
* arrives and holds the stream's `finish` part until the tool returns, so the
|
|
60
|
+
* model step's span — the only place its usage and finish reason exist — ends
|
|
61
|
+
* AFTER the tool. Events are stamped when they happen, never backdated, so
|
|
62
|
+
* that is the order they land in; `request_id` still pairs them. v7 closes
|
|
63
|
+
* the model call before it executes tools, so there a stream reads like GEN.
|
|
64
|
+
*/
|
|
65
|
+
const LEGACY_STREAM = [
|
|
66
|
+
"weather-agent agent_start",
|
|
67
|
+
"weather-agent model_request",
|
|
68
|
+
"weather-agent tool_use weather",
|
|
69
|
+
"weather-agent tool_result weather",
|
|
70
|
+
"weather-agent model_response",
|
|
71
|
+
"weather-agent model_request",
|
|
72
|
+
"weather-agent model_response",
|
|
73
|
+
"weather-agent agent_end",
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const tokens = (events: Event[]) => ofType(events, "model_response").map((e) => [e.input_tokens, e.output_tokens]);
|
|
77
|
+
|
|
78
|
+
describe.each(FIXTURES)("%s", (fixture) => {
|
|
79
|
+
const major = Number(fixture.split("-")[1]);
|
|
80
|
+
const STREAM = major >= 7 ? GEN : LEGACY_STREAM;
|
|
81
|
+
|
|
82
|
+
it("typechecks the README call sites as a customer's nodenext project", () => {
|
|
83
|
+
expect(typecheck(fixture)).toBe("");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe.each(FORMATS)("as %s", (format) => {
|
|
87
|
+
const run = (scenario: string, { expectNote = false } = {}) => {
|
|
88
|
+
const result = runAgent(fixture, format, scenario);
|
|
89
|
+
expect(result.status, describeTrace(result)).toBe(0);
|
|
90
|
+
// A warning from the SDK is how a silently-inert adapter shows itself
|
|
91
|
+
// ("could not resolve a session", "outside the supported range").
|
|
92
|
+
const lines = result.stderr.split("\n").filter((line) => line.includes("[failproofai-sdk]"));
|
|
93
|
+
if (expectNote) {
|
|
94
|
+
// The one deliberate exception: instrument("ai") on ai 4–6 says, once,
|
|
95
|
+
// that by itself it records nothing there — and nothing else is said.
|
|
96
|
+
expect(lines, describeTrace(result)).toHaveLength(1);
|
|
97
|
+
expect(lines[0], describeTrace(result)).toContain("registerGlobalTracer");
|
|
98
|
+
} else {
|
|
99
|
+
expect(lines, describeTrace(result)).toEqual([]);
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
/** instrument("ai") without the opt-in: inert, and noted, on ai 4–6. */
|
|
104
|
+
const noted = major < 7;
|
|
105
|
+
|
|
106
|
+
it("records generateText with a tool loop as one agent", () => {
|
|
107
|
+
const result = run("generate");
|
|
108
|
+
const { events } = result;
|
|
109
|
+
expect(shape(events), describeTrace(result)).toEqual(GEN);
|
|
110
|
+
expect(traceViolations(events), describeTrace(result)).toEqual([]);
|
|
111
|
+
expect(new Set(events.map((e) => e.session_id)).size).toBe(1);
|
|
112
|
+
expect(tokens(events)).toEqual([
|
|
113
|
+
[11, 7],
|
|
114
|
+
[23, 9],
|
|
115
|
+
]);
|
|
116
|
+
const responses = ofType(events, "model_response");
|
|
117
|
+
expect(responses.map((e) => e.stop_reason)).toEqual(["tool-calls", "stop"]);
|
|
118
|
+
for (const e of [...responses, ...ofType(events, "model_request")]) expect(e.model).toBe("mock-model");
|
|
119
|
+
for (const e of responses) expect(Number.isInteger(e.duration_ms)).toBe(true);
|
|
120
|
+
// The first step answered with a tool call and no text: its content is
|
|
121
|
+
// the call, not an empty string.
|
|
122
|
+
expect(responses[0]!.content).not.toBe("");
|
|
123
|
+
expect(JSON.stringify(responses[0]!.content)).toContain("weather");
|
|
124
|
+
expect(responses[1]!.content).toBe("It is 20C in Paris.");
|
|
125
|
+
const [use] = ofType(events, "tool_use");
|
|
126
|
+
expect(use!.tool_call_id).toBe("call-1");
|
|
127
|
+
expect(use!.input).toEqual({ city: "Paris" });
|
|
128
|
+
expect(ofType(events, "tool_result")[0]!.output).toEqual({ city: "Paris", celsius: 20 });
|
|
129
|
+
expect(ofType(events, "agent_end")[0]!.outcome).toBe("success");
|
|
130
|
+
expect(count(events, "error")).toBe(0);
|
|
131
|
+
for (const event of events) {
|
|
132
|
+
expect(event.framework).toBe("ai");
|
|
133
|
+
expect(String(event.framework_version)).toMatch(new RegExp(`^${major}\\.`));
|
|
134
|
+
}
|
|
135
|
+
expect(result.stdout).toContain("It is 20C in Paris.");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("records streamText like generateText, and stamps the request when it is made", () => {
|
|
139
|
+
const result = run("stream");
|
|
140
|
+
const { events } = result;
|
|
141
|
+
expect(shape(events), describeTrace(result)).toEqual(STREAM);
|
|
142
|
+
expect(traceViolations(events), describeTrace(result)).toEqual([]);
|
|
143
|
+
expect(tokens(events)).toEqual([
|
|
144
|
+
[13, 4],
|
|
145
|
+
[30, 6],
|
|
146
|
+
]);
|
|
147
|
+
const responses = ofType(events, "model_response");
|
|
148
|
+
expect(responses.map((e) => e.stop_reason)).toEqual(["tool-calls", "stop"]);
|
|
149
|
+
expect(responses[0]!.content).not.toBe("");
|
|
150
|
+
expect(responses[1]!.content).toBe("Rome is 25C.");
|
|
151
|
+
expect(ofType(events, "tool_use")[0]!.tool_call_id).toBe("call-s1");
|
|
152
|
+
const request = ofType(events, "model_request")[0]!;
|
|
153
|
+
const use = ofType(events, "tool_use")[0]!;
|
|
154
|
+
expect(String(request.timestamp) <= String(use.timestamp)).toBe(true);
|
|
155
|
+
expect(result.stdout).toContain("Rome is 25C.");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it.each(["object", "stream-object"])("records %s as one agent with one model call", (scenario) => {
|
|
159
|
+
const result = run(scenario);
|
|
160
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
161
|
+
"extractor agent_start",
|
|
162
|
+
"extractor model_request",
|
|
163
|
+
"extractor model_response",
|
|
164
|
+
"extractor agent_end",
|
|
165
|
+
]);
|
|
166
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
167
|
+
expect(tokens(result.events)).toEqual([[5, 3]]);
|
|
168
|
+
expect(ofType(result.events, "model_response")[0]!.stop_reason).toBe("stop");
|
|
169
|
+
expect(result.stdout).toContain('"city":"Paris"');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("records a bare wrapModel call as its own run, named after the model", () => {
|
|
173
|
+
const result = run("wrap");
|
|
174
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
175
|
+
"mock-model agent_start",
|
|
176
|
+
"mock-model model_request",
|
|
177
|
+
"mock-model model_response",
|
|
178
|
+
"mock-model agent_end",
|
|
179
|
+
]);
|
|
180
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
181
|
+
const response = ofType(result.events, "model_response")[0]!;
|
|
182
|
+
expect([response.input_tokens, response.output_tokens]).toEqual([23, 9]);
|
|
183
|
+
expect(response.stop_reason).toBe("stop");
|
|
184
|
+
expect(response.content).toBe("It is 20C in Paris.");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("records a streamed wrapModel call with its usage", () => {
|
|
188
|
+
const result = run("wrap-stream");
|
|
189
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
190
|
+
"mock-model agent_start",
|
|
191
|
+
"mock-model model_request",
|
|
192
|
+
"mock-model model_response",
|
|
193
|
+
"mock-model agent_end",
|
|
194
|
+
]);
|
|
195
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
196
|
+
const response = ofType(result.events, "model_response")[0]!;
|
|
197
|
+
expect([response.input_tokens, response.output_tokens]).toEqual([30, 6]);
|
|
198
|
+
expect(response.stop_reason).toBe("stop");
|
|
199
|
+
expect(response.content).toBe("Rome is 25C.");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("records wrapModel calls under an enclosing agent() rather than as runs of their own", () => {
|
|
203
|
+
const result = run("wrap-in-agent");
|
|
204
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
205
|
+
"planner agent_start",
|
|
206
|
+
"planner model_request",
|
|
207
|
+
"planner model_response",
|
|
208
|
+
"planner model_request",
|
|
209
|
+
"planner model_response",
|
|
210
|
+
"planner agent_end",
|
|
211
|
+
]);
|
|
212
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
213
|
+
expect(new Set(result.events.map((e) => e.session_id))).toEqual(new Set(["req-1"]));
|
|
214
|
+
expect(tokens(result.events)).toEqual([
|
|
215
|
+
[11, 7],
|
|
216
|
+
[23, 9],
|
|
217
|
+
]);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("records each call once when telemetry() and wrapModel are both in use", () => {
|
|
221
|
+
const result = run("wrap-and-telemetry");
|
|
222
|
+
expect(shape(result.events), describeTrace(result)).toEqual(GEN);
|
|
223
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it.each(["instrument", "instrument-stream"])(
|
|
227
|
+
"%s: instrument('ai') records the same trace as telemetry() on ai 7, and on ai 4–6 records nothing and says so",
|
|
228
|
+
(scenario) => {
|
|
229
|
+
const result = run(scenario, { expectNote: noted });
|
|
230
|
+
expect(result.stdout).toContain('"instrumented":["ai"]');
|
|
231
|
+
if (major >= 7) {
|
|
232
|
+
expect(shape(result.events), describeTrace(result)).toEqual(scenario === "instrument" ? GEN : STREAM);
|
|
233
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
234
|
+
} else {
|
|
235
|
+
// v4–v6's only process-wide hook is the global OpenTelemetry slot,
|
|
236
|
+
// which instrument("ai") no longer takes by default.
|
|
237
|
+
expect(result.events, describeTrace(result)).toEqual([]);
|
|
238
|
+
expect(result.stdout).toContain(scenario === "instrument" ? "It is 20C in Paris." : "Rome is 25C.");
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
it.each(["instrument-global", "instrument-global-stream"])(
|
|
244
|
+
"%s: instrument('ai', { registerGlobalTracer: true }) records the same trace as telemetry()",
|
|
245
|
+
(scenario) => {
|
|
246
|
+
const result = run(scenario);
|
|
247
|
+
expect(result.stdout).toContain('"instrumented":["ai"]');
|
|
248
|
+
expect(shape(result.events), describeTrace(result)).toEqual(scenario === "instrument-global" ? GEN : STREAM);
|
|
249
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
it("leaves the global OpenTelemetry slot to a customer provider registered after instrument('ai')", () => {
|
|
254
|
+
const result = run("instrument-then-otel", { expectNote: noted });
|
|
255
|
+
const report = JSON.parse(result.stdout.trim().split("\n").pop()!) as {
|
|
256
|
+
text: string;
|
|
257
|
+
customer: "absent" | { registered: boolean; ended: string[] };
|
|
258
|
+
};
|
|
259
|
+
expect(report.text).toBe("It is 20C in Paris.");
|
|
260
|
+
if (major >= 7) {
|
|
261
|
+
// v7 has no OpenTelemetry dependency; the integration records the call.
|
|
262
|
+
expect(report.customer).toBe("absent");
|
|
263
|
+
expect(shape(result.events), describeTrace(result)).toEqual(GEN);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Their registration is accepted, and their tracer gets every span —
|
|
267
|
+
// the AI SDK's own and their service's.
|
|
268
|
+
expect(report.customer, describeTrace(result)).toEqual({
|
|
269
|
+
registered: true,
|
|
270
|
+
ended: expect.arrayContaining(["ai.generateText", "ai.generateText.doGenerate", "ai.toolCall", "http.request"]),
|
|
271
|
+
});
|
|
272
|
+
expect(result.events, describeTrace(result)).toEqual([]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("closes a streamed wrapModel call the reader cancels, as cancelled", () => {
|
|
276
|
+
const result = run("wrap-stream-cancel");
|
|
277
|
+
expect(result.stdout).toContain('"cancelled":true');
|
|
278
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
279
|
+
"mock-model agent_start",
|
|
280
|
+
"mock-model model_request",
|
|
281
|
+
"mock-model model_response",
|
|
282
|
+
"mock-model agent_end",
|
|
283
|
+
]);
|
|
284
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
285
|
+
const response = ofType(result.events, "model_response")[0]!;
|
|
286
|
+
expect(response.stop_reason).toBe("cancelled");
|
|
287
|
+
expect(response.error).toBeUndefined();
|
|
288
|
+
expect(ofType(result.events, "agent_end")[0]!.outcome).toBe("cancelled");
|
|
289
|
+
expect(count(result.events, "error")).toBe(0);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("closes a streamed wrapModel call whose stream errors, with the error", () => {
|
|
293
|
+
const result = run("wrap-stream-error");
|
|
294
|
+
expect(result.stdout).toContain('"threw":"connection reset"');
|
|
295
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
296
|
+
"mock-model agent_start",
|
|
297
|
+
"mock-model model_request",
|
|
298
|
+
"mock-model model_response",
|
|
299
|
+
"mock-model agent_end",
|
|
300
|
+
]);
|
|
301
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
302
|
+
const response = ofType(result.events, "model_response")[0]!;
|
|
303
|
+
expect(response.stop_reason).toBe("error");
|
|
304
|
+
expect(response.error).toMatch(/connection reset/);
|
|
305
|
+
expect(ofType(result.events, "agent_end")[0]!.outcome).toBe("failed");
|
|
306
|
+
expect(count(result.events, "error")).toBe(0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("records nothing after uninstrument()", () => {
|
|
310
|
+
const result = run("uninstrument", { expectNote: noted });
|
|
311
|
+
expect(result.stdout).toContain('"removed":["ai"]');
|
|
312
|
+
expect(result.events, describeTrace(result)).toEqual([]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("records a failing tool once, on its tool_result", () => {
|
|
316
|
+
const result = run("tool-error");
|
|
317
|
+
const { events } = result;
|
|
318
|
+
const toolResult = ofType(events, "tool_result")[0]!;
|
|
319
|
+
expect(toolResult.error, describeTrace(result)).toMatch(/weather service down/);
|
|
320
|
+
expect(count(events, "error"), describeTrace(result)).toBe(0);
|
|
321
|
+
expect(traceViolations(events), describeTrace(result)).toEqual([]);
|
|
322
|
+
if (major >= 5) {
|
|
323
|
+
// v5+ hands the failure back to the model as a tool-error result and
|
|
324
|
+
// the loop carries on — the agent recovered, so the run succeeded.
|
|
325
|
+
expect(shape(events), describeTrace(result)).toEqual(GEN);
|
|
326
|
+
expect(ofType(events, "agent_end")[0]!.outcome).toBe("success");
|
|
327
|
+
} else {
|
|
328
|
+
// v4 throws a ToolExecutionError out of generateText: the run failed.
|
|
329
|
+
expect(shape(events), describeTrace(result)).toEqual([...GEN.slice(0, 5), "weather-agent agent_end"]);
|
|
330
|
+
expect(ofType(events, "agent_end")[0]!.outcome).toBe("failed");
|
|
331
|
+
expect(result.stdout).toContain("threw");
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("records a failing model once, on its model_response", () => {
|
|
336
|
+
const result = run("model-error");
|
|
337
|
+
const { events } = result;
|
|
338
|
+
expect(result.stdout).toContain("model exploded");
|
|
339
|
+
expect(shape(events), describeTrace(result)).toEqual([
|
|
340
|
+
"weather-agent agent_start",
|
|
341
|
+
"weather-agent model_request",
|
|
342
|
+
"weather-agent model_response",
|
|
343
|
+
"weather-agent agent_end",
|
|
344
|
+
]);
|
|
345
|
+
expect(traceViolations(events), describeTrace(result)).toEqual([]);
|
|
346
|
+
const response = ofType(events, "model_response")[0]!;
|
|
347
|
+
expect(response.error).toMatch(/model exploded/);
|
|
348
|
+
expect(response.stop_reason).toBe("error");
|
|
349
|
+
expect(ofType(events, "agent_end")[0]!.outcome).toBe("failed");
|
|
350
|
+
expect(count(events, "error")).toBe(0);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("nests the call under an enclosing agent() scope", () => {
|
|
354
|
+
const result = run("scope");
|
|
355
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
356
|
+
"planner agent_start",
|
|
357
|
+
...GEN,
|
|
358
|
+
"planner agent_end",
|
|
359
|
+
]);
|
|
360
|
+
expect(new Set(result.events.map((e) => e.session_id))).toEqual(new Set(["req-1"]));
|
|
361
|
+
expect(ofType(result.events, "agent_start")[1]!.parent_id).toBe("planner");
|
|
362
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("runs the README call sites cleanly", () => {
|
|
366
|
+
const result = run("readme");
|
|
367
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
368
|
+
const agents = ofType(result.events, "agent_start").map((e) => e.agent_id);
|
|
369
|
+
expect(agents).toEqual(expect.arrayContaining(["answer-question", "tagged", "mock-model"]));
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Every commonly used surface of the SDK, on every major where it exists —
|
|
376
|
+
* `surfaces.ts` in each fixture. The cases above pin the README call sites
|
|
377
|
+
* and the adapter's core paths; these walk the rest of what customers call:
|
|
378
|
+
* the agent classes, embeddings, structured output, tool features, every way
|
|
379
|
+
* of consuming a stream, reasoning models and concurrency.
|
|
380
|
+
*
|
|
381
|
+
* Where the SDK itself makes a surface unrecordable, the case pins what IS
|
|
382
|
+
* recorded, with the reason beside it, so a change in either direction is
|
|
383
|
+
* seen.
|
|
384
|
+
*/
|
|
385
|
+
describe.each(FIXTURES)("%s: every surface", (fixture) => {
|
|
386
|
+
const major = Number(fixture.split("-")[1]);
|
|
387
|
+
/** The tool loop the scripted model runs: one tool call, then the answer. */
|
|
388
|
+
const loop = (agent: string, tool = "weather"): string[] => [
|
|
389
|
+
`${agent} agent_start`,
|
|
390
|
+
`${agent} model_request`,
|
|
391
|
+
`${agent} model_response`,
|
|
392
|
+
`${agent} tool_use ${tool}`,
|
|
393
|
+
`${agent} tool_result ${tool}`,
|
|
394
|
+
`${agent} model_request`,
|
|
395
|
+
`${agent} model_response`,
|
|
396
|
+
`${agent} agent_end`,
|
|
397
|
+
];
|
|
398
|
+
/** The same loop streamed: on v4–v6 the step's span ends after the tool it caused (see LEGACY_STREAM). */
|
|
399
|
+
const streamed = (agent: string): string[] =>
|
|
400
|
+
major >= 7
|
|
401
|
+
? loop(agent)
|
|
402
|
+
: [
|
|
403
|
+
`${agent} agent_start`,
|
|
404
|
+
`${agent} model_request`,
|
|
405
|
+
`${agent} tool_use weather`,
|
|
406
|
+
`${agent} tool_result weather`,
|
|
407
|
+
`${agent} model_response`,
|
|
408
|
+
`${agent} model_request`,
|
|
409
|
+
`${agent} model_response`,
|
|
410
|
+
`${agent} agent_end`,
|
|
411
|
+
];
|
|
412
|
+
const single = (agent: string): string[] => [
|
|
413
|
+
`${agent} agent_start`,
|
|
414
|
+
`${agent} model_request`,
|
|
415
|
+
`${agent} model_response`,
|
|
416
|
+
`${agent} agent_end`,
|
|
417
|
+
];
|
|
418
|
+
const hasAgentClass = major >= 5;
|
|
419
|
+
const hasApproval = major >= 6;
|
|
420
|
+
|
|
421
|
+
it("typechecks every surface program as a customer's nodenext project", () => {
|
|
422
|
+
expect(typecheck(fixture, "tsconfig.surfaces.json")).toBe("");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
describe.each(FORMATS)("as %s", (format) => {
|
|
426
|
+
const run = (scenario: string, { expectNote = false } = {}) => {
|
|
427
|
+
const result = runAgent(fixture, format, scenario, {}, "surfaces");
|
|
428
|
+
expect(result.status, describeTrace(result)).toBe(0);
|
|
429
|
+
const lines = result.stderr.split("\n").filter((line) => line.includes("[failproofai-sdk]"));
|
|
430
|
+
if (expectNote) {
|
|
431
|
+
expect(lines, describeTrace(result)).toHaveLength(1);
|
|
432
|
+
expect(lines[0], describeTrace(result)).toContain("registerGlobalTracer");
|
|
433
|
+
} else {
|
|
434
|
+
expect(lines, describeTrace(result)).toEqual([]);
|
|
435
|
+
}
|
|
436
|
+
return result;
|
|
437
|
+
};
|
|
438
|
+
/** Every JSON line the program reported, merged. */
|
|
439
|
+
const reported = (result: { stdout: string }): Record<string, unknown> =>
|
|
440
|
+
Object.assign(
|
|
441
|
+
{},
|
|
442
|
+
...result.stdout
|
|
443
|
+
.trim()
|
|
444
|
+
.split("\n")
|
|
445
|
+
.filter((line) => line.startsWith("{"))
|
|
446
|
+
.map((line) => JSON.parse(line) as Record<string, unknown>),
|
|
447
|
+
) as Record<string, unknown>;
|
|
448
|
+
const clean = (result: ReturnType<typeof run>) =>
|
|
449
|
+
expect(traceViolations(result.events), describeTrace(result)).toEqual([]);
|
|
450
|
+
|
|
451
|
+
// ---- 1. the agent classes (ai 5 Experimental_Agent, ai 6/7 ToolLoopAgent)
|
|
452
|
+
|
|
453
|
+
it.each(["agent-generate", "agent-stream"])("%s: one agent per call, named by the agent's functionId", (scenario) => {
|
|
454
|
+
const result = run(scenario);
|
|
455
|
+
if (!hasAgentClass) {
|
|
456
|
+
expect(reported(result).skipped).toBe("no agent class");
|
|
457
|
+
expect(result.events).toEqual([]);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
expect(shape(result.events), describeTrace(result)).toEqual(
|
|
461
|
+
scenario === "agent-generate" ? loop("support-agent") : streamed("support-agent"),
|
|
462
|
+
);
|
|
463
|
+
clean(result);
|
|
464
|
+
expect(tokens(result.events)).toEqual([
|
|
465
|
+
[11, 7],
|
|
466
|
+
[23, 9],
|
|
467
|
+
]);
|
|
468
|
+
expect(ofType(result.events, "tool_use")[0]!.tool_call_id).toBe("call-1");
|
|
469
|
+
expect(reported(result).text).toBe("It is 20C in Paris.");
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("agent-id: an agent's own `id` never reaches telemetry, so without a functionId it is named after the operation", () => {
|
|
473
|
+
// ToolLoopAgent spreads its settings into generateText, which drops `id`;
|
|
474
|
+
// no span attribute (v4–v6) and no v7 event carries it. `functionId` in
|
|
475
|
+
// the agent's telemetry settings is what names it.
|
|
476
|
+
const result = run("agent-id");
|
|
477
|
+
if (!hasAgentClass) return;
|
|
478
|
+
expect(shape(result.events), describeTrace(result)).toEqual(loop("ai.generateText"));
|
|
479
|
+
clean(result);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it.each(["agent-instrument", "agent-instrument-stream"])(
|
|
483
|
+
"%s: instrument('ai') records an agent on ai 7, and on ai 5–6 records nothing and says so",
|
|
484
|
+
(scenario) => {
|
|
485
|
+
const result = run(scenario, { expectNote: hasAgentClass && major < 7 });
|
|
486
|
+
if (!hasAgentClass) return;
|
|
487
|
+
if (major >= 7) {
|
|
488
|
+
expect(shape(result.events), describeTrace(result)).toEqual(loop("support-agent"));
|
|
489
|
+
clean(result);
|
|
490
|
+
} else {
|
|
491
|
+
expect(result.events, describeTrace(result)).toEqual([]);
|
|
492
|
+
}
|
|
493
|
+
expect(reported(result).text).toBe("It is 20C in Paris.");
|
|
494
|
+
},
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
it("agent-instrument-bare: with no telemetry setting at all, ai 7 still records the agent", () => {
|
|
498
|
+
const result = run("agent-instrument-bare");
|
|
499
|
+
if (!hasAgentClass) return;
|
|
500
|
+
if (major >= 7) {
|
|
501
|
+
expect(shape(result.events), describeTrace(result)).toEqual(loop("ai.generateText"));
|
|
502
|
+
clean(result);
|
|
503
|
+
} else {
|
|
504
|
+
// v5–v6 consult no tracer unless the call sets isEnabled.
|
|
505
|
+
expect(result.events, describeTrace(result)).toEqual([]);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it("agent-wrap: a wrapped model under an agent with no enclosing scope records each step as its own run", () => {
|
|
510
|
+
const result = run("agent-wrap");
|
|
511
|
+
if (!hasAgentClass) return;
|
|
512
|
+
// wrapModel sees model calls only: the tool loop above it is invisible.
|
|
513
|
+
expect(shape(result.events), describeTrace(result)).toEqual([...single("mock-model"), ...single("mock-model")]);
|
|
514
|
+
clean(result);
|
|
515
|
+
expect(new Set(result.events.map((e) => e.session_id)).size).toBe(2);
|
|
516
|
+
expect(tokens(result.events)).toEqual([
|
|
517
|
+
[11, 7],
|
|
518
|
+
[23, 9],
|
|
519
|
+
]);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("agent-wrap-in-scope: inside agent(), the agent's wrapped model calls are steps of that agent", () => {
|
|
523
|
+
const result = run("agent-wrap-in-scope");
|
|
524
|
+
if (!hasAgentClass) return;
|
|
525
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
526
|
+
"support-agent agent_start",
|
|
527
|
+
"support-agent model_request",
|
|
528
|
+
"support-agent model_response",
|
|
529
|
+
"support-agent model_request",
|
|
530
|
+
"support-agent model_response",
|
|
531
|
+
"support-agent agent_end",
|
|
532
|
+
]);
|
|
533
|
+
clean(result);
|
|
534
|
+
expect(new Set(result.events.map((e) => e.session_id))).toEqual(new Set(["req-1"]));
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// ---- 2. embeddings: an agent only when nothing encloses them
|
|
538
|
+
|
|
539
|
+
it("embed: a bare embed() is its own run, named by functionId, with its token count", () => {
|
|
540
|
+
const result = run("embed");
|
|
541
|
+
expect(shape(result.events), describeTrace(result)).toEqual(single("indexer"));
|
|
542
|
+
clean(result);
|
|
543
|
+
const response = ofType(result.events, "model_response")[0]!;
|
|
544
|
+
expect(response.input_tokens).toBe(3);
|
|
545
|
+
expect(response.model).toBe("mock-embedder");
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it("embed-many: a bare embedMany() is one run with a model pair per provider call", () => {
|
|
549
|
+
const result = run("embed-many");
|
|
550
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
551
|
+
"ai.embedMany agent_start",
|
|
552
|
+
"ai.embedMany model_request",
|
|
553
|
+
"ai.embedMany model_response",
|
|
554
|
+
"ai.embedMany model_request",
|
|
555
|
+
"ai.embedMany model_response",
|
|
556
|
+
"ai.embedMany agent_end",
|
|
557
|
+
]);
|
|
558
|
+
clean(result);
|
|
559
|
+
// maxEmbeddingsPerCall: 2 → three values are two provider calls.
|
|
560
|
+
expect(ofType(result.events, "model_response").map((e) => e.input_tokens)).toEqual([6, 3]);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("embed-in-agent: inside agent(), embeddings are model calls of that agent — no nested agents", () => {
|
|
564
|
+
const result = run("embed-in-agent");
|
|
565
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
566
|
+
"rag agent_start",
|
|
567
|
+
"rag model_request",
|
|
568
|
+
"rag model_response",
|
|
569
|
+
"rag model_request",
|
|
570
|
+
"rag model_response",
|
|
571
|
+
"rag model_request",
|
|
572
|
+
"rag model_response",
|
|
573
|
+
"rag agent_end",
|
|
574
|
+
]);
|
|
575
|
+
clean(result);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("embed-in-tool: an embedding inside a tool is a model call of the agent that ran the tool", () => {
|
|
579
|
+
const result = run("embed-in-tool");
|
|
580
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
581
|
+
"weather-agent agent_start",
|
|
582
|
+
"weather-agent model_request",
|
|
583
|
+
"weather-agent model_response",
|
|
584
|
+
"weather-agent tool_use weather",
|
|
585
|
+
"weather-agent model_request",
|
|
586
|
+
"weather-agent model_response",
|
|
587
|
+
"weather-agent tool_result weather",
|
|
588
|
+
"weather-agent model_request",
|
|
589
|
+
"weather-agent model_response",
|
|
590
|
+
"weather-agent agent_end",
|
|
591
|
+
]);
|
|
592
|
+
clean(result);
|
|
593
|
+
expect(ofType(result.events, "model_request").map((e) => e.model)).toEqual(["mock-model", "mock-embedder", "mock-model"]);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ---- 3. structured output
|
|
597
|
+
|
|
598
|
+
it("object-partial: streamObject's partialObjectStream, consumed to the end, is one agent", () => {
|
|
599
|
+
const result = run("object-partial");
|
|
600
|
+
expect(shape(result.events), describeTrace(result)).toEqual(single("extractor"));
|
|
601
|
+
clean(result);
|
|
602
|
+
expect(reported(result).object).toEqual({ city: "Paris" });
|
|
603
|
+
expect(reported(result).partials).toContainEqual({ city: "Paris" });
|
|
604
|
+
expect(tokens(result.events)).toEqual([[5, 3]]);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("object-invalid: a schema failure is one error on the failed agent, after the model's answer", () => {
|
|
608
|
+
const result = run("object-invalid");
|
|
609
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
610
|
+
"extractor agent_start",
|
|
611
|
+
"extractor model_request",
|
|
612
|
+
"extractor model_response",
|
|
613
|
+
"extractor error",
|
|
614
|
+
"extractor agent_end",
|
|
615
|
+
]);
|
|
616
|
+
clean(result);
|
|
617
|
+
expect(ofType(result.events, "error")[0]!.message).toMatch(/did not match schema/);
|
|
618
|
+
expect(ofType(result.events, "agent_end")[0]!.outcome).toBe("failed");
|
|
619
|
+
expect(reported(result).threw).toBe("AI_NoObjectGeneratedError");
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it.each(["text-output", "stream-output"])("%s: generateText / streamText with Output.object is one agent", (scenario) => {
|
|
623
|
+
const result = run(scenario);
|
|
624
|
+
expect(shape(result.events), describeTrace(result)).toEqual(single("extractor"));
|
|
625
|
+
clean(result);
|
|
626
|
+
expect(tokens(result.events)).toEqual([[5, 3]]);
|
|
627
|
+
if (scenario === "text-output") expect(reported(result).output).toEqual({ city: "Paris" });
|
|
628
|
+
else expect(reported(result).last).toEqual({ city: "Paris" });
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// ---- 4. tool features
|
|
632
|
+
|
|
633
|
+
it("parallel-tools: two calls in one step pair on the model's own ids, whatever order they finish in", () => {
|
|
634
|
+
const result = run("parallel-tools");
|
|
635
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
636
|
+
"weather-agent agent_start",
|
|
637
|
+
"weather-agent model_request",
|
|
638
|
+
"weather-agent model_response",
|
|
639
|
+
"weather-agent tool_use weather",
|
|
640
|
+
"weather-agent tool_use weather",
|
|
641
|
+
"weather-agent tool_result weather",
|
|
642
|
+
"weather-agent tool_result weather",
|
|
643
|
+
"weather-agent model_request",
|
|
644
|
+
"weather-agent model_response",
|
|
645
|
+
"weather-agent agent_end",
|
|
646
|
+
]);
|
|
647
|
+
clean(result);
|
|
648
|
+
const uses = ofType(result.events, "tool_use");
|
|
649
|
+
expect(uses.map((e) => [e.tool_call_id, e.input])).toEqual([
|
|
650
|
+
["call-p1", { city: "Paris" }],
|
|
651
|
+
["call-p2", { city: "Rome" }],
|
|
652
|
+
]);
|
|
653
|
+
// Rome returned first.
|
|
654
|
+
expect(ofType(result.events, "tool_result").map((e) => [e.tool_call_id, e.output])).toEqual([
|
|
655
|
+
["call-p2", { city: "Rome", celsius: 16 }],
|
|
656
|
+
["call-p1", { city: "Paris", celsius: 20 }],
|
|
657
|
+
]);
|
|
658
|
+
// The step's content lists both calls, inputs parsed, on every major.
|
|
659
|
+
expect(ofType(result.events, "model_response")[0]!.content).toEqual([
|
|
660
|
+
{ toolCallId: "call-p1", toolName: "weather", input: { city: "Paris" } },
|
|
661
|
+
{ toolCallId: "call-p2", toolName: "weather", input: { city: "Rome" } },
|
|
662
|
+
]);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("client-tool: a tool with no execute is a call in the model's answer, never a tool_use left open", () => {
|
|
666
|
+
const result = run("client-tool");
|
|
667
|
+
expect(shape(result.events), describeTrace(result)).toEqual(single("weather-agent"));
|
|
668
|
+
clean(result);
|
|
669
|
+
const response = ofType(result.events, "model_response")[0]!;
|
|
670
|
+
expect(response.stop_reason).toBe("tool-calls");
|
|
671
|
+
expect(response.content).toEqual([{ toolCallId: "call-c1", toolName: "ask", input: { city: "Paris" } }]);
|
|
672
|
+
expect(reported(result).toolCalls).toBe(1);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it("tool-choice-required: a forced tool call is recorded like any other", () => {
|
|
676
|
+
const result = run("tool-choice-required");
|
|
677
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
678
|
+
"weather-agent agent_start",
|
|
679
|
+
"weather-agent model_request",
|
|
680
|
+
"weather-agent model_response",
|
|
681
|
+
"weather-agent tool_use weather",
|
|
682
|
+
"weather-agent tool_result weather",
|
|
683
|
+
"weather-agent agent_end",
|
|
684
|
+
]);
|
|
685
|
+
clean(result);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("repair: the tool_use carries the REPAIRED input", () => {
|
|
689
|
+
const result = run("repair");
|
|
690
|
+
expect(shape(result.events), describeTrace(result)).toEqual(loop("weather-agent"));
|
|
691
|
+
clean(result);
|
|
692
|
+
// v4–v6 record the step as the model produced it; v7's
|
|
693
|
+
// onLanguageModelCallEnd already carries the repaired call.
|
|
694
|
+
expect(ofType(result.events, "model_response")[0]!.content).toEqual([
|
|
695
|
+
{ toolCallId: "call-r1", toolName: "weather", input: major >= 7 ? { city: "Paris" } : { town: "Paris" } },
|
|
696
|
+
]);
|
|
697
|
+
const use = ofType(result.events, "tool_use")[0]!;
|
|
698
|
+
expect([use.tool_call_id, use.input]).toEqual(["call-r1", { city: "Paris" }]);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it("prepare-step: a model switched by prepareStep is named on its own step", () => {
|
|
702
|
+
const result = run("prepare-step");
|
|
703
|
+
expect(shape(result.events), describeTrace(result)).toEqual(loop("weather-agent"));
|
|
704
|
+
clean(result);
|
|
705
|
+
expect(ofType(result.events, "model_response").map((e) => e.model)).toEqual(["mock-model", "mock-model-large"]);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it("unknown-tool: a call to a tool that does not exist", () => {
|
|
709
|
+
const result = run("unknown-tool");
|
|
710
|
+
clean(result);
|
|
711
|
+
if (major >= 5) {
|
|
712
|
+
// v5+ hands the model a tool error and the loop carries on; the SDK
|
|
713
|
+
// opens no tool span / execution for a tool it does not have.
|
|
714
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
715
|
+
"weather-agent agent_start",
|
|
716
|
+
"weather-agent model_request",
|
|
717
|
+
"weather-agent model_response",
|
|
718
|
+
"weather-agent model_request",
|
|
719
|
+
"weather-agent model_response",
|
|
720
|
+
"weather-agent agent_end",
|
|
721
|
+
]);
|
|
722
|
+
expect(ofType(result.events, "agent_end")[0]!.outcome).toBe("success");
|
|
723
|
+
} else {
|
|
724
|
+
// v4 throws NoSuchToolError out of generateText.
|
|
725
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
726
|
+
"weather-agent agent_start",
|
|
727
|
+
"weather-agent model_request",
|
|
728
|
+
"weather-agent model_response",
|
|
729
|
+
"weather-agent error",
|
|
730
|
+
"weather-agent agent_end",
|
|
731
|
+
]);
|
|
732
|
+
expect(ofType(result.events, "agent_end")[0]!.outcome).toBe("failed");
|
|
733
|
+
expect(reported(result).threw).toBe("AI_NoSuchToolError");
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("approval: a tool that needs approval runs in the call that carries the approval", () => {
|
|
738
|
+
const result = run("approval");
|
|
739
|
+
if (!hasApproval) {
|
|
740
|
+
expect(reported(result).skipped).toBe("no tool approval");
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
// Two calls, two runs: the first ends with the model's request for
|
|
744
|
+
// approval (a tool call in its content, nothing executed); the second
|
|
745
|
+
// executes the approved tool, then answers. Put both in one
|
|
746
|
+
// failproofai.session() to see them in one session.
|
|
747
|
+
expect(shape(result.events), describeTrace(result)).toEqual([
|
|
748
|
+
...single("travel-agent"),
|
|
749
|
+
"travel-agent agent_start",
|
|
750
|
+
"travel-agent tool_use book",
|
|
751
|
+
"travel-agent tool_result book",
|
|
752
|
+
"travel-agent model_request",
|
|
753
|
+
"travel-agent model_response",
|
|
754
|
+
"travel-agent agent_end",
|
|
755
|
+
]);
|
|
756
|
+
clean(result);
|
|
757
|
+
expect(ofType(result.events, "tool_use")[0]!.tool_call_id).toBe("call-a1");
|
|
758
|
+
expect(reported(result)).toMatchObject({ pending: 1, text: "Booked Paris." });
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
// ---- 5. consuming a stream
|
|
762
|
+
|
|
763
|
+
it.each(["stream-full", "stream-response", "stream-on-finish"])("%s: records the whole loop", (scenario) => {
|
|
764
|
+
const result = run(scenario);
|
|
765
|
+
expect(shape(result.events), describeTrace(result)).toEqual(streamed("weather-agent"));
|
|
766
|
+
clean(result);
|
|
767
|
+
expect(tokens(result.events)).toEqual([
|
|
768
|
+
[11, 7],
|
|
769
|
+
[23, 9],
|
|
770
|
+
]);
|
|
771
|
+
expect(ofType(result.events, "agent_end")[0]!.outcome).toBe("success");
|
|
772
|
+
const report = reported(result);
|
|
773
|
+
if (scenario === "stream-response") expect(report).toMatchObject({ status: 200, containsAnswer: true });
|
|
774
|
+
if (scenario === "stream-on-finish") expect(report.finished).toBe("It is 20C in Paris.");
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it("stream-abort: an AbortSignal mid-stream closes the model call and the agent as cancelled", () => {
|
|
778
|
+
const result = run("stream-abort");
|
|
779
|
+
expect(shape(result.events), describeTrace(result)).toEqual(single("counter"));
|
|
780
|
+
clean(result);
|
|
781
|
+
expect(ofType(result.events, "model_response")[0]!.stop_reason).toBe("cancelled");
|
|
782
|
+
expect(ofType(result.events, "agent_end")[0]!.outcome).toBe("cancelled");
|
|
783
|
+
expect(count(result.events, "error")).toBe(0);
|
|
784
|
+
expect(reported(result).after).toEqual({ openCalls: 0, stats: { runs: 0, links: 0 } });
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
it("stream-response-cancel-signal: a client that disconnects from a route passing abortSignal ends cancelled", () => {
|
|
788
|
+
const result = run("stream-response-cancel-signal");
|
|
789
|
+
expect(shape(result.events), describeTrace(result)).toEqual(single("counter"));
|
|
790
|
+
clean(result);
|
|
791
|
+
expect(ofType(result.events, "agent_end")[0]!.outcome).toBe("cancelled");
|
|
792
|
+
expect(reported(result).after).toEqual({ openCalls: 0, stats: { runs: 0, links: 0 } });
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it.each(["stream-response-cancel", "stream-unconsumed"])(
|
|
796
|
+
"%s: a stream nobody finishes reading is closed once it is garbage (ai 4–6); ai 7 cannot see it",
|
|
797
|
+
(scenario) => {
|
|
798
|
+
const result = run(scenario);
|
|
799
|
+
const report = reported(result);
|
|
800
|
+
if (major >= 7) {
|
|
801
|
+
// v7 fires no telemetry callback when the consumer cancels or never
|
|
802
|
+
// reads, and hands the integration no per-call object whose
|
|
803
|
+
// collection could stand in for one: the agent stays open while the
|
|
804
|
+
// process lives. Bounded (MAX_OPEN_CALLS), and avoidable by passing
|
|
805
|
+
// the request's abortSignal — see stream-response-cancel-signal. When
|
|
806
|
+
// the process exits, the exit hook closes it as failed rather than
|
|
807
|
+
// leaving it rendered as running forever.
|
|
808
|
+
expect(count(result.events, "agent_start"), describeTrace(result)).toBe(1);
|
|
809
|
+
expect(report.after).toMatchObject({ openCalls: 1 });
|
|
810
|
+
const ends = ofType(result.events, "agent_end");
|
|
811
|
+
expect(ends.map((e) => e.outcome), describeTrace(result)).toEqual(["failed"]);
|
|
812
|
+
expect(ends[0]!.summary).toMatch(/process exited/);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
// v4–v6: the SDK ends a stream's root span from a flush() that never
|
|
816
|
+
// runs; the adapter ends it when the span is collected.
|
|
817
|
+
clean(result);
|
|
818
|
+
const end = ofType(result.events, "agent_end")[0]!;
|
|
819
|
+
expect(end.outcome, describeTrace(result)).toBe("cancelled");
|
|
820
|
+
expect(end.fw_abandoned).toBe(true);
|
|
821
|
+
// (`report.before` is not asserted: an ordinary collection may already
|
|
822
|
+
// have closed it by then.)
|
|
823
|
+
expect(report.after).toEqual({ openCalls: 0, stats: { runs: 0, links: 0 } });
|
|
824
|
+
},
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
it("stream-error: a provider stream that breaks mid-way leaves nothing open", () => {
|
|
828
|
+
const result = run("stream-error");
|
|
829
|
+
expect(shape(result.events), describeTrace(result)).toEqual(single("counter"));
|
|
830
|
+
clean(result);
|
|
831
|
+
expect(reported(result).threw).toBe("connection reset");
|
|
832
|
+
expect(reported(result).after).toEqual({ openCalls: 0, stats: { runs: 0, links: 0 } });
|
|
833
|
+
const response = ofType(result.events, "model_response")[0]!;
|
|
834
|
+
const end = ofType(result.events, "agent_end")[0]!;
|
|
835
|
+
if (major >= 7) {
|
|
836
|
+
expect(response.stop_reason).toBe("error");
|
|
837
|
+
expect(response.error).toMatch(/connection reset/);
|
|
838
|
+
expect(end.outcome).toBe("failed");
|
|
839
|
+
} else {
|
|
840
|
+
// v4–v6 never end either span nor report the error to the tracer; the
|
|
841
|
+
// operation is closed when it is collected, and cannot say why.
|
|
842
|
+
expect(response.stop_reason).toBe("cancelled");
|
|
843
|
+
expect(end.outcome).toBe("cancelled");
|
|
844
|
+
expect(end.fw_abandoned).toBe(true);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// ---- 6. reasoning models
|
|
849
|
+
|
|
850
|
+
it.each(["reasoning-generate", "reasoning-stream", "reasoning-wrap"])(
|
|
851
|
+
"%s: reasoning parts leave the token count and the answer intact",
|
|
852
|
+
(scenario) => {
|
|
853
|
+
const result = run(scenario);
|
|
854
|
+
expect(shape(result.events), describeTrace(result)).toEqual(single(scenario === "reasoning-wrap" ? "mock-model" : "thinker"));
|
|
855
|
+
clean(result);
|
|
856
|
+
const response = ofType(result.events, "model_response")[0]!;
|
|
857
|
+
expect([response.input_tokens, response.output_tokens]).toEqual([12, 20]);
|
|
858
|
+
expect(response.stop_reason).toBe("stop");
|
|
859
|
+
expect(response.content).toBe("Paris.");
|
|
860
|
+
expect(reported(result).text).toBe("Paris.");
|
|
861
|
+
},
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
// ---- 7. concurrency
|
|
865
|
+
|
|
866
|
+
it.each(["concurrent", "concurrent-stream"])("%s: 10 concurrent calls in one session are 10 agents with no cross-talk", (scenario) => {
|
|
867
|
+
const result = run(scenario);
|
|
868
|
+
clean(result);
|
|
869
|
+
expect(new Set(result.events.map((e) => e.session_id))).toEqual(new Set(["busy"]));
|
|
870
|
+
const starts = ofType(result.events, "agent_start").map((e) => e.agent_id).sort();
|
|
871
|
+
expect(starts).toEqual(Array.from({ length: 10 }, (_, i) => `worker-${i}`).sort());
|
|
872
|
+
for (let i = 0; i < 10; i += 1) {
|
|
873
|
+
const mine = result.events.filter((e) => e.agent_id === `worker-${i}`);
|
|
874
|
+
expect(mine.map((e) => e.type).sort(), describeTrace(result)).toEqual(
|
|
875
|
+
["agent_end", "agent_start", "model_request", "model_request", "model_response", "model_response", "tool_result", "tool_use"],
|
|
876
|
+
);
|
|
877
|
+
expect(ofType(mine, "tool_use")[0]!.tool_call_id).toBe(`call-${i}`);
|
|
878
|
+
expect(ofType(mine, "tool_use")[0]!.input).toEqual({ city: `city-${i}` });
|
|
879
|
+
expect(ofType(mine, "tool_result")[0]!.tool_call_id).toBe(`call-${i}`);
|
|
880
|
+
expect(tokens(mine)).toEqual([
|
|
881
|
+
[100 + i, 1],
|
|
882
|
+
[200 + i, 2],
|
|
883
|
+
]);
|
|
884
|
+
// Each response pairs with a request of the SAME agent.
|
|
885
|
+
const requests = new Set(ofType(mine, "model_request").map((e) => e.request_id));
|
|
886
|
+
for (const response of ofType(mine, "model_response")) expect(requests.has(response.request_id)).toBe(true);
|
|
887
|
+
}
|
|
888
|
+
expect(reported(result).texts).toEqual(Array.from({ length: 10 }, (_, i) => `answer ${i}`));
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
it("concurrent-unscoped: 10 concurrent same-named calls outside any scope are 10 sessions of one agent each", () => {
|
|
892
|
+
const result = run("concurrent-unscoped");
|
|
893
|
+
clean(result);
|
|
894
|
+
const sessions = new Map<string, Event[]>();
|
|
895
|
+
for (const e of result.events) sessions.set(e.session_id, [...(sessions.get(e.session_id) ?? []), e]);
|
|
896
|
+
expect(sessions.size).toBe(10);
|
|
897
|
+
for (const events of sessions.values()) {
|
|
898
|
+
expect(shape(events)).toEqual(loop("worker"));
|
|
899
|
+
const i = Number(String(ofType(events, "tool_use")[0]!.tool_call_id).split("-")[1]);
|
|
900
|
+
expect(tokens(events)).toEqual([
|
|
901
|
+
[100 + i, 1],
|
|
902
|
+
[200 + i, 2],
|
|
903
|
+
]);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
it("concurrent-wrap: 10 concurrent bare wrapped-model calls are 10 runs, each its own model's", () => {
|
|
908
|
+
const result = run("concurrent-wrap");
|
|
909
|
+
clean(result);
|
|
910
|
+
const sessions = new Map<string, Event[]>();
|
|
911
|
+
for (const e of result.events) sessions.set(e.session_id, [...(sessions.get(e.session_id) ?? []), e]);
|
|
912
|
+
expect(sessions.size).toBe(10);
|
|
913
|
+
for (const events of sessions.values()) {
|
|
914
|
+
const name = events[0]!.agent_id;
|
|
915
|
+
expect(shape(events)).toEqual(single(name));
|
|
916
|
+
expect(tokens(events)).toEqual([[100 + Number(name.split("-")[1]), 1]]);
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
});
|
|
920
|
+
});
|