Skip to content

Develop#76

Merged
ssdeanx merged 2 commits intomainfrom
develop
Jan 7, 2026
Merged

Develop#76
ssdeanx merged 2 commits intomainfrom
develop

Conversation

@ssdeanx
Copy link
Owner

@ssdeanx ssdeanx commented Jan 7, 2026

No description provided.

- Introduced `text-analysis.tool.ts` for analyzing text with operations such as word count, sentence count, readability, sentiment analysis, language detection, and summarization.
- Implemented `text-processing.tool.ts` for processing and transforming text with operations like lowercase, uppercase, trim, and extracting information (numbers, emails, URLs).
- Added helper functions for text analysis including counting words, sentences, paragraphs, calculating readability, analyzing sentiment, detecting language, generating summaries, and extracting various data types.
- Created `url-tool.ts` which includes `urlValidationTool` for validating, parsing, normalizing, and analyzing URLs with operations like checking reachability and fetching metadata.
- Added `urlManipulationTool` for manipulating URLs by adding, removing, or updating query parameters, paths, and fragments.
- Implemented context schemas using Zod for both tools to define default settings and options.
- Enhanced logging for input and output stages of both tools to improve traceability and debugging.
Copilot AI review requested due to automatic review settings January 7, 2026 18:39
@continue
Copy link

continue bot commented Jan 7, 2026

All Green - Keep your PRs mergeable

Learn more

All Green is an AI agent that automatically:

✅ Addresses code review comments

✅ Fixes failing CI checks

✅ Resolves merge conflicts


Unsubscribe from All Green comments

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@github-actions
Copy link

github-actions bot commented Jan 7, 2026

🤖 Hi @ssdeanx, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

Caution

Review failed

The pull request is closed.

Summary by CodeRabbit

  • New Features

    • Added calculator tool with expression evaluation, unit conversion, and matrix operations
    • Added datetime and timezone management tools for date/time operations
    • Added comprehensive Git tools for status, diffs, commits, logs, branches, and config management
    • Added random data generator tool for testing
    • Added text analysis and processing tools for content metrics and transformations
  • Improvements

    • Enhanced existing tools with streaming input support and real-time progress reporting
    • Improved error handling and cancellation support across all tools
    • Better handling of API failures and timeouts with graceful degradation
  • Documentation

    • Updated tool documentation with complete lifecycle monitoring coverage

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

This PR introduces comprehensive streaming and cancellation support across the Mastra tools ecosystem, adding lifecycle hooks (onInputDelta, onInputStart, onInputAvailable, onOutput) for input/output observability, implementing AbortSignal-based cancellation handling, integrating OpenTelemetry tracing with progress events, and adding four new tool implementations (calculator, datetime, git operations, text analysis). It also bumps several package dependencies to newer patch versions.

Changes

