diff --git a/.github/agents/gpt-5-beast-mode.agent.md b/.github/agents/gpt-5-beast-mode.agent.md index 7a36e63a..aab5c393 100644 --- a/.github/agents/gpt-5-beast-mode.agent.md +++ b/.github/agents/gpt-5-beast-mode.agent.md @@ -1,6 +1,7 @@ --- description: 'Beast Mode 2.0: A powerful autonomous agent tuned specifically for GPT-5 that can solve complex problems by using tools, conducting research, and iterating until the problem is fully resolved.' -tools: ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'agent', 'lotus/*', 'mastrabeta/mastraBlog', 'mastrabeta/mastraChanges', 'mastrabeta/mastraDocs', 'mastrabeta/mastraExamples', 'mastrabeta/mastraMigration', 'multi_orchestrator/*', 'next-devtools/*', 's-ai/*', 'thoughtbox/*', 'docfork/*', 'vscode.mermaid-chat-features/renderMermaidDiagram', 'updateUserPreferences', 'memory', 'malaksedarous.copilot-context-optimizer/askAboutFile', 'malaksedarous.copilot-context-optimizer/runAndExtract', 'malaksedarous.copilot-context-optimizer/askFollowUp', 'malaksedarous.copilot-context-optimizer/researchTopic', 'malaksedarous.copilot-context-optimizer/deepResearch', 'ms-python.python/getPythonEnvironmentInfo', 'ms-python.python/getPythonExecutableCommand', 'ms-python.python/installPythonPackage', 'ms-python.python/configurePythonEnvironment', 'search/changes', 'ms-vscode.vscode-websearchforcopilot/websearch', 'todo', 'search/changes', 'vscode/openSimpleBrowser',"search/codebase", "edit/editFiles", 'vscode/extensions', 'web/githubRepo', 'vscode/openSimpleBrowser', 'read/problems'] +tools: ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'ast-grep-mcp/*', 'mastrabeta/mastraBlog', 'mastrabeta/mastraChanges', 'mastrabeta/mastraDocs', 'mastrabeta/mastraMigration', 'mastrabeta/mastraExamples', 'next-devtools/*', 's-ai/*', 'agent', 'vscode.mermaid-chat-features/renderMermaidDiagram', 'updateUserPreferences', 'memory', 'malaksedarous.copilot-context-optimizer/askAboutFile', 'malaksedarous.copilot-context-optimizer/runAndExtract', 'malaksedarous.copilot-context-optimizer/askFollowUp', 'malaksedarous.copilot-context-optimizer/researchTopic', 'malaksedarous.copilot-context-optimizer/deepResearch', 'ms-python.python/getPythonEnvironmentInfo', 'ms-python.python/getPythonExecutableCommand', 'ms-python.python/installPythonPackage', 'ms-python.python/configurePythonEnvironment', 'ms-vscode.vscode-websearchforcopilot/websearch', 'the0807.uv-toolkit/uv-init', 'the0807.uv-toolkit/uv-sync', 'the0807.uv-toolkit/uv-add', 'the0807.uv-toolkit/uv-add-dev', 'the0807.uv-toolkit/uv-upgrade', 'the0807.uv-toolkit/uv-clean', 'the0807.uv-toolkit/uv-lock', 'the0807.uv-toolkit/uv-venv', 'the0807.uv-toolkit/uv-run', 'the0807.uv-toolkit/uv-script-dep', 'the0807.uv-toolkit/uv-python-install', 'the0807.uv-toolkit/uv-python-pin', 'the0807.uv-toolkit/uv-tool-install', 'the0807.uv-toolkit/uvx-run', 'the0807.uv-toolkit/uv-activate-venv', 'todo'] +infer: true name: 'GPT 5 Beast Mode' --- @@ -99,9 +100,22 @@ If the host supports Responses API, chain prior reasoning (`previous_response_id ## Workflow (concise) 1) **Plan** — Break down the user request; enumerate files to edit. If unknown, perform a single targeted search (`search`/`usages`). Initialize **todos**. -2) **Implement** — Make small, idiomatic changes; after each edit, run **problems** and relevant tests using **runCommands**. // No +2) **Implement** — Make small, idiomatic changes; after each edit, run **problems** and relevant tests using **runCommands**. // No 3) **Verify** — Rerun tests; resolve any failures; only search again if validation uncovers new questions. 4) **Research (if needed)** — Use **fetch** for docs; always cite sources. ## Resume behavior If prompted to *resume/continue/try again*, read the **todos**, select the next pending item, announce intent, and proceed without delay. + +## Subagents + +The following subagents are available to assist with specialized tasks. Invoke them as needed to leverage their expertise. + +@runSubagent Expert Next.js Developer "Expert Next.js 16 developer specializing in App Router, Server Components, Cache Components, Turbopack, and modern React patterns with TypeScript" +@runSubagent Expert React Frontend Engineer "Expert React 19.2 frontend engineer specializing in modern hooks, Server Components, Actions, TypeScript, and performance optimization" +@runSubagent ADR-Generator "Expert agent for creating comprehensive Architectural Decision Records (ADRs) with structured formatting optimized for AI consumption and human readability." +@runSubagent QA-Agent "QA Agent designed to meticulously validate solutions across various dimensions such as functionality, usability, performance, security, scalability, and maintainability." +@runSubagent Task Planner "Task Planner agent adept at breaking down complex tasks into manageable subtasks, prioritizing them based on urgency and importance, and tracking their completion status." +@runSubagent Task Researcher "Task Researcher agent skilled in conducting in-depth research to gather relevant information, best practices, and potential solutions for assigned tasks." +@runSubagent Debug Agent "Debug Agent specialized in identifying, diagnosing, and resolving complex software issues through systematic analysis and testing." +@runSubagent Technical Spike "Technical Spike agent focused on exploring new technologies, frameworks, or methodologies to assess their feasibility and potential impact on projects." diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml new file mode 100644 index 00000000..7636ba6d --- /dev/null +++ b/.github/workflows/opencode.yml @@ -0,0 +1,31 @@ +name: opencode + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + opencode: + if: | + contains(github.event.comment.body, ' /oc') || + startsWith(github.event.comment.body, '/oc') || + contains(github.event.comment.body, ' /opencode') || + startsWith(github.event.comment.body, '/opencode') + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: read + issues: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Run opencode + uses: anomalyco/opencode/github@latest + env: + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + with: + model: opencode/grok-code \ No newline at end of file diff --git a/instrumentation.ts b/instrumentation.ts deleted file mode 100644 index 355bd746..00000000 --- a/instrumentation.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { registerOTel } from "@vercel/otel"; - -/** - * OTEL configuration is primarily handled by Mastra in `src/mastra/index.ts`. - * - * To avoid double instrumentation or conflicting exporters, Vercel's `registerOTel` - * is only used when explicitly enabled via the `VERCEL_OTEL_ENABLED` env flag. - * - * When `VERCEL_OTEL_ENABLED` is not set to `"true"`, this file becomes a no-op - * and Mastra's OTEL/exporter configuration is the single source of truth. - */ -const VERCEL_OTEL_ENABLED = process.env.VERCEL_OTEL_ENABLED === "true"; - -export function register() { - if (!VERCEL_OTEL_ENABLED) { - // Rely on Mastra's OTEL/exporter configuration in `src/mastra/index.ts`. - // Intentionally do **not** call `registerOTel` here to prevent - // duplicate instrumentation and conflicting OTEL SDK instances. - return; - } - - // When explicitly enabled, use Vercel's OTEL setup. - // Keep the service name aligned with Mastra's configuration (`"ai"`). - registerOTel({ - serviceName: "ai", - }); -} diff --git a/package-lock.json b/package-lock.json index 2516242b..c68eb4f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,45 +1,45 @@ { "name": "agentstack", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agentstack", - "version": "1.1.0", + "version": "1.1.1", "license": "ISC", "dependencies": { "@ai-sdk/google": "^2.0.52", "@ai-sdk/google-vertex": "^4.0.11", "@ai-sdk/openai": "^3.0.7", "@ai-sdk/openai-compatible": "^2.0.4", - "@ai-sdk/react": "^3.0.19", + "@ai-sdk/react": "^3.0.21", "@dotenvx/dotenvx": "^1.51.4", "@e2b/code-interpreter": "^2.3.3", "@emotion/react": "^11.14.0", - "@mastra/ai-sdk": "^1.0.0-beta.12", + "@mastra/ai-sdk": "^1.0.0-beta.13", "@mastra/auth-supabase": "^1.0.0-beta.1", - "@mastra/client-js": "^1.0.0-beta.19", - "@mastra/cloudflare-d1": "^1.0.0-beta.8", - "@mastra/core": "^1.0.0-beta.19", - "@mastra/deployer": "^1.0.0-beta.19", - "@mastra/evals": "^1.0.0-beta.3", - "@mastra/lance": "^1.0.0-beta.9", - "@mastra/libsql": "^1.0.0-beta.10", - "@mastra/loggers": "^1.0.0-beta.3", - "@mastra/mcp": "^1.0.0-beta.7", - "@mastra/memory": "^1.0.0-beta.10", - "@mastra/mongodb": "^1.0.0-beta.9", - "@mastra/observability": "^1.0.0-beta.9", - "@mastra/otel-exporter": "^1.0.0-beta.10", - "@mastra/pg": "^1.0.0-beta.11", - "@mastra/rag": "^2.0.0-beta.4", - "@mastra/react": "^0.1.0-beta.19", - "@mastra/upstash": "^1.0.0-beta.9", - "@mastra/vectorize": "^1.0.0-beta.2", - "@mastra/voice-google": "^0.12.0-beta.1", - "@mastra/voice-openai": "^0.12.0-beta.1", - "@mcpc-tech/acp-ai-provider": "^0.2.3", + "@mastra/client-js": "^1.0.0-beta.20", + "@mastra/cloudflare-d1": "^1.0.0-beta.9", + "@mastra/core": "^1.0.0-beta.20", + "@mastra/deployer": "^1.0.0-beta.20", + "@mastra/evals": "^1.0.0-beta.4", + "@mastra/lance": "^1.0.0-beta.10", + "@mastra/libsql": "^1.0.0-beta.11", + "@mastra/loggers": "^1.0.0-beta.4", + "@mastra/mcp": "^1.0.0-beta.8", + "@mastra/memory": "^1.0.0-beta.11", + "@mastra/mongodb": "^1.0.0-beta.10", + "@mastra/observability": "^1.0.0-beta.10", + "@mastra/otel-exporter": "^1.0.0-beta.11", + "@mastra/pg": "^1.0.0-beta.12", + "@mastra/rag": "^2.0.0-beta.5", + "@mastra/react": "^0.1.0-beta.20", + "@mastra/upstash": "^1.0.0-beta.10", + "@mastra/vectorize": "^1.0.0-beta.3", + "@mastra/voice-google": "^0.12.0-beta.2", + "@mastra/voice-openai": "^0.12.0-beta.2", + "@mcpc-tech/acp-ai-provider": "^0.2.4", "@monaco-editor/loader": "^1.7.0", "@monaco-editor/react": "^4.8.0-rc.3", "@next/bundle-analyzer": "^16.1.1", @@ -94,7 +94,7 @@ "excalidraw-to-svg": "^3.1.0", "fast-glob": "^3.3.3", "fast-xml-parser": "^5.3.3", - "framer-motion": "^12.24.10", + "framer-motion": "^12.24.11", "gray-matter": "^4.0.3", "husky": "^9.1.7", "input-otp": "^1.4.2", @@ -108,7 +108,7 @@ "monaco-editor-webpack-plugin": "^7.1.1", "monaco-languages": "^2.11.1", "monaco-themes": "^0.4.8", - "motion": "^12.24.10", + "motion": "^12.24.11", "nanoid": "^5.1.6", "next": "^16.1.1", "next-themes": "^0.4.6", @@ -174,7 +174,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.26", "ink-testing-library": "^4.0.0", - "mastra": "^1.0.0-beta.12", + "mastra": "^1.0.0-beta.13", "prettier": "^3.7.4", "tailwindcss": "^4.1.18", "tw-animate-css": "^1.4.0", @@ -606,13 +606,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "3.0.19", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.19.tgz", - "integrity": "sha512-qbwendrpzmf+T3mEg89bx1AR8syUCwloCcDwYDdb2yZiR6yAmJa4k1GDlHmEoHtWPRzqMx1dey9QO+MOLQnWyA==", + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.21.tgz", + "integrity": "sha512-fFFXJBXKtKgJwj7ajObXFCONUTcmCO+nEgd5JiFqoyfMb4xTSbTwn+8snhG+smcAwSJ7YwzZePwRYkse5CyPzg==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider-utils": "4.0.4", - "ai": "6.0.19", + "ai": "6.0.21", "swr": "^2.2.5", "throttleit": "2.1.0" }, @@ -5668,9 +5668,9 @@ ] }, "node_modules/@mastra/ai-sdk": { - "version": "1.0.0-beta.12", - "resolved": "https://registry.npmjs.org/@mastra/ai-sdk/-/ai-sdk-1.0.0-beta.12.tgz", - "integrity": "sha512-nCswHQG9y5d0BZnATlG8gCelE5tptRtznDAFFCbYhI2CljC87+x1SXB9kKYjwDG34j7DnXubm3GYMBZzFyXGJQ==", + "version": "1.0.0-beta.13", + "resolved": "https://registry.npmjs.org/@mastra/ai-sdk/-/ai-sdk-1.0.0-beta.13.tgz", + "integrity": "sha512-AG9Yf/vA7pY5W9ikf2/uG6vW2F06pPkLsVD7D2YmX90ST6vkL2BgI4eRJa4/nXMsuWemRYl7JEY6YUzbrGA9sQ==", "license": "Apache-2.0", "engines": { "node": ">=22.13.0" @@ -5693,13 +5693,13 @@ } }, "node_modules/@mastra/client-js": { - "version": "1.0.0-beta.19", - "resolved": "https://registry.npmjs.org/@mastra/client-js/-/client-js-1.0.0-beta.19.tgz", - "integrity": "sha512-7BakmlQeV+q8i/QGk3V8J0eEoZOAl6Aa9/DGND+Xv4PTrOIsW07Wb/V44wlTYgQxmwDfxGk7HNSCh6FSrz5ODw==", + "version": "1.0.0-beta.20", + "resolved": "https://registry.npmjs.org/@mastra/client-js/-/client-js-1.0.0-beta.20.tgz", + "integrity": "sha512-D+Ajgw00Q7i0X4OyhHzGYVTb4SpRCCJUl4KCPPxXKvqhIwF9QKpLuqdUXMcSmrvmnX+zl+I7QD1Js4PkIwCHGA==", "license": "Apache-2.0", "dependencies": { "@lukeed/uuid": "^2.0.1", - "@mastra/core": "1.0.0-beta.19", + "@mastra/core": "1.0.0-beta.20", "@mastra/schema-compat": "1.0.0-beta.5", "json-schema": "^0.4.0" }, @@ -5711,9 +5711,9 @@ } }, "node_modules/@mastra/cloudflare-d1": { - "version": "1.0.0-beta.8", - "resolved": "https://registry.npmjs.org/@mastra/cloudflare-d1/-/cloudflare-d1-1.0.0-beta.8.tgz", - "integrity": "sha512-vhzlFv0t5HgvQrtjO7WI78hoZTyUYRBHZ78Eobj7H5475ggbARRc8OYNlkGf3Za1dZxaieIq+QAjSJzGs1LgYw==", + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@mastra/cloudflare-d1/-/cloudflare-d1-1.0.0-beta.9.tgz", + "integrity": "sha512-llk1ImFCOC1CFowdJWGDNcAHOkh5EyubnuvI1rP8oRjjrRGXzKW5j6FMEslDkR8hNBeYhCoKTgkuRlnq1JTVkw==", "dependencies": { "cloudflare": "^4.5.0" }, @@ -5725,9 +5725,9 @@ } }, "node_modules/@mastra/core": { - "version": "1.0.0-beta.19", - "resolved": "https://registry.npmjs.org/@mastra/core/-/core-1.0.0-beta.19.tgz", - "integrity": "sha512-XgLw3JL3vYbwLX/cHECwXSHF1JnMPIqIsh5iD/meJCd6395CbtvgU+SYsph1meF6jI3BzuJl4olV8+IaK1YgGQ==", + "version": "1.0.0-beta.20", + "resolved": "https://registry.npmjs.org/@mastra/core/-/core-1.0.0-beta.20.tgz", + "integrity": "sha512-XQZ1RLh1CdZlZLHZzhh34crOQM3x5iNdFiWZSnx408RNzJ4SmYYJA5iRtYDLEIIzxgAasa4H7Ym3Y7jKcbDO7g==", "license": "Apache-2.0", "peer": true, "dependencies": { @@ -5743,9 +5743,9 @@ "@modelcontextprotocol/sdk": "^1.17.5", "@sindresorhus/slugify": "^2.2.1", "dotenv": "^16.6.1", - "hono": "^4.10.5", + "hono": "^4.11.3", "hono-openapi": "^1.1.1", - "js-tiktoken": "^1.0.20", + "js-tiktoken": "^1.0.21", "json-schema": "^0.4.0", "lru-cache": "^11.2.2", "p-map": "^7.0.3", @@ -5908,14 +5908,14 @@ "license": "BSD-3-Clause" }, "node_modules/@mastra/deployer": { - "version": "1.0.0-beta.19", - "resolved": "https://registry.npmjs.org/@mastra/deployer/-/deployer-1.0.0-beta.19.tgz", - "integrity": "sha512-1295gZEEEKNBDS81idE+C+/s0LQIoGITP4x1OMkYtGCf+TOR3cygCU58Li/imG3ZNJJXy6sg/LdpgBnPBd0BfA==", + "version": "1.0.0-beta.20", + "resolved": "https://registry.npmjs.org/@mastra/deployer/-/deployer-1.0.0-beta.20.tgz", + "integrity": "sha512-6ZA6VOzQ3DCpq1oUf/Xz/aACIjFS346RvrgINRWVJjEypX2TOZitLbN/SnX90n6daWkSd569E9NU8za8TKLP1A==", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.5", "@babel/preset-typescript": "^7.27.1", - "@mastra/server": "1.0.0-beta.19", + "@mastra/server": "1.0.0-beta.20", "@optimize-lodash/rollup-plugin": "^5.0.2", "@rollup/plugin-alias": "6.0.0", "@rollup/plugin-commonjs": "29.0.0", @@ -5928,7 +5928,7 @@ "esbuild": "^0.25.10", "find-workspaces": "^0.3.1", "fs-extra": "^11.3.2", - "hono": "^4.10.5", + "hono": "^4.11.3", "local-pkg": "^1.1.2", "resolve-from": "^5.0.0", "resolve.exports": "^2.0.3", @@ -5947,9 +5947,9 @@ } }, "node_modules/@mastra/evals": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@mastra/evals/-/evals-1.0.0-beta.3.tgz", - "integrity": "sha512-GRWKOxHVmzUHK/cwkpZgnduraqKJiu7ieI6tovF5tnrcjg2fCpzJj68OaOkCgGoeDe2xYprwXvmv7TATBHfQ5Q==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@mastra/evals/-/evals-1.0.0-beta.4.tgz", + "integrity": "sha512-z6fA59jV3zxW85gdVlZIeomXPO73V+o0NK4fV4AYwtJrdAkQBTGrynS+aU1TpYhcW06HDXaoZ7YikinCE1mUlQ==", "license": "Apache-2.0", "dependencies": { "compromise": "^14.14.4", @@ -5962,14 +5962,13 @@ }, "peerDependencies": { "@mastra/core": ">=1.0.0-0 <2.0.0-0", - "ai": "^4.0.0 || ^5.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "node_modules/@mastra/lance": { - "version": "1.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@mastra/lance/-/lance-1.0.0-beta.9.tgz", - "integrity": "sha512-iSzRUHpxT24kGqDFZ1hiXc1/stXf34OaMKfi/L+uCNPX60GJdjpfcd8xQIay6W6adNRTuVlxGRCp11cRMt1SOQ==", + "version": "1.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@mastra/lance/-/lance-1.0.0-beta.10.tgz", + "integrity": "sha512-JEgHhomppf9uc1xWCIiMIlf0K4Hwu4DS7NtBMq41rZQbqFnIhffa/S10neGzljZqn3AwHuZ3tODKNahdhptd3w==", "dependencies": { "@lancedb/lancedb": "^0.22.3", "apache-arrow": "^18.1.0" @@ -5982,9 +5981,9 @@ } }, "node_modules/@mastra/libsql": { - "version": "1.0.0-beta.10", - "resolved": "https://registry.npmjs.org/@mastra/libsql/-/libsql-1.0.0-beta.10.tgz", - "integrity": "sha512-UNGsyJiMOCtkrD1pEomgu4HzqtxBqc27jroYgTvkk2rj0PoMiOJw1RQxwkoUDIPRB+XRaDHrETF/Vs+QNj+65Q==", + "version": "1.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@mastra/libsql/-/libsql-1.0.0-beta.11.tgz", + "integrity": "sha512-GLwS57q7lnbjpes3WzWwD/TTpOMRUi9MDkdPYQ1BAAEa7j39RGxS06TXPamy7T/7xN6zqliZbAkRs1TKQCNHiQ==", "license": "Apache-2.0", "dependencies": { "@libsql/client": "^0.15.15" @@ -5997,9 +5996,9 @@ } }, "node_modules/@mastra/loggers": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@mastra/loggers/-/loggers-1.0.0-beta.3.tgz", - "integrity": "sha512-+gAjvyCcntGx3sCMmlKZuMMpgoZLTGgttYtAHWAsa6/rCpbeK0d3m2a2VBD+gIgH25bpf9UGVuhUa2DGGNAHHQ==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@mastra/loggers/-/loggers-1.0.0-beta.4.tgz", + "integrity": "sha512-sioCtUFZ83EoXOCg5k7eA8rq1Y+cH4SFVMG98xjNapB0Wpzr8ODSVElAg0e/u+jIVAqBVSUi0I/i0BNL5irHoQ==", "license": "Apache-2.0", "dependencies": { "pino": "^10.1.0", @@ -6013,14 +6012,14 @@ } }, "node_modules/@mastra/mcp": { - "version": "1.0.0-beta.7", - "resolved": "https://registry.npmjs.org/@mastra/mcp/-/mcp-1.0.0-beta.7.tgz", - "integrity": "sha512-o7EABwrOHiMtkpRNQDaXiohdtxzyqktQRXG0kf5ms69227PkwL7QYdhDofLecNkajIPz3EyFmZq9HRB2ZlVHFg==", + "version": "1.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@mastra/mcp/-/mcp-1.0.0-beta.8.tgz", + "integrity": "sha512-GZt6ii9fe+5OLhXAeZYVjOOZvwX79tOQg1pskCZwoXI9X5C85EtWAGtG5905GLXkVxwmcwEwn8JMMloUDP8XNQ==", "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^14.2.1", "@modelcontextprotocol/sdk": "^1.17.5", - "exit-hook": "^4.0.0", + "exit-hook": "^5.0.1", "fast-deep-equal": "^3.1.3", "uuid": "^11.1.0", "zod-from-json-schema": "^0.5.0", @@ -6035,14 +6034,14 @@ } }, "node_modules/@mastra/memory": { - "version": "1.0.0-beta.10", - "resolved": "https://registry.npmjs.org/@mastra/memory/-/memory-1.0.0-beta.10.tgz", - "integrity": "sha512-XZnbbMhJz+LuJXpR75Nnj+3FAxxXN9guC/yTQZmI3XW6VFKPUmF1dyzuzzRaSe3Zq0kfGzb8HdujoCp24oDdiA==", + "version": "1.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@mastra/memory/-/memory-1.0.0-beta.11.tgz", + "integrity": "sha512-LO7E9wjG8JlyTIlmy/zWHA08H8U+JGvI1oGrOfw0tB87Bb4Moqlaw59YBrzpUDE6GUsr6b8t5LPi1IaTCf4z+Q==", "license": "Apache-2.0", "dependencies": { "@mastra/schema-compat": "1.0.0-beta.5", "async-mutex": "^0.5.0", - "js-tiktoken": "^1.0.20", + "js-tiktoken": "^1.0.21", "json-schema": "^0.4.0", "lru-cache": "^11.2.2", "xxhash-wasm": "^1.1.0" @@ -6056,9 +6055,9 @@ } }, "node_modules/@mastra/mongodb": { - "version": "1.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@mastra/mongodb/-/mongodb-1.0.0-beta.9.tgz", - "integrity": "sha512-gZAIl2z4RoTKmIm2gTirEgMJfKhSAjsqdL7mqs7dCfXch8/JFl555yTJPA7xRcTXlgrh89YU5jUr52qOzKASYQ==", + "version": "1.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@mastra/mongodb/-/mongodb-1.0.0-beta.10.tgz", + "integrity": "sha512-APHaZl3M2JIVdE4iLFAtK5szu+ypnEBwe7noDrLzFzGmzh4vlmkL7uIlJO9BH7iW2pcgCwojoh+vwKKC46OGZg==", "dependencies": { "@types/mongodb": "^4.0.7", "cloudflare": "^4.5.0", @@ -6073,9 +6072,9 @@ } }, "node_modules/@mastra/observability": { - "version": "1.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@mastra/observability/-/observability-1.0.0-beta.9.tgz", - "integrity": "sha512-ukwa0wbkIfp5lh1/rWFT6JxyoxArG9NFVyatGgugsb2/R6Oc/LJLmyjUxrF1SEp9lW2ILur2RGSR/sB9ftFWDQ==", + "version": "1.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@mastra/observability/-/observability-1.0.0-beta.10.tgz", + "integrity": "sha512-uaVEupKgLYiiOtx5xASl4zPqEaqqkbI6dzwMUP05RaaMXTHqvD3X5+uVyrtL04PgXjVv1GNxPw9UxMYFUDGutg==", "license": "Apache-2.0", "engines": { "node": ">=22.13.0" @@ -6085,12 +6084,12 @@ } }, "node_modules/@mastra/otel-exporter": { - "version": "1.0.0-beta.10", - "resolved": "https://registry.npmjs.org/@mastra/otel-exporter/-/otel-exporter-1.0.0-beta.10.tgz", - "integrity": "sha512-uspq3CKab9orS9qZl0AThzh3dz9IhAEPviugcNLk4g4BOlJUiUoAnwdViMM6NPVacI5x1cHjqMRXNdCYdfoa+A==", + "version": "1.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@mastra/otel-exporter/-/otel-exporter-1.0.0-beta.11.tgz", + "integrity": "sha512-PQJZIu/SGlt5euFDzvvBQpK991F+b+H6V67nWIfaAtVW3AmNLZoJaO8v7uR+dNTkROX200WUP86lL95QPWH6fQ==", "license": "Apache-2.0", "dependencies": { - "@mastra/observability": "1.0.0-beta.9", + "@mastra/observability": "1.0.0-beta.10", "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^2.1.0", "@opentelemetry/resources": "^2.1.0", @@ -6203,9 +6202,9 @@ } }, "node_modules/@mastra/pg": { - "version": "1.0.0-beta.11", - "resolved": "https://registry.npmjs.org/@mastra/pg/-/pg-1.0.0-beta.11.tgz", - "integrity": "sha512-NtygplIXax+cT315mSwGQqQtO+EUJan86Lez3oqv+uBdxeHARXMpL+LtlfNUXVg++PTsTDuOWKBOgv633GBHOQ==", + "version": "1.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@mastra/pg/-/pg-1.0.0-beta.12.tgz", + "integrity": "sha512-rmAgq/qS8pfogK+kwYty46InHoAZu8qj4jseRIfIMz932sbAvRqIp2JdI0TQ3b17MrfO73XZKqsRfqpJGpjH2w==", "license": "Apache-2.0", "dependencies": { "async-mutex": "^0.5.0", @@ -6220,14 +6219,14 @@ } }, "node_modules/@mastra/rag": { - "version": "2.0.0-beta.4", - "resolved": "https://registry.npmjs.org/@mastra/rag/-/rag-2.0.0-beta.4.tgz", - "integrity": "sha512-5s54P5DhYGX2tZWBs31/6q6uq0vT6Be7JIa+GZOHaZM4br0CIbwusirXiljMhHFFr+85L9r0QMbbHg0MhzoIjg==", + "version": "2.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@mastra/rag/-/rag-2.0.0-beta.5.tgz", + "integrity": "sha512-IofM/6+uZ3FD4K60RuQxclW1gKwkGpUViT7iKh2OoNDh4e4qWTAnF/FkbaSUxrTL0dLpkCzrVLmPZLKMaO0X1w==", "license": "Apache-2.0", "dependencies": { "@paralleldrive/cuid2": "^2.3.1", "big.js": "^7.0.1", - "js-tiktoken": "^1.0.20", + "js-tiktoken": "^1.0.21", "node-html-better-parser": "^1.5.8", "pathe": "^2.0.3", "zeroentropy": "0.1.0-alpha.6" @@ -6242,13 +6241,13 @@ } }, "node_modules/@mastra/react": { - "version": "0.1.0-beta.19", - "resolved": "https://registry.npmjs.org/@mastra/react/-/react-0.1.0-beta.19.tgz", - "integrity": "sha512-Ey2sUrrQICbhpMo/38lcnuF9cX9k7gKcicvccMspjOAOXnZ6tjwD8mT1+GGTJ/oWCSbXOgdDSA4dscx1MDyJfg==", + "version": "0.1.0-beta.20", + "resolved": "https://registry.npmjs.org/@mastra/react/-/react-0.1.0-beta.20.tgz", + "integrity": "sha512-Mdkcf8BJUGk9ltGZ5IcIl1p9HkxWUZLA98wveSgBVf1c+abVlBZGbmyFHn7IX02WPuBm+SaTD2w+Ll9tMbYawg==", "license": "Apache-2.0", "dependencies": { "@lukeed/uuid": "^2.0.1", - "@mastra/client-js": "1.0.0-beta.19", + "@mastra/client-js": "1.0.0-beta.20", "@radix-ui/react-tooltip": "^1.2.7", "hast-util-to-jsx-runtime": "^2.3.6", "lucide-react": "^0.522.0", @@ -6260,7 +6259,8 @@ }, "peerDependencies": { "react": ">=19.0.0", - "react-dom": ">=19.0.0" + "react-dom": ">=19.0.0", + "zod": "^3.25.0 || ^4.0.0" } }, "node_modules/@mastra/react/node_modules/@shikijs/core": { @@ -6400,12 +6400,12 @@ } }, "node_modules/@mastra/server": { - "version": "1.0.0-beta.19", - "resolved": "https://registry.npmjs.org/@mastra/server/-/server-1.0.0-beta.19.tgz", - "integrity": "sha512-FFtgjwcra7tfc2d4CteKHwkqS+a5zAdzs6zjI0skEUNTMFAkXHaPrMAVdx2q2trGvj4ARZBygROafOAGfBdLDg==", + "version": "1.0.0-beta.20", + "resolved": "https://registry.npmjs.org/@mastra/server/-/server-1.0.0-beta.20.tgz", + "integrity": "sha512-jmMFo7fYfPTpOh+2fTz1GPwjdQU0L8MOVWH4tirpir/LQqOe2e1gII1Yx2Okucki5lijZ6occH4mZREP5QRZvw==", "license": "Apache-2.0", "dependencies": { - "hono": "^4.10.5" + "hono": "^4.11.3" }, "engines": { "node": ">=22.13.0" @@ -6416,12 +6416,12 @@ } }, "node_modules/@mastra/upstash": { - "version": "1.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@mastra/upstash/-/upstash-1.0.0-beta.9.tgz", - "integrity": "sha512-KoxWsSiwK4CU1mUHoU+qJHlvYDYb952swXnPWZmVqMOAcvBE8f7sfYz4tTKF8Mxr8i5ReStVXXZ0IHVB3kfhkA==", + "version": "1.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@mastra/upstash/-/upstash-1.0.0-beta.10.tgz", + "integrity": "sha512-4sYxKwmq4Su4CyYZf8JO8bHhz6OdlT8gmDcLQX1b0Tg9M5TGpiXmIVegmcC5ag+EWQjgRmBl71hkH5INgzcbfg==", "license": "Apache-2.0", "dependencies": { - "@upstash/redis": "^1.35.6", + "@upstash/redis": "^1.36.0", "@upstash/vector": "^1.2.2" }, "engines": { @@ -6432,9 +6432,9 @@ } }, "node_modules/@mastra/vectorize": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@mastra/vectorize/-/vectorize-1.0.0-beta.2.tgz", - "integrity": "sha512-x7uZFzro3nBzCmtHEO/wte3X18U/GA+Vn1bUC5LQ9AVKvbpjDuIQ4UaJMQkxXqGuJN1XjSjIphC7cWaiKoIqew==", + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@mastra/vectorize/-/vectorize-1.0.0-beta.3.tgz", + "integrity": "sha512-yUb1E1opoK/HN65j8u/BfyhJQOCZwDqoPVtJjPI817EZm0eXbc74qwqBEKoDqS4B4AH24K0X33wicQ6FjBIlIw==", "license": "Apache-2.0", "dependencies": { "cloudflare": "^4.5.0" @@ -6447,9 +6447,9 @@ } }, "node_modules/@mastra/voice-google": { - "version": "0.12.0-beta.1", - "resolved": "https://registry.npmjs.org/@mastra/voice-google/-/voice-google-0.12.0-beta.1.tgz", - "integrity": "sha512-FfyMOcTVaRGHMVJbXM/GIurbpN2hHHiZDbqoTHgDYpQO/h3mhzI83GPLWqc/p7Lsrvy48xIz+4sR6Hl3trVWRQ==", + "version": "0.12.0-beta.2", + "resolved": "https://registry.npmjs.org/@mastra/voice-google/-/voice-google-0.12.0-beta.2.tgz", + "integrity": "sha512-Wx9q0YaSaqdmKIVJc4UFsFScziZb0Ej/0h6u8HIwv1VqMk7dzLk8BQtRSiXfS19+Q/8sgvpaTTzJgKB0Vy5wlA==", "license": "Apache-2.0", "dependencies": { "@google-cloud/speech": "^6.7.1", @@ -6464,9 +6464,9 @@ } }, "node_modules/@mastra/voice-openai": { - "version": "0.12.0-beta.1", - "resolved": "https://registry.npmjs.org/@mastra/voice-openai/-/voice-openai-0.12.0-beta.1.tgz", - "integrity": "sha512-l/sO84ce2zxT28to+r2Q4Ie7QtdmdIu+2F67UiRyDwPvGEteim7PtHlxHLqLJtU+BSJORjTIBM8Z2kOLmcZ4WA==", + "version": "0.12.0-beta.2", + "resolved": "https://registry.npmjs.org/@mastra/voice-openai/-/voice-openai-0.12.0-beta.2.tgz", + "integrity": "sha512-1v2iOatciiNYhTVz5PwerIh+k7z/hpA6gADVm6zXVc7+r3nv3a5N0gefbDxQJglkQgsCqwd8yMrDM8Yjx9BhjA==", "license": "Apache-2.0", "dependencies": { "openai": "^5.11.0" @@ -6501,9 +6501,9 @@ } }, "node_modules/@mcpc-tech/acp-ai-provider": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@mcpc-tech/acp-ai-provider/-/acp-ai-provider-0.2.3.tgz", - "integrity": "sha512-0Kkv1q5ByMYKQZknH7krFUXX4ZJPmIa13QuNPMEsWTAJLG7fxanAKc+FLNIlhG3eAwXYPbW60B47rLjpPZD3gg==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@mcpc-tech/acp-ai-provider/-/acp-ai-provider-0.2.4.tgz", + "integrity": "sha512-fv7xUseKpm646u3R+kmL5c9dLnJlUMXGey2lG1JWQlPkLO1el9NPbBqhRqCvuez5hyueW6awjHfkF4qfznIblg==", "dependencies": { "@agentclientprotocol/sdk": "^0.4.8", "@ai-sdk/provider": "^3.0.0", @@ -14495,7 +14495,6 @@ "resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-18.1.0.tgz", "integrity": "sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/helpers": "^0.5.11", "@types/command-line-args": "^5.2.3", @@ -14812,9 +14811,9 @@ } }, "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -14823,7 +14822,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -18738,12 +18737,12 @@ } }, "node_modules/exit-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-4.0.0.tgz", - "integrity": "sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-5.0.1.tgz", + "integrity": "sha512-LHebYV6wSOObiWC894M36qqoWbGWFDZFJnCAch+JUYTyI5EJAlF3Lhv9sqLKoUDjTj3X6SsIxwy41mw2AqFfdA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -19525,12 +19524,12 @@ "license": "MIT" }, "node_modules/framer-motion": { - "version": "12.24.10", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.24.10.tgz", - "integrity": "sha512-8yoyMkCn2RmV9UB9mfmMuzKyenQe909hRQRl0yGBhbZJjZZ9bSU87NIGAruqCXCuTNCA0qHw2LWLrcXLL9GF6A==", + "version": "12.24.11", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.24.11.tgz", + "integrity": "sha512-cNTxTGvFKcY/eQcuGMpG2rb7DOxEFW+A8h5MFhwPqQrn4Jyz9hFhJAlHpngxZIkS0Kk+rZiYmuxNMgO5dGxeYQ==", "license": "MIT", "dependencies": { - "motion-dom": "^12.24.10", + "motion-dom": "^12.24.11", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, @@ -23185,16 +23184,16 @@ } }, "node_modules/mastra": { - "version": "1.0.0-beta.12", - "resolved": "https://registry.npmjs.org/mastra/-/mastra-1.0.0-beta.12.tgz", - "integrity": "sha512-MnDXEHZbJkU/p8g/6IeTrHKCo/Uw6pbkNah0+NoOrrjnWxQKexEwsBveqOd62CWsXQBpizXBh3AYXC0gsgUpqQ==", + "version": "1.0.0-beta.13", + "resolved": "https://registry.npmjs.org/mastra/-/mastra-1.0.0-beta.13.tgz", + "integrity": "sha512-tDA5p29kfarA0mdIdgPAqe3N9WphmDMPUkOTgZKSt9sPwQQ/KtbIUevjy9/5g6SnM55pp2drGUFKy8UWy3HhxA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@clack/prompts": "^0.11.0", "@expo/devcert": "^1.2.1", - "@mastra/deployer": "^1.0.0-beta.15", - "@mastra/loggers": "^1.0.0-beta.3", + "@mastra/deployer": "^1.0.0-beta.20", + "@mastra/loggers": "^1.0.0-beta.4", "commander": "^14.0.2", "dotenv": "^17.2.3", "execa": "^9.6.1", @@ -25236,12 +25235,12 @@ } }, "node_modules/motion": { - "version": "12.24.10", - "resolved": "https://registry.npmjs.org/motion/-/motion-12.24.10.tgz", - "integrity": "sha512-P3tEZfL9N9Feb2sXVZaF085DHNyd4SO/X76Fo37pFv6DDAeL3vU4vYpTZBWiwW66qnF11VmFCM9DxnpAG825yg==", + "version": "12.24.11", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.24.11.tgz", + "integrity": "sha512-nfon8E7NDNh42QTJHwB9gjnfdGLFLf0eCu0izAJ7qmZ1NFODuVp+n20kfe5r5PEQlJyONjI0znSKm7QN8szQAQ==", "license": "MIT", "dependencies": { - "framer-motion": "^12.24.10", + "framer-motion": "^12.24.11", "tslib": "^2.4.0" }, "peerDependencies": { @@ -25262,9 +25261,9 @@ } }, "node_modules/motion-dom": { - "version": "12.24.10", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.24.10.tgz", - "integrity": "sha512-H3HStYaJ6wANoZVNT0ZmYZHGvrpvi9pKJRzsgNEHkdITR4Qd9FFu2e9sH4e2Phr4tKCmyyloex6SOSmv0Tlq+g==", + "version": "12.24.11", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.24.11.tgz", + "integrity": "sha512-DlWOmsXMJrV8lzZyd+LKjG2CXULUs++bkq8GZ2Sr0R0RRhs30K2wtY+LKiTjhmJU3W61HK+rB0GLz6XmPvTA1A==", "license": "MIT", "dependencies": { "motion-utils": "^12.24.10" @@ -34113,6 +34112,7 @@ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", + "peer": true, "peerDependencies": { "zod": "^3.25 || ^4" } diff --git a/package.json b/package.json index 74311acb..e4f3f851 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agentstack", - "version": "1.1.0", + "version": "1.1.1", "description": "Multi-agent frameworks and tools for building AI applications with Mastra.", "main": "index.js", "scripts": { @@ -46,33 +46,33 @@ "@ai-sdk/google-vertex": "^4.0.11", "@ai-sdk/openai": "^3.0.7", "@ai-sdk/openai-compatible": "^2.0.4", - "@ai-sdk/react": "^3.0.19", + "@ai-sdk/react": "^3.0.21", "@dotenvx/dotenvx": "^1.51.4", "@e2b/code-interpreter": "^2.3.3", "@emotion/react": "^11.14.0", - "@mastra/ai-sdk": "^1.0.0-beta.12", + "@mastra/ai-sdk": "^1.0.0-beta.13", "@mastra/auth-supabase": "^1.0.0-beta.1", - "@mastra/client-js": "^1.0.0-beta.19", - "@mastra/cloudflare-d1": "^1.0.0-beta.8", - "@mastra/core": "^1.0.0-beta.19", - "@mastra/deployer": "^1.0.0-beta.19", - "@mastra/evals": "^1.0.0-beta.3", - "@mastra/lance": "^1.0.0-beta.9", - "@mastra/libsql": "^1.0.0-beta.10", - "@mastra/loggers": "^1.0.0-beta.3", - "@mastra/mcp": "^1.0.0-beta.7", - "@mastra/memory": "^1.0.0-beta.10", - "@mastra/mongodb": "^1.0.0-beta.9", - "@mastra/observability": "^1.0.0-beta.9", - "@mastra/otel-exporter": "^1.0.0-beta.10", - "@mastra/pg": "^1.0.0-beta.11", - "@mastra/rag": "^2.0.0-beta.4", - "@mastra/react": "^0.1.0-beta.19", - "@mastra/upstash": "^1.0.0-beta.9", - "@mastra/vectorize": "^1.0.0-beta.2", - "@mastra/voice-google": "^0.12.0-beta.1", - "@mastra/voice-openai": "^0.12.0-beta.1", - "@mcpc-tech/acp-ai-provider": "^0.2.3", + "@mastra/client-js": "^1.0.0-beta.20", + "@mastra/cloudflare-d1": "^1.0.0-beta.9", + "@mastra/core": "^1.0.0-beta.20", + "@mastra/deployer": "^1.0.0-beta.20", + "@mastra/evals": "^1.0.0-beta.4", + "@mastra/lance": "^1.0.0-beta.10", + "@mastra/libsql": "^1.0.0-beta.11", + "@mastra/loggers": "^1.0.0-beta.4", + "@mastra/mcp": "^1.0.0-beta.8", + "@mastra/memory": "^1.0.0-beta.11", + "@mastra/mongodb": "^1.0.0-beta.10", + "@mastra/observability": "^1.0.0-beta.10", + "@mastra/otel-exporter": "^1.0.0-beta.11", + "@mastra/pg": "^1.0.0-beta.12", + "@mastra/rag": "^2.0.0-beta.5", + "@mastra/react": "^0.1.0-beta.20", + "@mastra/upstash": "^1.0.0-beta.10", + "@mastra/vectorize": "^1.0.0-beta.3", + "@mastra/voice-google": "^0.12.0-beta.2", + "@mastra/voice-openai": "^0.12.0-beta.2", + "@mcpc-tech/acp-ai-provider": "^0.2.4", "@monaco-editor/loader": "^1.7.0", "@monaco-editor/react": "^4.8.0-rc.3", "@next/bundle-analyzer": "^16.1.1", @@ -127,7 +127,7 @@ "excalidraw-to-svg": "^3.1.0", "fast-glob": "^3.3.3", "fast-xml-parser": "^5.3.3", - "framer-motion": "^12.24.10", + "framer-motion": "^12.24.11", "gray-matter": "^4.0.3", "husky": "^9.1.7", "input-otp": "^1.4.2", @@ -141,7 +141,7 @@ "monaco-editor-webpack-plugin": "^7.1.1", "monaco-languages": "^2.11.1", "monaco-themes": "^0.4.8", - "motion": "^12.24.10", + "motion": "^12.24.11", "nanoid": "^5.1.6", "next": "^16.1.1", "next-themes": "^0.4.6", @@ -207,7 +207,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.26", "ink-testing-library": "^4.0.0", - "mastra": "^1.0.0-beta.12", + "mastra": "^1.0.0-beta.13", "prettier": "^3.7.4", "tailwindcss": "^4.1.18", "tw-animate-css": "^1.4.0", @@ -222,7 +222,7 @@ "morgan": "^1.10.1", "js-yaml": "^4.1.1", "multer": "^2.0.2", - "body-parser": "^2.2.1", + "body-parser": "^2.2.2", "jws": "^4.0.1", "qs": "^6.14.1", "eslint": "^9.39.2", diff --git a/src/mastra/agents/acpAgent.ts b/src/mastra/agents/acpAgent.ts index 1efe1ad4..cdecfc69 100644 --- a/src/mastra/agents/acpAgent.ts +++ b/src/mastra/agents/acpAgent.ts @@ -96,7 +96,7 @@ User: ${userId} | Role: ${roleConstraint} // containerTags: ['acp-agent'] // }), }, - outputProcessors: [new TokenLimiterProcessor(1048576)], + outputProcessors: [new TokenLimiterProcessor(128576)], workflows: {}, scorers: { diff --git a/src/mastra/index.ts b/src/mastra/index.ts index 2f7fff97..fbe952ce 100644 --- a/src/mastra/index.ts +++ b/src/mastra/index.ts @@ -292,8 +292,7 @@ export const mastra = new Mastra({ semanticRecall: true, workingMemory: { enabled: true }, threads: { generateTitle: true }, - }, - readOnly: false, + } }, maxSteps: 50, includeRawChunks: true, @@ -319,8 +318,7 @@ export const mastra = new Mastra({ semanticRecall: true, workingMemory: { enabled: true }, threads: { generateTitle: true }, - }, - readOnly: false, + } }, maxSteps: 50, includeRawChunks: true, diff --git a/src/mastra/tools/AGENTS.md b/src/mastra/tools/AGENTS.md index 0b16e4bd..54ed6e60 100644 --- a/src/mastra/tools/AGENTS.md +++ b/src/mastra/tools/AGENTS.md @@ -14,6 +14,59 @@ Encapsulate 30+ atomic operational capabilities (security checks, vector queries, content fetch, data processing) in auditable, schema-validated units invoked by agents. Supports financial intelligence, RAG pipelines, web scraping, document processing, and more with Zod schemas, full test coverage, and production-grade error handling. +## createTool() + +The `createTool()` function is used to define custom tools that your Mastra agents can execute. Tools extend an agent's capabilities by allowing it to interact with external systems, perform calculations, or access specific data. + +### Usage Example + +```typescript +// src/mastra/tools/reverse-tool.ts +import { createTool } from "@mastra/core/tools"; +import { z } from "zod"; + +export const tool = createTool({ + id: "test-tool", + description: "Reverse the input string", + inputSchema: z.object({ + input: z.string(), + }), + outputSchema: z.object({ + output: z.string(), + }), + execute: async (inputData) => { + const reversed = inputData.input.split("").reverse().join(""); + + return { + output: reversed, + }; + }, +}); +``` + +### Parameters + +- **`id`**: `string` - A unique identifier for the tool. +- **`description`**: `string` - A description of what the tool does. This is used by the agent to decide when to use the tool. +- **`inputSchema?`**: `Zod schema` - A Zod schema defining the expected input parameters for the tool's `execute` function. +- **`outputSchema?`**: `Zod schema` - A Zod schema defining the expected output structure of the tool's `execute` function. +- **`suspendSchema?`**: `Zod schema` - A Zod schema defining the structure of the payload passed to `suspend()`. This payload is returned to the client when the tool suspends execution. +- **`resumeSchema?`**: `Zod schema` - A Zod schema defining the expected structure of `resumeData` when the tool is resumed. Used by the agent to extract data from user messages when `autoResumeSuspendedTools` is enabled. +- **`requireApproval?`**: `boolean` - When true, the tool requires explicit approval before execution. The agent will emit a `tool-call-approval` chunk and pause until approved or declined. +- **`execute`**: `function` - The function that contains the tool's logic. It receives two parameters: the validated input data (first parameter) and an optional execution context object (second parameter) containing `requestContext`, `tracingContext`, `abortSignal`, and other execution metadata. + - **`input`**: `z.infer` - The validated input data based on inputSchema + - **`context?`**: `ToolExecutionContext` - Optional execution context containing metadata +- **`onInputStart?`**: `function` - Optional callback invoked when the tool call input streaming begins. Receives `toolCallId`, `messages`, and `abortSignal`. +- **`onInputDelta?`**: `function` - Optional callback invoked for each incremental chunk of input text as it streams in. Receives `inputTextDelta`, `toolCallId`, `messages`, and `abortSignal`. +- **`onInputAvailable?`**: `function` - Optional callback invoked when the complete tool input is available and parsed. Receives the validated `input` object, `toolCallId`, `messages`, and `abortSignal`. +- **`onOutput?`**: `function` - Optional callback invoked after the tool has successfully executed and returned output. Receives the tool's `output`, `toolCallId`, `messages`, and `abortSignal`. + +### Returns + +The `createTool()` function returns a `Tool` object. + +- **`Tool`**: `object` - An object representing the defined tool, ready to be added to an agent. + ## Categories ### 1. 💰 Financial Data APIs @@ -161,6 +214,8 @@ For a typical streaming tool call, the hooks are invoked in this order: 4. Tool's `execute` function runs 5. `onOutput` → Tool has completed successfully +**Implementation requirement:** Lifecycle hooks (`onInputStart`, `onInputDelta`, `onInputAvailable`, `onOutput`) **must be declared after** the tool's `execute` property in the `createTool()` call. This ordering ensures tooling that relies on the execute initialization sees hooks only after the execution context is available. + ### Implementation Pattern All lifecycle hooks use structured logging with the Mastra logger for consistent, searchable logs: @@ -177,6 +232,10 @@ export const exampleTool = createTool({ outputSchema: z.object({ result: z.string(), }), + execute: async (inputData, context) => { + // Tool implementation + return { result: 'success' } + }, onInputStart: ({ toolCallId, messages, abortSignal }) => { log.info('Tool input streaming started', { toolCallId, @@ -212,10 +271,6 @@ export const exampleTool = createTool({ hook: 'onOutput', }) }, - execute: async (inputData, context) => { - // Tool implementation - return { result: 'success' } - }, }) ``` @@ -269,7 +324,21 @@ node generate-hooks.js my-new-tool.ts myNewTool inputField1 inputField2 📋 Copy these hooks into your createTool definition: onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('myNewTool tool input streaming started', { toolCallId, messageCount: messages.length, hook: 'onInputStart' }); + log.info('myNewTool tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart' + }); +}, +onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('myNewTool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta' + }); }, onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { log.info('myNewTool received input', { @@ -298,6 +367,87 @@ onOutput: ({ output, toolCallId, toolName, abortSignal }) => { This ensures consistency across all tools and reduces boilerplate code. +## RequestContext Usage Patterns + +All tools should define and use typed RequestContext interfaces to ensure type safety and clear documentation of expected context values. This pattern provides better IntelliSense, prevents runtime errors, and makes the tool's context requirements explicit. + +### Interface Definition Pattern + +```typescript +import type { RequestContext } from '@mastra/core/request-context' + +export interface ToolNameContext extends RequestContext { + userId?: string + workspaceId?: string + temperatureUnit?: 'celsius' | 'fahrenheit' + maxRows?: number + // Add other context properties as needed +} +``` + +### Usage in Tools + +```typescript +export const exampleTool = createTool({ + id: 'example-tool', + description: 'Example tool with typed RequestContext', + inputSchema: z.object({ + param: z.string(), + }), + outputSchema: z.object({ + result: z.string(), + }), + execute: async (inputData, context) => { + // Extract typed context + const requestContext = context?.requestContext as ToolNameContext + + // Access context values with type safety + const userId = requestContext?.userId + const workspaceId = requestContext?.workspaceId + const temperatureUnit = requestContext?.temperatureUnit ?? 'celsius' + const maxRows = requestContext?.maxRows ?? 1000 + + // Use context values in tool logic + log.info('Tool executed with context', { + userId, + workspaceId, + temperatureUnit, + maxRows, + }) + + return { result: 'success' } + }, +}) +``` + +### Benefits + +- **Type Safety**: Prevents typos and ensures correct property access +- **Documentation**: Interface serves as living documentation of context requirements +- **IntelliSense**: Better IDE support and autocomplete +- **Runtime Safety**: Catches context usage errors at compile time +- **Consistency**: Standardized pattern across all tools + +### Common Context Properties + +- **`userId`** (`string`): Current user identifier for user-specific data filtering +- **`workspaceId`** (`string`): Workspace/organization ID for multi-tenant data isolation +- **`temperatureUnit`** (`'celsius' | 'fahrenheit'`): Temperature display preference for weather tools +- **`maxRows`** (`number`): Maximum result limit for data processing tools +- **`locale`** (`string`): User locale/language for internationalization +- **`permissions`** (`string[]`): User permission array for authorization checks + +### Implementation Checklist + +When adding RequestContext to a tool: + +- [ ] Define interface extending `RequestContext` +- [ ] Include all context properties the tool uses +- [ ] Use optional properties (`?:`) for non-required context +- [ ] Cast context in execute function: `as ToolNameContext` +- [ ] Provide sensible defaults for optional properties +- [ ] Document context requirements in tool description if needed + ## Testing 97% coverage via Vitest. Run: diff --git a/src/mastra/tools/alpha-vantage.tool.ts b/src/mastra/tools/alpha-vantage.tool.ts index 9bccf592..dfaea24a 100644 --- a/src/mastra/tools/alpha-vantage.tool.ts +++ b/src/mastra/tools/alpha-vantage.tool.ts @@ -82,45 +82,6 @@ export const alphaVantageCryptoTool = createTool({ }) .optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('Alpha Vantage tool input streaming started', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputStart', - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('Alpha Vantage tool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputDelta', - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('Alpha Vantage crypto received input', { - toolCallId, - messageCount: messages.length, - symbol: input.symbol, - market: input.market, - function: input.function, - abortSignal: abortSignal?.aborted, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - const dataKeys = output.data ? Object.keys(output.data).length : 0 - log.info('Alpha Vantage crypto completed', { - toolCallId, - toolName, - symbol: output.metadata?.symbol ?? 'unknown', - dataKeys, - abortSignal: abortSignal?.aborted, - hook: 'onOutput', - }) - }, execute: async (inputData, context) => { const span = trace .getTracer('alpha-vantage-crypto-tool', '1.0.0') @@ -323,6 +284,45 @@ export const alphaVantageCryptoTool = createTool({ throw error instanceof Error ? error : new Error(errMsg) } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Alpha Vantage tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Alpha Vantage tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Alpha Vantage crypto received input', { + toolCallId, + messageCount: messages.length, + symbol: input.symbol, + market: input.market, + function: input.function, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + const dataKeys = output.data ? Object.keys(output.data).length : 0 + log.info('Alpha Vantage crypto completed', { + toolCallId, + toolName, + symbol: output.metadata?.symbol ?? 'unknown', + dataKeys, + abortSignal: abortSignal?.aborted, + hook: 'onOutput', + }) + }, }) /** @@ -410,53 +410,7 @@ export const alphaVantageStockTool = createTool({ }) .optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('alphaVantageStockTool tool input streaming started', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputStart' }); - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('alphaVantageStockTool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputDelta', - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('alphaVantageStockTool received input', { - toolCallId, - messageCount: messages.length, - inputData: { - function: input.function, - symbol: input.symbol, - interval: input.interval, - outputsize: input.outputsize, - datatype: input.datatype, - indicator: input.indicator, - time_period: input.time_period, - series_type: input.series_type, - }, - abortSignal: abortSignal?.aborted, - hook: 'onInputAvailable' - }); - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('alphaVantageStockTool completed', { - toolCallId, - toolName, - outputData: { - data: output.data, - metadata: output.metadata, - }, - abortSignal: abortSignal?.aborted, - hook: 'onOutput' - }); - }, execute: async (inputData, context) => { const span = trace .getTracer('alpha-vantage-stock-tool', '1.0.0') @@ -692,6 +646,44 @@ export const alphaVantageStockTool = createTool({ throw error instanceof Error ? error : new Error(errorMessage) } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Alpha Vantage stock tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Alpha Vantage stock tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Alpha Vantage stock received input', { + toolCallId, + messageCount: messages.length, + symbol: input.symbol, + function: input.function, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + const dataKeys = output.data ? Object.keys(output.data).length : 0 + log.info('Alpha Vantage stock completed', { + toolCallId, + toolName, + symbol: output.metadata?.symbol ?? 'unknown', + dataKeys, + abortSignal: abortSignal?.aborted, + hook: 'onOutput', + }) + }, }) /** @@ -811,53 +803,6 @@ export const alphaVantageTool = createTool({ }) .optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('alphaVantageTool tool input streaming started', { toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputStart' }); - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('alphaVantageTool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputDelta', - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('alphaVantageTool received input', { - toolCallId, - messageCount: messages.length, - inputData: { - function: input.function, - symbol: input.symbol, - market: input.market, - interval: input.interval, - outputsize: input.outputsize, - datatype: input.datatype, - indicator: input.indicator, - time_period: input.time_period, - series_type: input.series_type, - economic_indicator: input.economic_indicator, - }, - abortSignal: abortSignal?.aborted, - hook: 'onInputAvailable' - }); - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('alphaVantageTool completed', { - toolCallId, - toolName, - outputData: { - data: output.data, - metadata: output.metadata, - }, - abortSignal: abortSignal?.aborted, - hook: 'onOutput' - }); - }, execute: async (inputData, context) => { const span = trace .getTracer('alpha-vantage-tool', '1.0.0') @@ -1108,6 +1053,53 @@ export const alphaVantageTool = createTool({ throw error instanceof Error ? error : new Error(errorMessage) } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('alphaVantageTool tool input streaming started', { toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart' }); + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('alphaVantageTool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('alphaVantageTool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + function: input.function, + symbol: input.symbol, + market: input.market, + interval: input.interval, + outputsize: input.outputsize, + datatype: input.datatype, + indicator: input.indicator, + time_period: input.time_period, + series_type: input.series_type, + economic_indicator: input.economic_indicator, + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable' + }); + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('alphaVantageTool completed', { + toolCallId, + toolName, + outputData: { + data: output.data, + metadata: output.metadata, + }, + abortSignal: abortSignal?.aborted, + hook: 'onOutput' + }); + }, }) export type AlphaVantageCryptoUITool = InferUITool< diff --git a/src/mastra/tools/calculator.tool.ts b/src/mastra/tools/calculator.tool.ts index d8bb10a8..bcd07069 100644 --- a/src/mastra/tools/calculator.tool.ts +++ b/src/mastra/tools/calculator.tool.ts @@ -4,15 +4,15 @@ import { trace } from '@opentelemetry/api' import { z } from 'zod' import { log } from '../config/logger' -// Define the Zod schema for the runtime context -const calculatorToolContextSchema = z.object({ - precision: z.number().default(6), - allowComplexExpressions: z.boolean().default(true), - maxExpressionLength: z.number().default(1000), -}) +import type { RequestContext } from '@mastra/core/request-context' + +// RequestContext interface for Calculator tool +export interface CalculatorToolContext extends RequestContext { + precision?: number + allowComplexExpressions?: boolean + maxExpressionLength?: number +} -// Infer the TypeScript type from the Zod schema -export type CalculatorToolContext = z.infer // Enhanced mathematical functions and constants const MATH_CONSTANTS = { @@ -176,7 +176,7 @@ function convertUnits(value: number, fromUnit: string, toUnit: string): number { ['celsius', 'fahrenheit', 'kelvin'].includes(fromUnit) && ['celsius', 'fahrenheit', 'kelvin'].includes(toUnit) ) { - return convertTemperature(value, fromUnit as any, toUnit as any) + return convertTemperature(value, fromUnit as 'celsius' | 'fahrenheit' | 'kelvin', toUnit as 'celsius' | 'fahrenheit' | 'kelvin') } // Regular unit conversion @@ -319,57 +319,14 @@ export const calculatorTool = createTool({ variables: z.record(z.string(), z.number()).optional(), message: z.string().optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('Calculator tool input streaming started', { - toolCallId, - messageCount: messages.length, - hook: 'onInputStart', - abortSignal: abortSignal?.aborted, - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('Calculator tool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - hook: 'onInputDelta', - abortSignal: abortSignal?.aborted, - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('Calculator tool received complete input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - inputData: { - expressionLength: input.expression.length, - variableCount: input.variables - ? Object.keys(input.variables).length - : 0, - }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('Calculator tool completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - outputData: { - success: output.success, - result: output.result, - }, - hook: 'onOutput', - }) - }, + execute: async (inputData, context) => { const writer = context?.writer - const requestContext = context?.requestContext - const { precision, allowComplexExpressions, maxExpressionLength } = - calculatorToolContextSchema.parse( - requestContext?.get('calculatorToolContext') - ) + const requestCtx = context?.requestContext as CalculatorToolContext | undefined + const precision = requestCtx?.precision ?? 6 + const allowComplexExpressions = requestCtx?.allowComplexExpressions ?? true + const maxExpressionLength = requestCtx?.maxExpressionLength ?? 1000 const tracer = trace.getTracer('calculator-tool', '1.0.0') const span = tracer.startSpan('calculate') @@ -392,9 +349,10 @@ export const calculatorTool = createTool({ ) } + const vars = (inputData.variables ?? {}) const result = evaluateExpression( inputData.expression, - (inputData.variables as Record) || {} + vars ) const actualPrecision = inputData.precision || precision const formattedResult = result.toFixed(actualPrecision) @@ -425,7 +383,9 @@ export const calculatorTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`Calculator evaluation failed: ${errorMsg}`) + log.error(`Calculator evaluation failed: ${errorMsg}`, { + error: errorMsg, + }) if (e instanceof Error) { span.recordException(e) @@ -443,6 +403,47 @@ export const calculatorTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Calculator tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Calculator tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Calculator tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + expression: input.expression, + variablesCount: Object.keys(input.variables ?? {}).length, + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('Calculator tool completed', { + toolCallId, + toolName, + outputData: { + success: output.success, + hasResult: output.result !== undefined, + }, + abortSignal: abortSignal?.aborted, + hook: 'onOutput', + }) + }, }) export const unitConverterTool = createTool({ @@ -539,7 +540,9 @@ export const unitConverterTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`Unit conversion failed: ${errorMsg}`) + log.error(`Unit conversion failed: ${errorMsg}`, { + error: errorMsg, + }) if (e instanceof Error) { span.recordException(e) @@ -559,6 +562,48 @@ export const unitConverterTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Unit Converter tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Unit Converter tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Unit Converter tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + value: input.value, + fromUnit: input.fromUnit, + toUnit: input.toUnit, + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('Unit Converter tool completed', { + toolCallId, + toolName, + outputData: { + success: output.success, + result: output.formattedResult, + }, + abortSignal: abortSignal?.aborted, + hook: 'onOutput', + }) + }, }) export const matrixCalculatorTool = createTool({ @@ -614,28 +659,31 @@ export const matrixCalculatorTool = createTool({ }) try { - let result: any + let result: number[][] | number switch (inputData.operation) { case 'add': - if (!inputData.matrixB) - {throw new Error('Matrix B required for addition')} + if (!inputData.matrixB) { + throw new Error('Matrix B required for addition') + } result = MATRIX_OPERATIONS.add( inputData.matrixA, inputData.matrixB ) break case 'subtract': - if (!inputData.matrixB) - {throw new Error('Matrix B required for subtraction')} + if (!inputData.matrixB) { + throw new Error('Matrix B required for subtraction') + } result = MATRIX_OPERATIONS.subtract( inputData.matrixA, inputData.matrixB ) break case 'multiply': - if (!inputData.matrixB) - {throw new Error('Matrix B required for multiplication')} + if (!inputData.matrixB) { + throw new Error('Matrix B required for multiplication') + } result = MATRIX_OPERATIONS.multiply( inputData.matrixA, inputData.matrixB @@ -699,7 +747,9 @@ export const matrixCalculatorTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`Matrix operation failed: ${errorMsg}`) + log.error(`Matrix operation failed: ${errorMsg}`, { + error: errorMsg, + }) if (e instanceof Error) { span.recordException(e) @@ -730,6 +780,47 @@ export const matrixCalculatorTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Matrix Calculator tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Matrix Calculator tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Matrix Calculator tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + operation: input.operation, + matrixADims: [input.matrixA.length, input.matrixA[0]?.length || 0], + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('Matrix Calculator tool completed', { + toolCallId, + toolName, + outputData: { + success: output.success, + operation: output.operation, + }, + abortSignal: abortSignal?.aborted, + hook: 'onOutput', + }) + }, }) // Helper functions @@ -737,7 +828,9 @@ function factorial(n: number): number { if (n < 0 || !Number.isInteger(n)) { throw new Error('Factorial requires a non-negative integer') } - if (n === 0 || n === 1) {return 1} + if (n === 0 || n === 1) { + return 1 + } let result = 1 for (let i = 2; i <= n; i++) { result *= i @@ -749,8 +842,12 @@ function fibonacci(n: number): number { if (n < 0 || !Number.isInteger(n)) { throw new Error('Fibonacci requires a non-negative integer') } - if (n === 0) {return 0} - if (n === 1) {return 1} + if (n === 0) { + return 0 + } + if (n === 1) { + return 1 + } let a = 0, b = 1 for (let i = 2; i <= n; i++) { @@ -788,11 +885,19 @@ function isPrime(n: number): boolean { if (!Number.isInteger(n)) { throw new Error('Prime check requires an integer') } - if (n <= 1) {return false} - if (n <= 3) {return true} - if (n % 2 === 0 || n % 3 === 0) {return false} + if (n <= 1) { + return false + } + if (n <= 3) { + return true + } + if (n % 2 === 0 || n % 3 === 0) { + return false + } for (let i = 5; i * i <= n; i += 6) { - if (n % i === 0 || n % (i + 2) === 0) {return false} + if (n % i === 0 || n % (i + 2) === 0) { + return false + } } return true } @@ -825,9 +930,12 @@ function permutations(n: number, k: number): number { function calculateDeterminant(matrix: number[][]): number { const n = matrix.length - if (n === 1) {return matrix[0][0]} - if (n === 2) - {return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]} + if (n === 1) { + return matrix[0][0] + } + if (n === 2) { + return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0] + } let det = 0 for (let i = 0; i < n; i++) { diff --git a/src/mastra/tools/color-change-tool.ts b/src/mastra/tools/color-change-tool.ts index c38b926b..a157912e 100644 --- a/src/mastra/tools/color-change-tool.ts +++ b/src/mastra/tools/color-change-tool.ts @@ -14,6 +14,32 @@ export const colorChangeTool = createTool({ inputSchema: z.object({ color: z.string(), }), + execute: async ({ color }, context) => { + await context?.writer?.custom({ + type: 'data-tool-progress', + data: { + status: 'in-progress', + message: `🎨 Changing background color to ${color}`, + stage: 'changeColor', + }, + id: 'changeColor', + }) + + // On the server, we just return the color. + // The client-side UI will handle the actual style change when it receives the tool result or call. + + await context?.writer?.custom({ + type: 'data-tool-progress', + data: { + status: 'done', + message: `✅ Background color changed to ${color}`, + stage: 'changeColor', + }, + id: 'changeColor', + }) + + return { success: true, color } + }, onInputStart: ({ toolCallId, messages, abortSignal }) => { log.info('Color change tool input streaming started', { toolCallId, @@ -50,30 +76,4 @@ export const colorChangeTool = createTool({ hook: 'onOutput', }) }, - execute: async ({ color }, context) => { - await context?.writer?.custom({ - type: 'data-tool-progress', - data: { - status: 'in-progress', - message: `🎨 Changing background color to ${color}`, - stage: 'changeColor', - }, - id: 'changeColor', - }) - - // On the server, we just return the color. - // The client-side UI will handle the actual style change when it receives the tool result or call. - - await context?.writer?.custom({ - type: 'data-tool-progress', - data: { - status: 'done', - message: `✅ Background color changed to ${color}`, - stage: 'changeColor', - }, - id: 'changeColor', - }) - - return { success: true, color } - }, }) diff --git a/src/mastra/tools/copywriter-agent-tool.ts b/src/mastra/tools/copywriter-agent-tool.ts index e68d82ad..0e63328c 100644 --- a/src/mastra/tools/copywriter-agent-tool.ts +++ b/src/mastra/tools/copywriter-agent-tool.ts @@ -67,54 +67,6 @@ export const copywriterTool = createTool({ .optional() .describe('Approximate word count of the content'), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('copywriterTool tool input streaming started', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputStart', - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('copywriterTool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputDelta', - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('copywriterTool received input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - inputData: { - topic: input.topic, - contentType: input.contentType, - targetAudience: input.targetAudience, - tone: input.tone, - length: input.length, - specificRequirements: input.specificRequirements, - }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('copywriterTool completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - outputData: { - contentType: output.contentType, - title: output.title, - summary: output.summary, - keyPoints: output.keyPoints, - wordCount: output.wordCount, - }, - hook: 'onOutput', - }) - }, execute: async (inputData, context) => { const writer = context?.writer const mastra = context?.mastra @@ -424,6 +376,54 @@ export const copywriterTool = createTool({ throw new Error(`Failed to generate content: ${errorMsg}`) } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('copywriterTool tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('copywriterTool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('copywriterTool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + topic: input.topic, + contentType: input.contentType, + targetAudience: input.targetAudience, + tone: input.tone, + length: input.length, + specificRequirements: input.specificRequirements, + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('copywriterTool completed', { + toolCallId, + toolName, + outputData: { + content: output.content, + title: output.title, + summary: output.summary, + keyPoints: output.keyPoints, + wordCount: output.wordCount, + }, + abortSignal: abortSignal?.aborted, + hook: 'onOutput', + }) + }, }) // InferUITools expects a ToolSet (an object mapping ids to tools), not a single Tool. diff --git a/src/mastra/tools/datetime.tool.ts b/src/mastra/tools/datetime.tool.ts index 9c8cc908..0e94997c 100644 --- a/src/mastra/tools/datetime.tool.ts +++ b/src/mastra/tools/datetime.tool.ts @@ -4,15 +4,13 @@ import { trace } from '@opentelemetry/api' import { z } from 'zod' import { log } from '../config/logger' -// Define the Zod schema for the runtime context -const dateTimeToolContextSchema = z.object({ - defaultTimezone: z.string().default('UTC'), - defaultLocale: z.string().default('en-US'), - allowFutureDates: z.boolean().default(true), -}) +import type { RequestContext } from '@mastra/core/request-context' -// Infer the TypeScript type from the Zod schema -export type DateTimeToolContext = z.infer +export interface DateTimeToolContext extends RequestContext { + defaultTimezone?: string + defaultLocale?: string + allowFutureDates?: boolean +} export const dateTimeTool = createTool({ id: 'datetime', @@ -89,56 +87,20 @@ export const dateTimeTool = createTool({ input: z.string().optional(), message: z.string().optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('DateTime tool input streaming started', { - toolCallId, - messageCount: messages.length, - hook: 'onInputStart', - abortSignal: abortSignal?.aborted, - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('DateTime tool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - hook: 'onInputDelta', - abortSignal: abortSignal?.aborted, - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('DateTime tool received complete input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - inputData: { - operation: input.operation, - input: input.input, - format: input.format, - }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('DateTime tool completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - outputData: { - success: output.success, - operation: output.operation, - }, - hook: 'onOutput', - }) - }, + execute: async (inputData, context) => { const writer = context?.writer - const requestContext = context?.requestContext - - const { defaultTimezone, defaultLocale } = - dateTimeToolContextSchema.parse( - requestContext?.get('dateTimeToolContext') - ) + const requestCtx = context?.requestContext as DateTimeToolContext | undefined + const defaultTimezone = requestCtx?.defaultTimezone ?? 'UTC' + const defaultLocale = requestCtx?.defaultLocale ?? 'en-US' + const allowFutureDates = requestCtx?.allowFutureDates ?? true + + // Log extracted context values so linter doesn't flag them as unused + log.info('DateTime tool context', { + defaultTimezone, + defaultLocale, + allowFutureDates, + }) const tracer = trace.getTracer('datetime-tool', '1.0.0') const span = tracer.startSpan('datetime-operation', { @@ -316,7 +278,9 @@ export const dateTimeTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`DateTime operation failed: ${errorMsg}`) + log.error(`DateTime operation failed: ${errorMsg}`, { + error: errorMsg, + }) if (e instanceof Error) { span.recordException(e) @@ -333,6 +297,41 @@ export const dateTimeTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('DateTime tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('DateTime tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('DateTime tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { operation: input.operation }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('DateTime tool completed', { + toolCallId, + toolName, + abortSignal: abortSignal?.aborted, + outputData: { success: output.success, operation: output.operation }, + hook: 'onOutput', + }) + }, }) export const timeZoneTool = createTool({ @@ -456,7 +455,9 @@ export const timeZoneTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`Timezone operation failed: ${errorMsg}`) + log.error(`Timezone operation failed: ${errorMsg}`, { + error: errorMsg, + }) if (e instanceof Error) { span.recordException(e) @@ -472,6 +473,41 @@ export const timeZoneTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Timezone tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Timezone tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Timezone tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { operation: input.operation, timezone: input.timezone }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('Timezone tool completed', { + toolCallId, + toolName, + abortSignal: abortSignal?.aborted, + outputData: { success: output.success, operation: output.operation }, + hook: 'onOutput', + }) + }, }) // Helper functions @@ -602,10 +638,6 @@ function getAvailableTimezones(): string[] { function getTimezoneInfo(timezone: string) { const offset = getTimezoneOffset(timezone) - const now = new Date() - const utcTime = now.getTime() + now.getTimezoneOffset() * 60 * 1000 - const localTime = utcTime + offset * 60 * 60 * 1000 - const localDate = new Date(localTime) // Determine if DST (simplified - checks if offset differs from standard) const isDST = Math.abs(offset) > Math.abs(getTimezoneOffset(timezone)) diff --git a/src/mastra/tools/fs.ts b/src/mastra/tools/fs.ts index 68c8dbe1..d6590749 100644 --- a/src/mastra/tools/fs.ts +++ b/src/mastra/tools/fs.ts @@ -15,50 +15,6 @@ export const fsTool = createTool({ outputSchema: z.object({ message: z.string(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('FS tool input streaming started', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputStart', - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('FS tool received input chunk', { - toolCallId, - inputTextDelta, - abortSignal: abortSignal?.aborted, - messageCount: messages.length, - hook: 'onInputDelta', - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('FS tool received input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - action: input.action, - file: input.file, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - const isSuccess = !output.message.startsWith('Error:') - const logLevel = isSuccess ? 'info' : 'warn' - const message = - output.message.length > 100 - ? output.message.substring(0, 100) + '...' - : output.message - - log[logLevel]('FS tool operation completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - success: isSuccess, - message, - hook: 'onOutput', - }) - }, execute: async (inputData, context) => { const writer = context?.writer const abortSignal = context?.abortSignal @@ -85,7 +41,7 @@ export const fsTool = createTool({ }) try { // Check for cancellation before file operations - if (abortSignal && abortSignal.aborted) { + if (abortSignal?.aborted) { span?.setStatus({ code: 2, message: 'Operation cancelled during file operations', @@ -163,4 +119,48 @@ export const fsTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('FS tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('FS tool received input chunk', { + toolCallId, + inputTextDelta, + abortSignal: abortSignal?.aborted, + messageCount: messages.length, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('FS tool received input', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + action: input.action, + file: input.file, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + const isSuccess = !output.message.startsWith('Error:') + const logLevel = isSuccess ? 'info' : 'warn' + const message = + output.message.length > 100 + ? output.message.substring(0, 100) + '...' + : output.message + + log[logLevel]('FS tool operation completed', { + toolCallId, + toolName, + abortSignal: abortSignal?.aborted, + success: isSuccess, + message, + hook: 'onOutput', + }) + }, }) diff --git a/src/mastra/tools/git-local.tool.ts b/src/mastra/tools/git-local.tool.ts index fd132402..616edd01 100644 --- a/src/mastra/tools/git-local.tool.ts +++ b/src/mastra/tools/git-local.tool.ts @@ -6,17 +6,15 @@ import execa from 'execa' import { z } from 'zod' import { log } from '../config/logger' -// Define the Zod schema for the runtime context -const gitToolContextSchema = z.object({ - defaultBranch: z.string().default('main'), - allowForce: z.boolean().default(false), - allowAmend: z.boolean().default(false), - maxCommits: z.number().default(100), - timeout: z.number().default(30000), -}) - -// Infer the TypeScript type from the Zod schema -export type GitToolContext = z.infer +import type { RequestContext } from '@mastra/core/request-context' + +export interface GitToolContext extends RequestContext { + defaultBranch?: string + allowForce?: boolean + allowAmend?: boolean + maxCommits?: number + timeout?: number +} // Enhanced Git Status Tool with more detailed information export const gitStatusTool = createTool({ @@ -64,54 +62,11 @@ export const gitStatusTool = createTool({ conflictedFiles: z.array(z.string()), message: z.string().optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('Git status tool input streaming started', { - toolCallId, - messageCount: messages.length, - hook: 'onInputStart', - abortSignal: abortSignal?.aborted, - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('Git status tool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - hook: 'onInputDelta', - abortSignal: abortSignal?.aborted, - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('Git status tool received complete input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - inputData: { repoPath: input.repoPath, porcelain: input.porcelain }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('Git status tool completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - outputData: { - success: output.success, - isClean: output.isClean, - stagedCount: output.stagedFiles.length, - unstagedCount: output.unstagedFiles.length, - untrackedCount: output.untrackedFiles.length, - }, - hook: 'onOutput', - }) - }, + execute: async (inputData, context) => { const writer = context?.writer - const requestContext = context?.requestContext - - const { timeout } = gitToolContextSchema.parse( - requestContext?.get('gitToolContext') - ) + const requestCtx = context?.requestContext as GitToolContext | undefined + const timeout = requestCtx?.timeout ?? 30000 const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-status') @@ -279,6 +234,47 @@ export const gitStatusTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Git status tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Git status tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Git status tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { repoPath: input.repoPath, porcelain: input.porcelain }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('Git status tool completed', { + toolCallId, + toolName, + abortSignal: abortSignal?.aborted, + outputData: { + success: output.success, + isClean: output.isClean, + stagedCount: output.stagedFiles.length, + unstagedCount: output.unstagedFiles.length, + untrackedCount: output.untrackedFiles.length, + }, + hook: 'onOutput', + }) + }, }) // Enhanced Git Diff Tool with better formatting and options @@ -419,7 +415,10 @@ export const gitDiffTool = createTool({ .filter((line) => line.trim()) if (lines.length > 0) { const summaryLine = lines[lines.length - 1] - const match = /(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/.exec(summaryLine) + const match = + /(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/.exec( + summaryLine + ) if (match) { stats = { files: parseInt(match[1]) || 0, @@ -466,7 +465,7 @@ export const gitDiffTool = createTool({ id: 'git:diff', }) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-diff') span.setAttributes({ 'tool.output.success': true, @@ -484,7 +483,7 @@ export const gitDiffTool = createTool({ const errorMsg = e instanceof Error ? e.message : String(e) log.error(`Git diff failed: ${errorMsg}`) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-diff') if (e instanceof Error) { span.recordException(e) @@ -545,9 +544,9 @@ export const gitCommitTool = createTool({ const writer = context?.writer const requestContext = context?.requestContext - const { allowAmend, timeout } = gitToolContextSchema.parse( - requestContext?.get('gitToolContext') - ) + const requestCtx = context?.requestContext as GitToolContext | undefined + const allowAmend = requestCtx?.allowAmend ?? false + const timeout = requestCtx?.timeout ?? 30000 await writer?.custom({ type: 'data-tool-progress', @@ -649,7 +648,7 @@ export const gitCommitTool = createTool({ id: 'git:commit', }) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-commit') span.setAttributes({ 'tool.output.success': true, @@ -667,7 +666,7 @@ export const gitCommitTool = createTool({ const errorMsg = e instanceof Error ? e.message : String(e) log.error(`Git commit failed: ${errorMsg}`) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-commit') if (e instanceof Error) { span.recordException(e) @@ -771,9 +770,9 @@ export const gitLogTool = createTool({ const writer = context?.writer const requestContext = context?.requestContext - const { maxCommits, timeout } = gitToolContextSchema.parse( - requestContext?.get('gitToolContext') - ) + const requestCtx = context?.requestContext as GitToolContext | undefined + const maxCommits = requestCtx?.maxCommits ?? 100 + const timeout = requestCtx?.timeout ?? 30000 await writer?.custom({ type: 'data-tool-progress', @@ -865,7 +864,17 @@ export const gitLogTool = createTool({ const [hash, author, date, ...messageParts] = line.split('|') if (hash && author && date) { - const commit = { + const commit: { + hash: string + author: string + date: string + message: string + stats?: { + insertions?: number + deletions?: number + files?: number + } + } = { hash, author, date, @@ -885,7 +894,10 @@ export const gitLogTool = createTool({ } ) const statOutput = statResult.stdout || '' - const statMatch = /(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/.exec(statOutput) + const statMatch = + /(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/.exec( + statOutput + ) if (statMatch) { commit.stats = { files: parseInt(statMatch[1]), @@ -893,7 +905,7 @@ export const gitLogTool = createTool({ deletions: parseInt(statMatch[3]) || 0, } } - } catch (statError) { + } catch { // Ignore stat errors } } @@ -928,7 +940,7 @@ export const gitLogTool = createTool({ // Sum insertions and deletions if (commit.stats) { totalInsertions += commit.stats.insertions ?? 0 - totalDeletions += commit.stats.deletions || 0 + totalDeletions += commit.stats.deletions ?? 0 } } @@ -952,7 +964,7 @@ export const gitLogTool = createTool({ id: 'git:log', }) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-log') span.setAttributes({ 'tool.output.success': true, @@ -971,7 +983,7 @@ export const gitLogTool = createTool({ const errorMsg = e instanceof Error ? e.message : String(e) log.error(`Git log failed: ${errorMsg}`) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-log') if (e instanceof Error) { span.recordException(e) @@ -1346,7 +1358,7 @@ export const gitBranchTool = createTool({ id: 'git:branch', }) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-branch') span.setAttributes({ 'tool.output.success': true, @@ -1362,7 +1374,7 @@ export const gitBranchTool = createTool({ const errorMsg = e instanceof Error ? e.message : String(e) log.error(`Git branch failed: ${errorMsg}`) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-branch') if (e instanceof Error) { span.recordException(e) @@ -1668,7 +1680,7 @@ export const gitStashTool = createTool({ id: 'git:stash', }) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-stash') span.setAttributes({ 'tool.output.success': true, @@ -1684,7 +1696,7 @@ export const gitStashTool = createTool({ const errorMsg = e instanceof Error ? e.message : String(e) log.error(`Git stash failed: ${errorMsg}`) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-stash') if (e instanceof Error) { span.recordException(e) @@ -1732,7 +1744,7 @@ export const gitConfigTool = createTool({ }), outputSchema: z.object({ success: z.boolean(), - config: z.record(z.string()).optional(), + config: z.record(z.string(), z.string()).optional(), value: z.string().optional(), validation: z .object({ @@ -1763,7 +1775,7 @@ export const gitConfigTool = createTool({ }) try { - const cwd = inputData.repoPath || process.cwd() + const cwd = inputData.repoPath ?? process.cwd() let result: any = {} switch (inputData.operation) { @@ -1925,7 +1937,7 @@ export const gitConfigTool = createTool({ id: 'git:config', }) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-config') span.setAttributes({ 'tool.output.success': true, @@ -1941,7 +1953,7 @@ export const gitConfigTool = createTool({ const errorMsg = e instanceof Error ? e.message : String(e) log.error(`Git config failed: ${errorMsg}`) - const tracer = trace.getTracer('git-tool') + const tracer = trace.getTracer('git-tool', '1.0.0') const span = tracer.startSpan('git-config') if (e instanceof Error) { span.recordException(e) @@ -1963,16 +1975,17 @@ function validateConfigValue(value: string, expectedType: string) { let actualType = 'string' switch (expectedType) { - case 'int': - { const intVal = parseInt(value) + case 'int': { + const intVal = parseInt(value) if (isNaN(intVal)) { errors.push('Value is not a valid integer') } else { actualType = 'int' } - break } - case 'bool': - { const boolVal = value.toLowerCase() + break + } + case 'bool': { + const boolVal = value.toLowerCase() if ( !['true', 'false', '1', '0', 'yes', 'no', 'on', 'off'].includes( boolVal @@ -1982,7 +1995,8 @@ function validateConfigValue(value: string, expectedType: string) { } else { actualType = 'bool' } - break } + break + } case 'string': default: actualType = 'string' diff --git a/src/mastra/tools/jwt-auth.tool.ts b/src/mastra/tools/jwt-auth.tool.ts index 8a079f6e..95d9a3ee 100644 --- a/src/mastra/tools/jwt-auth.tool.ts +++ b/src/mastra/tools/jwt-auth.tool.ts @@ -23,43 +23,6 @@ export const jwtAuthTool = createTool({ exp: z.number().optional(), iat: z.number().optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('JWT auth tool input streaming started', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - hook: 'onInputStart', - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('JWT auth tool received input chunk', { - toolCallId, - inputTextDelta, - abortSignal: abortSignal?.aborted, - messageCount: messages.length, - hook: 'onInputDelta', - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('JWT auth received complete input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - note: 'no parameters required', - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('JWT auth completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - user: output.sub, - roles: output.roles, - tenant: output.tenant || 'none', - hook: 'onOutput', - }) - }, execute: async (inputData, context) => { const writer = context?.writer const requestContext = context?.requestContext @@ -102,7 +65,7 @@ export const jwtAuthTool = createTool({ try { // Check for cancellation before JWT verification - if (abortSignal && abortSignal.aborted) { + if (abortSignal?.aborted) { span?.setStatus({ code: 2, message: 'Operation cancelled during JWT verification', @@ -159,4 +122,41 @@ export const jwtAuthTool = createTool({ throw new Error('JWT verification failed: Unknown error') } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('JWT auth tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('JWT auth tool received input chunk', { + toolCallId, + inputTextDelta, + abortSignal: abortSignal?.aborted, + messageCount: messages.length, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('JWT auth received complete input', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + note: 'no parameters required', + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('JWT auth completed', { + toolCallId, + toolName, + abortSignal: abortSignal?.aborted, + user: output.sub, + roles: output.roles, + tenant: output.tenant ?? 'none', + hook: 'onOutput', + }) + }, }) diff --git a/src/mastra/tools/polygon-tools.ts b/src/mastra/tools/polygon-tools.ts index a2de9d4e..9466f3b4 100644 --- a/src/mastra/tools/polygon-tools.ts +++ b/src/mastra/tools/polygon-tools.ts @@ -118,7 +118,7 @@ export const polygonStockQuotesTool = createTool({ }, onOutput: ({ output, toolCallId, toolName, abortSignal }) => { const hasError = !!output.error - const dataPoints = output.metadata?.count || 0 + const dataPoints = output.metadata?.count ?? 0 log[hasError ? 'warn' : 'info']('Polygon stock quotes completed', { toolCallId, toolName, @@ -958,7 +958,7 @@ export const polygonStockFundamentalsTool = createTool({ }) // Check for cancellation before API call - if (abortSignal && abortSignal.aborted) { + if (abortSignal?.aborted) { rootSpan.setStatus({ code: 2, message: 'Operation cancelled during API call', @@ -1459,6 +1459,7 @@ export const polygonCryptoAggregatesTool = createTool({ error: z.string().optional(), }), execute: async (inputData, context) => { + const startTime = Date.now() const writer = context?.writer const requestContext = context?.requestContext @@ -1739,6 +1740,7 @@ export const polygonCryptoSnapshotsTool = createTool({ error: z.string().optional(), }), execute: async (inputData, context) => { + const startTime = Date.now() const writer = context?.writer const requestContext = context?.requestContext diff --git a/src/mastra/tools/random-generator.tool.ts b/src/mastra/tools/random-generator.tool.ts index 7f4e2ad4..97e5a4fb 100644 --- a/src/mastra/tools/random-generator.tool.ts +++ b/src/mastra/tools/random-generator.tool.ts @@ -2,7 +2,7 @@ import type { InferUITool } from '@mastra/core/tools' import { createTool } from '@mastra/core/tools' import { trace } from '@opentelemetry/api' import { z } from 'zod' -import { log } from '../config/logger' +import { log, logError } from '../config/logger' // Define the Zod schema for the runtime context const randomToolContextSchema = z.object({ @@ -74,53 +74,15 @@ export const randomGeneratorTool = createTool({ count: z.number(), message: z.string().optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('Random generator tool input streaming started', { - toolCallId, - messageCount: messages.length, - hook: 'onInputStart', - abortSignal: abortSignal?.aborted, - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('Random generator tool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - hook: 'onInputDelta', - abortSignal: abortSignal?.aborted, - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('Random generator tool received complete input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - inputData: { - type: input.type, - count: input.count, - }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('Random generator tool completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - outputData: { - success: output.success, - type: output.type, - count: output.count, - }, - hook: 'onOutput', - }) - }, execute: async (inputData, context) => { - const writer = context?.writer + const requestContext = context?.requestContext as RandomGeneratorRequestContext + const localeFromContext = requestContext?.locale ?? 'en' const tracer = trace.getTracer('random-generator-tool', '1.0.0') const span = tracer.startSpan('random-generate') + const writer = context?.writer + + await writer?.custom({ type: 'data-tool-progress', @@ -195,11 +157,11 @@ function generateRandomItem(type: string, options?: any): any { switch (type) { case 'string': return generateRandomString( - options?.length || 10, - options?.includeSpecial || false + options?.length ?? 10, + options?.includeSpecial ?? false ) case 'number': - return generateRandomNumber(options?.min || 0, options?.max || 100) + return generateRandomNumber(options?.min ?? 0, options?.max ?? 100) case 'boolean': return Math.random() > 0.5 case 'date': @@ -209,25 +171,22 @@ function generateRandomItem(type: string, options?: any): any { case 'email': return generateRandomEmail() case 'name': - return generateRandomName(options?.locale || 'en') + return generateRandomName(options?.locale ?? 'en') case 'address': - return generateRandomAddress(options?.locale || 'en') + return generateRandomAddress(options?.locale ?? 'en') case 'array': return generateRandomArray( - options?.length || 5, - options?.itemType || 'string' + options?.length ?? 5, + options?.itemType ?? 'string' ) case 'object': - return generateRandomObject(options?.properties || 3) + return generateRandomObject(options?.properties ?? 3) default: throw new Error(`Unknown type: ${type}`) } } -function generateRandomString( - length: number, - includeSpecial = false -): string { +function generateRandomString(length: number, includeSpecial = false): string { const chars = includeSpecial ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()' : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' @@ -259,14 +218,11 @@ function generateRandomDate(format?: string): string { } function generateUUID(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( - /[xy]/g, - (c) => { - const r = (Math.random() * 16) | 0 - const v = c === 'x' ? r : (r & 0x3) | 0x8 - return v.toString(16) - } - ) + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0 + const v = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) } function generateRandomEmail(): string { @@ -350,8 +306,8 @@ function generateRandomName(locale = 'en'): string { } const names1 = - firstNames[locale as keyof typeof firstNames] || firstNames.en - const names2 = lastNames[locale as keyof typeof lastNames] || lastNames.en + firstNames[locale as keyof typeof firstNames] ?? firstNames.en + const names2 = lastNames[locale as keyof typeof lastNames] ?? lastNames.en const first = names1[Math.floor(Math.random() * names1.length)] const last = names2[Math.floor(Math.random() * names2.length)] @@ -389,7 +345,7 @@ function generateRandomAddress(locale = 'en'): string { } const street = streets[Math.floor(Math.random() * streets.length)] - const cityList = cities[locale as keyof typeof cities] || cities.en + const cityList = cities[locale as keyof typeof cities] ?? cities.en const city = cityList[Math.floor(Math.random() * cityList.length)] const number = generateRandomNumber(1, 9999) diff --git a/src/mastra/tools/text-analysis.tool.ts b/src/mastra/tools/text-analysis.tool.ts index 255b42f6..f989cc75 100644 --- a/src/mastra/tools/text-analysis.tool.ts +++ b/src/mastra/tools/text-analysis.tool.ts @@ -4,16 +4,12 @@ import { trace } from '@opentelemetry/api' import { z } from 'zod' import { log } from '../config/logger' -// Define the Zod schema for the runtime context -const textAnalysisToolContextSchema = z.object({ - defaultLanguage: z.string().default('en'), - includeAdvancedMetrics: z.boolean().default(false), -}) +import type { RequestContext } from '@mastra/core/request-context' -// Infer the TypeScript type from the Zod schema -export type TextAnalysisToolContext = z.infer< - typeof textAnalysisToolContextSchema -> +export interface TextAnalysisToolContext extends RequestContext { + defaultLanguage?: string + includeAdvancedMetrics?: boolean +} export const textAnalysisTool = createTool({ id: 'text-analysis', @@ -64,56 +60,11 @@ export const textAnalysisTool = createTool({ textLength: z.number(), message: z.string().optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('Text analysis tool input streaming started', { - toolCallId, - messageCount: messages.length, - hook: 'onInputStart', - abortSignal: abortSignal?.aborted, - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('Text analysis tool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - hook: 'onInputDelta', - abortSignal: abortSignal?.aborted, - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('Text analysis tool received complete input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - inputData: { - textLength: input.text.length, - operationsCount: input.operations.length, - }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('Text analysis tool completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - outputData: { - success: output.success, - operationsCompleted: output.operations.length, - textLength: output.textLength, - }, - hook: 'onOutput', - }) - }, execute: async (inputData, context) => { const writer = context?.writer - const requestContext = context?.requestContext - - const { defaultLanguage, includeAdvancedMetrics } = - textAnalysisToolContextSchema.parse( - requestContext?.get('textAnalysisToolContext') - ) + const requestCtx = context?.requestContext as TextAnalysisToolContext | undefined + const defaultLanguage = requestCtx?.defaultLanguage ?? 'en' + const includeAdvancedMetrics = requestCtx?.includeAdvancedMetrics ?? false const tracer = trace.getTracer('text-analysis-tool', '1.0.0') const span = tracer.startSpan('text-analysis', { @@ -135,8 +86,14 @@ export const textAnalysisTool = createTool({ }) try { - const results: Record = {} + type TextAnalysisResult = + | string + | number + | { score: number; level: string; grade?: string } + | { polarity: number; subjectivity: number; confidence: number } + const results: Record = {} const language = inputData.language ?? defaultLanguage + const includeAdvancedMetricsFlag = includeAdvancedMetrics ?? false for (const operation of inputData.operations) { switch (operation) { @@ -156,7 +113,7 @@ export const textAnalysisTool = createTool({ ) break case 'sentiment': - if (includeAdvancedMetrics) { + if (includeAdvancedMetricsFlag) { results[operation] = analyzeSentiment( inputData.text, language @@ -200,7 +157,7 @@ export const textAnalysisTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`Text analysis failed: ${errorMsg}`) + log.error(`Text analysis failed: ${errorMsg}`, { error: errorMsg }) if (e instanceof Error) { span.recordException(e) @@ -217,6 +174,47 @@ export const textAnalysisTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Text analysis tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Text analysis tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Text analysis tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + textLength: input.text?.length ?? 0, + operationsCount: input.operations?.length ?? 0, + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('Text analysis tool completed', { + toolCallId, + toolName, + outputData: { + success: output.success, + operationsCompleted: output.operations?.length ?? 0, + }, + abortSignal: abortSignal?.aborted, + hook: 'onOutput', + }) + }, }) export const textProcessingTool = createTool({ @@ -327,7 +325,8 @@ export const textProcessingTool = createTool({ } // Apply options - if (inputData.options?.removeNewlines) { + const removeNewlinesFlag = inputData.options?.removeNewlines ?? false + if (removeNewlinesFlag) { processedText = processedText.replace(/\n/g, ' ') } @@ -364,7 +363,9 @@ export const textProcessingTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`Text processing failed: ${errorMsg}`) + log.error(`Text processing failed: ${errorMsg}`, { + error: errorMsg, + }) if (e instanceof Error) { span.recordException(e) @@ -407,6 +408,7 @@ function countParagraphs(text: string): number { } function calculateReadability(text: string, language: string) { + const words = countWords(text) const sentences = countSentences(text) @@ -457,6 +459,7 @@ function calculateReadability(text: string, language: string) { } function analyzeSentiment(text: string, language: string) { + // Simplified sentiment analysis (positive/negative word counting) const positiveWords = [ 'good', @@ -488,8 +491,12 @@ function analyzeSentiment(text: string, language: string) { let negativeCount = 0 for (const word of words) { - if (positiveWords.includes(word)) {positiveCount++} - if (negativeWords.includes(word)) {negativeCount++} + if (positiveWords.includes(word)) { + positiveCount++ + } + if (negativeWords.includes(word)) { + negativeCount++ + } } const polarity = positiveCount - negativeCount @@ -556,26 +563,42 @@ function detectLanguage(text: string): string { let frenchScore = 0 for (const word of words) { - if (englishWords.includes(word)) {englishScore++} - if (spanishWords.includes(word)) {spanishScore++} - if (frenchWords.includes(word)) {frenchScore++} + if (englishWords.includes(word)) { + englishScore++ + } + if (spanishWords.includes(word)) { + spanishScore++ + } + if (frenchWords.includes(word)) { + frenchScore++ + } } const maxScore = Math.max(englishScore, spanishScore, frenchScore) - if (maxScore === 0) {return 'unknown'} + if (maxScore === 0) { + return 'unknown' + } - if (englishScore === maxScore) {return 'en'} - if (spanishScore === maxScore) {return 'es'} + if (englishScore === maxScore) { + return 'en' + } + if (spanishScore === maxScore) { + return 'es' + } return 'fr' } function generateSummary(text: string): string { const words = text.split(/\s+/) - if (words.length <= 50) {return text} + if (words.length <= 50) { + return text + } // Simple extractive summary - take first and last sentences const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0) - if (sentences.length <= 2) {return text} + if (sentences.length <= 2) { + return text + } return ( sentences[0].trim() + diff --git a/src/mastra/tools/url-tool.ts b/src/mastra/tools/url-tool.ts index 0b48ad28..1c25085f 100644 --- a/src/mastra/tools/url-tool.ts +++ b/src/mastra/tools/url-tool.ts @@ -4,15 +4,13 @@ import { trace } from '@opentelemetry/api' import { z } from 'zod' import { log } from '../config/logger' -// Define the Zod schema for the runtime context -const urlToolContextSchema = z.object({ - defaultProtocol: z.string().default('https'), - allowLocalhost: z.boolean().default(false), - timeout: z.number().default(5000), -}) +import type { RequestContext } from '@mastra/core/request-context' -// Infer the TypeScript type from the Zod schema -export type UrlToolContext = z.infer +export interface UrlToolContext extends RequestContext { + defaultProtocol?: string + allowLocalhost?: boolean + timeout?: number +} export const urlValidationTool = createTool({ id: 'url-validation', @@ -44,7 +42,8 @@ export const urlValidationTool = createTool({ }), outputSchema: z.object({ success: z.boolean(), - results: z.record(z.string(), + results: z.record( + z.string(), z.union([ z.boolean(), z.string(), @@ -70,53 +69,14 @@ export const urlValidationTool = createTool({ originalUrl: z.string(), message: z.string().optional(), }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('URL validation tool input streaming started', { - toolCallId, - messageCount: messages.length, - hook: 'onInputStart', - abortSignal: abortSignal?.aborted, - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('URL validation tool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - hook: 'onInputDelta', - abortSignal: abortSignal?.aborted, - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('URL validation tool received complete input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - inputData: { - url: input.url, - operationsCount: input.operations.length, - }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('URL validation tool completed', { - toolCallId, - toolName, - abortSignal: abortSignal?.aborted, - outputData: { - success: output.success, - operationsCompleted: output.operations.length, - }, - hook: 'onOutput', - }) - }, execute: async (inputData, context) => { const writer = context?.writer - const requestContext = context?.requestContext - const { defaultProtocol, allowLocalhost, timeout } = - urlToolContextSchema.parse(requestContext?.get('urlToolContext')) + + const requestCtx = context?.requestContext as UrlToolContext | undefined + const defaultProtocol = requestCtx?.defaultProtocol ?? 'https' + const allowLocalhost = requestCtx?.allowLocalhost ?? false + const timeout = requestCtx?.timeout ?? 5000 const tracer = trace.getTracer('url-validation-tool', '1.0.0') const span = tracer.startSpan('url-validation') @@ -132,7 +92,26 @@ export const urlValidationTool = createTool({ }) try { - const results: Record = {} + type UrlValidationResult = + | boolean + | string + | { + protocol: string + hostname: string + port?: string + pathname: string + search: string + hash: string + query: Record + } + | { + status?: number + contentType?: string + contentLength?: number + lastModified?: string + title?: string + } + const results: Record = {} for (const operation of inputData.operations) { switch (operation) { @@ -161,8 +140,9 @@ export const urlValidationTool = createTool({ inputData.options?.userAgent ) break - case 'get-metadata': - if (inputData.options?.followRedirects) { + case 'get-metadata': { + const followRedirects = inputData.options?.followRedirects ?? false + if (followRedirects) { results[operation] = await getUrlMetadata( inputData.url, inputData.options?.timeout ?? timeout, @@ -173,6 +153,7 @@ export const urlValidationTool = createTool({ 'Metadata fetching disabled (followRedirects=false)' } break + } case 'shorten': results[operation] = await shortenUrl(inputData.url) break @@ -205,7 +186,7 @@ export const urlValidationTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`URL validation failed: ${errorMsg}`) + log.error(`URL validation failed: ${errorMsg}`, { error: errorMsg }) if (e instanceof Error) { span.recordException(e) @@ -222,6 +203,47 @@ export const urlValidationTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('URL validation tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('URL validation tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('URL validation tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + url: input.url, + operationsCount: input.operations.length, + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('URL validation tool completed', { + toolCallId, + toolName, + abortSignal: abortSignal?.aborted, + outputData: { + success: output.success, + operationsCompleted: output.operations.length, + }, + hook: 'onOutput', + }) + }, }) export const urlManipulationTool = createTool({ @@ -298,7 +320,10 @@ export const urlManipulationTool = createTool({ switch (operation) { case 'add-query': - if (inputData.parameters?.query) { + if ( + inputData.parameters && + 'query' in inputData.parameters + ) { currentUrl = addQueryParams( currentUrl, inputData.parameters.query as Record< @@ -309,7 +334,10 @@ export const urlManipulationTool = createTool({ } break case 'remove-query': - if (inputData.parameters?.queryKeys) { + if ( + inputData.parameters && + 'queryKeys' in inputData.parameters + ) { currentUrl = removeQueryParams( currentUrl, inputData.parameters.queryKeys as string[] @@ -317,7 +345,10 @@ export const urlManipulationTool = createTool({ } break case 'update-query': - if (inputData.parameters?.query) { + if ( + inputData.parameters && + 'query' in inputData.parameters + ) { currentUrl = updateQueryParams( currentUrl, inputData.parameters.query as Record< @@ -328,7 +359,10 @@ export const urlManipulationTool = createTool({ } break case 'add-path': - if (inputData.parameters?.path) { + if ( + inputData.parameters && + 'path' in inputData.parameters + ) { currentUrl = addPath( currentUrl, inputData.parameters.path as string @@ -336,7 +370,10 @@ export const urlManipulationTool = createTool({ } break case 'replace-path': - if (inputData.parameters?.path) { + if ( + inputData.parameters && + 'path' in inputData.parameters + ) { currentUrl = replacePath( currentUrl, inputData.parameters.path as string @@ -344,7 +381,10 @@ export const urlManipulationTool = createTool({ } break case 'add-fragment': - if (inputData.parameters?.fragment) { + if ( + inputData.parameters && + 'fragment' in inputData.parameters + ) { currentUrl = addFragment( currentUrl, inputData.parameters.fragment as string @@ -355,7 +395,10 @@ export const urlManipulationTool = createTool({ currentUrl = removeFragment(currentUrl) break case 'change-protocol': - if (inputData.parameters?.protocol) { + if ( + inputData.parameters && + 'protocol' in inputData.parameters + ) { currentUrl = changeProtocol( currentUrl, inputData.parameters.protocol as string @@ -363,7 +406,10 @@ export const urlManipulationTool = createTool({ } break case 'change-hostname': - if (inputData.parameters?.hostname) { + if ( + inputData.parameters && + 'hostname' in inputData.parameters + ) { currentUrl = changeHostname( currentUrl, inputData.parameters.hostname as string @@ -371,13 +417,18 @@ export const urlManipulationTool = createTool({ } break case 'change-port': - currentUrl = changePort( - currentUrl, - inputData.parameters?.port as - | string - | number - | undefined - ) + if ( + inputData.parameters && + 'port' in inputData.parameters + ) { + currentUrl = changePort( + currentUrl, + inputData.parameters.port as + | string + | number + | undefined + ) + } break default: throw new Error(`Unknown operation: ${operation}`) @@ -419,7 +470,9 @@ export const urlManipulationTool = createTool({ } } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) - log.error(`URL manipulation failed: ${errorMsg}`) + log.error(`URL manipulation failed: ${errorMsg}`, { + error: errorMsg, + }) if (e instanceof Error) { span.recordException(e) @@ -437,6 +490,47 @@ export const urlManipulationTool = createTool({ } } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('URL manipulation tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('URL manipulation tool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('URL manipulation tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + baseUrl: input.baseUrl, + operationsCount: input.operations.length, + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('URL manipulation tool completed', { + toolCallId, + toolName, + abortSignal: abortSignal?.aborted, + outputData: { + success: output.success, + resultUrl: output.resultUrl, + }, + hook: 'onOutput', + }) + }, }) // Helper functions @@ -488,7 +582,7 @@ function parseUrl(url: string) { function normalizeUrl(url: string, defaultProtocol = 'https'): string { try { // If no protocol, add default - if (!(/^https?:\/\//.exec(url))) { + if (!/^https?:\/\//.exec(url)) { url = `${defaultProtocol}://${url}` } @@ -530,10 +624,11 @@ async function checkUrlReachability( const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) + const headers = userAgent ? { 'User-Agent': userAgent } : undefined const response = await fetch(url, { method: 'HEAD', signal: controller.signal, - headers: userAgent ? { 'User-Agent': userAgent } : undefined, + headers, }) clearTimeout(timeoutId) @@ -543,11 +638,7 @@ async function checkUrlReachability( } } -async function getUrlMetadata( - url: string, - timeout = 5000, - userAgent?: string -) { +async function getUrlMetadata(url: string, timeout = 5000, userAgent?: string) { try { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) @@ -573,14 +664,15 @@ async function getUrlMetadata( } } -async function shortenUrl(url: string): Promise { +async function shortenUrl(_url: string): Promise { + void _url // For demo purposes, return a mock shortened URL // In a real implementation, you'd call a URL shortening service const mockShortId = Math.random().toString(36).substring(2, 8) return `https://short.ly/${mockShortId}` } -function addQueryParams(url: string, params: Record): string { +function addQueryParams(url: string, params: Record): string { const urlObj = new URL(url) Object.entries(params).forEach(([key, value]) => { urlObj.searchParams.set(key, String(value)) @@ -594,7 +686,7 @@ function removeQueryParams(url: string, keys: string[]): string { return urlObj.toString() } -function updateQueryParams(url: string, params: Record): string { +function updateQueryParams(url: string, params: Record): string { const urlObj = new URL(url) Object.entries(params).forEach(([key, value]) => { if (value === null || value === undefined) { @@ -657,29 +749,35 @@ function changePort(url: string, port?: string | number): string { function getOperationDescription( operation: string, - params?: Record + params?: Record ): string { switch (operation) { - case 'add-query': - return `Added query parameters: ${Object.keys(params?.query ?? {}).join(', ')}` - case 'remove-query': - return `Removed query parameters: ${((params?.queryKeys as string[]) || []).join(', ')}` - case 'update-query': - return `Updated query parameters: ${Object.keys(params?.query ?? {}).join(', ')}` + case 'add-query': { + const queryKeys = params && typeof params.query === 'object' && params.query !== null ? Object.keys(params.query as Record).join(', ') : '' + return `Added query parameters: ${queryKeys}` + } + case 'remove-query': { + const keys = params && 'queryKeys' in params && Array.isArray((params)['queryKeys']) ? ((params)['queryKeys'] as string[]).join(', ') : '' + return `Removed query parameters: ${keys}` + } + case 'update-query': { + const updated = params && typeof params.query === 'object' && params.query !== null ? Object.keys(params.query as Record).join(', ') : '' + return `Updated query parameters: ${updated}` + } case 'add-path': - return `Added path segment: ${(params?.path as string) || ''}` + return `Added path segment: ${params?.['path'] ?? ''}` case 'replace-path': - return `Replaced path with: ${(params?.path as string) || ''}` + return `Replaced path with: ${params?.['path'] ?? ''}` case 'add-fragment': - return `Added fragment: ${(params?.fragment as string) || ''}` + return `Added fragment: ${params?.['fragment'] ?? ''}` case 'remove-fragment': return 'Removed fragment' case 'change-protocol': - return `Changed protocol to: ${(params?.protocol as string) || ''}` + return `Changed protocol to: ${params?.['protocol'] ?? ''}` case 'change-hostname': - return `Changed hostname to: ${(params?.hostname as string) || ''}` + return `Changed hostname to: ${params?.['hostname'] ?? ''}` case 'change-port': - return `Changed port to: ${params?.port ?? 'default'}` + return `Changed port to: ${params?.['port'] ?? 'default'}` default: return operation } diff --git a/src/mastra/tools/weather-tool.ts b/src/mastra/tools/weather-tool.ts index 289fe1e5..c74d3f6e 100644 --- a/src/mastra/tools/weather-tool.ts +++ b/src/mastra/tools/weather-tool.ts @@ -5,13 +5,11 @@ import { trace } from '@opentelemetry/api' import { z } from 'zod' import { log } from '../config/logger' -// Define the Zod schema for the runtime context -const weatherToolContextSchema = z.object({ - temperatureUnit: z.enum(['celsius', 'fahrenheit']).default('celsius'), -}) +export interface WeatherToolContext extends RequestContext { + temperatureUnit?: 'celsius' | 'fahrenheit' +} -// Infer the TypeScript type from the Zod schema -export type WeatherToolContext = z.infer +export type WeatherToolContextType = WeatherToolContext interface GeocodingResponse extends RequestContext { results?: Array<{ @@ -48,53 +46,13 @@ export const weatherTool = createTool({ location: z.string(), unit: z.string(), // Add unit to output schema }), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('Weather tool input streaming started', { - toolCallId, - messageCount: messages.length, - hook: 'onInputStart', - abortSignal: abortSignal?.aborted, - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('Weather tool received input chunk', { - toolCallId, - inputTextDelta, - abortSignal: abortSignal?.aborted, - messageCount: messages.length, - hook: 'onInputDelta', - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('Weather tool received complete input', { - toolCallId, - messageCount: messages.length, - abortSignal: abortSignal?.aborted, - inputData: { location: input.location }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('Weather tool completed', { - toolCallId, - toolName, - outputData: { - location: output.location, - temperature: output.temperature, - unit: output.unit, - conditions: output.conditions, - }, - hook: 'onOutput', - abortSignal: abortSignal?.aborted, - }) - }, + execute: async (inputData, context) => { const writer = context?.writer - const requestContext = context?.requestContext const abortSignal = context?.abortSignal // Check if operation was already cancelled - if (abortSignal?.aborted) { + if (abortSignal?.aborted === true) { throw new Error('Weather lookup cancelled') } @@ -108,9 +66,8 @@ export const weatherTool = createTool({ id: 'get-weather', }) - const { temperatureUnit } = weatherToolContextSchema.parse( - requestContext?.get('weatherToolContext') - ) + const requestCtx = context?.requestContext as WeatherToolContext | undefined + const temperatureUnit = requestCtx?.temperatureUnit ?? 'celsius' log.info( `Fetching weather for location: ${inputData.location} in ${temperatureUnit}` @@ -233,6 +190,46 @@ export const weatherTool = createTool({ throw error } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('Weather tool input streaming started', { + toolCallId, + messageCount: messages.length, + hook: 'onInputStart', + abortSignal: abortSignal?.aborted, + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('Weather tool received input chunk', { + toolCallId, + inputTextDelta, + abortSignal: abortSignal?.aborted, + messageCount: messages.length, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('Weather tool received input', { + toolCallId, + messageCount: messages.length, + inputData: { location: input.location }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('Weather tool completed', { + toolCallId, + toolName, + outputData: { + location: output.location, + temperature: output.temperature, + unit: output.unit, + conditions: output.conditions, + }, + hook: 'onOutput', + abortSignal: abortSignal?.aborted, + }) + }, }) const getWeather = async ( @@ -241,7 +238,7 @@ const getWeather = async ( abortSignal?: AbortSignal ) => { const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1` - const geocodingResponse = await fetch(geocodingUrl) + const geocodingResponse = await fetch(geocodingUrl, { signal: abortSignal }) const geocodingData = (await geocodingResponse.json()) as GeocodingResponse if (!geocodingData.results?.[0]) { @@ -252,7 +249,7 @@ const getWeather = async ( const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code&temperature_unit=${unit}` - const response = await fetch(weatherUrl) + const response = await fetch(weatherUrl, { signal: abortSignal }) const data = (await response.json()) as WeatherResponse return { diff --git a/src/mastra/tools/web-scraper-tool.ts b/src/mastra/tools/web-scraper-tool.ts index ead538d2..8a30d2c4 100644 --- a/src/mastra/tools/web-scraper-tool.ts +++ b/src/mastra/tools/web-scraper-tool.ts @@ -1091,7 +1091,7 @@ export const webScraperTool = createTool({ }) // Check for cancellation before crawling - if (abortSignal && abortSignal.aborted) { + if (abortSignal?.aborted) { throw new Error('Web scraping cancelled during crawling') } diff --git a/src/mastra/tools/write-note.ts b/src/mastra/tools/write-note.ts index aef2f627..d07a133a 100644 --- a/src/mastra/tools/write-note.ts +++ b/src/mastra/tools/write-note.ts @@ -22,41 +22,8 @@ export const writeNoteTool = createTool({ .describe('The markdown content of the note.'), }), outputSchema: z.string().nonempty(), - onInputStart: ({ toolCallId, messages, abortSignal }) => { - log.info('writeNoteTool tool input streaming started', { - toolCallId, - messageCount: messages.length, - hook: 'onInputStart', - }) - }, - onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { - log.info('writeNoteTool received input chunk', { - toolCallId, - inputTextDelta, - messageCount: messages.length, - hook: 'onInputDelta', - }) - }, - onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { - log.info('writeNoteTool received input', { - toolCallId, - messageCount: messages.length, - inputData: { - title: input.title, - content: input.content, - }, - hook: 'onInputAvailable', - }) - }, - onOutput: ({ output, toolCallId, toolName, abortSignal }) => { - log.info('writeNoteTool completed', { - toolCallId, - toolName, - outputData: output, - hook: 'onOutput', - }) - }, execute: async (inputData) => { + const startTime = Date.now() // Get tracer from OpenTelemetry API @@ -99,6 +66,44 @@ export const writeNoteTool = createTool({ return `Error writing note: ${errorMessage}` } }, + onInputStart: ({ toolCallId, messages, abortSignal }) => { + log.info('writeNoteTool tool input streaming started', { + toolCallId, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputStart', + }) + }, + onInputDelta: ({ inputTextDelta, toolCallId, messages, abortSignal }) => { + log.info('writeNoteTool received input chunk', { + toolCallId, + inputTextDelta, + messageCount: messages.length, + abortSignal: abortSignal?.aborted, + hook: 'onInputDelta', + }) + }, + onInputAvailable: ({ input, toolCallId, messages, abortSignal }) => { + log.info('writeNoteTool received input', { + toolCallId, + messageCount: messages.length, + inputData: { + title: input.title, + content: input.content, + }, + abortSignal: abortSignal?.aborted, + hook: 'onInputAvailable', + }) + }, + onOutput: ({ output, toolCallId, toolName, abortSignal }) => { + log.info('writeNoteTool completed', { + toolCallId, + toolName, + outputData: output, + abortSignal: abortSignal?.aborted, + hook: 'onOutput', + }) + }, }) export type WriteNoteUITool = InferUITool