Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions frameworks/mastra/using-a-tool/.cursorrules/agent.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
description: Guidelines for writing Agentuity AI Agents in TypeScript
globs: "**/src/agents/**/index.ts"
alwaysApply: true
---

# AI Agent File

- Prefer using the `agentuity agent create` command to create a new Agent
- Prefer loading types from the node modules package `@agentuity/sdk` in the node_modules folder
- The file should export a default function
- Prefer naming the default function Agent or the name of the Agent based on the context of the Agent description
- All code should be in Typescript format
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Spell “TypeScript” correctly.

User-facing docs should use the correct casing.

-- All code should be in Typescript format
+- All code should be in TypeScript format
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- All code should be in Typescript format
- All code should be in TypeScript format
🤖 Prompt for AI Agents
frameworks/mastra/using-a-tool/.cursorrules/agent.mdc around line 13: the
document currently spells "Typescript" incorrectly; update the text to use the
correct casing "TypeScript" wherever it appears (line 13 and any other
occurrences in this file) so user-facing docs use the proper, consistent
branding.

- Use the provided logger from the `AgentContext` interface such as `ctx.logger.info("my message: %s", "hello")`

## Example Agent File

```typescript
import type { AgentRequest, AgentResponse, AgentContext } from "@agentuity/sdk";

export default async function Agent(req: AgentRequest, resp: AgentResponse, ctx: AgentContext) {
return resp.json({"hello":"world"});
}
```

### AgentRequest

The AgentRequest interface provides a set of helper methods and public variables which can be used for working with data has been passed to the Agent.

### AgentResponse

The AgentResponse interface provides a set of helper methods for responding with different data formats from the Agent.

### AgentContext

The AgentContext has information specific to the incoming Agent request and a set of helper methods for accessing AI services like KeyValue storage and Vector storage.
9 changes: 9 additions & 0 deletions frameworks/mastra/using-a-tool/.cursorrules/agentuity.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Guidelines for the Agentuity AI Configuration file
globs: "agentuity.yaml"
alwaysApply: true
---

# Agentuity Configuration File

This file is used by agentuity to configure the AI Agent project. You should NOT suggest edits to this file.
9 changes: 9 additions & 0 deletions frameworks/mastra/using-a-tool/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules/
.env
.env.local
.env.development
.env.production
.agentuity/
dist/
*.log
.DS_Store
74 changes: 74 additions & 0 deletions frameworks/mastra/using-a-tool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Mastra Using a Tool - Agentuity Example

This example demonstrates how to wrap a Mastra agent that uses tools within the Agentuity framework. It preserves the original Mastra framework functionality while providing Agentuity SDK interfaces.

## Overview

This agent uses the Mastra framework to create a London weather assistant that has access to historical weather data for the current year. The agent can answer questions about weather patterns, temperature records, rainfall, and other meteorological data.

## Original Mastra Example