Cohort / File(s) Summary
Dependency Bumps
package.json
Updated versions: @ai-sdk/google-vertex (^4.0.7 → ^4.0.11), @ai-sdk/react (^3.0.13 → ^3.0.19), framer-motion (^12.24.7 → ^12.24.10), motion (^12.24.7 → ^12.24.10), react-resizable-panels (^4.2.2 → ^4.3.0), shiki (^3.20.0 → ^3.21.0)
Documentation Update
src/mastra/tools/AGENTS.md
Version updated to 2.15.0; claims all 40+ tools now have complete lifecycle hook coverage (onInputStart, onInputDelta, onInputAvailable, onOutput); added Hook Generator Utility section with example usage
ArXiv Tools Enhancement
src/mastra/tools/arxiv.tool.ts
Expanded arxivTool, arxivPdfParserTool, and arxivPaperDownloaderTool with streaming hooks, cancellation support (abortSignal checks), OpenTelemetry tracing, progress events, enhanced error handling; updated parseArxivXml signature with explicit return type; added query construction for author/title/category/id search
Browser Tool
src/mastra/tools/browser-tool.ts
Added onInputDelta hook; introduced AbortSignal-based cancellation with early checks; enhanced AbortError handling with span status updates and cancellation progress events
Calculator Tools (New)
src/mastra/tools/calculator.tool.ts
New tool module with calculatorTool (expression evaluation), unitConverterTool (unit conversions), matrixCalculatorTool (matrix operations); includes runtime context schema, lifecycle hooks, OpenTelemetry tracing, safety checks, and math helper functions
Calendar Tools
src/mastra/tools/calendar-tool.ts
Expanded listEvents, getTodayEvents, getUpcomingEvents, findFreeSlots with explicit inputSchema/outputSchema, streaming hooks (onInputStart, onInputDelta, onInputAvailable, onOutput); refactored AppleScript parsing with per-line error handling; added private parseAppleScriptOutput method
Code Analysis Tools
src/mastra/tools/code-analysis.tool.ts
Added streaming lifecycle hooks (onInputStart, onInputDelta, onInputAvailable, onOutput); integrated OpenTelemetry tracing; refactored execute to emit progress via data-tool-progress events; added detailed logging at key points
Code Chunking Tool
src/mastra/tools/code-chunking.ts
Added streaming hooks; refactored chunking logic to branch by file extension (TS/JS via ts-morph, Python via PythonParser); introduced OpenTelemetry tracing with error handling; improved fallback chunking strategy
Code Search Tool
src/mastra/tools/code-search.tool.ts
Added onInputDelta hook; expanded execute with AbortSignal cancellation checks, new file discovery flow (globs/files/directories), RE2-based search support, context-aware matching, comprehensive statistics (totalMatches, filesSearched); enhanced AbortError handling
Color Change Tool
src/mastra/tools/color-change-tool.ts
Added onInputDelta hook; extended logging to include abortSignal in onInputStart/onInputAvailable/onOutput; updated progress event emission with data-tool-progress; standardized string quotes to single quotes
Copywriter Agent Tool
src/mastra/tools/copywriter-agent-tool.ts
Added onInputDelta callback; enhanced prompt construction with userId/targetAudience/tone/specificRequirements; improved streaming/non-streaming path handling; refined error handling within streaming branch; updated span attributes for success/metadata
CSV-to-JSON Tool
src/mastra/tools/csv-to-json.tool.ts
Added onInputDelta hook; introduced AbortSignal checks and file I/O guards; added maxRows enforcement; enhanced lifecycle hooks with abortSignal logging; explicit AbortError handling path with progress updates
Data Validator Tool
src/mastra/tools/data-validator.tool.ts
Refactored with centralized buildZodSchema helper; added onInputDelta lifecycle hook; streamlined schema construction via switch-based approach; standardized string literals to single quotes
DateTime Tools (New)
src/mastra/tools/datetime.tool.ts
New tool module with dateTimeTool (parse/format/add/subtract/diff/now/validate operations) and timeZoneTool (convert/list/info operations); includes runtime context schema, streaming hooks, OpenTelemetry tracing, helper functions for formatting/arithmetic/timezone utilities
Diff Review Tool
src/mastra/tools/diff-review.tool.ts
Added onInputDelta hook; reworked processing to compute diffs via structured patch path with explicit Hunks/Changes construction; improved progress logging and span-based error handling; returns comprehensive DiffReviewOutput with statistics
Editor Agent Tool
src/mastra/tools/editor-agent-tool.ts
Rewrote tool from minimal to full descriptor with explicit id/description/inputSchema/outputSchema; added streaming hooks (onInputStart/onInputDelta/onInputAvailable/onOutput); implemented complete editor-agent orchestration with prompt construction, agent streaming, JSON parsing, span tracing
Evaluate Result Tool
src/mastra/tools/evaluateResultTool.ts
Added onInputDelta handler; reworked execute to support agent streaming with fallback generate path; added streaming/error handling with span recording; enhanced URL-duplication protection; strengthened error handling with progress messages
Execa Tool
src/mastra/tools/execa-tool.ts
Added onInputDelta handler; retained existing command execution logic; minor formatting/style updates to chalk usage and indentation
Extract Learnings Tool
src/mastra/tools/extractLearningsTool.ts
Added onInputDelta handler; reworked execute with streaming-based path for Mastra agent; added prompt construction with content truncation; enhanced error handling with schema validation and fallback returns
File System Tool
src/mastra/tools/fs.ts
Added onInputDelta hook; introduced AbortSignal cancellation checks and early returns; implemented action-driven switch (write/read/append); added comprehensive progress reporting via data-tool-progress; explicit AbortError handling
Find References Tool
src/mastra/tools/find-references.tool.ts
Added onInputDelta handler; expanded tool configuration with new lifecycle hooks; refactored execute for incremental input handling, expanded progress events, and modular file discovery; added Python analysis integration; improved error handling and exception recording
Find Symbol Tool
src/mastra/tools/find-symbol.tool.ts
Introduced onInputDelta and enhanced onInputStart with abort signal; replaced monolithic logic with structured multi-phase search (TS/JS via ts-morph, Python via PythonParser); added observability with tracing/progress events; enhanced cancellation and error handling
Git Local Tools (New)
src/mastra/tools/git-local.tool.ts
New tool module with seven git tools: gitStatusTool, gitDiffTool, gitCommitTool, gitLogTool, gitBranchTool, gitStashTool, gitConfigTool; includes runtime context schema, streaming hooks, OpenTelemetry tracing, robust error handling; supports branch management, diff generation, commit history, stash operations, config management
JWT Auth Tool
src/mastra/tools/jwt-auth.tool.ts
Added onInputDelta logging; added pre-check for abortSignal.aborted before JWT processing; introduced in-progress progress event emission; added cancellation-aware error handling with AbortError path; enhanced logging with abort signal visibility
JSON-to-CSV Tool
src/mastra/tools/json-to-csv.tool.ts
Replaced monolithic implementation with streaming-enabled version; added lifecycle hooks (onInputStart/onInputDelta/onInputAvailable/onOutput); introduced AbortSignal checks and progress emission; enhanced CSV escaping logic; added maxRows guard via configuration context
Multi-String Edit Tool
src/mastra/tools/multi-string-edit.tool.ts
Minor formatting adjustments to MultiStringEditContext and schemas (indentation/spacing); refactored helper functions (fileExists, isPathWithinBoundary, processFileEdit) with improved spacing; preserved core batch-processing and rollback logic
PDF Data Conversion Tool
src/mastra/tools/pdf-data-conversion.tool.ts
Added streaming public tool interface with inputSchema/outputSchema and lifecycle hooks; implemented execute with file validation, text/metadata extraction via unpdf, optional normalization and markdown conversion, conditional output formatting; exported helper sub-tools
PDF Tool
src/mastra/tools/pdf.ts
Added onInputDelta callback for input delta logging; minor formatting/style changes (single quotes); retained existing lifecycle callbacks and PDF reading logic
Polygon Tools
src/mastra/tools/polygon-tools.ts
Added onInputDelta hook and abort signal logging across tools; introduced pre-call AbortSignal cancellation checks; added redacted URL handling for API calls; enhanced AbortError handling with span updates and progress messages; improved error handling precision for API responses
Random Generator Tool (New)
src/mastra/tools/random-generator.tool.ts
New tool module with randomGeneratorTool supporting multiple data types (string/number/boolean/date/uuid/email/name/address/array/object); includes runtime context schema, streaming hooks, OpenTelemetry tracing, type-specific generation helpers, error handling with span logging
Serpapi Academic Tool
src/mastra/tools/serpapi-academic-local.tool.ts
Added onInputDelta handler; introduced AbortSignal cancellation support with checks before/after API calls; enhanced error handling for AbortError with span status/completion messages and warnings
Serpapi News Tool
src/mastra/tools/serpapi-news-trends.tool.ts
Added onInputDelta handler to log input chunks as they arrive alongside toolCallId
Serpapi Search Tool
src/mastra/tools/serpapi-search.tool.ts
Added onInputDelta hook with abort logging; introduced pre-API-call cancellation checks; enhanced error handling for AbortError with span updates, progress events, and warning logs; added abort signal logging to existing hooks
Test Generator Tool
src/mastra/tools/test-generator.tool.ts
Introduced onInputDelta handler; refined tracing instrumentation; adjusted data-tool-progress message structure; preserved core file-parsing and test-generation logic
Text Analysis Tools (New)
src/mastra/tools/text-analysis.tool.ts
New tool module with textAnalysisTool (word-count/sentence-count/paragraph-count/readability/sentiment/language-detect/summary) and textProcessingTool (case conversion/trimming/punctuation removal/number/email/URL extraction); includes runtime context schema, streaming hooks, OpenTelemetry tracing, helper functions for text operations
Tool Exports
src/mastra/tools/index.ts
Added explicit named exports: calculatorTool/unitConverterTool/matrixCalculatorTool, dateTimeTool/timeZoneTool, gitStatusTool/gitDiffTool/gitCommitTool/gitLogTool, randomGeneratorTool, textAnalysisTool/textProcessingTool, urlValidationTool/urlManipulationTool; reorganized data-file-manager exports into multi-line format

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • sourcery-ai

