failproofai 1.0.7-beta.1 → 1.0.7-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- 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.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.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.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.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.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.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.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.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.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 +7 -7
- package/.next/standalone/.next/server/app/settings/page.js +2 -2
- 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/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/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_18qtd42._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0yxwl6j._.js → [root-of-the-server]__0l44ual._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_214wgrp._.js → _1mel6y1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_0bn2oo8._.js → _1v-jvrv._.js} +1 -1
- 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_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.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 +22 -22
- package/.next/standalone/.next/static/chunks/043j99m8ykg__.css +2 -0
- package/.next/standalone/.next/static/chunks/0fqd7m_u81mi5.js +1 -0
- package/.next/standalone/.next/static/chunks/129ag2bw93bdh.js +1 -0
- package/.next/standalone/.next/static/chunks/{29fql3nbnfc9q.js → 1qd741hzlmjbo.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0vmd180qfntfb.js → 2_pltstd8-xgs.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1u5zsejmgrir_.js → 2c8j9l6j_b1ci.js} +1 -1
- package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +1 -0
- package/.next/standalone/.next/static/chunks/{258668t68du6b.js → 3brze37td_wnc.js} +1 -1
- package/.next/standalone/.next/static/chunks/3otmypm6j_xfo.js +6 -0
- package/.next/standalone/.next/static/chunks/{12tvm75t5ffui.js → 3ugmd_7dyn0id.js} +1 -1
- package/.next/standalone/.next/static/chunks/3yxro_r2_o9ad.js +69 -0
- package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
- package/.next/standalone/app/actions/get-jev-config.ts +38 -23
- package/.next/standalone/app/actions/update-jev-config.ts +1 -1
- package/.next/standalone/app/settings/jev-panel.tsx +33 -29
- package/.next/standalone/package.json +10 -10
- 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 +119 -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 +281 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +13 -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 +168 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +13 -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 +463 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +15 -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 +579 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +15 -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 +6417 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +15 -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/README.md +2 -2
- package/dist/cli.mjs +208 -27
- package/dist/worker.mjs +120 -14
- package/package.json +10 -10
- package/scripts/build-policy-pack.mjs +14 -0
- package/src/audit/features.ts +3 -2
- package/src/hooks/builtin-policies.ts +156 -5
- package/src/hooks/jev-cli.ts +57 -1
- package/src/hooks/manager.ts +1 -1
- package/src/hooks/pack-cli.ts +150 -18
- package/src/hooks/pack-store.ts +1 -1
- package/src/hooks/policy-catalog.ts +148 -9
- package/src/hooks/types.ts +1 -1
- package/.next/standalone/.next/static/chunks/0cd-_8-c-m1ea.js +0 -6
- package/.next/standalone/.next/static/chunks/0qrbdkv9qmvli.js +0 -69
- package/.next/standalone/.next/static/chunks/0uldbut9y2-e8.js +0 -1
- package/.next/standalone/.next/static/chunks/285spx855h_3r.css +0 -2
- 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/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,1802 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mastra (`@mastra/core`).
|
|
3
|
+
*
|
|
4
|
+
* ## The mapping
|
|
5
|
+
*
|
|
6
|
+
* Mastra has no Python counterpart, so this is derived from the rule every
|
|
7
|
+
* adapter follows — "a framework construct becomes an agent if and only if it
|
|
8
|
+
* owns an LLM decision loop and has its own goal; everything else with a start
|
|
9
|
+
* and an end becomes the closest kind of leaf" — with CrewAI (agents calling
|
|
10
|
+
* agents) and LlamaIndex (workflow steps) as the analogues:
|
|
11
|
+
*
|
|
12
|
+
* | Mastra | FailproofAI |
|
|
13
|
+
* |---|---|
|
|
14
|
+
* | `Agent.generate()` / `.stream()` (and the legacy / VNext variants) | `agent_start` / `agent_end`, `agent_id` = the agent's `name` (its `id` if unnamed), never a UUID |
|
|
15
|
+
* | a sub-agent another agent delegates to (`agents: {…}`) | nested `agent_start` / `agent_end`, `parent_id` = the caller, inside the caller's `agent-<name>` tool call |
|
|
16
|
+
* | each LLM step of the loop | `model_request` / `model_response`, paired on `request_id`, with the model id, integer tokens, a string `stop_reason` and `duration_ms` |
|
|
17
|
+
* | a tool the model calls | `tool_use` / `tool_result` carrying the MODEL's `toolCallId`, attributed to the agent whose loop called it |
|
|
18
|
+
* | a workflow run (`run.start()`, `.stream()`, `.resume()`) | `agent_start` / `agent_end`, `agent_id` = the workflow id |
|
|
19
|
+
* | a workflow step (incl. every branch taken, parallel step and loop iteration) | `hook_triggered` / `hook_completed`, `trigger_event="workflow_step"` |
|
|
20
|
+
* | a nested workflow, or an agent used as a step | a nested agent under the workflow, inside that step's hook |
|
|
21
|
+
* | a step's `suspend()` … `run.resume({ step, resumeData })` | `human_wait` + `agent_pause` … `agent_resume` + `human_input` on the SAME workflow span, which stays open while suspended; ids `<runId>:<step path>` |
|
|
22
|
+
* | `agent.network()` | ONE agent (`fw_method="network"`); the router Mastra builds per decision (`routing-agent`) is that agent's own model steps, and the agent it delegates to is nested under it |
|
|
23
|
+
* | a run an input or output processor blocks (`abort()` / tripwire) | `agent_end` `outcome="rejected"` |
|
|
24
|
+
* | Mastra's own machinery — the agentic loop and a network are built from internal workflows | nothing |
|
|
25
|
+
*
|
|
26
|
+
* **Sessions.** A root run (nothing enclosing it, no `failproofai.session()` /
|
|
27
|
+
* `agent()` scope) takes the conversation id Mastra gives it, the rule every
|
|
28
|
+
* adapter follows ("a conversation spanning runs is one session where the
|
|
29
|
+
* framework provides a conversation id"): an agent run's memory thread
|
|
30
|
+
* (`memory: { thread }`, 0.x `threadId`), so every turn of a thread is one
|
|
31
|
+
* session; a workflow run's run id, so a suspended run and its resume — in
|
|
32
|
+
* this process or another — are one session. Otherwise the session is new.
|
|
33
|
+
* An enclosing scope always wins, and a nested run is in its parent's.
|
|
34
|
+
*
|
|
35
|
+
* A streamed call finalises when Mastra finishes the stream — which it does as
|
|
36
|
+
* the caller consumes it — not when `stream()` returns: the model events carry
|
|
37
|
+
* the real token counts and stop reason, and `agent_end` is not emitted while
|
|
38
|
+
* the loop is still running. A failure is recorded once, on the event it
|
|
39
|
+
* happened in (the `model_response`, the `tool_result`, the `hook_completed`),
|
|
40
|
+
* and closes the enclosing agent `failed`; there is no separate `error` event
|
|
41
|
+
* for each layer the exception unwound through.
|
|
42
|
+
*
|
|
43
|
+
* Token counts are the provider's own, read off each step. A STREAMED step
|
|
44
|
+
* from an OpenAI-compatible endpoint carries none unless the request asked for
|
|
45
|
+
* them (`stream_options.include_usage`), and the model Mastra's router builds
|
|
46
|
+
* for a `{ id, url }` / custom provider does not ask — Mastra itself then
|
|
47
|
+
* reports zero usage — so such a step is recorded without tokens. Nothing a
|
|
48
|
+
* call passes turns it on (`providerOptions` cannot: the provider overwrites
|
|
49
|
+
* `stream_options`); a model built with usage on — `@ai-sdk/openai`, or
|
|
50
|
+
* `createOpenAICompatible({ includeUsage: true })` — is recorded with them.
|
|
51
|
+
*
|
|
52
|
+
* ## Why prototype patching, and where
|
|
53
|
+
*
|
|
54
|
+
* Mastra 1.x has a real tracing extension point — an `ObservabilityExporter`
|
|
55
|
+
* receiving `agent_run` / `model_step` / `tool_call` / `workflow_step` spans —
|
|
56
|
+
* but it only exists for agents and workflows registered on a `Mastra` instance
|
|
57
|
+
* configured with `@mastra/observability`. A bare `new Agent(...)` gets a no-op
|
|
58
|
+
* tracer and emits nothing to hook, and that is the most common way Mastra is
|
|
59
|
+
* used outside its dev server. It is also absent from 0.x in this shape. So the
|
|
60
|
+
* adapter patches the few PROTOTYPE methods every run goes through, on both
|
|
61
|
+
* lines alike:
|
|
62
|
+
*
|
|
63
|
+
* * `Agent.prototype.generate` / `.stream` (+ variants) and `.network` — the
|
|
64
|
+
* agent span.
|
|
65
|
+
* * `Agent.prototype.__runInputProcessors` — where an input processor's
|
|
66
|
+
* `abort()` lands. A blocked `stream()` calls none of its callbacks, so this
|
|
67
|
+
* is what ends it (1.x also exposes the stream's finish; 0.x does not).
|
|
68
|
+
* * `Agent.prototype.resolveModelConfig` — every model the agent will call
|
|
69
|
+
* passes through here, per run. The resolved model is handed back behind a
|
|
70
|
+
* proxy whose `doGenerate` / `doStream` are observed: that is the AI SDK
|
|
71
|
+
* `LanguageModel` contract (V1–V3), which Mastra itself calls once per LLM
|
|
72
|
+
* step, so each step is one pair with the provider's own usage and finish
|
|
73
|
+
* reason — rather than one collapsed pair read off the final result.
|
|
74
|
+
* * `Agent.prototype.convertTools` — the one step that turns every kind of tool
|
|
75
|
+
* (assigned, memory, toolset, client, sub-agent, workflow-as-tool) into what
|
|
76
|
+
* the loop executes. Wrapping its OUTPUT covers tools created before
|
|
77
|
+
* `instrument()` ran, which patching the `createTool` export never could: an
|
|
78
|
+
* ES-module namespace is read-only, and a Mastra tool's `execute` lives on
|
|
79
|
+
* the instance.
|
|
80
|
+
* * `Run.prototype._start` / `_resume` (+ `_restart`, `_timeTravel`) and
|
|
81
|
+
* `DefaultExecutionEngine.prototype.executeStep` — the workflow span and its
|
|
82
|
+
* steps. Runs Mastra marks internal (`isInternalWorkflow`, or a
|
|
83
|
+
* `tracingPolicy.internal` with the WORKFLOW bit) are skipped, which is what
|
|
84
|
+
* keeps the agentic loop's own `execution-workflow` / `agentic-loop` out.
|
|
85
|
+
*
|
|
86
|
+
* Every copy of `@mastra/core` the application loads is patched — see
|
|
87
|
+
* `compat.requireModuleCopies()`: the ES-module and CommonJS builds are two
|
|
88
|
+
* unrelated sets of classes.
|
|
89
|
+
*
|
|
90
|
+
* ## How events find their agent
|
|
91
|
+
*
|
|
92
|
+
* An adapter-private `AsyncLocalStorage` carries the Mastra run that is
|
|
93
|
+
* executing (`Frame`). It never binds FailproofAI identity — it only answers
|
|
94
|
+
* "which of OUR runs is this?", and every event is still emitted through the
|
|
95
|
+
* `RunTracker` with an explicit parent key. The model proxy and the tool
|
|
96
|
+
* wrappers capture the frame when the run builds them, so an event keeps its
|
|
97
|
+
* agent even when Mastra later executes it from a stream the caller is
|
|
98
|
+
* pulling on a different async path. A tool call runs its body inside a
|
|
99
|
+
* `tool` frame of the calling agent, which is what nests a sub-agent under the
|
|
100
|
+
* agent that delegated to it — and, inside a workflow step, what attributes a
|
|
101
|
+
* tool to the agent that called it rather than to the workflow.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
105
|
+
import { randomUUID } from "node:crypto";
|
|
106
|
+
|
|
107
|
+
import { current as currentIdentity } from "../context.js";
|
|
108
|
+
import { agent as agentScope } from "../scopes.js";
|
|
109
|
+
import * as compat from "./compat.js";
|
|
110
|
+
import * as core from "./core.js";
|
|
111
|
+
import type { Adapter } from "./core.js";
|
|
112
|
+
|
|
113
|
+
const NAME = "mastra";
|
|
114
|
+
const PACKAGE = "@mastra/core";
|
|
115
|
+
const INSTALL = "npm install @mastra/core";
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The oldest release the patch points above all exist in with this shape:
|
|
119
|
+
* `resolveModelConfig` and `Run._start` both arrived in the 0.20 line, when
|
|
120
|
+
* `generate` / `stream` became the loop that calls a `LanguageModelV2` once per
|
|
121
|
+
* step. Earlier releases route model calls through the AI SDK's own
|
|
122
|
+
* `generateText` and run workflows through a different `Run`, so nothing here
|
|
123
|
+
* would see a model step or a workflow step.
|
|
124
|
+
*/
|
|
125
|
+
const MINIMUM = "0.20.0";
|
|
126
|
+
|
|
127
|
+
/*
|
|
128
|
+
* ## Lifetime
|
|
129
|
+
*
|
|
130
|
+
* `instrument()` hands Mastra objects that outlive it: a model behind a proxy,
|
|
131
|
+
* tools behind wrappers, a stream the caller is still reading. Restoring the
|
|
132
|
+
* prototypes does not reach any of them, so every recording path checks that
|
|
133
|
+
* the tracker it began on is still LIVE (`live()`), and nothing recreates a
|
|
134
|
+
* tracker behind `uninstrument()`'s back:
|
|
135
|
+
*
|
|
136
|
+
* * `tracker` exists exactly while `enabled` — created by `install()`, dropped
|
|
137
|
+
* by `uninstall()`. A proxy, wrapper, span or frame remembers the tracker it
|
|
138
|
+
* was built under; once that tracker is gone it is a pass-through for good,
|
|
139
|
+
* even after a later `instrument()` creates a new one.
|
|
140
|
+
* * `uninstall()` turns `enabled` off FIRST, then closes what is still open —
|
|
141
|
+
* model steps, tool calls, workflow steps (marked `fw_incomplete`), then
|
|
142
|
+
* agents `cancelled` — so the trace ends where the recording did instead of
|
|
143
|
+
* leaving spans `ongoing` forever.
|
|
144
|
+
* * Open spans are tracked only so teardown can close them. Every end path
|
|
145
|
+
* removes its own entry; a stream nobody ever consumes has no end path, so
|
|
146
|
+
* the registries are bounded like the tracker's own run table.
|
|
147
|
+
* * `wrapTool()` works without `instrument()` — it is the call-site helper for
|
|
148
|
+
* bundled applications — so a call with no live installation records on a
|
|
149
|
+
* `standalone` tracker, which is self-contained (its run opens and closes in
|
|
150
|
+
* the one call) and never touched by `uninstall()`.
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
let enabled = false;
|
|
154
|
+
let tracker: core.RunTracker | null = null;
|
|
155
|
+
let standalone: core.RunTracker | null = null;
|
|
156
|
+
let patcher: core.Patcher | null = null;
|
|
157
|
+
|
|
158
|
+
function newTracker(options: Record<string, unknown> = {}): core.RunTracker {
|
|
159
|
+
return new core.RunTracker(NAME, {
|
|
160
|
+
baseFields: core.frameworkFields(NAME, PACKAGE),
|
|
161
|
+
fieldLimit: typeof options.captureLimit === "number" ? options.captureLimit : undefined,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** May something begun on `t` still record? */
|
|
166
|
+
function live(t: core.RunTracker | null | undefined): t is core.RunTracker {
|
|
167
|
+
return t !== null && t !== undefined && ((enabled && t === tracker) || t === standalone);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Same bound as `RunTracker`'s own run table. */
|
|
171
|
+
const MAX_OPEN = 10_000;
|
|
172
|
+
|
|
173
|
+
/** A span that is open until it is `closed`, on the tracker it began on. */
|
|
174
|
+
interface Span {
|
|
175
|
+
tracker: core.RunTracker;
|
|
176
|
+
closed: boolean;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Open spans, oldest first, forgetting the oldest past `MAX_OPEN`. */
|
|
180
|
+
class OpenSpans<T extends Span> {
|
|
181
|
+
private readonly items = new Set<T>();
|
|
182
|
+
|
|
183
|
+
add(item: T): void {
|
|
184
|
+
while (this.items.size >= MAX_OPEN) {
|
|
185
|
+
const oldest = this.items.values().next();
|
|
186
|
+
if (oldest.done) break;
|
|
187
|
+
this.items.delete(oldest.value);
|
|
188
|
+
}
|
|
189
|
+
this.items.add(item);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** End `item`: true when it was still open on a live tracker. */
|
|
193
|
+
close(item: T): boolean {
|
|
194
|
+
this.items.delete(item);
|
|
195
|
+
if (item.closed) return false;
|
|
196
|
+
item.closed = true;
|
|
197
|
+
return live(item.tracker);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Remove and return everything still open, newest first. */
|
|
201
|
+
drain(): T[] {
|
|
202
|
+
const all = [...this.items].reverse().filter((item) => !item.closed);
|
|
203
|
+
this.items.clear();
|
|
204
|
+
for (const item of all) item.closed = true;
|
|
205
|
+
return all;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
get size(): number {
|
|
209
|
+
return this.items.size;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** The Mastra run currently executing, as far as this adapter is concerned. */
|
|
214
|
+
interface Frame {
|
|
215
|
+
/** The tracker the run was begun on; a frame from a dead one records nothing. */
|
|
216
|
+
tracker: core.RunTracker;
|
|
217
|
+
/** Our `RunTracker` key for the agent or workflow run. */
|
|
218
|
+
key: string;
|
|
219
|
+
/** The `Agent` or workflow `Run` instance that owns the run. */
|
|
220
|
+
owner: object;
|
|
221
|
+
/** `tool` while a tool the agent called is executing. */
|
|
222
|
+
kind: "agent" | "workflow" | "tool";
|
|
223
|
+
/** Set on the frame of an `agent.network()` run, whose router it absorbs. */
|
|
224
|
+
network?: boolean;
|
|
225
|
+
/** Ends the agent run this frame is, for an end no Mastra callback reports. */
|
|
226
|
+
close?: (outcome: string) => void;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const frames = new AsyncLocalStorage<Frame>();
|
|
230
|
+
|
|
231
|
+
/** Mastra workflow run id -> our key, so a step can find its workflow span. */
|
|
232
|
+
const workflowRuns = new Map<string, string>();
|
|
233
|
+
|
|
234
|
+
const WRAPPED = Symbol.for("failproofai.wrapped");
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Our own model proxies and agent-tool wrappers, to what they wrap. A reused
|
|
238
|
+
* agent can hand a later run the proxy or tool an earlier run built; that one
|
|
239
|
+
* is bound to the earlier run (or a dead installation), so it is unwrapped and
|
|
240
|
+
* observed afresh rather than skipped as "already wrapped".
|
|
241
|
+
*/
|
|
242
|
+
const modelProxies = new WeakMap<object, object>();
|
|
243
|
+
const toolWrappers = new WeakMap<object, (...args: unknown[]) => unknown>();
|
|
244
|
+
|
|
245
|
+
function markWrapped<T extends object>(wrapper: T, original: unknown): T {
|
|
246
|
+
(wrapper as Record<symbol, unknown>)[WRAPPED] = original;
|
|
247
|
+
return wrapper;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Small readers. Each takes whatever Mastra or a provider handed us and
|
|
252
|
+
// returns a value the schema accepts, or undefined — never throws.
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
function errorOf(error: unknown): { type: string; message: string } {
|
|
256
|
+
if (error instanceof Error) return { type: error.name || "Error", message: error.message };
|
|
257
|
+
if (typeof error === "object" && error !== null) {
|
|
258
|
+
const value = error as { name?: unknown; message?: unknown };
|
|
259
|
+
if (typeof value.message === "string") {
|
|
260
|
+
return { type: typeof value.name === "string" ? value.name : "Error", message: value.message };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return { type: typeof error, message: String(error) };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const describeError = (error: unknown): string => {
|
|
267
|
+
const detail = errorOf(error);
|
|
268
|
+
return `${detail.type}: ${detail.message}`;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
function asInt(value: unknown): number | undefined {
|
|
272
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
273
|
+
? Math.round(value)
|
|
274
|
+
: undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* A token count in any of the provider shapes: a number (V1 `promptTokens`,
|
|
279
|
+
* V2 `inputTokens`) or V3's `{ total, … }` breakdown.
|
|
280
|
+
*/
|
|
281
|
+
function tokenCount(value: unknown): number | undefined {
|
|
282
|
+
if (typeof value === "object" && value !== null) return asInt((value as { total?: unknown }).total);
|
|
283
|
+
return asInt(value);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function usageOf(usage: unknown): { inputTokens?: number; outputTokens?: number } {
|
|
287
|
+
if (typeof usage !== "object" || usage === null) return {};
|
|
288
|
+
const value = usage as Record<string, unknown>;
|
|
289
|
+
return {
|
|
290
|
+
inputTokens: tokenCount(value.inputTokens ?? value.promptTokens),
|
|
291
|
+
outputTokens: tokenCount(value.outputTokens ?? value.completionTokens),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** V1/V2 finish reasons are strings; V3's are `{ unified, raw }`. */
|
|
296
|
+
function finishReasonOf(value: unknown): string | undefined {
|
|
297
|
+
if (typeof value === "string" && value) return value;
|
|
298
|
+
if (typeof value === "object" && value !== null) {
|
|
299
|
+
const { unified, raw } = value as { unified?: unknown; raw?: unknown };
|
|
300
|
+
if (typeof unified === "string" && unified) return unified;
|
|
301
|
+
if (typeof raw === "string" && raw) return raw;
|
|
302
|
+
}
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function agentLabel(instance: unknown): { label: string; rawId?: string } {
|
|
307
|
+
const value = instance as { name?: unknown; id?: unknown } | undefined;
|
|
308
|
+
const raw = typeof value?.name === "string" && value.name ? value.name : value?.id;
|
|
309
|
+
const label = core.normalizeAgentId(raw, "mastra-agent");
|
|
310
|
+
const rawId = typeof value?.id === "string" && value.id !== label ? value.id : undefined;
|
|
311
|
+
return { label, rawId };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The run's goal: the prompt, when the caller handed one over as text. */
|
|
315
|
+
function goalOf(input: unknown): string | undefined {
|
|
316
|
+
if (typeof input === "string") return input;
|
|
317
|
+
if (!Array.isArray(input)) return undefined;
|
|
318
|
+
for (let i = input.length - 1; i >= 0; i -= 1) {
|
|
319
|
+
const item = input[i] as unknown;
|
|
320
|
+
if (typeof item === "string") return item;
|
|
321
|
+
const message = item as { role?: unknown; content?: unknown } | null;
|
|
322
|
+
if (message?.role === "user" && typeof message.content === "string") return message.content;
|
|
323
|
+
}
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** One `LanguageModel` prompt part, reduced to what a reader needs. */
|
|
328
|
+
function partToWire(part: unknown): unknown {
|
|
329
|
+
const value = (part ?? {}) as Record<string, unknown>;
|
|
330
|
+
switch (value.type) {
|
|
331
|
+
case "text":
|
|
332
|
+
return { type: "text", text: value.text };
|
|
333
|
+
case "tool-call":
|
|
334
|
+
return { type: "tool_call", id: value.toolCallId, name: value.toolName, input: value.input ?? value.args };
|
|
335
|
+
case "tool-result":
|
|
336
|
+
return { type: "tool_result", id: value.toolCallId, name: value.toolName, output: value.output ?? value.result };
|
|
337
|
+
default:
|
|
338
|
+
return { type: value.type };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function contentToWire(content: unknown): unknown {
|
|
343
|
+
if (!Array.isArray(content)) return content;
|
|
344
|
+
if (content.every((part) => (part as { type?: unknown } | null)?.type === "text")) {
|
|
345
|
+
return content.map((part) => (part as { text?: unknown }).text).join("");
|
|
346
|
+
}
|
|
347
|
+
return content.map(partToWire);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* A `LanguageModel` call's prompt as `{messages, system}`.
|
|
352
|
+
*
|
|
353
|
+
* The provider-level prompt is what the model actually receives — the agent's
|
|
354
|
+
* instructions as a system message, memory, the tool results of earlier steps
|
|
355
|
+
* — so it is recorded rather than what the caller passed to `generate()`.
|
|
356
|
+
*/
|
|
357
|
+
function promptOf(options: unknown): {
|
|
358
|
+
messages?: Array<Record<string, unknown>>;
|
|
359
|
+
system?: unknown;
|
|
360
|
+
tools?: Array<Record<string, unknown>>;
|
|
361
|
+
} {
|
|
362
|
+
const value = (options ?? {}) as { prompt?: unknown; tools?: unknown; mode?: { tools?: unknown } };
|
|
363
|
+
const out: ReturnType<typeof promptOf> = {};
|
|
364
|
+
if (Array.isArray(value.prompt)) {
|
|
365
|
+
const system: unknown[] = [];
|
|
366
|
+
const messages: Array<Record<string, unknown>> = [];
|
|
367
|
+
for (const item of value.prompt) {
|
|
368
|
+
const message = (item ?? {}) as { role?: unknown; content?: unknown };
|
|
369
|
+
if (message.role === "system") system.push(message.content);
|
|
370
|
+
else messages.push({ role: message.role, content: contentToWire(message.content) });
|
|
371
|
+
}
|
|
372
|
+
out.messages = messages;
|
|
373
|
+
if (system.length > 0) out.system = system.length === 1 ? system[0] : system;
|
|
374
|
+
}
|
|
375
|
+
const tools = Array.isArray(value.tools) ? value.tools : value.mode?.tools;
|
|
376
|
+
if (Array.isArray(tools) && tools.length > 0) {
|
|
377
|
+
out.tools = tools.map((tool) => {
|
|
378
|
+
const entry = (tool ?? {}) as { name?: unknown; description?: unknown };
|
|
379
|
+
return { name: entry.name, description: entry.description };
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return out;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ---------------------------------------------------------------------------
|
|
386
|
+
// Model steps
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
interface ModelCall extends Span {
|
|
390
|
+
requestId: string;
|
|
391
|
+
runKey: string;
|
|
392
|
+
model?: string;
|
|
393
|
+
started: number;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const openModels = new OpenSpans<ModelCall>();
|
|
397
|
+
|
|
398
|
+
interface ModelOutcome {
|
|
399
|
+
model?: string;
|
|
400
|
+
finishReason?: string;
|
|
401
|
+
usage?: unknown;
|
|
402
|
+
text?: string;
|
|
403
|
+
toolCalls?: Array<Record<string, unknown>>;
|
|
404
|
+
error?: unknown;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function modelIdOf(model: object): string | undefined {
|
|
408
|
+
const id = (model as { modelId?: unknown }).modelId;
|
|
409
|
+
return typeof id === "string" && id ? id : undefined;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function beginModelCall(frame: Frame, model: object, options: unknown): ModelCall | undefined {
|
|
413
|
+
const t = frame.tracker;
|
|
414
|
+
if (!live(t)) return undefined;
|
|
415
|
+
const runKey = frame.key;
|
|
416
|
+
const requestId = randomUUID();
|
|
417
|
+
const modelId = modelIdOf(model);
|
|
418
|
+
const provider = (model as { provider?: unknown }).provider;
|
|
419
|
+
t.emit("modelRequest", requestId, {
|
|
420
|
+
parentKey: runKey,
|
|
421
|
+
model: modelId,
|
|
422
|
+
...promptOf(options),
|
|
423
|
+
requestId,
|
|
424
|
+
...core.fwFields({ provider: typeof provider === "string" ? provider : undefined }),
|
|
425
|
+
});
|
|
426
|
+
const call: ModelCall = { tracker: t, closed: false, requestId, runKey, model: modelId, started: Date.now() };
|
|
427
|
+
openModels.add(call);
|
|
428
|
+
return call;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function endModelCall(call: ModelCall, outcome: ModelOutcome): void {
|
|
432
|
+
// Already closed — by teardown, when its installation is gone.
|
|
433
|
+
if (!openModels.close(call)) return;
|
|
434
|
+
const usage = usageOf(outcome.usage);
|
|
435
|
+
call.tracker.emit("modelResponse", call.requestId, {
|
|
436
|
+
parentKey: call.runKey,
|
|
437
|
+
model: outcome.model ?? call.model,
|
|
438
|
+
stopReason: outcome.finishReason ?? (outcome.error !== undefined ? "error" : undefined),
|
|
439
|
+
inputTokens: usage.inputTokens,
|
|
440
|
+
outputTokens: usage.outputTokens,
|
|
441
|
+
content: outcome.text ? outcome.text : undefined,
|
|
442
|
+
role: "assistant",
|
|
443
|
+
requestId: call.requestId,
|
|
444
|
+
error: outcome.error === undefined ? undefined : describeError(outcome.error),
|
|
445
|
+
...core.fwFields({
|
|
446
|
+
duration_ms: core.ms(Date.now() - call.started),
|
|
447
|
+
tool_calls: outcome.toolCalls?.length ? outcome.toolCalls : undefined,
|
|
448
|
+
}),
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** What a `doGenerate` result says, in any of the V1–V3 shapes. */
|
|
453
|
+
function generateOutcome(result: unknown): ModelOutcome {
|
|
454
|
+
const value = (result ?? {}) as {
|
|
455
|
+
content?: unknown;
|
|
456
|
+
text?: unknown;
|
|
457
|
+
toolCalls?: unknown;
|
|
458
|
+
finishReason?: unknown;
|
|
459
|
+
usage?: unknown;
|
|
460
|
+
response?: { modelId?: unknown };
|
|
461
|
+
};
|
|
462
|
+
let text = typeof value.text === "string" ? value.text : "";
|
|
463
|
+
const toolCalls: Array<Record<string, unknown>> = [];
|
|
464
|
+
if (Array.isArray(value.content)) {
|
|
465
|
+
for (const part of value.content) {
|
|
466
|
+
const entry = (part ?? {}) as Record<string, unknown>;
|
|
467
|
+
if (entry.type === "text" && typeof entry.text === "string") text += entry.text;
|
|
468
|
+
if (entry.type === "tool-call") {
|
|
469
|
+
toolCalls.push({ id: entry.toolCallId, name: entry.toolName, input: entry.input ?? entry.args });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (Array.isArray(value.toolCalls)) {
|
|
474
|
+
for (const call of value.toolCalls) {
|
|
475
|
+
const entry = (call ?? {}) as Record<string, unknown>;
|
|
476
|
+
toolCalls.push({ id: entry.toolCallId, name: entry.toolName, input: entry.args ?? entry.input });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
model: typeof value.response?.modelId === "string" ? value.response.modelId : undefined,
|
|
481
|
+
finishReason: finishReasonOf(value.finishReason),
|
|
482
|
+
usage: value.usage,
|
|
483
|
+
text,
|
|
484
|
+
toolCalls,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Fold one `doStream` part into the step's outcome. The stream is the only
|
|
490
|
+
* place a streamed step's usage and finish reason exist, so they are read as
|
|
491
|
+
* they pass rather than from Mastra's aggregate afterwards.
|
|
492
|
+
*/
|
|
493
|
+
function foldStreamPart(outcome: ModelOutcome, part: unknown): void {
|
|
494
|
+
const value = (part ?? {}) as Record<string, unknown>;
|
|
495
|
+
switch (value.type) {
|
|
496
|
+
case "text-delta": {
|
|
497
|
+
const delta = value.delta ?? value.textDelta;
|
|
498
|
+
if (typeof delta === "string") outcome.text = (outcome.text ?? "") + delta;
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
case "tool-call":
|
|
502
|
+
(outcome.toolCalls ??= []).push({
|
|
503
|
+
id: value.toolCallId,
|
|
504
|
+
name: value.toolName,
|
|
505
|
+
input: value.input ?? value.args,
|
|
506
|
+
});
|
|
507
|
+
break;
|
|
508
|
+
case "response-metadata":
|
|
509
|
+
if (typeof value.modelId === "string" && value.modelId) outcome.model = value.modelId;
|
|
510
|
+
break;
|
|
511
|
+
case "finish":
|
|
512
|
+
outcome.finishReason = finishReasonOf(value.finishReason);
|
|
513
|
+
outcome.usage = value.usage;
|
|
514
|
+
break;
|
|
515
|
+
case "error":
|
|
516
|
+
outcome.error = value.error;
|
|
517
|
+
break;
|
|
518
|
+
default:
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
type ModelMethod = (options: unknown) => PromiseLike<unknown>;
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* One observed `doGenerate` / `doStream` call: a step.
|
|
527
|
+
*
|
|
528
|
+
* The result decides how the step ends, not the method name. A provider's
|
|
529
|
+
* `doGenerate` answers with the finished content, but the model Mastra 0.x
|
|
530
|
+
* resolves is its own V5 wrapper, whose `doGenerate` ALSO answers with a
|
|
531
|
+
* `stream` — reading the result as a finished generation there records a step
|
|
532
|
+
* with no tokens and no stop reason. So a result carrying a readable stream is
|
|
533
|
+
* observed as one, whichever method produced it.
|
|
534
|
+
*/
|
|
535
|
+
function observedCall(target: object, original: ModelMethod, frame: Frame, name: string): ModelMethod {
|
|
536
|
+
const observed = async function (options: unknown): Promise<unknown> {
|
|
537
|
+
// Built under an installation that is gone: the model, untouched.
|
|
538
|
+
if (!live(frame.tracker)) return original.call(target, options);
|
|
539
|
+
const call = core.callSafely(beginModelCall, [frame, target, options], `${NAME}.model`);
|
|
540
|
+
let result: unknown;
|
|
541
|
+
try {
|
|
542
|
+
result = await original.call(target, options);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
if (call) core.callSafely(endModelCall, [call, { error }], `${NAME}.model`);
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
if (!call) return result;
|
|
548
|
+
const stream = (result as { stream?: unknown } | null)?.stream;
|
|
549
|
+
if (typeof (stream as ReadableStream | undefined)?.getReader !== "function") {
|
|
550
|
+
core.callSafely(
|
|
551
|
+
(value: unknown) => endModelCall(call, generateOutcome(value)),
|
|
552
|
+
[result],
|
|
553
|
+
`${NAME}.model`,
|
|
554
|
+
);
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
const outcome: ModelOutcome = {};
|
|
558
|
+
return {
|
|
559
|
+
...(result as object),
|
|
560
|
+
// Pull-based, so a consumer that cancels still closes the step (see
|
|
561
|
+
// core.observeStream). A cancel ends it with the cancel reason as its error.
|
|
562
|
+
stream: core.observeStream(
|
|
563
|
+
stream as ReadableStream<unknown>,
|
|
564
|
+
(part) => foldStreamPart(outcome, part),
|
|
565
|
+
(error) => endModelCall(call, error === undefined ? outcome : { ...outcome, error: outcome.error ?? error }),
|
|
566
|
+
`${NAME}.stream`,
|
|
567
|
+
),
|
|
568
|
+
};
|
|
569
|
+
};
|
|
570
|
+
Object.defineProperty(observed, "name", { value: name, configurable: true });
|
|
571
|
+
return observed;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Hand a resolved model back behind a proxy that observes its two call
|
|
576
|
+
* methods and is otherwise the model.
|
|
577
|
+
*
|
|
578
|
+
* Every other method is bound to the real model, and every property read with
|
|
579
|
+
* the real model as receiver: a provider class that keeps state in `#private`
|
|
580
|
+
* fields throws when one of its methods runs with a proxy as `this`.
|
|
581
|
+
*/
|
|
582
|
+
function observeModel(model: unknown, frame: Frame): unknown {
|
|
583
|
+
if (typeof model !== "object" || model === null) return model;
|
|
584
|
+
// One of ours from an earlier run: observe the model it wraps, for THIS run.
|
|
585
|
+
const target = modelProxies.get(model) ?? model;
|
|
586
|
+
const doGenerate = (target as { doGenerate?: unknown }).doGenerate;
|
|
587
|
+
const doStream = (target as { doStream?: unknown }).doStream;
|
|
588
|
+
if (typeof doGenerate !== "function" && typeof doStream !== "function") return model;
|
|
589
|
+
if ((target as Record<symbol, unknown>)[WRAPPED] !== undefined) return model;
|
|
590
|
+
|
|
591
|
+
const cache = new Map<PropertyKey, unknown>();
|
|
592
|
+
const proxy = new Proxy(target, {
|
|
593
|
+
get(object, property) {
|
|
594
|
+
if (property === WRAPPED) return target;
|
|
595
|
+
const value: unknown = Reflect.get(object, property);
|
|
596
|
+
if (typeof value !== "function" || property === "constructor") return value;
|
|
597
|
+
let bound = cache.get(property);
|
|
598
|
+
if (bound === undefined || (bound as { [WRAPPED]?: unknown })[WRAPPED] !== value) {
|
|
599
|
+
const method = value as ModelMethod;
|
|
600
|
+
bound =
|
|
601
|
+
property === "doGenerate" || property === "doStream"
|
|
602
|
+
? observedCall(object, method, frame, property)
|
|
603
|
+
: (method as (...args: unknown[]) => unknown).bind(object);
|
|
604
|
+
markWrapped(bound as object, value);
|
|
605
|
+
cache.set(property, bound);
|
|
606
|
+
}
|
|
607
|
+
return bound;
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
modelProxies.set(proxy, target);
|
|
611
|
+
return proxy;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ---------------------------------------------------------------------------
|
|
615
|
+
// Tools
|
|
616
|
+
// ---------------------------------------------------------------------------
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* A tool call that returned a failure instead of throwing it.
|
|
620
|
+
*
|
|
621
|
+
* Mastra 0.x catches a tool's exception and hands the model a `MastraError`
|
|
622
|
+
* (1.x re-throws it); both lines return a `{ error: true, message }` object
|
|
623
|
+
* for input that fails validation. Either way the call failed, and a
|
|
624
|
+
* `tool_result` recording it as output would say otherwise.
|
|
625
|
+
*/
|
|
626
|
+
function failureOf(value: unknown): unknown {
|
|
627
|
+
if (value instanceof Error) return value;
|
|
628
|
+
if (typeof value === "object" && value !== null) {
|
|
629
|
+
const flagged = value as { error?: unknown; message?: unknown };
|
|
630
|
+
if (flagged.error === true && typeof flagged.message === "string") return flagged;
|
|
631
|
+
}
|
|
632
|
+
return undefined;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function inputRecord(input: unknown): Record<string, unknown> | undefined {
|
|
636
|
+
if (input === undefined || input === null) return undefined;
|
|
637
|
+
if (typeof input === "object" && !Array.isArray(input)) return input as Record<string, unknown>;
|
|
638
|
+
return { input };
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
interface ToolCall extends Span {
|
|
642
|
+
key: string;
|
|
643
|
+
toolName: string;
|
|
644
|
+
toolCallId: string;
|
|
645
|
+
parentKey?: string;
|
|
646
|
+
/** Set when the call opened its own root run, which it must close. */
|
|
647
|
+
ownRun?: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const openTools = new OpenSpans<ToolCall>();
|
|
651
|
+
|
|
652
|
+
function beginToolCall(
|
|
653
|
+
t: core.RunTracker,
|
|
654
|
+
toolName: string,
|
|
655
|
+
toolCallId: string,
|
|
656
|
+
input: unknown,
|
|
657
|
+
parentKey: string | undefined,
|
|
658
|
+
): ToolCall | undefined {
|
|
659
|
+
if (!live(t)) return undefined;
|
|
660
|
+
let ownRun: string | undefined;
|
|
661
|
+
if (parentKey === undefined && currentIdentity().sessionId === null) {
|
|
662
|
+
// Nothing is running and no scope is open: a tool called on its own is its
|
|
663
|
+
// own run, exactly as a bare LangChain tool is, rather than an event
|
|
664
|
+
// dropped for want of a session.
|
|
665
|
+
ownRun = randomUUID();
|
|
666
|
+
t.startAgent(ownRun, { agentId: core.normalizeAgentId(toolName, "tool"), ...core.fwFields({ kind: "tool" }) });
|
|
667
|
+
parentKey = ownRun;
|
|
668
|
+
}
|
|
669
|
+
const key = `${parentKey ?? "ambient"}:tool:${toolCallId}:${randomUUID()}`;
|
|
670
|
+
t.emit("toolUse", key, { parentKey, toolName, toolCallId, input: inputRecord(input) });
|
|
671
|
+
const call: ToolCall = { tracker: t, closed: false, key, toolName, toolCallId, parentKey, ownRun };
|
|
672
|
+
openTools.add(call);
|
|
673
|
+
return call;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function endToolCall(call: ToolCall, output: unknown, error?: unknown): void {
|
|
677
|
+
// Already closed — by teardown, when its installation is gone.
|
|
678
|
+
if (!openTools.close(call)) return;
|
|
679
|
+
const t = call.tracker;
|
|
680
|
+
const failure = error ?? failureOf(output);
|
|
681
|
+
t.emit("toolResult", call.key, {
|
|
682
|
+
parentKey: call.parentKey,
|
|
683
|
+
toolName: call.toolName,
|
|
684
|
+
toolCallId: call.toolCallId,
|
|
685
|
+
output: failure === undefined ? output : undefined,
|
|
686
|
+
error: failure === undefined ? undefined : describeError(failure),
|
|
687
|
+
});
|
|
688
|
+
if (call.ownRun !== undefined) {
|
|
689
|
+
t.endAgent(call.ownRun, { outcome: failure === undefined ? "success" : "failed" });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Run `execute` bracketed by a tool span; the one `try` only re-throws.
|
|
695
|
+
* `frame` is what the tool's own body runs inside, when there is one.
|
|
696
|
+
*/
|
|
697
|
+
function runTool(
|
|
698
|
+
begin: () => ToolCall | undefined,
|
|
699
|
+
execute: () => unknown,
|
|
700
|
+
frame: Frame | undefined,
|
|
701
|
+
): unknown {
|
|
702
|
+
const call = core.callSafely(begin, [], `${NAME}.tool`);
|
|
703
|
+
if (!call) return execute();
|
|
704
|
+
const end = (output: unknown, error?: unknown): void => {
|
|
705
|
+
core.callSafely(endToolCall, [call, output, error], `${NAME}.tool`);
|
|
706
|
+
};
|
|
707
|
+
let result: unknown;
|
|
708
|
+
try {
|
|
709
|
+
result = frame ? frames.run(frame, execute) : execute();
|
|
710
|
+
} catch (error) {
|
|
711
|
+
end(undefined, error);
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
714
|
+
if (typeof (result as PromiseLike<unknown> | null)?.then === "function") {
|
|
715
|
+
return (result as PromiseLike<unknown>).then(
|
|
716
|
+
(value) => {
|
|
717
|
+
end(value);
|
|
718
|
+
return value;
|
|
719
|
+
},
|
|
720
|
+
(error: unknown) => {
|
|
721
|
+
end(undefined, error);
|
|
722
|
+
throw error;
|
|
723
|
+
},
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
end(result);
|
|
727
|
+
return result;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** A copy of `tool` with a new `execute`, keeping its prototype and fields. */
|
|
731
|
+
function withExecute<T extends object>(tool: T, execute: unknown): T {
|
|
732
|
+
return Object.assign(Object.create(Object.getPrototypeOf(tool) as object | null) as T, tool, { execute });
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Wrap the tools one agent run is about to execute.
|
|
737
|
+
*
|
|
738
|
+
* These are the loop's converted tools, which all share the AI SDK signature
|
|
739
|
+
* `execute(input, { toolCallId, … })` whatever kind of tool they came from, on
|
|
740
|
+
* both major lines — so the input is the validated arguments and the id is the
|
|
741
|
+
* model's own.
|
|
742
|
+
*/
|
|
743
|
+
function observeTools(tools: unknown, runFrame: Frame, agent: object): unknown {
|
|
744
|
+
if (typeof tools !== "object" || tools === null) return tools;
|
|
745
|
+
const out: Record<string, unknown> = { ...(tools as Record<string, unknown>) };
|
|
746
|
+
const frame: Frame = { tracker: runFrame.tracker, key: runFrame.key, owner: agent, kind: "tool" };
|
|
747
|
+
for (const [name, tool] of Object.entries(out)) {
|
|
748
|
+
const execute = (tool as { execute?: unknown } | null)?.execute;
|
|
749
|
+
if (typeof tool !== "object" || tool === null || typeof execute !== "function") continue;
|
|
750
|
+
// One of ours from an earlier run: wrap what it wraps, for THIS run.
|
|
751
|
+
const ours = toolWrappers.get(execute);
|
|
752
|
+
if (ours === undefined && (execute as unknown as Record<symbol, unknown>)[WRAPPED] !== undefined) continue;
|
|
753
|
+
const original = ours ?? (execute as (...args: unknown[]) => unknown);
|
|
754
|
+
const wrapped = function failproofaiToolExecute(this: unknown, ...args: unknown[]): unknown {
|
|
755
|
+
// Built under an installation that is gone: the tool, untouched.
|
|
756
|
+
if (!live(frame.tracker)) return original.apply(this, args);
|
|
757
|
+
const options = args[1] as { toolCallId?: unknown } | undefined;
|
|
758
|
+
const toolCallId = typeof options?.toolCallId === "string" ? options.toolCallId : randomUUID();
|
|
759
|
+
return runTool(
|
|
760
|
+
() => beginToolCall(frame.tracker, name, toolCallId, args[0], frame.key),
|
|
761
|
+
() => original.apply(this, args),
|
|
762
|
+
frame,
|
|
763
|
+
);
|
|
764
|
+
};
|
|
765
|
+
toolWrappers.set(wrapped, original);
|
|
766
|
+
out[name] = withExecute(tool, markWrapped(wrapped, original));
|
|
767
|
+
}
|
|
768
|
+
return out;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* The input and call id of a direct `tool.execute(...)` call, in either
|
|
773
|
+
* major's signature: 1.x `execute(input, { agent: { toolCallId } })`, 0.x
|
|
774
|
+
* `execute({ context: input, runtimeContext, … }, { toolCallId })`.
|
|
775
|
+
*/
|
|
776
|
+
function toolCallArgs(args: unknown[]): { input: unknown; toolCallId?: string } {
|
|
777
|
+
const [first, second] = args as [unknown, Record<string, unknown> | undefined];
|
|
778
|
+
const pick = (...values: unknown[]): string | undefined =>
|
|
779
|
+
values.find((value): value is string => typeof value === "string" && value !== "");
|
|
780
|
+
const legacy =
|
|
781
|
+
typeof first === "object" &&
|
|
782
|
+
first !== null &&
|
|
783
|
+
"context" in first &&
|
|
784
|
+
("runtimeContext" in first || second === undefined || typeof second.toolCallId === "string");
|
|
785
|
+
if (legacy) {
|
|
786
|
+
const envelope = first as { context?: unknown; toolCallId?: unknown };
|
|
787
|
+
return { input: envelope.context, toolCallId: pick(second?.toolCallId, envelope.toolCallId) };
|
|
788
|
+
}
|
|
789
|
+
const agentContext = second?.agent as { toolCallId?: unknown } | undefined;
|
|
790
|
+
return { input: first, toolCallId: pick(agentContext?.toolCallId, second?.toolCallId) };
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Wrap one Mastra tool's `execute` so a DIRECT call is recorded.
|
|
795
|
+
*
|
|
796
|
+
* A tool an agent executes is already recorded by `instrument()` — including
|
|
797
|
+
* tools built before it ran — so this is for the calls no agent makes: a tool
|
|
798
|
+
* invoked from your own code or a workflow step. Inside a Mastra run or a
|
|
799
|
+
* `failproofai` scope it records under that; with nothing open it records the
|
|
800
|
+
* call as its own run, named after the tool. A wrapped tool handed to an agent
|
|
801
|
+
* is not recorded twice.
|
|
802
|
+
*/
|
|
803
|
+
export function wrapTool<T extends { id?: string; execute?: (...args: never[]) => unknown }>(
|
|
804
|
+
tool: T,
|
|
805
|
+
): T {
|
|
806
|
+
const execute = tool.execute;
|
|
807
|
+
if (typeof execute !== "function" || core.isWrapped(execute)) return tool;
|
|
808
|
+
const toolName = typeof tool.id === "string" && tool.id ? tool.id : "tool";
|
|
809
|
+
const original = execute as unknown as (...args: unknown[]) => unknown;
|
|
810
|
+
|
|
811
|
+
const wrapped = function failproofaiToolExecute(this: unknown, ...args: unknown[]): unknown {
|
|
812
|
+
const frame = frames.getStore();
|
|
813
|
+
// The agent's own tool wrapper is already recording this very call; and
|
|
814
|
+
// inside a run whose installation is gone, nothing records.
|
|
815
|
+
if (frame?.kind === "tool" || (frame !== undefined && !live(frame.tracker))) {
|
|
816
|
+
return original.apply(this, args);
|
|
817
|
+
}
|
|
818
|
+
return runTool(
|
|
819
|
+
() => {
|
|
820
|
+
const { input, toolCallId } = toolCallArgs(args);
|
|
821
|
+
const t = frame?.tracker ?? (enabled && tracker !== null ? tracker : (standalone ??= newTracker()));
|
|
822
|
+
return beginToolCall(t, toolName, toolCallId ?? randomUUID(), input, frame?.key);
|
|
823
|
+
},
|
|
824
|
+
() => original.apply(this, args),
|
|
825
|
+
undefined,
|
|
826
|
+
);
|
|
827
|
+
};
|
|
828
|
+
// A new object rather than a mutation: a Mastra tool may be frozen, and
|
|
829
|
+
// assigning to a frozen object fails silently in sloppy mode. The prototype
|
|
830
|
+
// is kept so it is still a `Tool` to Mastra's own checks.
|
|
831
|
+
return withExecute(tool, markWrapped(wrapped, original));
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// ---------------------------------------------------------------------------
|
|
835
|
+
// Agents
|
|
836
|
+
// ---------------------------------------------------------------------------
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* How an agent method is called: `generate` resolves with the finished run,
|
|
840
|
+
* `stream` resolves as soon as the loop starts and finishes as the caller
|
|
841
|
+
* consumes it, and `network` resolves with a stream that runs on its own.
|
|
842
|
+
*/
|
|
843
|
+
type AgentCallMode = "generate" | "stream" | "network";
|
|
844
|
+
|
|
845
|
+
interface AgentRun {
|
|
846
|
+
tracker: core.RunTracker;
|
|
847
|
+
key: string;
|
|
848
|
+
frame: Frame;
|
|
849
|
+
args: unknown[];
|
|
850
|
+
started: number;
|
|
851
|
+
ended: boolean;
|
|
852
|
+
/**
|
|
853
|
+
* Set when this call is recorded as part of an enclosing span rather than as
|
|
854
|
+
* a span of its own — a network's router — so it never emits `agent_end`.
|
|
855
|
+
*/
|
|
856
|
+
borrowed?: boolean;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function endAgentRun(run: AgentRun, outcome: string): void {
|
|
860
|
+
if (run.ended) return;
|
|
861
|
+
run.ended = true;
|
|
862
|
+
if (run.borrowed) return;
|
|
863
|
+
// Closed `cancelled` already, by the teardown that made it not live.
|
|
864
|
+
if (!live(run.tracker)) return;
|
|
865
|
+
run.tracker.endAgent(run.key, {
|
|
866
|
+
outcome,
|
|
867
|
+
...core.fwFields({ duration_ms: core.ms(Date.now() - run.started) }),
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/** How a finished `generate()` / `onFinish` result ended. */
|
|
872
|
+
function outcomeOf(result: unknown): string {
|
|
873
|
+
const value = (result ?? {}) as { error?: unknown; finishReason?: unknown; tripwire?: unknown };
|
|
874
|
+
if (value.tripwire) return "rejected";
|
|
875
|
+
if (value.error || value.finishReason === "error") return "failed";
|
|
876
|
+
return "success";
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Compose our end-of-stream callbacks with the caller's.
|
|
881
|
+
*
|
|
882
|
+
* Mastra calls `onFinish` / `onError` / `onAbort` when the stream actually
|
|
883
|
+
* finishes — as it is consumed — which is the moment the run ends. Reading one
|
|
884
|
+
* of the output's promise getters instead would START consumption: Mastra
|
|
885
|
+
* drains the stream into a buffer the first time one is touched, so observing
|
|
886
|
+
* the run would change what it does.
|
|
887
|
+
*/
|
|
888
|
+
function withStreamCallbacks(options: unknown, run: AgentRun): Record<string, unknown> {
|
|
889
|
+
const base = (typeof options === "object" && options !== null ? options : {}) as Record<string, unknown>;
|
|
890
|
+
const chain =
|
|
891
|
+
(name: string, ours: (...args: unknown[]) => void) =>
|
|
892
|
+
(...args: unknown[]): unknown => {
|
|
893
|
+
core.callSafely(ours, args, `${NAME}.${name}`);
|
|
894
|
+
const theirs = base[name];
|
|
895
|
+
return typeof theirs === "function" ? (theirs as (...a: unknown[]) => unknown).apply(base, args) : undefined;
|
|
896
|
+
};
|
|
897
|
+
return {
|
|
898
|
+
...base,
|
|
899
|
+
onFinish: chain("onFinish", (result) => endAgentRun(run, outcomeOf(result))),
|
|
900
|
+
onError: chain("onError", () => endAgentRun(run, "failed")),
|
|
901
|
+
onAbort: chain("onAbort", () => endAgentRun(run, "cancelled")),
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Close a streamed run that no callback will close.
|
|
907
|
+
*
|
|
908
|
+
* A stream a processor blocks — an input processor's `abort()`, or one on the
|
|
909
|
+
* output stream — ends with a `tripwire` chunk and calls none of `onFinish` /
|
|
910
|
+
* `onError` / `onAbort` (1.x and 0.x alike), so without this its agent stayed
|
|
911
|
+
* open forever. The output's `_waitUntilFinished()` settles when the stream
|
|
912
|
+
* does, without starting consumption the way its promise getters would; a run
|
|
913
|
+
* the callbacks already closed makes this a no-op.
|
|
914
|
+
*/
|
|
915
|
+
function closeWhenFinished(run: AgentRun, output: unknown): void {
|
|
916
|
+
const wait = (output as { _waitUntilFinished?: unknown } | null)?._waitUntilFinished;
|
|
917
|
+
if (typeof wait !== "function") return;
|
|
918
|
+
const waiting = (wait as () => unknown).call(output);
|
|
919
|
+
const finish = (): void => {
|
|
920
|
+
endAgentRun(run, (output as { tripwire?: unknown }).tripwire ? "rejected" : "success");
|
|
921
|
+
};
|
|
922
|
+
Promise.resolve(waiting).then(
|
|
923
|
+
() => core.callSafely(finish, [], `${NAME}.stream`),
|
|
924
|
+
() => undefined,
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Close a network run when its stream does.
|
|
930
|
+
*
|
|
931
|
+
* `network()` resolves with a stream that runs by itself — its workflow starts
|
|
932
|
+
* as the stream is constructed and does not wait on the reader — and whose
|
|
933
|
+
* `status` getter settles once it has finished, without consuming anything.
|
|
934
|
+
*/
|
|
935
|
+
function closeNetworkWhenFinished(run: AgentRun, stream: unknown): void {
|
|
936
|
+
const status = (stream as { status?: unknown } | null)?.status;
|
|
937
|
+
if (typeof (status as PromiseLike<unknown> | undefined)?.then !== "function") {
|
|
938
|
+
endAgentRun(run, "success");
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const site = `${NAME}.network`;
|
|
942
|
+
Promise.resolve(status).then(
|
|
943
|
+
(value) => core.callSafely(endAgentRun, [run, workflowOutcome(value)], site),
|
|
944
|
+
() => core.callSafely(endAgentRun, [run, "failed"], site),
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const nonEmpty = (value: unknown): string | undefined =>
|
|
949
|
+
typeof value === "string" && value !== "" ? value : undefined;
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* The conversation a call belongs to: its memory thread and resource, in the
|
|
953
|
+
* 1.x shape (`memory: { thread, resource }`, the thread a string or `{ id }`)
|
|
954
|
+
* or 0.x's older top-level `threadId` / `resourceId`.
|
|
955
|
+
*/
|
|
956
|
+
function conversationOf(options: unknown): { thread?: string; resource?: string } {
|
|
957
|
+
const value = (typeof options === "object" && options !== null ? options : {}) as {
|
|
958
|
+
threadId?: unknown;
|
|
959
|
+
resourceId?: unknown;
|
|
960
|
+
memory?: unknown;
|
|
961
|
+
};
|
|
962
|
+
const memory = (typeof value.memory === "object" && value.memory !== null ? value.memory : {}) as {
|
|
963
|
+
thread?: unknown;
|
|
964
|
+
resource?: unknown;
|
|
965
|
+
};
|
|
966
|
+
const threadObject = (typeof memory.thread === "object" && memory.thread !== null ? memory.thread : {}) as {
|
|
967
|
+
id?: unknown;
|
|
968
|
+
};
|
|
969
|
+
return {
|
|
970
|
+
thread: nonEmpty(memory.thread) ?? nonEmpty(threadObject.id) ?? nonEmpty(value.threadId),
|
|
971
|
+
resource: nonEmpty(memory.resource) ?? nonEmpty(value.resourceId),
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* The agent an `agent.network()` builds for itself to route with.
|
|
977
|
+
*
|
|
978
|
+
* Every routing decision and completion check of a network is a fresh
|
|
979
|
+
* `routing-agent` running on the network agent's own model. It IS the
|
|
980
|
+
* network's decision loop, so its model calls are recorded as the network
|
|
981
|
+
* agent's own steps rather than as one more agent per decision.
|
|
982
|
+
*/
|
|
983
|
+
function isRoutingAgent(agent: object): boolean {
|
|
984
|
+
const { id, name } = agent as { id?: unknown; name?: unknown };
|
|
985
|
+
return id === "routing-agent" || name === "routing-agent";
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Whether a run is a root nothing has put in a session yet — then the
|
|
990
|
+
* conversation id the framework gives it is its session. An enclosing
|
|
991
|
+
* `failproofai.session()` / `agent()` scope always wins.
|
|
992
|
+
*/
|
|
993
|
+
function isUnscopedRoot(parent: Frame | undefined): boolean {
|
|
994
|
+
return parent === undefined && currentIdentity().sessionId === null;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function beginAgentRun(agent: object, method: string, args: unknown[], mode: AgentCallMode): AgentRun | undefined {
|
|
998
|
+
const t = tracker;
|
|
999
|
+
if (!enabled || t === null) return undefined;
|
|
1000
|
+
const parent = frames.getStore();
|
|
1001
|
+
// Inside a run whose installation is gone: that whole tree is unrecorded.
|
|
1002
|
+
if (parent !== undefined && !live(parent.tracker)) return undefined;
|
|
1003
|
+
// Re-entry from inside the same run — 0.x's `generate` is `stream` under the
|
|
1004
|
+
// hood — is the same agent span, not a nested one.
|
|
1005
|
+
if (parent?.kind === "agent" && parent.owner === agent) return undefined;
|
|
1006
|
+
if (parent?.network === true && isRoutingAgent(agent)) {
|
|
1007
|
+
// The network's own router: recorded on the network's span, not its own.
|
|
1008
|
+
return {
|
|
1009
|
+
tracker: t,
|
|
1010
|
+
key: parent.key,
|
|
1011
|
+
frame: { tracker: t, key: parent.key, owner: agent, kind: "agent" },
|
|
1012
|
+
args,
|
|
1013
|
+
started: Date.now(),
|
|
1014
|
+
ended: false,
|
|
1015
|
+
borrowed: true,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
const key = randomUUID();
|
|
1019
|
+
const { label, rawId } = agentLabel(agent);
|
|
1020
|
+
const { thread, resource } = conversationOf(args[1]);
|
|
1021
|
+
t.startAgent(key, {
|
|
1022
|
+
agentId: label,
|
|
1023
|
+
parentKey: parent?.key,
|
|
1024
|
+
// A memory thread is a conversation that spans runs: the session, for a
|
|
1025
|
+
// run nothing else has put in one.
|
|
1026
|
+
sessionId: isUnscopedRoot(parent) ? thread : undefined,
|
|
1027
|
+
goal: goalOf(args[0]),
|
|
1028
|
+
...core.fwFields({
|
|
1029
|
+
agent_id: rawId,
|
|
1030
|
+
method,
|
|
1031
|
+
streaming: mode === "stream" || undefined,
|
|
1032
|
+
thread_id: thread,
|
|
1033
|
+
resource_id: resource,
|
|
1034
|
+
}),
|
|
1035
|
+
});
|
|
1036
|
+
const run: AgentRun = {
|
|
1037
|
+
tracker: t,
|
|
1038
|
+
key,
|
|
1039
|
+
frame: { tracker: t, key, owner: agent, kind: "agent", network: mode === "network" || undefined },
|
|
1040
|
+
args,
|
|
1041
|
+
started: Date.now(),
|
|
1042
|
+
ended: false,
|
|
1043
|
+
};
|
|
1044
|
+
run.frame.close = (outcome: string): void => endAgentRun(run, outcome);
|
|
1045
|
+
if (mode === "stream") {
|
|
1046
|
+
const next = [...args];
|
|
1047
|
+
while (next.length < 2) next.push(undefined);
|
|
1048
|
+
next[1] = withStreamCallbacks(args[1], run);
|
|
1049
|
+
run.args = next;
|
|
1050
|
+
}
|
|
1051
|
+
return run;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function patchAgentMethod(prototype: object, method: string, mode: AgentCallMode): boolean {
|
|
1055
|
+
const original = (prototype as Record<string, unknown>)[method];
|
|
1056
|
+
if (typeof original !== "function" || core.isWrapped(original)) return false;
|
|
1057
|
+
const fn = original as (...args: unknown[]) => unknown;
|
|
1058
|
+
const site = `${NAME}.${method}`;
|
|
1059
|
+
|
|
1060
|
+
const wrapper = function failproofaiAgentCall(this: object, ...args: unknown[]): unknown {
|
|
1061
|
+
const run = core.callSafely(beginAgentRun, [this, method, args, mode], site);
|
|
1062
|
+
if (!run) return fn.apply(this, args);
|
|
1063
|
+
const settle = (value: unknown): void => {
|
|
1064
|
+
if (run.borrowed) return;
|
|
1065
|
+
// A stream ends through its callbacks — `stream()` returning only means
|
|
1066
|
+
// the loop has started — or, when none will fire, as it finishes.
|
|
1067
|
+
if (mode === "stream") core.callSafely(closeWhenFinished, [run, value], site);
|
|
1068
|
+
else if (mode === "network") core.callSafely(closeNetworkWhenFinished, [run, value], site);
|
|
1069
|
+
else core.callSafely(endAgentRun, [run, outcomeOf(value)], site);
|
|
1070
|
+
};
|
|
1071
|
+
let result: unknown;
|
|
1072
|
+
try {
|
|
1073
|
+
result = frames.run(run.frame, () => fn.apply(this, run.args));
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
core.callSafely(endAgentRun, [run, "failed"], site);
|
|
1076
|
+
throw error;
|
|
1077
|
+
}
|
|
1078
|
+
if (typeof (result as PromiseLike<unknown> | null)?.then === "function") {
|
|
1079
|
+
return (result as PromiseLike<unknown>).then(
|
|
1080
|
+
(value) => {
|
|
1081
|
+
settle(value);
|
|
1082
|
+
return value;
|
|
1083
|
+
},
|
|
1084
|
+
(error: unknown) => {
|
|
1085
|
+
core.callSafely(endAgentRun, [run, "failed"], site);
|
|
1086
|
+
throw error;
|
|
1087
|
+
},
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
settle(result);
|
|
1091
|
+
return result;
|
|
1092
|
+
};
|
|
1093
|
+
Object.defineProperty(wrapper, "name", { value: fn.name, configurable: true });
|
|
1094
|
+
return patcher!.patch(prototype, method, markWrapped(wrapper, original));
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* End a run the moment an input processor blocks its prompt.
|
|
1099
|
+
*
|
|
1100
|
+
* `__runInputProcessors` is where every input processor's `abort()` lands, and
|
|
1101
|
+
* its result says so (`tripwire` in 1.x, `tripwireTriggered` in 0.x). A
|
|
1102
|
+
* blocked `generate()` resolves with the tripwire and ends there anyway, but a
|
|
1103
|
+
* blocked `stream()` calls none of its callbacks — and 0.x's output offers no
|
|
1104
|
+
* other end signal — so without this its agent stayed open for good. Nothing
|
|
1105
|
+
* the run would have done follows a blocked prompt, so it ends here.
|
|
1106
|
+
*/
|
|
1107
|
+
function patchInputProcessors(prototype: object): void {
|
|
1108
|
+
const method = "__runInputProcessors";
|
|
1109
|
+
const original = (prototype as Record<string, unknown>)[method];
|
|
1110
|
+
if (typeof original !== "function" || core.isWrapped(original)) return;
|
|
1111
|
+
const fn = original as (...args: unknown[]) => unknown;
|
|
1112
|
+
const site = `${NAME}.inputProcessors`;
|
|
1113
|
+
const blocked = (value: unknown): boolean => {
|
|
1114
|
+
const result = (value ?? {}) as { tripwire?: unknown; tripwireTriggered?: unknown };
|
|
1115
|
+
return result.tripwireTriggered === true || (result.tripwire !== undefined && result.tripwire !== null && result.tripwire !== false);
|
|
1116
|
+
};
|
|
1117
|
+
const wrapper = function failproofaiInputProcessors(this: object, ...args: unknown[]): unknown {
|
|
1118
|
+
const frame = frames.getStore();
|
|
1119
|
+
const result = fn.apply(this, args);
|
|
1120
|
+
const close = frame?.kind === "agent" && frame.owner === this ? frame.close : undefined;
|
|
1121
|
+
if (close === undefined) return result;
|
|
1122
|
+
const check = (value: unknown): void => {
|
|
1123
|
+
if (blocked(value)) close("rejected");
|
|
1124
|
+
};
|
|
1125
|
+
if (typeof (result as PromiseLike<unknown> | null)?.then === "function") {
|
|
1126
|
+
return (result as PromiseLike<unknown>).then((value) => {
|
|
1127
|
+
core.callSafely(check, [value], site);
|
|
1128
|
+
return value;
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
core.callSafely(check, [result], site);
|
|
1132
|
+
return result;
|
|
1133
|
+
};
|
|
1134
|
+
Object.defineProperty(wrapper, "name", { value: fn.name, configurable: true });
|
|
1135
|
+
patcher!.patch(prototype, method, markWrapped(wrapper, original));
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Patch a method that BUILDS something the run will use later, so the result
|
|
1140
|
+
* is observed with the run that built it. `observe` sees only calls made by
|
|
1141
|
+
* the agent's own run; any other caller gets Mastra's value untouched.
|
|
1142
|
+
*/
|
|
1143
|
+
function patchBuilder(
|
|
1144
|
+
prototype: object,
|
|
1145
|
+
method: string,
|
|
1146
|
+
observe: (value: unknown, frame: Frame, agent: object) => unknown,
|
|
1147
|
+
): boolean {
|
|
1148
|
+
const original = (prototype as Record<string, unknown>)[method];
|
|
1149
|
+
if (typeof original !== "function" || core.isWrapped(original)) return false;
|
|
1150
|
+
const fn = original as (...args: unknown[]) => unknown;
|
|
1151
|
+
const site = `${NAME}.${method}`;
|
|
1152
|
+
const wrapper = function failproofaiBuilder(this: object, ...args: unknown[]): unknown {
|
|
1153
|
+
const frame = frames.getStore();
|
|
1154
|
+
const result = fn.apply(this, args);
|
|
1155
|
+
if (frame?.kind !== "agent" || frame.owner !== this || !live(frame.tracker)) return result;
|
|
1156
|
+
const apply = (value: unknown): unknown => {
|
|
1157
|
+
const observed = core.callSafely(observe, [value, frame, this], site);
|
|
1158
|
+
return observed === undefined ? value : observed;
|
|
1159
|
+
};
|
|
1160
|
+
return typeof (result as PromiseLike<unknown> | null)?.then === "function"
|
|
1161
|
+
? (result as PromiseLike<unknown>).then(apply)
|
|
1162
|
+
: apply(result);
|
|
1163
|
+
};
|
|
1164
|
+
Object.defineProperty(wrapper, "name", { value: fn.name, configurable: true });
|
|
1165
|
+
return patcher!.patch(prototype, method, markWrapped(wrapper, original));
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// ---------------------------------------------------------------------------
|
|
1169
|
+
// Workflows
|
|
1170
|
+
// ---------------------------------------------------------------------------
|
|
1171
|
+
|
|
1172
|
+
interface WorkflowRunLike {
|
|
1173
|
+
workflowId?: unknown;
|
|
1174
|
+
runId?: unknown;
|
|
1175
|
+
isInternalWorkflow?: unknown;
|
|
1176
|
+
tracingPolicy?: { internal?: unknown };
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/** `InternalSpans.WORKFLOW` — Mastra's own bit for "this workflow is plumbing". */
|
|
1180
|
+
const INTERNAL_WORKFLOW = 1;
|
|
1181
|
+
|
|
1182
|
+
function isInternalRun(run: WorkflowRunLike): boolean {
|
|
1183
|
+
if (run.isInternalWorkflow === true) return true;
|
|
1184
|
+
const internal = run.tracingPolicy?.internal;
|
|
1185
|
+
return typeof internal === "number" && (internal & INTERNAL_WORKFLOW) !== 0;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/** A workflow's result status as an `agent_end` outcome. */
|
|
1189
|
+
function workflowOutcome(status: unknown): string {
|
|
1190
|
+
switch (status) {
|
|
1191
|
+
case "success":
|
|
1192
|
+
return "success";
|
|
1193
|
+
case "failed":
|
|
1194
|
+
return "failed";
|
|
1195
|
+
case "canceled":
|
|
1196
|
+
case "cancelled":
|
|
1197
|
+
return "cancelled";
|
|
1198
|
+
case "tripwire":
|
|
1199
|
+
return "rejected";
|
|
1200
|
+
default:
|
|
1201
|
+
return typeof status === "string" && status ? status : "success";
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
interface WorkflowSpan {
|
|
1206
|
+
tracker: core.RunTracker;
|
|
1207
|
+
key: string;
|
|
1208
|
+
frame: Frame;
|
|
1209
|
+
runId?: string;
|
|
1210
|
+
started: number;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* The workflows `agent.network()` is built from. 1.x marks them internal
|
|
1215
|
+
* (`tracingPolicy.internal`); 0.x marks them nothing, so inside a network run
|
|
1216
|
+
* they are recognised by id — anywhere else a workflow of that name is the
|
|
1217
|
+
* user's and recorded as usual.
|
|
1218
|
+
*/
|
|
1219
|
+
const NETWORK_WORKFLOWS: ReadonlySet<unknown> = new Set([
|
|
1220
|
+
"agent-loop-main-workflow",
|
|
1221
|
+
"Agent-Network-Outer-Workflow",
|
|
1222
|
+
"iteration-with-validation",
|
|
1223
|
+
]);
|
|
1224
|
+
|
|
1225
|
+
// ---------------------------------------------------------------------------
|
|
1226
|
+
// Suspend / resume (human in the loop)
|
|
1227
|
+
// ---------------------------------------------------------------------------
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* A workflow run that suspended and waits to be resumed.
|
|
1231
|
+
*
|
|
1232
|
+
* A suspended run deliberately keeps its agent open, as a LangGraph interrupt
|
|
1233
|
+
* does: closing it would close the pause with it, zeroing the one interval
|
|
1234
|
+
* that measures how long the human took. The resume continues the SAME span.
|
|
1235
|
+
* A resume that lands in another process — the ordinary deployment shape, one
|
|
1236
|
+
* worker suspends and whichever picks up the approval resumes from storage —
|
|
1237
|
+
* finds nothing here, starts a new span in the same session (a root workflow's
|
|
1238
|
+
* session is its run id) and closes the pause by its deterministic id.
|
|
1239
|
+
*/
|
|
1240
|
+
interface PausedRun {
|
|
1241
|
+
tracker: core.RunTracker;
|
|
1242
|
+
key: string;
|
|
1243
|
+
started: number;
|
|
1244
|
+
/** Open pause id -> the step path it waits on. */
|
|
1245
|
+
pauses: Map<string, string>;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/** Mastra workflow run id -> its paused span. */
|
|
1249
|
+
const pausedRuns = new Map<string, PausedRun>();
|
|
1250
|
+
|
|
1251
|
+
function rememberPaused(runId: string, paused: PausedRun): void {
|
|
1252
|
+
pausedRuns.delete(runId);
|
|
1253
|
+
while (pausedRuns.size >= MAX_OPEN) {
|
|
1254
|
+
const oldest = pausedRuns.entries().next();
|
|
1255
|
+
if (oldest.done) break;
|
|
1256
|
+
pausedRuns.delete(oldest.value[0]);
|
|
1257
|
+
// Never closed from here: the resume may still come, in any process.
|
|
1258
|
+
oldest.value[1].tracker.forget(oldest.value[1].key);
|
|
1259
|
+
}
|
|
1260
|
+
pausedRuns.set(runId, paused);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/** A suspend payload or resume answer as the text a human read or wrote. */
|
|
1264
|
+
function humanText(value: unknown): string | undefined {
|
|
1265
|
+
if (value === undefined || value === null) return undefined;
|
|
1266
|
+
if (typeof value === "string") return value;
|
|
1267
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
1268
|
+
const { prompt, question, message } = value as Record<string, unknown>;
|
|
1269
|
+
const text = prompt ?? question ?? message;
|
|
1270
|
+
if (typeof text === "string") return text;
|
|
1271
|
+
}
|
|
1272
|
+
try {
|
|
1273
|
+
return JSON.stringify(value);
|
|
1274
|
+
} catch {
|
|
1275
|
+
return undefined;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/** The steps a `suspended` result waits on: their path, and what each asked. */
|
|
1280
|
+
function suspendedStepsOf(result: unknown): Array<{ path: string; payload: unknown }> {
|
|
1281
|
+
const value = (result ?? {}) as {
|
|
1282
|
+
suspended?: unknown;
|
|
1283
|
+
steps?: Record<string, { status?: unknown; suspendPayload?: unknown } | undefined>;
|
|
1284
|
+
suspendPayload?: Record<string, unknown>;
|
|
1285
|
+
};
|
|
1286
|
+
const payloadOf = (id: string): unknown => value.steps?.[id]?.suspendPayload ?? value.suspendPayload?.[id];
|
|
1287
|
+
const out: Array<{ path: string; payload: unknown }> = [];
|
|
1288
|
+
if (Array.isArray(value.suspended)) {
|
|
1289
|
+
for (const entry of value.suspended) {
|
|
1290
|
+
const ids = (Array.isArray(entry) ? entry : [entry]).filter(
|
|
1291
|
+
(id): id is string => typeof id === "string" && id !== "",
|
|
1292
|
+
);
|
|
1293
|
+
if (ids.length > 0) out.push({ path: ids.join("."), payload: payloadOf(ids[0]!) });
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (out.length === 0 && typeof value.steps === "object" && value.steps !== null) {
|
|
1297
|
+
for (const [id, step] of Object.entries(value.steps)) {
|
|
1298
|
+
if (step?.status === "suspended") out.push({ path: id, payload: step.suspendPayload });
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return out;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* The step a `run.resume({ step, resumeData })` resumes, as a path: a step id,
|
|
1306
|
+
* a `Step`, or an array of either for a step inside a nested workflow.
|
|
1307
|
+
*/
|
|
1308
|
+
function resumeTargetOf(params: unknown): { path?: string; answer: unknown } {
|
|
1309
|
+
const value = (params ?? {}) as { step?: unknown; resumeData?: unknown };
|
|
1310
|
+
const ids = (Array.isArray(value.step) ? value.step : [value.step])
|
|
1311
|
+
.map((step: unknown) => (typeof step === "string" ? step : (step as { id?: unknown } | null)?.id))
|
|
1312
|
+
.filter((id): id is string => typeof id === "string" && id !== "");
|
|
1313
|
+
return { path: ids.length > 0 ? ids.join(".") : undefined, answer: value.resumeData };
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const pauseIdOf = (runId: string, path: string): string => `${runId}:${path}`;
|
|
1317
|
+
|
|
1318
|
+
/** `human_wait` + `agent_pause` for each step newly suspended, in that order. */
|
|
1319
|
+
function suspendRun(span: WorkflowSpan, runId: string, result: unknown): void {
|
|
1320
|
+
const existing = pausedRuns.get(runId);
|
|
1321
|
+
const paused: PausedRun =
|
|
1322
|
+
existing !== undefined && existing.key === span.key
|
|
1323
|
+
? existing
|
|
1324
|
+
: { tracker: span.tracker, key: span.key, started: span.started, pauses: new Map() };
|
|
1325
|
+
for (const { path, payload } of suspendedStepsOf(result)) {
|
|
1326
|
+
const pauseId = pauseIdOf(runId, path);
|
|
1327
|
+
if (paused.pauses.has(pauseId)) continue;
|
|
1328
|
+
paused.pauses.set(pauseId, path);
|
|
1329
|
+
const marker = core.fwFields({ step_id: path, workflow_run_id: runId });
|
|
1330
|
+
span.tracker.emit("humanWait", span.key, {
|
|
1331
|
+
inputId: pauseId,
|
|
1332
|
+
prompt: humanText(payload),
|
|
1333
|
+
reason: "mastra_suspend",
|
|
1334
|
+
...marker,
|
|
1335
|
+
});
|
|
1336
|
+
span.tracker.emit("agentPause", span.key, { pauseId, reason: "mastra_suspend", ...marker });
|
|
1337
|
+
}
|
|
1338
|
+
rememberPaused(runId, paused);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* `agent_resume` + `human_input` for the pauses a resume answers: the step it
|
|
1343
|
+
* names (and anything nested under it), or every open pause when it names
|
|
1344
|
+
* none. With no pause open in this process, the one the named step must have
|
|
1345
|
+
* opened elsewhere — the same id, since it is derived from the run and step.
|
|
1346
|
+
*/
|
|
1347
|
+
function resumeRun(span: WorkflowSpan, runId: string, params: unknown, paused: PausedRun | undefined): void {
|
|
1348
|
+
const { path, answer } = resumeTargetOf(params);
|
|
1349
|
+
const response = humanText(answer);
|
|
1350
|
+
const emit = (pauseId: string, stepPath: string, elsewhere: boolean): void => {
|
|
1351
|
+
const marker = core.fwFields({
|
|
1352
|
+
step_id: stepPath,
|
|
1353
|
+
workflow_run_id: runId,
|
|
1354
|
+
resumed_elsewhere: elsewhere || undefined,
|
|
1355
|
+
});
|
|
1356
|
+
span.tracker.emit("agentResume", span.key, { pauseId, reason: "mastra_resume", ...marker });
|
|
1357
|
+
span.tracker.emit("humanInput", span.key, { inputId: pauseId, response, ...marker });
|
|
1358
|
+
};
|
|
1359
|
+
if (paused === undefined) {
|
|
1360
|
+
if (path !== undefined) emit(pauseIdOf(runId, path), path, true);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
for (const [pauseId, stepPath] of [...paused.pauses]) {
|
|
1364
|
+
if (path !== undefined && stepPath !== path && !stepPath.startsWith(`${path}.`)) continue;
|
|
1365
|
+
paused.pauses.delete(pauseId);
|
|
1366
|
+
emit(pauseId, stepPath, false);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function patchRunMethod(prototype: object, method: string): boolean {
|
|
1371
|
+
const original = (prototype as Record<string, unknown>)[method];
|
|
1372
|
+
if (typeof original !== "function" || core.isWrapped(original)) return false;
|
|
1373
|
+
const fn = original as (...args: unknown[]) => unknown;
|
|
1374
|
+
const site = `${NAME}.workflow`;
|
|
1375
|
+
const resuming = method === "_resume";
|
|
1376
|
+
|
|
1377
|
+
const begin = (run: WorkflowRunLike & object, args: unknown[]): WorkflowSpan | undefined => {
|
|
1378
|
+
const t = tracker;
|
|
1379
|
+
if (!enabled || t === null || isInternalRun(run)) return undefined;
|
|
1380
|
+
const parent = frames.getStore();
|
|
1381
|
+
if (parent !== undefined && !live(parent.tracker)) return undefined;
|
|
1382
|
+
if (parent?.kind === "workflow" && parent.owner === run) return undefined;
|
|
1383
|
+
// A network's own machinery (0.x marks it nothing): its agents land on
|
|
1384
|
+
// the network's span.
|
|
1385
|
+
if (parent?.network === true && NETWORK_WORKFLOWS.has(run.workflowId)) return undefined;
|
|
1386
|
+
const runId = typeof run.runId === "string" ? run.runId : undefined;
|
|
1387
|
+
|
|
1388
|
+
const paused = resuming && runId !== undefined ? pausedRuns.get(runId) : undefined;
|
|
1389
|
+
if (paused !== undefined && paused.tracker === t && t.isOpen(paused.key)) {
|
|
1390
|
+
// Resuming a run this process suspended: the same span, continued.
|
|
1391
|
+
const span: WorkflowSpan = {
|
|
1392
|
+
tracker: t,
|
|
1393
|
+
key: paused.key,
|
|
1394
|
+
frame: { tracker: t, key: paused.key, owner: run, kind: "workflow" },
|
|
1395
|
+
runId,
|
|
1396
|
+
started: paused.started,
|
|
1397
|
+
};
|
|
1398
|
+
workflowRuns.set(runId!, paused.key);
|
|
1399
|
+
resumeRun(span, runId!, args[0], paused);
|
|
1400
|
+
return span;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
const key = randomUUID();
|
|
1404
|
+
t.startAgent(key, {
|
|
1405
|
+
agentId: core.normalizeAgentId(run.workflowId, "workflow"),
|
|
1406
|
+
parentKey: parent?.key,
|
|
1407
|
+
// A root run's session is its run id — the one thing its resume, in
|
|
1408
|
+
// this process or another, is sure to share with it.
|
|
1409
|
+
sessionId: isUnscopedRoot(parent) ? runId : undefined,
|
|
1410
|
+
...core.fwFields({ kind: "workflow", workflow_run_id: runId, method: method.replace(/^_/, "") }),
|
|
1411
|
+
});
|
|
1412
|
+
const span: WorkflowSpan = {
|
|
1413
|
+
tracker: t,
|
|
1414
|
+
key,
|
|
1415
|
+
frame: { tracker: t, key, owner: run, kind: "workflow" },
|
|
1416
|
+
runId,
|
|
1417
|
+
started: Date.now(),
|
|
1418
|
+
};
|
|
1419
|
+
if (runId !== undefined) {
|
|
1420
|
+
workflowRuns.set(runId, key);
|
|
1421
|
+
if (resuming) resumeRun(span, runId, args[0], undefined);
|
|
1422
|
+
}
|
|
1423
|
+
return span;
|
|
1424
|
+
};
|
|
1425
|
+
const end = (span: WorkflowSpan, value: unknown, failed = false): void => {
|
|
1426
|
+
if (span.runId !== undefined && workflowRuns.get(span.runId) === span.key) workflowRuns.delete(span.runId);
|
|
1427
|
+
// Closed `cancelled` already, by the teardown that made it not live.
|
|
1428
|
+
if (!live(span.tracker)) return;
|
|
1429
|
+
const status = failed ? "failed" : (value as { status?: unknown } | null)?.status;
|
|
1430
|
+
if (status === "suspended" && span.runId !== undefined) {
|
|
1431
|
+
suspendRun(span, span.runId, value);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
if (span.runId !== undefined && pausedRuns.get(span.runId)?.key === span.key) pausedRuns.delete(span.runId);
|
|
1435
|
+
span.tracker.endAgent(span.key, {
|
|
1436
|
+
outcome: workflowOutcome(status),
|
|
1437
|
+
...core.fwFields({ duration_ms: core.ms(Date.now() - span.started) }),
|
|
1438
|
+
});
|
|
1439
|
+
};
|
|
1440
|
+
|
|
1441
|
+
const wrapper = function failproofaiWorkflowRun(this: WorkflowRunLike & object, ...args: unknown[]): unknown {
|
|
1442
|
+
const span = core.callSafely(begin, [this, args], site);
|
|
1443
|
+
if (!span) return fn.apply(this, args);
|
|
1444
|
+
let result: unknown;
|
|
1445
|
+
try {
|
|
1446
|
+
result = frames.run(span.frame, () => fn.apply(this, args));
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
core.callSafely(end, [span, undefined, true], site);
|
|
1449
|
+
throw error;
|
|
1450
|
+
}
|
|
1451
|
+
if (typeof (result as PromiseLike<unknown> | null)?.then === "function") {
|
|
1452
|
+
return (result as PromiseLike<unknown>).then(
|
|
1453
|
+
(value) => {
|
|
1454
|
+
core.callSafely(end, [span, value], site);
|
|
1455
|
+
return value;
|
|
1456
|
+
},
|
|
1457
|
+
(error: unknown) => {
|
|
1458
|
+
core.callSafely(end, [span, undefined, true], site);
|
|
1459
|
+
throw error;
|
|
1460
|
+
},
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
core.callSafely(end, [span, result], site);
|
|
1464
|
+
return result;
|
|
1465
|
+
};
|
|
1466
|
+
Object.defineProperty(wrapper, "name", { value: fn.name, configurable: true });
|
|
1467
|
+
return patcher!.patch(prototype, method, markWrapped(wrapper, original));
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
let stepSequence = 0;
|
|
1471
|
+
|
|
1472
|
+
interface StepSpan extends Span {
|
|
1473
|
+
runKey: string;
|
|
1474
|
+
hookName: string;
|
|
1475
|
+
hookId: string;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
const openSteps = new OpenSpans<StepSpan>();
|
|
1479
|
+
|
|
1480
|
+
function beginStep(params: unknown): StepSpan | undefined {
|
|
1481
|
+
const t = tracker;
|
|
1482
|
+
if (!enabled || t === null) return undefined;
|
|
1483
|
+
const value = (params ?? {}) as {
|
|
1484
|
+
step?: { id?: unknown };
|
|
1485
|
+
runId?: unknown;
|
|
1486
|
+
executionContext?: { runId?: unknown };
|
|
1487
|
+
prevOutput?: unknown;
|
|
1488
|
+
};
|
|
1489
|
+
const runId = value.executionContext?.runId ?? value.runId;
|
|
1490
|
+
const runKey = typeof runId === "string" ? workflowRuns.get(runId) : undefined;
|
|
1491
|
+
const hookName = value.step?.id;
|
|
1492
|
+
if (runKey === undefined || typeof hookName !== "string" || !hookName) return undefined;
|
|
1493
|
+
// Only a step of the recorded run ITSELF. An agent that runs inside it
|
|
1494
|
+
// executes its own loop as internal workflows, and one called with the
|
|
1495
|
+
// run's id — as 0.x's network calls its sub-agents — hands those internal
|
|
1496
|
+
// steps the same run id; they execute in the agent's frame, not the run's.
|
|
1497
|
+
const frame = frames.getStore();
|
|
1498
|
+
if (frame?.kind !== "workflow" || frame.key !== runKey) return undefined;
|
|
1499
|
+
stepSequence += 1;
|
|
1500
|
+
const hookId = `${runKey}:${hookName}:${stepSequence}`;
|
|
1501
|
+
t.emit("hookTriggered", runKey, {
|
|
1502
|
+
hookName,
|
|
1503
|
+
hookId,
|
|
1504
|
+
triggerEvent: "workflow_step",
|
|
1505
|
+
input: value.prevOutput,
|
|
1506
|
+
});
|
|
1507
|
+
const span: StepSpan = { tracker: t, closed: false, runKey, hookName, hookId };
|
|
1508
|
+
openSteps.add(span);
|
|
1509
|
+
return span;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
interface StepResult {
|
|
1513
|
+
status?: unknown;
|
|
1514
|
+
output?: unknown;
|
|
1515
|
+
error?: unknown;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* The step result inside what `executeStep` resolved with: `{ result: {…} }`
|
|
1520
|
+
* from 0.24 on, the bare `{ status, output, error }` before that.
|
|
1521
|
+
*/
|
|
1522
|
+
function stepResultOf(value: unknown): StepResult {
|
|
1523
|
+
if (typeof value !== "object" || value === null) return {};
|
|
1524
|
+
const wrapped = (value as { result?: unknown }).result;
|
|
1525
|
+
if (typeof wrapped === "object" && wrapped !== null && "status" in wrapped) return wrapped;
|
|
1526
|
+
return value;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* A failed step does not throw out of `executeStep` — Mastra catches it and
|
|
1531
|
+
* resolves with `status: "failed"` — so the failure is read off the result.
|
|
1532
|
+
* 0.x hands the error over as a rendered string with its stack attached; only
|
|
1533
|
+
* the first line is the error.
|
|
1534
|
+
*/
|
|
1535
|
+
function stepOutcome(value: unknown, thrown?: unknown): { outcome: string; output?: unknown; error?: string } {
|
|
1536
|
+
const result = stepResultOf(value);
|
|
1537
|
+
const error = thrown ?? (result.status === "failed" ? (result.error ?? "step failed") : undefined);
|
|
1538
|
+
if (error !== undefined) {
|
|
1539
|
+
return { outcome: "failed", error: typeof error === "string" ? error.split("\n")[0] : describeError(error) };
|
|
1540
|
+
}
|
|
1541
|
+
return {
|
|
1542
|
+
outcome: typeof result.status === "string" && result.status ? workflowOutcome(result.status) : "success",
|
|
1543
|
+
output: result.output,
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function endStep(span: StepSpan, value: unknown, thrown?: unknown): void {
|
|
1548
|
+
// Already closed — by teardown, when its installation is gone.
|
|
1549
|
+
if (!openSteps.close(span)) return;
|
|
1550
|
+
span.tracker.emit("hookCompleted", span.runKey, {
|
|
1551
|
+
hookName: span.hookName,
|
|
1552
|
+
hookId: span.hookId,
|
|
1553
|
+
...stepOutcome(value, thrown),
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function patchExecuteStep(prototype: object): boolean {
|
|
1558
|
+
const original = (prototype as Record<string, unknown>).executeStep;
|
|
1559
|
+
if (typeof original !== "function" || core.isWrapped(original)) return false;
|
|
1560
|
+
const fn = original as (...args: unknown[]) => unknown;
|
|
1561
|
+
const site = `${NAME}.step`;
|
|
1562
|
+
const wrapper = function failproofaiStep(this: unknown, ...args: unknown[]): unknown {
|
|
1563
|
+
const span = core.callSafely(beginStep, [args[0]], site);
|
|
1564
|
+
if (!span) return fn.apply(this, args);
|
|
1565
|
+
let result: unknown;
|
|
1566
|
+
try {
|
|
1567
|
+
result = fn.apply(this, args);
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
core.callSafely(endStep, [span, undefined, error], site);
|
|
1570
|
+
throw error;
|
|
1571
|
+
}
|
|
1572
|
+
if (typeof (result as PromiseLike<unknown> | null)?.then === "function") {
|
|
1573
|
+
return (result as PromiseLike<unknown>).then(
|
|
1574
|
+
(value) => {
|
|
1575
|
+
core.callSafely(endStep, [span, value], site);
|
|
1576
|
+
return value;
|
|
1577
|
+
},
|
|
1578
|
+
(error: unknown) => {
|
|
1579
|
+
core.callSafely(endStep, [span, undefined, error], site);
|
|
1580
|
+
throw error;
|
|
1581
|
+
},
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
core.callSafely(endStep, [span, result], site);
|
|
1585
|
+
return result;
|
|
1586
|
+
};
|
|
1587
|
+
Object.defineProperty(wrapper, "name", { value: fn.name, configurable: true });
|
|
1588
|
+
return patcher!.patch(prototype, "executeStep", markWrapped(wrapper, original));
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* Run `body` inside an agent span named for a workflow.
|
|
1593
|
+
*
|
|
1594
|
+
* Mastra workflow runs are recorded automatically once `instrument("mastra")`
|
|
1595
|
+
* has run — the run as an agent, its steps as hooks — so this is not needed
|
|
1596
|
+
* for them. It stays for grouping work that is not a Mastra workflow (or runs
|
|
1597
|
+
* where the adapter is not installed) under one named span.
|
|
1598
|
+
*/
|
|
1599
|
+
export function workflow<T>(workflowName: string, body: () => T): T {
|
|
1600
|
+
return agentScope(core.normalizeAgentId(workflowName, "workflow"), { fw_kind: "workflow" }, body);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// ---------------------------------------------------------------------------
|
|
1604
|
+
// Install
|
|
1605
|
+
// ---------------------------------------------------------------------------
|
|
1606
|
+
|
|
1607
|
+
type ClassLike = { prototype: object };
|
|
1608
|
+
|
|
1609
|
+
const AGENT_METHODS = [
|
|
1610
|
+
["generate", "generate"],
|
|
1611
|
+
["stream", "stream"],
|
|
1612
|
+
["generateVNext", "generate"],
|
|
1613
|
+
["streamVNext", "stream"],
|
|
1614
|
+
["generateLegacy", "generate"],
|
|
1615
|
+
["streamLegacy", "stream"],
|
|
1616
|
+
["network", "network"],
|
|
1617
|
+
] as const;
|
|
1618
|
+
|
|
1619
|
+
function installAgent(Agent: ClassLike): number {
|
|
1620
|
+
let patched = 0;
|
|
1621
|
+
for (const [method, mode] of AGENT_METHODS) {
|
|
1622
|
+
if (patchAgentMethod(Agent.prototype, method, mode)) patched += 1;
|
|
1623
|
+
}
|
|
1624
|
+
const proto = Agent.prototype as Record<string, unknown>;
|
|
1625
|
+
if (compat.probe(NAME, "Agent.resolveModelConfig", () => typeof proto.resolveModelConfig === "function")) {
|
|
1626
|
+
patchBuilder(Agent.prototype, "resolveModelConfig", (model, frame) => observeModel(model, frame));
|
|
1627
|
+
}
|
|
1628
|
+
if (compat.probe(NAME, "Agent.convertTools", () => typeof proto.convertTools === "function")) {
|
|
1629
|
+
patchBuilder(Agent.prototype, "convertTools", (tools, frame, agent) => observeTools(tools, frame, agent));
|
|
1630
|
+
}
|
|
1631
|
+
// Optional, and silent when absent: without it a blocked stream is closed
|
|
1632
|
+
// by its finish (1.x) or at teardown (0.x), rather than never recorded.
|
|
1633
|
+
patchInputProcessors(Agent.prototype);
|
|
1634
|
+
return patched;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
function installWorkflows(module: unknown): void {
|
|
1638
|
+
const { Run, DefaultExecutionEngine } = (module ?? {}) as {
|
|
1639
|
+
Run?: ClassLike;
|
|
1640
|
+
DefaultExecutionEngine?: ClassLike;
|
|
1641
|
+
};
|
|
1642
|
+
if (compat.probe(NAME, "workflows Run._start", () => typeof (Run?.prototype as Record<string, unknown> | undefined)?._start === "function")) {
|
|
1643
|
+
for (const method of ["_start", "_resume", "_restart", "_timeTravel"]) patchRunMethod(Run!.prototype, method);
|
|
1644
|
+
}
|
|
1645
|
+
if (
|
|
1646
|
+
compat.probe(
|
|
1647
|
+
NAME,
|
|
1648
|
+
"workflows DefaultExecutionEngine.executeStep",
|
|
1649
|
+
() => typeof (DefaultExecutionEngine?.prototype as Record<string, unknown> | undefined)?.executeStep === "function",
|
|
1650
|
+
)
|
|
1651
|
+
) {
|
|
1652
|
+
patchExecuteStep(DefaultExecutionEngine!.prototype);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
export const adapter: Adapter = {
|
|
1657
|
+
name: NAME,
|
|
1658
|
+
|
|
1659
|
+
async install(options: Record<string, unknown> = {}): Promise<void> {
|
|
1660
|
+
compat.checkVersion(NAME, PACKAGE, {
|
|
1661
|
+
minimum: MINIMUM,
|
|
1662
|
+
below: "2.0.0",
|
|
1663
|
+
reason: "model steps are observed at resolveModelConfig and workflow runs at Run._start",
|
|
1664
|
+
});
|
|
1665
|
+
const agentCopies = await compat.requireModuleCopies("@mastra/core/agent", INSTALL);
|
|
1666
|
+
// Workflows are optional: an app with no workflows is ordinary, and a
|
|
1667
|
+
// workflow module that fails to load must not cost the agent patches below.
|
|
1668
|
+
const workflowCopies = await compat
|
|
1669
|
+
.requireModuleCopies("@mastra/core/workflows", INSTALL)
|
|
1670
|
+
.catch(() => [] as unknown[]);
|
|
1671
|
+
|
|
1672
|
+
// A fresh tracker per installation, never one left over: whatever an
|
|
1673
|
+
// earlier installation built stays bound to ITS tracker, which is dead.
|
|
1674
|
+
teardown();
|
|
1675
|
+
tracker = newTracker(options);
|
|
1676
|
+
patcher = new core.Patcher();
|
|
1677
|
+
|
|
1678
|
+
let patched = 0;
|
|
1679
|
+
for (const copy of agentCopies) {
|
|
1680
|
+
const Agent = (copy as { Agent?: unknown }).Agent;
|
|
1681
|
+
if (typeof Agent === "function") patched += installAgent(Agent);
|
|
1682
|
+
}
|
|
1683
|
+
if (patched === 0) {
|
|
1684
|
+
teardown();
|
|
1685
|
+
throw new Error(
|
|
1686
|
+
"could not patch any Agent generate/stream method — this build of @mastra/core " +
|
|
1687
|
+
"exposes none of them under a writable name.",
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
for (const copy of workflowCopies) installWorkflows(copy);
|
|
1691
|
+
enabled = true;
|
|
1692
|
+
},
|
|
1693
|
+
|
|
1694
|
+
uninstall(): void {
|
|
1695
|
+
teardown();
|
|
1696
|
+
},
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
/**
|
|
1700
|
+
* Stop recording and close what is still open, on the tracker it opened on.
|
|
1701
|
+
* The switch goes FIRST: nothing may begin a span while the rest are closed.
|
|
1702
|
+
*/
|
|
1703
|
+
function teardown(): void {
|
|
1704
|
+
enabled = false;
|
|
1705
|
+
patcher?.restoreAll();
|
|
1706
|
+
patcher = null;
|
|
1707
|
+
workflowRuns.clear();
|
|
1708
|
+
pausedRuns.clear();
|
|
1709
|
+
const t = tracker;
|
|
1710
|
+
tracker = null;
|
|
1711
|
+
const models = openModels.drain();
|
|
1712
|
+
const tools = openTools.drain();
|
|
1713
|
+
const steps = openSteps.drain();
|
|
1714
|
+
if (t === null) return;
|
|
1715
|
+
const site = `${NAME}.uninstall`;
|
|
1716
|
+
const incomplete = core.fwFields({ incomplete: true });
|
|
1717
|
+
// Innermost first — a step's model calls and tools before the step — and
|
|
1718
|
+
// every leaf before the agents that own it.
|
|
1719
|
+
for (const call of models) {
|
|
1720
|
+
if (call.tracker !== t) continue;
|
|
1721
|
+
core.callSafely(
|
|
1722
|
+
() =>
|
|
1723
|
+
t.emit("modelResponse", call.requestId, {
|
|
1724
|
+
parentKey: call.runKey,
|
|
1725
|
+
model: call.model,
|
|
1726
|
+
stopReason: "incomplete", // the LangChain adapter's word for a model call cut off mid-flight
|
|
1727
|
+
requestId: call.requestId,
|
|
1728
|
+
...core.fwFields({ duration_ms: core.ms(Date.now() - call.started), incomplete: true }),
|
|
1729
|
+
}),
|
|
1730
|
+
[],
|
|
1731
|
+
site,
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
for (const call of tools) {
|
|
1735
|
+
if (call.tracker !== t) continue;
|
|
1736
|
+
core.callSafely(
|
|
1737
|
+
() =>
|
|
1738
|
+
t.emit("toolResult", call.key, {
|
|
1739
|
+
parentKey: call.parentKey,
|
|
1740
|
+
toolName: call.toolName,
|
|
1741
|
+
toolCallId: call.toolCallId,
|
|
1742
|
+
...incomplete,
|
|
1743
|
+
}),
|
|
1744
|
+
[],
|
|
1745
|
+
site,
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
for (const span of steps) {
|
|
1749
|
+
if (span.tracker !== t) continue;
|
|
1750
|
+
core.callSafely(
|
|
1751
|
+
() =>
|
|
1752
|
+
t.emit("hookCompleted", span.runKey, {
|
|
1753
|
+
hookName: span.hookName,
|
|
1754
|
+
hookId: span.hookId,
|
|
1755
|
+
outcome: "cancelled",
|
|
1756
|
+
...incomplete,
|
|
1757
|
+
}),
|
|
1758
|
+
[],
|
|
1759
|
+
site,
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
core.callSafely(() => t.closeOpenAgents("cancelled"), [], site);
|
|
1763
|
+
t.reset();
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* The pure readers above and two lifecycle probes, for this package's own
|
|
1768
|
+
* unit tests.
|
|
1769
|
+
*
|
|
1770
|
+
* @internal Not part of the public API — `stripInternal` drops it from the
|
|
1771
|
+
* published declarations: their shapes follow Mastra's and the providers' internals, and
|
|
1772
|
+
* change whenever those do. Nothing exported references it.
|
|
1773
|
+
*/
|
|
1774
|
+
export const _internals = {
|
|
1775
|
+
/** Whether an installation is live. */
|
|
1776
|
+
isEnabled: (): boolean => enabled,
|
|
1777
|
+
/** How many suspended workflow runs are waiting on a resume. */
|
|
1778
|
+
pausedRuns: (): number => pausedRuns.size,
|
|
1779
|
+
/** How many spans of each kind are open, for the bookkeeping tests. */
|
|
1780
|
+
openSpans: (): Record<string, number> => ({
|
|
1781
|
+
models: openModels.size,
|
|
1782
|
+
tools: openTools.size,
|
|
1783
|
+
steps: openSteps.size,
|
|
1784
|
+
workflowRuns: workflowRuns.size,
|
|
1785
|
+
agents: tracker?.openAgents().length ?? 0,
|
|
1786
|
+
}),
|
|
1787
|
+
usageOf,
|
|
1788
|
+
finishReasonOf,
|
|
1789
|
+
promptOf,
|
|
1790
|
+
generateOutcome,
|
|
1791
|
+
foldStreamPart,
|
|
1792
|
+
toolCallArgs,
|
|
1793
|
+
isInternalRun,
|
|
1794
|
+
workflowOutcome,
|
|
1795
|
+
stepResultOf,
|
|
1796
|
+
stepOutcome,
|
|
1797
|
+
conversationOf,
|
|
1798
|
+
isRoutingAgent,
|
|
1799
|
+
suspendedStepsOf,
|
|
1800
|
+
resumeTargetOf,
|
|
1801
|
+
humanText,
|
|
1802
|
+
};
|