This example is based on the [Mastra "Using a Tool" documentation](https://mastra.ai/en/examples/agents/using-a-tool), which demonstrates how to add tools to Mastra agents.

## Features

- **Historical Weather Data**: Access to year-to-date weather information for London
- **Tool Integration**: Uses Mastra's `createTool` function to define the weather data tool
- **Agentuity Wrapper**: Wrapped with Agentuity SDK for seamless integration
- **Error Handling**: Proper error logging using Agentuity's context logger

## Project Structure

```
src/
├── agents/
│ └── london-weather-agent/
│ └── index.ts # Main agent implementation
└── tools/
└── london-weather-tool.ts # Weather data tool definition
```

## Tool Functionality

The `londonWeatherTool` provides:
- Daily temperature maximums and minimums
- Precipitation/rainfall data
- Wind speed measurements
- Snowfall amounts
- Data from January 1st of current year to present

## Usage Examples

- "How many times has it rained this year?"
- "What was the highest temperature recorded this year?"
- "Show me the weather trends for the past month"
- "What's the average rainfall so far this year?"

## Setup

1. Install dependencies:
```bash
npm install
```

2. Start development server:
```bash
agentuity dev
```

3. Test the agent in the Agentuity Console

## Dependencies

- **@mastra/core**: Core Mastra framework for agent and tool creation
- **@ai-sdk/openai**: OpenAI integration for the language model
- **@agentuity/sdk**: Agentuity SDK for request/response handling
- **zod**: Schema validation for tool inputs/outputs

## Implementation Notes

This example follows the Agentuity wrapper pattern:
- Preserves all original Mastra framework imports and functionality
- Uses Agentuity SDK types for request/response handling
- Implements proper error handling with context logging
- Provides a welcome function with example prompts
17 changes: 17 additions & 0 deletions frameworks/mastra/using-a-tool/agentuity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: mastra-using-a-tool
description: Mastra agent with London weather tool wrapped in Agentuity SDK
version: 0.0.1
runtime: nodejs

agents:
london-weather-agent:
name: London Weather Agent
description: Provides historical weather data for London using Mastra tools
path: src/agents/london-weather-agent

development:
port: 3000

deployment:
memory: 512
timeout: 30
3 changes: 3 additions & 0 deletions frameworks/mastra/using-a-tool/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { serve } from "@agentuity/sdk";

serve();
28 changes: 28 additions & 0 deletions frameworks/mastra/using-a-tool/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "mastra-using-a-tool",
"description": "Mastra agent with London weather tool wrapped in Agentuity SDK",
"version": "0.0.1",
"main": "index.js",
"type": "module",
"scripts": {
"build": "agentuity build",
"prestart": "agentuity bundle",
"start": "node --env-file .env .agentuity/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["agentuity", "agent", "ai", "ai agent", "mastra", "weather"],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^22.13.9",
"mastra": "^0.2.9-alpha.4",
"typescript": "^5.8.2"
},
"dependencies": {
"@agentuity/sdk": "^0.0.73",
"@ai-sdk/openai": "^1.2.1",
"@mastra/core": "^0.5.0-alpha.4",
"ai": "^4.1.53",
"zod": "^3.24.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { AgentRequest, AgentResponse, AgentContext } from "@agentuity/sdk";
import { openai } from "@ai-sdk/openai";
import { Agent } from "@mastra/core";
import { londonWeatherTool } from "../../tools/london-weather-tool";
Comment on lines +1 to +4
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix fatal name collision: local function shadows imported Mastra Agent.

export default async function Agent(...) shadows the imported Agent class. new Agent({...}) will throw “Agent is not a constructor” at runtime (async functions aren’t constructible). Rename one side.

-import type { AgentRequest, AgentResponse, AgentContext } from "@agentuity/sdk";
-import { openai } from "@ai-sdk/openai";
-import { Agent } from "@mastra/core";
+import type { AgentRequest, AgentResponse, AgentContext } from "@agentuity/sdk";
+import { openai } from "@ai-sdk/openai";
+import { Agent as MastraAgent } from "@mastra/core";
@@
-export default async function Agent(
+export default async function handler(
   req: AgentRequest,
   resp: AgentResponse,
   ctx: AgentContext,
 ) {
@@
-    const londonWeatherAgent = new Agent({
+    const londonWeatherAgent = new MastraAgent({
       name: "london-weather-agent",

This also resolves the Biome “noRedeclare” warning.

Also applies to: 18-41

🤖 Prompt for AI Agents
In frameworks/mastra/using-a-tool/src/agents/london-weather-agent/index.ts
around lines 1-4 and 18-41, the module declares an async function named Agent
which collides with the imported Agent class and causes a runtime “Agent is not
a constructor” and a noRedeclare warning; rename the exported async function to
something like createLondonWeatherAgent (or buildAgent) and update its export
default accordingly, and update any internal references/usages within the file
to call the new function name so the imported Agent class remains available for
instantiation (e.g., keep using new Agent({...}) unaffected).


export function welcome() {
return {
message: "I'm a London weather assistant with access to historical weather data for the current year. I can help you analyze weather patterns, trends, and specific data points.",
prompts: [
"How many times has it rained this year?",
"What was the highest temperature recorded this year?",
"Show me the weather trends for the past month",
"What's the average rainfall so far this year?",
],
};
}

export default async function Agent(
req: AgentRequest,
resp: AgentResponse,
ctx: AgentContext,
) {
try {
const prompt = req.text() ?? "What is the weather data for London this year?";

const londonWeatherAgent = new Agent({
name: "london-weather-agent",
description: "Provides historical information about London weather",
instructions: `You are a helpful assistant with access to historical weather data for London.

The data is provided for the current calendar year from January 1st up to today. When responding:
- Use the londonWeatherTool to fetch current year's weather data for London
- The data includes daily temperature maximums and minimums, rainfall, wind speed, and snowfall
- Answer the user's question using that data
- Keep responses concise, factual, and informative
- If the question cannot be answered with available data, say so clearly`,
model: openai("gpt-4o"),
tools: { londonWeatherTool },
});

const result = await londonWeatherAgent.generate(prompt);
return resp.text(result.text);
} catch (error) {
ctx.logger.error(
"Error generating response: %s",
error instanceof Error ? error.message : String(error),
);

return resp.text(
"I'm sorry, I encountered an error while processing your request.",
);
}
}
34 changes: 34 additions & 0 deletions frameworks/mastra/using-a-tool/src/tools/london-weather-tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createTool } from "@mastra/core";
import { z } from "zod";

export const londonWeatherTool = createTool({
id: "london-weather-tool",
description: "Returns year-to-date historical weather data for London",
outputSchema: z.object({
date: z.array(z.string()),
temp_max: z.array(z.number()),
temp_min: z.array(z.number()),
rainfall: z.array(z.number()),
windspeed: z.array(z.number()),
snowfall: z.array(z.number()),
}),
execute: async () => {
const startDate = new Date().getFullYear() + "-01-01";
const endDate = new Date().toISOString().split("T")[0];

const response = await fetch(
`https://archive-api.open-meteo.com/v1/archive?latitude=51.5072&longitude=-0.1276&start_date=${startDate}&end_date=${endDate}&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,snowfall_sum`,
);

const { daily } = await response.json();

return {
date: daily.time,
temp_max: daily.temperature_2m_max,
temp_min: daily.temperature_2m_min,
rainfall: daily.precipitation_sum,
windspeed: daily.wind_speed_10m_max,
snowfall: daily.snowfall_sum,
};
},
Comment on lines +15 to +33
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling, timeout, and response shape validation for the external API call.

Current code assumes success and valid shape; a network error or schema drift will crash the tool.

   execute: async () => {
-    const startDate = new Date().getFullYear() + "-01-01";
-    const endDate = new Date().toISOString().split("T")[0];
-
-    const response = await fetch(
-      `https://archive-api.open-meteo.com/v1/archive?latitude=51.5072&longitude=-0.1276&start_date=${startDate}&end_date=${endDate}&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,snowfall_sum`,
-    );
-
-    const { daily } = await response.json();
-
-    return {
-      date: daily.time,
-      temp_max: daily.temperature_2m_max,
-      temp_min: daily.temperature_2m_min,
-      rainfall: daily.precipitation_sum,
-      windspeed: daily.wind_speed_10m_max,
-      snowfall: daily.snowfall_sum,
-    };
+    const now = new Date();
+    const startDate = `${now.getFullYear()}-01-01`;
+    const endDate = now.toISOString().split("T")[0];
+
+    const controller = new AbortController();
+    const timeout = setTimeout(() => controller.abort(), 10_000);
+    try {
+      const url =
+        `https://archive-api.open-meteo.com/v1/archive` +
+        `?latitude=51.5072&longitude=-0.1276` +
+        `&start_date=${startDate}&end_date=${endDate}` +
+        `&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,snowfall_sum`;
+
+      const response = await fetch(url, {
+        signal: controller.signal,
+        headers: { accept: "application/json" },
+      });
+      if (!response.ok) {
+        throw new Error(`Open-Meteo request failed: ${response.status} ${response.statusText}`);
+      }
+      const json = await response.json();
+      const daily = json?.daily;
+      if (!daily || !Array.isArray(daily.time)) {
+        throw new Error("Unexpected response shape: missing daily time series");
+      }
+      const normalize = (arr: unknown[]) => (Array.isArray(arr) ? arr.map((v: any) => (v ?? 0)) : []);
+      return {
+        date: daily.time,
+        temp_max: normalize(daily.temperature_2m_max),
+        temp_min: normalize(daily.temperature_2m_min),
+        rainfall: normalize(daily.precipitation_sum),
+        windspeed: normalize(daily.wind_speed_10m_max),
+        snowfall: normalize(daily.snowfall_sum),
+      };
+    } finally {
+      clearTimeout(timeout);
+    }
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
execute: async () => {
const startDate = new Date().getFullYear() + "-01-01";
const endDate = new Date().toISOString().split("T")[0];
const response = await fetch(
`https://archive-api.open-meteo.com/v1/archive?latitude=51.5072&longitude=-0.1276&start_date=${startDate}&end_date=${endDate}&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,snowfall_sum`,
);
const { daily } = await response.json();
return {
date: daily.time,
temp_max: daily.temperature_2m_max,
temp_min: daily.temperature_2m_min,
rainfall: daily.precipitation_sum,
windspeed: daily.wind_speed_10m_max,
snowfall: daily.snowfall_sum,
};
},
execute: async () => {
// Use a single Date instance and template literals for clarity
const now = new Date();
const startDate = `${now.getFullYear()}-01-01`;
const endDate = now.toISOString().split("T")[0];
// Abort after 10 seconds to avoid hanging requests
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10_000);
try {
const url =
`https://archive-api.open-meteo.com/v1/archive` +
`?latitude=51.5072&longitude=-0.1276` +
`&start_date=${startDate}&end_date=${endDate}` +
`&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,snowfall_sum`;
const response = await fetch(url, {
signal: controller.signal,
headers: { accept: "application/json" },
});
if (!response.ok) {
throw new Error(`Open-Meteo request failed: ${response.status} ${response.statusText}`);
}
// Parse and validate response shape
const json = await response.json();
const daily = json?.daily;
if (!daily || !Array.isArray(daily.time)) {
throw new Error("Unexpected response shape: missing daily time series");
}
// Normalize arrays, replacing null/undefined with 0
const normalize = (arr: unknown[]) =>
Array.isArray(arr) ? arr.map((v: any) => (v ?? 0)) : [];
return {
date: daily.time,
temp_max: normalize(daily.temperature_2m_max),
temp_min: normalize(daily.temperature_2m_min),
rainfall: normalize(daily.precipitation_sum),
windspeed: normalize(daily.wind_speed_10m_max),
snowfall: normalize(daily.snowfall_sum),
};
} finally {
clearTimeout(timeout);
}
},
🤖 Prompt for AI Agents
In frameworks/mastra/using-a-tool/src/tools/london-weather-tool.ts around lines
15 to 33, the external fetch assumes success and a fixed response shape; add
robust error handling, a fetch timeout, and validate the response schema before
returning. Wrap the fetch and JSON parse in a try/catch, use an AbortController
with a reasonable timeout (e.g., 8–10s) to abort slow requests, check
response.ok and throw a descriptive error if not, and validate that the parsed
JSON contains a daily object with the expected arrays (time, temperature_2m_max,
temperature_2m_min, precipitation_sum, wind_speed_10m_max, snowfall_sum) of
matching lengths; if validation fails, throw or return a clear error so callers
can handle it. Ensure all thrown errors include context (URL, dates) and clean
up the abort timer in finally.

});
23 changes: 23 additions & 0 deletions frameworks/mastra/using-a-tool/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": true
},
"include": ["src/**/*", "index.ts"],
"exclude": ["node_modules", "dist"]
}