🐰 Hop hop hooray! Tools now dance with streams so grand,
Delta hooks and abort signals across the land!
New datetime, calculator, git—oh my!
Each emits progress as moments pass by,
With tracing to guide them up to the sky! ✨

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

📜 Recent review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6357f20 and bc25e76.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (43)
  • package.json
  • src/mastra/tools/AGENTS.md
  • src/mastra/tools/arxiv.tool.ts
  • src/mastra/tools/browser-tool.ts
  • src/mastra/tools/calculator.tool.ts
  • src/mastra/tools/calendar-tool.ts
  • src/mastra/tools/code-analysis.tool.ts
  • src/mastra/tools/code-chunking.ts
  • src/mastra/tools/code-search.tool.ts
  • src/mastra/tools/color-change-tool.ts
  • src/mastra/tools/copywriter-agent-tool.ts
  • src/mastra/tools/csv-to-json.tool.ts
  • src/mastra/tools/data-validator.tool.ts
  • src/mastra/tools/datetime.tool.ts
  • src/mastra/tools/diff-review.tool.ts
  • src/mastra/tools/editor-agent-tool.ts
  • src/mastra/tools/evaluateResultTool.ts
  • src/mastra/tools/execa-tool.ts
  • src/mastra/tools/extractLearningsTool.ts
  • src/mastra/tools/find-references.tool.ts
  • src/mastra/tools/find-symbol.tool.ts
  • src/mastra/tools/fs.ts
  • src/mastra/tools/git-local.tool.ts
  • src/mastra/tools/github.ts
  • src/mastra/tools/index.ts
  • src/mastra/tools/json-to-csv.tool.ts
  • src/mastra/tools/jwt-auth.tool.ts
  • src/mastra/tools/multi-string-edit.tool.ts
  • src/mastra/tools/pdf-data-conversion.tool.ts
  • src/mastra/tools/pdf.ts
  • src/mastra/tools/polygon-tools.ts
  • src/mastra/tools/random-generator.tool.ts
  • src/mastra/tools/serpapi-academic-local.tool.ts
  • src/mastra/tools/serpapi-news-trends.tool.ts
  • src/mastra/tools/serpapi-search.tool.ts
  • src/mastra/tools/test-generator.tool.ts
  • src/mastra/tools/text-analysis.tool.ts
  • src/mastra/tools/url-tool.ts
  • src/mastra/tools/weather-tool.ts
  • src/mastra/tools/web-scraper-tool.ts
  • src/mastra/tools/write-note.ts
  • tests/test-results/test-results.json
  • ui/sidebar.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements comprehensive code formatting and standardization improvements across the codebase, with a focus on consistent semicolon usage, quote style normalization, and the addition of onInputDelta lifecycle hooks to existing tools.

  • Standardized all code to use single quotes and semicolons consistently
  • Added onInputDelta hooks to multiple tool implementations for enhanced streaming support
  • Introduced new tool modules including URL validation, text analysis, random generation, datetime operations, and local Git operations
  • Added cancellation handling with abortSignal checks across multiple tools

Reviewed changes

Copilot reviewed 43 out of 44 changed files in this pull request and generated no comments.

Show a summary per file
File Description
ui/sidebar.tsx Added constant declarations for sidebar configuration values
tests/test-results/test-results.json Updated test execution timestamp
src/mastra/tools/write-note.ts Reformatted with consistent quotes/semicolons and added onInputDelta hook
src/mastra/tools/web-scraper-tool.ts Added abort signal handling and onInputDelta hook
src/mastra/tools/weather-tool.ts Enhanced cancellation handling and added import type modifier
src/mastra/tools/url-tool.ts New file implementing URL validation and manipulation tools
src/mastra/tools/text-analysis.tool.ts New file implementing text analysis and processing tools
src/mastra/tools/test-generator.tool.ts Reformatted and added onInputDelta hook
src/mastra/tools/serpapi-*.tool.ts Added onInputDelta hooks and abort signal logging
src/mastra/tools/random-generator.tool.ts New file implementing random data generation tool
src/mastra/tools/polygon-tools.ts Added abort signal handling and error handling improvements
src/mastra/tools/pdf*.ts Reformatted with consistent style and added onInputDelta hooks
src/mastra/tools/multi-string-edit.tool.ts Reformatted and added onInputDelta hook
src/mastra/tools/jwt-auth.tool.ts Added cancellation handling and onInputDelta hook
src/mastra/tools/json-to-csv.tool.ts Added abort signal checks and onInputDelta hook
src/mastra/tools/index.ts Added exports for new tools (calculator, datetime, git-local, random, text-analysis, url)
src/mastra/tools/github.ts Added cancellation handling and onInputDelta hook
src/mastra/tools/git-local.tool.ts New file implementing comprehensive local Git operations
src/mastra/tools/fs.ts Added abort signal handling and onInputDelta hook
src/mastra/tools/find-*.tool.ts Reformatted with consistent style and added onInputDelta hooks
src/mastra/tools/extract*.ts Reformatted with consistent semicolons/quotes and added onInputDelta hooks
src/mastra/tools/execa-tool.ts Reformatted and added onInputDelta hook
src/mastra/tools/editor-agent-tool.ts Reformatted with consistent style and added onInputDelta hook
src/mastra/tools/diff-review.tool.ts Reformatted and added onInputDelta hook
src/mastra/tools/datetime.tool.ts New file implementing date/time manipulation and timezone conversion
src/mastra/tools/data-validator.tool.ts Reformatted with consistent style and added onInputDelta hook
src/mastra/tools/csv-to-json.tool.ts Added abort signal handling and onInputDelta hook

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, we are unable to review this pull request

The GitHub API does not allow us to fetch diffs exceeding 20000 lines

@github-actions
Copy link

github-actions bot commented Jan 7, 2026

🤖 I'm sorry @ssdeanx, but I was unable to process your request. Please see the logs for more details.

@ssdeanx ssdeanx merged commit 681668a into main Jan 7, 2026
21 of 28 checks passed
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ssdeanx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a substantial expansion of the agent's functional toolkit, adding new capabilities for advanced mathematical computations, date and time management, local Git repository interactions, random data generation, and sophisticated text analysis. Concurrently, it significantly enhances the robustness and user experience of existing tools by implementing detailed input streaming feedback and cancellation mechanisms, ensuring smoother and more controllable long-running operations.

Highlights

  • New Tool Integrations: Introduced several powerful new tools: a comprehensive Calculator (with unit and matrix operations), a DateTime and TimeZone manager, a suite of local Git operations (status, diff, commit, log, branch, stash, config), a Random Data Generator, and Text Analysis and Processing tools. These significantly expand the agent's capabilities.
  • Enhanced Tool Observability and Cancellation: Many existing tools, including ArXiv, Browser, CSV/JSON converters, File System operations, Find Symbol, JWT Auth, Polygon financial tools, and SerpAPI search tools, now feature onInputDelta lifecycle hooks and robust abortSignal handling. This improves real-time feedback during tool execution and enables reliable cancellation of long-running tasks.
  • Dependency Updates: Updated several core dependencies, including @ai-sdk/google-vertex, @ai-sdk/react, framer-motion, motion, react-resizable-panels, and shiki, ensuring the project benefits from the latest features and stability improvements.
  • Improved Tool Documentation: The AGENTS.md documentation has been updated to reflect that all tools now have complete lifecycle hook coverage, enhancing observability, debugging, and analytics across the entire toolset.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request primarily focuses on enhancing tool lifecycle observability by adding onInputDelta hooks and abortSignal handling to numerous existing tools, including arxiv.tool.ts, browser-tool.ts, code-search.tool.ts, csv-to-json.tool.ts, fs.ts, jwt-auth.tool.ts, polygon-tools.ts, serpapi-academic-local.tool.ts, serpapi-news-trends.tool.ts, and serpapi-search.tool.ts. It also introduces several new tools: a comprehensive calculator.tool.ts with expression evaluation, unit conversion, and matrix operations; datetime.tool.ts for date/time manipulation and timezone conversions; git-local.tool.ts offering advanced Git operations like status, diff, commit, log, branch, stash, and config management; random-generator.tool.ts for generating various random data types; text-analysis.tool.ts for text statistics and processing; and url-tool.ts for URL validation and manipulation. Dependency versions in package.json were updated, and the AGENTS.md documentation was revised to reflect the complete implementation of lifecycle hooks across all tools. Review comments highlight a security vulnerability in calculator.tool.ts due to new Function() usage, inaccuracies in datetime.tool.ts's date difference and timezone conversion logic, missing abortSignal propagation in execaTool.ts and copywriter-agent-tool.ts, an incorrect error message in polygon-tools.ts's AbortError handling, and a request to re-include startIndex and maxResults in serpapi-academic-local.tool.ts's onOutput logging.

Comment on lines +135 to +170
function evaluateExpression(
expression: string,
variables: Record<string, number> = {}
): number {
// Remove whitespace
const cleanExpr = expression.replace(/\s+/g, '')

// Basic security checks
if (/[^0-9+\-*/().\s,a-zA-Z_]/.test(cleanExpr)) {
throw new Error('Invalid characters in expression')
}

if (
cleanExpr.includes('__proto__') ||
cleanExpr.includes('prototype') ||
cleanExpr.includes('constructor')
) {
throw new Error('Potentially unsafe expression')
}

// Create function with safe context and variables
const context = { ...createSafeContext(), ...variables }
const func = new Function(...Object.keys(context), `return ${cleanExpr}`)

try {
const result = func(...Object.values(context))
if (typeof result !== 'number' || !isFinite(result)) {
throw new Error('Expression did not evaluate to a valid number')
}
return result
} catch (error) {
throw new Error(
`Evaluation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The evaluateExpression function uses new Function(...) to evaluate a string as code. This is a significant security vulnerability as it can allow for arbitrary code execution, even with the existing sanitization attempts. A malicious expression could bypass the checks. Please use a dedicated and safe math expression parser library (like math.js) to prevent potential remote code execution (RCE) vulnerabilities.

Comment on lines +530 to +555
function calculateDateDiff(from: Date, to: Date) {
const diffMs = to.getTime() - from.getTime()

const years = Math.floor(diffMs / (1000 * 60 * 60 * 24 * 365.25))
const months = Math.floor(
(diffMs % (1000 * 60 * 60 * 24 * 365.25)) /
(1000 * 60 * 60 * 24 * 30.44)
)
const days = Math.floor(
(diffMs % (1000 * 60 * 60 * 24 * 30.44)) / (1000 * 60 * 60 * 24)
)
const hours = Math.floor(
(diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
)
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
const seconds = Math.floor((diffMs % (1000 * 60)) / 1000)

return {
years: years || undefined,
months: months || undefined,
days: days || undefined,
hours: hours || undefined,
minutes: minutes || undefined,
seconds: seconds || undefined,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The calculateDateDiff function uses a fixed average for month length (30.44), which will lead to inaccurate results for date differences, especially around month boundaries. For correct date arithmetic, please consider using a robust date library like date-fns or luxon.

Comment on lines +557 to +570
function convertTimezone(date: Date, fromTz: string, toTz: string): string {
// For now, we'll use a simple approach. In a real implementation,
// you'd want to use a proper timezone library like 'date-fns-tz' or 'luxon'
// This is a simplified version that assumes UTC offsets

// Get timezone offsets (this is approximate and not production-ready)
const fromOffset = getTimezoneOffset(fromTz)
const toOffset = getTimezoneOffset(toTz)

const utcTime = date.getTime() - fromOffset * 60 * 1000
const targetTime = utcTime + toOffset * 60 * 1000

return new Date(targetTime).toISOString()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The convertTimezone function uses a simplified, hardcoded map of timezone offsets and does not account for Daylight Saving Time (DST). This will produce incorrect results for many timezones and dates. The comment "not production-ready" highlights this. Please replace this implementation with a proper timezone library like date-fns-tz or luxon to ensure correct timezone conversions.

Comment on lines +69 to +141
execute: async (inputData, context) => {
const writer = context?.writer
const requestContext = context?.requestContext

const tracer = trace.getTracer('execa-tool', '1.0.0');
const span = tracer.startSpan('execa-tool', {
attributes: {
'tool.id': 'execa-tool',
'tool.input.command': inputData.command,
'tool.input.args': inputData.args.join(' '),
}
});
const tracer = trace.getTracer('execa-tool', '1.0.0')
const span = tracer.startSpan('execa-tool', {
attributes: {
'tool.id': 'execa-tool',
'tool.input.command': inputData.command,
'tool.input.args': inputData.args.join(' '),
},
})

const { command, args, cwd, timeout, env } = inputData
await writer?.custom({ type: 'data-tool-progress', data: { status: 'in-progress', message: `💻 Executing command: ${command} ${args.join(' ')}`, stage: 'execaTool' }, id: 'execaTool' });
try {
log.info(
chalk.green(`Running command: ${command} ${args.join(' ')}`)
)
const optionsEnv: NodeJS.ProcessEnv = { ...process.env, ...(env ?? {}) };
const result = await execa(command, args, {
all: true,
stdio: 'pipe',
cwd,
timeout,
env: optionsEnv,
})
const output = result.all ?? ''
await writer?.custom({ type: 'data-tool-progress', data: { status: 'done', message: '✅ Command executed successfully', stage: 'execaTool' }, id: 'execaTool' });
span.setAttributes({
'tool.output.success': true,
'tool.output.outputLength': output.length,
});
span.end();
return { message: chalk.green(output) }
} catch (e) {
const errorMsg = e instanceof Error ? e.message : String(e);
log.error(errorMsg)
span.recordException(e instanceof Error ? e : new Error(errorMsg));
span.setStatus({ code: 2, message: errorMsg });
span.end();
const execaErr = e as ExecaErrorType;
if (e instanceof Error && 'all' in e) {
return { message: execaErr.all ?? execaErr.message ?? 'Command failed' }
}
return { message: errorMsg || 'Error' }
}
},
const { command, args, cwd, timeout, env } = inputData
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message: `💻 Executing command: ${command} ${args.join(' ')}`,
stage: 'execaTool',
},
id: 'execaTool',
})
try {
log.info(
chalk.green(`Running command: ${command} ${args.join(' ')}`)
)
const optionsEnv: NodeJS.ProcessEnv = {
...process.env,
...(env ?? {}),
}
const result = await execa(command, args, {
all: true,
stdio: 'pipe',
cwd,
timeout,
env: optionsEnv,
})
const output = result.all ?? ''
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'done',
message: '✅ Command executed successfully',
stage: 'execaTool',
},
id: 'execaTool',
})
span.setAttributes({
'tool.output.success': true,
'tool.output.outputLength': output.length,
})
span.end()
return { message: chalk.green(output) }
} catch (e) {
const errorMsg = e instanceof Error ? e.message : String(e)
log.error(errorMsg)
span.recordException(e instanceof Error ? e : new Error(errorMsg))
span.setStatus({ code: 2, message: errorMsg })
span.end()
const execaErr = e as ExecaErrorType
if (e instanceof Error && 'all' in e) {
return {
message:
execaErr.all ?? execaErr.message ?? 'Command failed',
}
}
return { message: errorMsg || 'Error' }
}
},
})

export type ExecaUITool = InferUITool<typeof execaTool>;
export type ExecaUITool = InferUITool<typeof execaTool>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The execaTool executes external commands, which can be long-running or hang. However, it does not implement abortSignal handling. The execa library supports AbortSignal. Please pass the abortSignal from the tool context to the execa call to allow for cancellation of the command.

Comment on lines +258 to +266
onOutput: ({ output, toolCallId, toolName, abortSignal }) => {
log.info('ArXiv search completed', {
toolCallId,
toolName,
abortSignal: abortSignal?.aborted,
papersFound: output.papers.length,
totalResults: output.total_results,
hook: 'onOutput',
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logging in onOutput has been updated and no longer includes startIndex and maxResults. Since these values are part of the tool's output and were previously logged, it would be beneficial for observability to re-include them for consistency.

Comment on lines +159 to +177
// Handle AbortError specifically
if (e instanceof Error && e.name === 'AbortError') {
const cancelMessage = `Browser operation cancelled for ${inputData.url}`
span.setStatus({ code: 2, message: cancelMessage })
span.end()

await context?.writer?.custom({
type: 'data-tool-progress',
data: {
status: 'done',
message: `🛑 ${cancelMessage}`,
stage: 'browserTool',
},
id: 'browserTool',
})

log.warn(cancelMessage)
return { message: `Error: ${cancelMessage}` }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The AbortError is caught, but instead of re-throwing an error to signal failure, it returns a success-like object with an error message: { message: Error: ${cancelMessage} }. The execute function should throw an exception on failure to ensure the calling agent handles it as an error. Returning an error message in a success payload can lead to unexpected behavior.

Comment on lines +1656 to +1674
if (error instanceof Error && error.name === 'AbortError') {
const cancelMessage = `Polygon stock fundamentals cancelled for ${inputData.symbol}`
const totalDuration = Date.now() - startTime
rootSpan.setStatus({ code: 2, message: cancelMessage })
rootSpan.end()

await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'done',
message: `🛑 ${cancelMessage}`,
stage: 'polygon-stock-fundamentals',
},
id: 'polygon-stock-fundamentals',
})

log.warn(cancelMessage)
throw new Error(cancelMessage)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There appears to be a copy-paste error in the AbortError handling. The error message is Polygon stock fundamentals cancelled for ${inputData.symbol}, but this is inside the polygonCryptoAggregatesTool. The message should refer to "crypto aggregates" instead of "stock fundamentals".

Comment on lines +118 to 427
execute: async (inputData, context) => {
const writer = context?.writer
const mastra = context?.mastra

const userIdVal = context?.requestContext?.get('userId')
const userId = typeof userIdVal === 'string' && userIdVal.trim().length > 0 ? userIdVal : 'anonymous'
const {
topic,
contentType = 'blog',
targetAudience,
tone,
length = 'medium',
specificRequirements,
} = inputData
const userIdVal = context?.requestContext?.get('userId')
const userId =
typeof userIdVal === 'string' && userIdVal.trim().length > 0
? userIdVal
: 'anonymous'
const {
topic,
contentType = 'blog',
targetAudience,
tone,
length = 'medium',
specificRequirements,
} = inputData

await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message: `✍️ Starting copywriter agent for ${contentType} about "${topic}"`,
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
});
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message: `✍️ Starting copywriter agent for ${contentType} about "${topic}"`,
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})

const span = trace.getTracer('copywriter-agent-tool', '1.0.0').startSpan('copywriter-generate', {
attributes: {
'tool.id': 'copywriter-agent',
'tool.input.topic': topic,
'tool.input.contentType': contentType,
'tool.input.targetAudience': targetAudience,
'tool.input.tone': tone,
'tool.input.length': length,
}
});
const span = trace
.getTracer('copywriter-agent-tool', '1.0.0')
.startSpan('copywriter-generate', {
attributes: {
'tool.id': 'copywriter-agent',
'tool.input.topic': topic,
'tool.input.contentType': contentType,
'tool.input.targetAudience': targetAudience,
'tool.input.tone': tone,
'tool.input.length': length,
},
})

try {
const agent = mastra?.getAgent?.('copywriterAgent') ?? copywriterAgent
try {
const agent =
mastra?.getAgent?.('copywriterAgent') ?? copywriterAgent

// Validate agent has an invocation method (generate or stream).
if (typeof agent.generate !== 'function' && typeof agent.stream !== 'function') {
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'done',
message: 'Copywriter agent is not available in this runtime.',
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})
// Validate agent has an invocation method (generate or stream).
if (
typeof agent.generate !== 'function' &&
typeof agent.stream !== 'function'
) {
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'done',
message:
'Copywriter agent is not available in this runtime.',
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})

return {
content: `Unable to generate content: copywriterAgent is not available.`,
contentType,
title: undefined,
summary: undefined,
keyPoints: [],
wordCount: 0,
}
}
return {
content: `Unable to generate content: copywriterAgent is not available.`,
contentType,
title: undefined,
summary: undefined,
keyPoints: [],
wordCount: 0,
}
}

// Build the prompt with context
let prompt = `Create ${length} ${contentType} content about: ${topic}`
// Build the prompt with context
let prompt = `Create ${length} ${contentType} content about: ${topic}`

if (userId !== undefined) {
prompt += `\n\nUser: ${userId}`
}
if (userId !== undefined) {
prompt += `\n\nUser: ${userId}`
}

if (typeof targetAudience === 'string' && targetAudience.trim().length > 0) {
prompt += `\n\nTarget audience: ${targetAudience}`
}
if (
typeof targetAudience === 'string' &&
targetAudience.trim().length > 0
) {
prompt += `\n\nTarget audience: ${targetAudience}`
}

if (typeof tone === 'string' && tone.trim().length > 0) {
prompt += `\n\nDesired tone: ${tone}`
}
if (typeof tone === 'string' && tone.trim().length > 0) {
prompt += `\n\nDesired tone: ${tone}`
}

if (
typeof specificRequirements === 'string' &&
specificRequirements.trim().length > 0
) {
prompt += `\n\nSpecific requirements: ${specificRequirements}`
}
if (
typeof specificRequirements === 'string' &&
specificRequirements.trim().length > 0
) {
prompt += `\n\nSpecific requirements: ${specificRequirements}`
}

// Add content type specific guidance
switch (contentType) {
case 'blog':
prompt +=
'\n\nCreate a well-structured blog post with engaging introduction, body sections, and conclusion.'
break
case 'marketing':
prompt +=
'\n\nCreate persuasive marketing copy that highlights benefits and includes clear calls-to-action.'
break
case 'social':
prompt +=
'\n\nCreate concise, engaging social media content optimized for sharing and engagement.'
break
case 'technical':
prompt +=
'\n\nCreate clear, accurate technical content with proper explanations and examples.'
break
case 'business':
prompt +=
'\n\nCreate professional business communication with clear objectives and actionable content.'
break
case 'creative':
prompt +=
'\n\nCreate engaging creative content with storytelling elements and vivid language.'
break
case 'general': {
throw new Error('Not implemented yet: "general" case')
}
}
// Add content type specific guidance
switch (contentType) {
case 'blog':
prompt +=
'\n\nCreate a well-structured blog post with engaging introduction, body sections, and conclusion.'
break
case 'marketing':
prompt +=
'\n\nCreate persuasive marketing copy that highlights benefits and includes clear calls-to-action.'
break
case 'social':
prompt +=
'\n\nCreate concise, engaging social media content optimized for sharing and engagement.'
break
case 'technical':
prompt +=
'\n\nCreate clear, accurate technical content with proper explanations and examples.'
break
case 'business':
prompt +=
'\n\nCreate professional business communication with clear objectives and actionable content.'
break
case 'creative':
prompt +=
'\n\nCreate engaging creative content with storytelling elements and vivid language.'
break
case 'general': {
throw new Error('Not implemented yet: "general" case')
}
}

await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message: '🤖 Generating content...',
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message: '🤖 Generating content...',
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})

let contentText = ''
if (typeof agent.stream === 'function') {
try {
await writer?.custom({ type: 'data-tool-progress', data: { status: 'in-progress', message: '🔁 Streaming content from copywriter agent', stage: 'copywriter-agent' }, id: 'copywriter-agent' });
const stream = await agent.stream(prompt) as MastraModelOutput | undefined
let contentText = ''
if (typeof agent.stream === 'function') {
try {
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message:
'🔁 Streaming content from copywriter agent',
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})
const stream = (await agent.stream(prompt)) as
| MastraModelOutput
| undefined

if (stream?.textStream && writer) {
await writer?.custom({ type: 'data-tool-progress', data: { status: 'in-progress', message: '🔁 Streaming text from copywriter agent', stage: 'copywriter-agent' }, id: 'copywriter-agent' });
await stream.textStream.pipeTo(writer as unknown as WritableStream)
} else if (stream?.fullStream && writer) {
await writer?.custom({ type: 'data-tool-progress', data: { status: 'in-progress', message: '🔁 Streaming from copywriter agent (full stream)', stage: 'copywriter-agent' }, id: 'copywriter-agent' });
await stream.fullStream.pipeTo(writer as unknown as WritableStream)
}
if (stream?.textStream && writer) {
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message:
'🔁 Streaming text from copywriter agent',
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})
await stream.textStream.pipeTo(
writer as unknown as WritableStream
)
} else if (stream?.fullStream && writer) {
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'in-progress',
message:
'🔁 Streaming from copywriter agent (full stream)',
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})
await stream.fullStream.pipeTo(
writer as unknown as WritableStream
)
}

const text = (await stream?.text) ?? ''
const responseObject = stream?.object ?? (() => { try { return JSON.parse(text) } catch { return {} } })()
const text = (await stream?.text) ?? ''
const responseObject =
stream?.object ??
(() => {
try {
return JSON.parse(text)
} catch {
return {}
}
})()

if ((Boolean(responseObject)) && typeof responseObject === 'object') {
const obj = responseObject as Record<string, unknown>
const contentVal = obj.content
if (typeof contentVal === 'string') {
contentText = contentVal
if (
Boolean(responseObject) &&
typeof responseObject === 'object'
) {
const obj = responseObject as Record<string, unknown>
const contentVal = obj.content
if (typeof contentVal === 'string') {
contentText = contentVal
} else {
contentText = text
}
} else {
contentText = text
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
span.recordException(
err instanceof Error ? err : new Error(msg)
)
try {
span.setStatus({
code: SpanStatusCode.ERROR,
message: msg,
})
} catch {
/* ignore */
}
span.end()
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'done',
message: `❌ Error generating content: ${msg}`,
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})
throw err
}
} else {
contentText = text
const response = await agent.generate(prompt)
const responseObject =
response.object ??
(() => {
try {
return JSON.parse(response.text)
} catch {
return undefined
}
})()
const obj = responseObject as
| Record<string, unknown>
| undefined
if (obj && typeof obj.content === 'string') {
contentText = obj.content
} else {
contentText = response.text
}
}
} else {
contentText = text
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
span.recordException(err instanceof Error ? err : new Error(msg))
try { span.setStatus({ code: SpanStatusCode.ERROR, message: msg }) } catch { /* ignore */ }
span.end()
await writer?.custom({ type: 'data-tool-progress', data: { status: 'done', message: `❌ Error generating content: ${msg}`, stage: 'copywriter-agent' }, id: 'copywriter-agent' });
throw err
}
} else {
const response = await agent.generate(prompt)
const responseObject = response.object ?? (() => { try { return JSON.parse(response.text) } catch { return undefined } })()
const obj = responseObject as Record<string, unknown> | undefined
if (obj && typeof obj.content === 'string') {
contentText = obj.content
} else {
contentText = response.text
}
}

// Final progress 'done' event
await writer?.custom({ type: 'data-tool-progress', data: { status: 'done', message: '✅ Content generated', stage: 'copywriter-agent' }, id: 'copywriter-agent' });
// Final progress 'done' event
await writer?.custom({
type: 'data-tool-progress',
data: {
status: 'done',
message: '✅ Content generated',
stage: 'copywriter-agent',
},
id: 'copywriter-agent',
})

// Parse and structure the response
const content = contentText
const wordCount = content.split(/\s+/).length
// Parse and structure the response
const content = contentText
const wordCount = content.split(/\s+/).length

// Extract title if present (look for # or ## at start)
const titleMatch = /^#{1,2}\s+(.+)$/m.exec(content)
const title = titleMatch ? titleMatch[1] : undefined
// Extract title if present (look for # or ## at start)
const titleMatch = /^#{1,2}\s+(.+)$/m.exec(content)
const title = titleMatch ? titleMatch[1] : undefined

// Create a simple summary from the first paragraph or first few sentences
const firstParagraph =
content.split('\n\n')[0] ?? content.split('\n')[0] ?? ''
const summary =
firstParagraph.length > 200
? firstParagraph.substring(0, 200) + '...'
: firstParagraph
// Create a simple summary from the first paragraph or first few sentences
const firstParagraph =
content.split('\n\n')[0] ?? content.split('\n')[0] ?? ''
const summary =
firstParagraph.length > 200
? firstParagraph.substring(0, 200) + '...'
: firstParagraph

span.setAttribute('tool.output.success', true);
span.setAttribute('tool.output.wordCount', wordCount);
span.setAttribute('tool.output.contentLength', content.length);
span.end();
span.setAttribute('tool.output.success', true)
span.setAttribute('tool.output.wordCount', wordCount)
span.setAttribute('tool.output.contentLength', content.length)
span.end()

return {
content,
contentType,
title,
summary,
keyPoints: [], // Could be enhanced to extract key points
wordCount,
}
} catch (error) {
const errorMsg =
error instanceof Error ? error.message : 'Unknown error'
log.error('Copywriter agent tool error:', {
error: errorMsg,
topic,
contentType,
})
span?.setAttribute('tool.output.success', false);
span?.setAttribute('tool.output.error', errorMsg);
span?.end();
throw new Error(`Failed to generate content: ${errorMsg}`)
}
},
return {
content,
contentType,
title,
summary,
keyPoints: [], // Could be enhanced to extract key points
wordCount,
}
} catch (error) {
const errorMsg =
error instanceof Error ? error.message : 'Unknown error'
log.error('Copywriter agent tool error:', {
error: errorMsg,
topic,
contentType,
})
span?.setAttribute('tool.output.success', false)
span?.setAttribute('tool.output.error', errorMsg)
span?.end()
throw new Error(`Failed to generate content: ${errorMsg}`)
}
},
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This tool calls another agent, which can be a long-running operation. However, it does not implement abortSignal handling. The agent.stream or agent.generate calls should ideally accept an abortSignal from the tool context to allow for cancellation. Please consider propagating the abortSignal to the agent call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant