Skip to content
Merged
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
18 changes: 9 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!-- AGENTS-META {"title":"Mastra Root","version":"2.2.0","applies_to":"/","last_updated":"2025-11-27T00:00:00Z","status":"stable"} -->
<!-- AGENTS-META {"title":"Mastra Root","version":"2.3.0","applies_to":"/","last_updated":"2025-12-05T00:00:00Z","status":"stable"} -->
# AGENTS

## Project Overview

Mastra is a production-grade multi-agent framework for building agent-driven applications and RAG (retrieval-augmented generation) workflows. It provides **30+ enterprise tools**, **22+ specialized agents**, **10 workflows**, **4 agent networks**, **A2A/MCP orchestration**, and now a **complete UI component library** (49 components) for scalable AI systems. Key capabilities include **financial intelligence**, **RAG pipelines**, **observability**, **secure governance**, and **AI chat interfaces**.
Mastra is a production-grade multi-agent framework for building agent-driven applications and RAG (retrieval-augmented generation) workflows. It provides **34+ enterprise tools**, **38 specialized agents**, **10 workflows**, **4 agent networks**, **A2A/MCP orchestration**, and a **complete UI component library** (64 components: 30 AI Elements + 34 base UI) for scalable AI systems. Key capabilities include **financial intelligence**, **RAG pipelines**, **observability**, **secure governance**, and **AI chat interfaces**.

This repo is structured to keep tools, agents, workflows, networks, UI components, and configs separated, with strict Zod schemas for tool inputs/outputs and strong environment-based configuration in `src/mastra/config`.

Expand Down Expand Up @@ -89,9 +89,9 @@ NEXT_PUBLIC_MASTRA_API_URL=http://localhost:4111

## Architecture & conventions

- **Frontend** (`app/`, `ui/`, `src/components/ai-elements/`): Next.js 16 App Router with React 19. AI Elements (30 components) for chat/reasoning/canvas UIs. shadcn/ui base (19 components) in `ui/`. Tailwind CSS 4 with oklch color variables.
- **Tools** (`src/mastra/tools`): 30+ tools implementing `createTool({ id, inputSchema, outputSchema, execute })` with strict Zod schemas. Categories: Financial (Polygon, Finnhub, AlphaVantage), Research (SerpAPI, ArXiv), Data (CSV, JSON), RAG (chunking, embeddings).
- **Agents** (`src/mastra/agents`): 22+ agents composing tools into specialized behaviors (research, stock analysis, content creation, data processing).
- **Frontend** (`app/`, `ui/`, `src/components/ai-elements/`): Next.js 16 App Router with React 19. AI Elements (30 components) for chat/reasoning/canvas UIs. shadcn/ui base (34 components) in `ui/`. Tailwind CSS 4 with oklch color variables.
- **Tools** (`src/mastra/tools`): 34+ tools implementing `createTool({ id, inputSchema, outputSchema, execute })` with strict Zod schemas. Categories: Financial (Polygon, Finnhub, AlphaVantage), Research (SerpAPI, ArXiv), Data (CSV, JSON), RAG (chunking, embeddings).
- **Agents** (`src/mastra/agents`): 38 agents composing tools into specialized behaviors (research, stock analysis, content creation, data processing, business/legal, charting, image processing).
- **Networks** (`src/mastra/networks`): 4 agent networks for routing and orchestration (agentNetwork, dataPipelineNetwork, reportGenerationNetwork, researchPipelineNetwork).
- **Workflows** (`src/mastra/workflows`): 10 multi-step workflows using Mastra DSL (weather, content, financial reports, document processing, research synthesis).
- **Config** (`src/mastra/config`): Centralized provider clients (Google, OpenAI, Anthropic, OpenRouter, Vertex), pg-storage with PgVector, and role hierarchy.
Expand Down Expand Up @@ -120,11 +120,11 @@ NEXT_PUBLIC_MASTRA_API_URL=http://localhost:4111
## Where to look for more info

- `app/AGENTS.md`: Next.js App Router pages and layouts
- `ui/AGENTS.md`: shadcn/ui base components (19 components)
- `ui/AGENTS.md`: shadcn/ui base components (34 components)
- `src/components/ai-elements/AGENTS.md`: AI Elements library (30 components)
- `src/mastra/AGENTS.md`: top-level code-agent focused docs (this file is mirrored to subfolders)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor grammar fix: use hyphen in compound modifier.

Per static analysis, "code-agent focused" should use a hyphen when used as a compound adjective.

-- `src/mastra/AGENTS.md`: top-level code-agent focused docs (this file is mirrored to subfolders)
+- `src/mastra/AGENTS.md`: top-level code-agent-focused docs (this file is mirrored to subfolders)
📝 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
- `src/mastra/AGENTS.md`: top-level code-agent focused docs (this file is mirrored to subfolders)
- `src/mastra/AGENTS.md`: top-level code-agent-focused docs (this file is mirrored to subfolders)
🧰 Tools
🪛 LanguageTool

[grammar] ~125-~125: Use a hyphen to join words.
Context: .../mastra/AGENTS.md`: top-level code-agent focused docs (this file is mirrored to s...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In AGENTS.md around line 125, the phrase "code-agent focused" should be
corrected to the compound modifier "code-agent-focused"; update that occurrence
(and any other identical occurrences in the file) to use a hyphen so the
compound adjective is grammatically correct.

- `src/mastra/tools/AGENTS.md`: 30+ tools and their patterns
- `src/mastra/agents/AGENTS.md`: 22+ agents catalog
- `src/mastra/tools/AGENTS.md`: 34+ tools and their patterns
- `src/mastra/agents/AGENTS.md`: 38 agents catalog
- `src/mastra/workflows/AGENTS.md`: 10 workflow definitions
- `src/mastra/networks/AGENTS.md`: 4 agent networks
- `src/mastra/config/AGENTS.md`: configuration and storage guidance
Expand All @@ -135,4 +135,4 @@ NEXT_PUBLIC_MASTRA_API_URL=http://localhost:4111
If you need more details for a subdirectory, open the folder-specific `AGENTS.md` which contains persona, purpose, and actionable commands.

---
Last updated: 2025-11-27
Last updated: 2025-12-05
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- AGENTS-META {"title":"AgentStack README","version":"3.2.0","applies_to":"/","last_updated":"2025-11-27T00:00:00Z","status":"stable"} -->
<!-- AGENTS-META {"title":"AgentStack README","version":"3.3.0","applies_to":"/","last_updated":"2025-12-05T00:00:00Z","status":"stable"} -->

<div align="center">

Expand All @@ -13,7 +13,7 @@
[![wakatime](https://wakatime.com/badge/user/7a2fb9a0-188b-4568-887f-7645f9249e62/project/e44412f3-9bcc-4661-b79d-23160d90dfe0.svg)](https://wakatime.com/badge/user/7a2fb9a0-188b-4568-887f-7645f9249e62/project/e44412f3-9bcc-4661-b79d-23160d90dfe0)
[![GitMCP](https://img.shields.io/endpoint?url=https://gitmcp.io/badge/ssdeanx/AgentStack)](https://gitmcp.io/ssdeanx/AgentStack)

**AgentStack** is a **production-grade multi-agent framework** built on Mastra, delivering **30+ enterprise tools**, **25+ specialized agents**, **10 workflows**, **4 agent networks**, **49 UI components**, and **A2A/MCP orchestration** for scalable AI systems. Focuses on **financial intelligence**, **RAG pipelines**, **observability**, **secure governance**, and **AI chat interfaces**.
**AgentStack** is a **production-grade multi-agent framework** built on Mastra, delivering **34+ enterprise tools**, **38 specialized agents**, **10 workflows**, **4 agent networks**, **64 UI components** (30 AI Elements + 34 base), and **A2A/MCP orchestration** for scalable AI systems. Focuses on **financial intelligence**, **RAG pipelines**, **observability**, **secure governance**, and **AI chat interfaces**.

[![@mastra/core](https://img.shields.io/npm/v/@mastra/core.svg)](https://www.npmjs.com/package/@mastra/core)
[![@mastra/pg](https://img.shields.io/npm/v/@mastra/pg.svg)](https://www.npmjs.com/package/@mastra/pg)
Expand Down Expand Up @@ -44,7 +44,7 @@
| **Multi-Agent** | ✅ **A2A MCP + parallel orchestration** | ⚠️ Sequential | ✅ Sequential | ✅ Custom |
| **Governance** | ✅ **JWT/RBAC + path traversal + HTML sanitization** | ❌ Custom | ❌ None | ❌ None |
| **TypeScript** | ✅ **Zod schemas everywhere** | ⚠️ JS/TS mix | ⚠️ JS focus | ❌ Python |
| **UI Components** | ✅ **49 AI Elements + shadcn/ui** | ❌ None | ❌ None | ❌ None |
| **UI Components** | ✅ **64 AI Elements + shadcn/ui** | ❌ None | ❌ None | ❌ None |
| **Tests** | ✅ **97% Vitest coverage** | ⚠️ Partial | ❌ Sparse | ⚠️ Partial |

**Built for production**: Secure, observable, testable agents with **zero-config** PgVector RAG + **enterprise financial APIs**.
Expand All @@ -53,11 +53,11 @@

- **💰 Financial Intelligence**: 20+ tools (Polygon quotes/aggs/fundamentals, Finnhub analysis, AlphaVantage indicators)
- **🔍 Semantic RAG**: PgVector (3072D embeddings) + MDocument chunking + rerank + graph traversal
- **🤖 25+ Agents**: Research → Learn → Report → Edit → Analyze (stock/crypto/copywriter/evaluator/data pipeline)
- **🤖 38 Agents**: Research → Learn → Report → Edit → Analyze (stock/crypto/copywriter/evaluator/data pipeline/business-legal/charting/image)
- **📋 10 Workflows**: Weather, content, financial reports, document processing, research synthesis, learning extraction
- **🌐 4 Agent Networks**: Primary routing, data pipeline, report generation, research pipeline
- **🔌 A2A/MCP**: MCP server coordinates parallel agents (research+stock→report)
- **🎨 49 UI Components**: AI Elements (30 chat/reasoning/canvas components) + shadcn/ui (19 base primitives)
- **🎨 64 UI Components**: AI Elements (30 chat/reasoning/canvas components) + shadcn/ui (34 base primitives)
- **📊 Full Observability**: Arize/Phoenix traces + 10+ custom scorers (diversity/quality/completeness)
- **🛡️ Enterprise Security**: JWT auth, RBAC, path validation, HTML sanitization, secrets masking
- **⚡ Extensible**: Model registry (Gemini/OpenAI/Anthropic), Zod schemas everywhere
Expand All @@ -67,7 +67,7 @@
```mermaid
graph TB
subgraph "� Frontend (Next.js 16)"
UI[AI Elements + shadcn/ui<br/>• 30 AI Components<br/>• 19 Base Primitives]
UI[AI Elements + shadcn/ui<br/>• 30 AI Components<br/>• 34 Base Primitives]
App[App Router<br/>• React 19<br/>• Tailwind CSS 4]
end

Expand All @@ -76,8 +76,8 @@ graph TB
end

subgraph "🎯 AgentStack Runtime"
Coord --> Agents[22+ Agents<br/>• ResearchAgent<br/>• StockAnalysis<br/>• Copywriter<br/>• ReportAgent]
Agents --> Tools[30+ Tools<br/>• Polygon/Finnhub<br/>• SerpAPI 10+<br/>• PgVector RAG<br/>• PDF→MD]
Coord --> Agents[38 Agents<br/>• ResearchAgent<br/>• StockAnalysis<br/>• Copywriter<br/>• ReportAgent]
Agents --> Tools[34+ Tools<br/>• Polygon/Finnhub<br/>• SerpAPI 10+<br/>• PgVector RAG<br/>• PDF→MD]
Agents --> Workflows[Research→Report<br/>Weather→Activities]
end

Expand Down Expand Up @@ -212,14 +212,14 @@ npm run start
```bash
# Frontend
app/ # 📱 Next.js 16 App Router (layouts, pages)
ui/ # 🎨 shadcn/ui base components (19 primitives)
ui/ # 🎨 shadcn/ui base components (34 primitives)
src/components/ai-elements/ # 🤖 AI Elements (30 chat/reasoning/canvas components)

# Backend
src/mastra/
├── index.ts # 🎯 Mastra bootstrap (25+ agents, 10 workflows, 4 networks, MCP)
├── agents/ # 🤖 22+ agents (research/stock/copywriter/report/data pipeline...)
├── tools/ # 🔧 30+ tools (financial/RAG/scrape/PDF/SerpAPI...)
├── index.ts # 🎯 Mastra bootstrap (38 agents, 10 workflows, 4 networks, MCP)
├── agents/ # 🤖 38 agents (research/stock/copywriter/report/data pipeline/business-legal/charting...)
├── tools/ # 🔧 34+ tools (financial/RAG/scrape/PDF/SerpAPI...)
├── workflows/ # 📋 10 workflows (weather, content, financial, document, research)
├── networks/ # 🌐 4 agent networks (routing/coordination)
├── config/ # ⚙️ Models/PgVector/Logging/Auth
Expand Down Expand Up @@ -333,10 +333,10 @@ npm run mcp-server

## 📚 **Resources**

- **[UI Components](ui/AGENTS.md)**: 19 shadcn/ui base components
- **[UI Components](ui/AGENTS.md)**: 34 shadcn/ui base components
- **[AI Elements](src/components/ai-elements/AGENTS.md)**: 30 AI chat/reasoning/canvas components
- **[Agents Catalog](src/mastra/agents/AGENTS.md)**: 22+ agents
- **[Tools Matrix](src/mastra/tools/AGENTS.md)**: 30+ tools
- **[Agents Catalog](src/mastra/agents/AGENTS.md)**: 38 agents
- **[Tools Matrix](src/mastra/tools/AGENTS.md)**: 34+ tools
- **[Workflows](src/mastra/workflows/AGENTS.md)**: 10 multi-step workflows
- **[Networks](src/mastra/networks/AGENTS.md)**: 4 agent networks
- **[Config Guide](src/mastra/config/AGENTS.md)**: Setup + env vars
Expand All @@ -350,7 +350,7 @@ npm run mcp-server
- [x] **A2A MCP**: Parallel orchestration (✅ Live)
- [x] **10 Workflows**: Sequential, parallel, branch, loop, foreach, suspend/resume (✅ Live)
- [x] **4 Agent Networks**: Routing and coordination (✅ Live)
- [x] **UI Components**: AI Elements + shadcn/ui (49 components) (✅ Live)
- [x] **UI Components**: AI Elements + shadcn/ui (64 components) (✅ Live)
- [ ] **Chat Interface**: Full agent chat UI with AI Elements
- [ ] **LangSmith/Phoenix**: Eval dashboards
- [ ] **Docker/Helm**: K8s deploy
Expand All @@ -362,4 +362,4 @@ npm run mcp-server
🐦 **Follow [@ssdeanx](https://x.com/ssdeanx)**
📘 **[Docs](https://agentstack.ai)** (Coming Q1 2026)

_Last updated: 2025-11-27 | v3.2.0_
_Last updated: 2025-12-05 | v3.3.0_
75 changes: 16 additions & 59 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,21 @@
import { mastra } from "@/src/mastra";
import { createAgentStreamResponse } from "@/lib/client-stream-to-ai-sdk";
import type { UIMessage } from "ai";

export const maxDuration = 60;

interface ChatRequestBody {
messages: UIMessage[];
agentId?: string;
threadId?: string;
resourceId?: string;
memory?: {
thread?: string | { id: string; resourceId?: string };
resource?: string;
options?: {
lastMessages?: number;
semanticRecall?: boolean;
workingMemory?: { enabled?: boolean };
};
};
maxSteps?: number;
}
import { mastra } from "../../../src/mastra";
import { RuntimeContext } from "@mastra/core/runtime-context";
Comment on lines +1 to +2
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Import path uses relative path instead of alias.

The import uses "../../../src/mastra" instead of the path alias "@/src/mastra". While functional, path aliases improve readability and maintainability. The AI summary indicates this was changed from the alias to relative path.

-import { mastra } from "../../../src/mastra";
+import { mastra } from "@/src/mastra";
📝 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
import { mastra } from "../../../src/mastra";
import { RuntimeContext } from "@mastra/core/runtime-context";
import { mastra } from "@/src/mastra";
import { RuntimeContext } from "@mastra/core/runtime-context";
🤖 Prompt for AI Agents
In app/api/chat/route.ts around lines 1 to 2, the file imports mastra using a
relative path ("../../../src/mastra"); update the import to use the project path
alias (e.g. "@/src/mastra") so it reads import { mastra } from "@/src/mastra";
ensure other imports use the alias consistently and that tsconfig/next config
contains the corresponding path mapping so the build resolves the alias.


Comment on lines +1 to 3
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 export const maxDuration = 60; line was removed. This configuration is important for serverless environments like Vercel to prevent the API route from timing out on long-running agent executions. Without it, the function might time out prematurely, using the platform's default which is often shorter. I recommend re-adding it to ensure stability for potentially long-running agent tasks.

import { mastra } from "../../../src/mastra";
import { RuntimeContext } from "@mastra/core/runtime-context";

export const maxDuration = 60;

Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The removed maxDuration export could cause timeout issues. The previous implementation set maxDuration = 60 which is important for Next.js API routes that need longer execution times for streaming responses. Without this, the route may timeout prematurely on platforms like Vercel.

Suggested change
export const maxDuration = 60;

Copilot uses AI. Check for mistakes.
export async function POST(req: Request) {
const body: ChatRequestBody = await req.json();

// Get available agents dynamically from mastra
const agentsMap = await mastra.getAgents();
const availableAgents = Object.keys(agentsMap);

// Use first available agent if none specified
const agentId = body.agentId || availableAgents[0];

if (!agentId || !availableAgents.includes(agentId)) {
return Response.json(
{ error: `Invalid or missing agentId. Available: ${availableAgents.join(", ")}` },
{ status: 400 }
);
}
const { messages, data } = await req.json();
Copy link

Choose a reason for hiding this comment

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

logic: No validation for required messages field - will throw if undefined/missing from request body

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/api/chat/route.ts
Line: 5:5

Comment:
**logic:** No validation for required `messages` field - will throw if undefined/missing from request body

How can I resolve this? If you propose a fix, please make it concise.

Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

Missing input validation for required fields. The code assumes messages exists but doesn't validate it, which could lead to runtime errors if the request body is malformed. Add validation to check that messages is present and is an array before processing.

Suggested change
const { messages, data } = await req.json();
const { messages, data } = await req.json();
if (!Array.isArray(messages)) {
return new Response(
JSON.stringify({ error: "Missing or invalid 'messages' field. Must be an array." }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}

Copilot uses AI. Check for mistakes.
const myAgent = mastra.getAgent("weatherAgent");
Comment on lines 4 to +6
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing input validation for request body.

As per coding guidelines, API routes should validate user input server-side using a validation library like zod or yup. The messages and data fields are destructured without validation, which could lead to runtime errors or unexpected behavior.

+import { z } from "zod";
+
+const ChatRequestSchema = z.object({
+  messages: z.array(z.object({
+    role: z.string(),
+    content: z.string(),
+  })),
+  data: z.record(z.unknown()).optional(),
+});
+
 export async function POST(req: Request) {
-  const { messages, data } = await req.json();
+  const body = await req.json();
+  const { messages, data } = ChatRequestSchema.parse(body);
   const myAgent = mastra.getAgent("weatherAgent");
📝 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
export async function POST(req: Request) {
const body: ChatRequestBody = await req.json();
// Get available agents dynamically from mastra
const agentsMap = await mastra.getAgents();
const availableAgents = Object.keys(agentsMap);
// Use first available agent if none specified
const agentId = body.agentId || availableAgents[0];
if (!agentId || !availableAgents.includes(agentId)) {
return Response.json(
{ error: `Invalid or missing agentId. Available: ${availableAgents.join(", ")}` },
{ status: 400 }
);
}
const { messages, data } = await req.json();
const myAgent = mastra.getAgent("weatherAgent");
import { z } from "zod";
const ChatRequestSchema = z.object({
messages: z.array(z.object({
role: z.string(),
content: z.string(),
})),
data: z.record(z.unknown()).optional(),
});
export async function POST(req: Request) {
const body = await req.json();
const { messages, data } = ChatRequestSchema.parse(body);
const myAgent = mastra.getAgent("weatherAgent");
🤖 Prompt for AI Agents
In app/api/chat/route.ts around lines 4 to 6, the request body is destructured
into messages and data without validation; add server-side validation (use zod
or yup) by defining a schema for the expected shape (e.g., messages as an array
of message objects and data as an optional object), parse the incoming JSON with
the schema (use safeParse/validate) and if validation fails return a 400
response with an error message; on success use the typed/validated values for
the rest of the handler to avoid runtime errors.

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Hardcoded agent name reduces flexibility.

The agent is hardcoded to "weatherAgent". Consider accepting the agent ID from the request body or environment variable to support different agents, similar to how createAgentStreamResponse in lib/client-stream-to-ai-sdk.ts accepts agentId as a parameter.

🤖 Prompt for AI Agents
In app/api/chat/route.ts around line 6 the agent is hardcoded as "weatherAgent";
update the handler to accept an agentId (e.g., from the request body or a query
param) and pass that to mastra.getAgent instead of the literal string; validate
the incoming agentId (non-empty string), fall back to a sensible default or to
an environment variable like process.env.DEFAULT_AGENT if not provided, and
ensure any errors for unknown/missing agents are handled and logged before
proceeding.

Copy link

Choose a reason for hiding this comment

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

logic: Agent ID mismatch - weatherAgent doesn't exist. The agent is registered with ID weather-agent (see src/mastra/agents/weather-agent.ts:18). This will cause runtime errors.

Suggested change
const myAgent = mastra.getAgent("weatherAgent");
const myAgent = mastra.getAgent("weather-agent");
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/api/chat/route.ts
Line: 6:6

Comment:
**logic:** Agent ID mismatch - `weatherAgent` doesn't exist. The agent is registered with ID `weather-agent` (see `src/mastra/agents/weather-agent.ts:18`). This will cause runtime errors.

```suggestion
  const myAgent = mastra.getAgent("weather-agent");
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +5 to +6
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The chat API is hardcoded to use only "weatherAgent", removing the ability to select different agents dynamically. The previous implementation supported dynamic agent selection via agentId parameter. This is a breaking change that significantly reduces the API's flexibility and contradicts the PR description which mentions "streamline agent selection" - this completely removes it instead.

Suggested change
const { messages, data } = await req.json();
const myAgent = mastra.getAgent("weatherAgent");
const { messages, data, agentId } = await req.json();
const myAgent = mastra.getAgent(agentId ?? "weatherAgent");

Copilot uses AI. Check for mistakes.

if (!body.messages?.length) {
return Response.json({ error: "messages required" }, { status: 400 });
}

try {
return await createAgentStreamResponse(mastra as Parameters<typeof createAgentStreamResponse>[0], agentId, body.messages, {
threadId: body.threadId,
resourceId: body.resourceId,
memory: body.memory,
maxSteps: body.maxSteps ?? 50,
});
} catch (error) {
return Response.json(
{ error: error instanceof Error ? error.message : "Stream failed" },
{ status: 500 }
);
const runtimeContext = new RuntimeContext();

if (data) {
for (const [key, value] of Object.entries(data)) {
runtimeContext.set(key, value);
}
}
}

export async function GET() {
const agentsMap = await mastra.getAgents();
const availableAgents = Object.keys(agentsMap);
return Response.json({ agents: availableAgents, count: availableAgents.length });
}
const stream = await myAgent.stream(messages, {
runtimeContext,
format: "aisdk",
});
return stream.toUIMessageStreamResponse();
Comment on lines 4 to +20
Copy link

Choose a reason for hiding this comment

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

logic: No try-catch error handling - unhandled exceptions will crash the API route instead of returning proper error responses

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/api/chat/route.ts
Line: 4:20

Comment:
**logic:** No try-catch error handling - unhandled exceptions will crash the API route instead of returning proper error responses

How can I resolve this? If you propose a fix, please make it concise.

}
Comment on lines 4 to +21
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 chat API has been hardcoded to only use the weatherAgent. This significantly reduces the flexibility of this endpoint, which previously supported dynamic agent selection. While the PR description mentions this is a simplification, it makes the generic /api/chat route less reusable. To restore flexibility while keeping the new RuntimeContext logic, I suggest selecting the agent based on a property in the data object from the request body. This also includes adding a check to ensure the agent exists, providing a clearer error message to the client if it doesn't.

export async function POST(req: Request) {
  const { messages, data } = await req.json();

  const agentId = data?.agentId ?? "weatherAgent";
  const agent = mastra.getAgent(agentId);

  if (!agent) {
    const availableAgents = Object.keys(await mastra.getAgents());
    return Response.json(
      { error: `Agent '${agentId}' not found. Available agents: ${availableAgents.join(', ')}` },
      { status: 400 }
    );
  }

  const runtimeContext = new RuntimeContext();

  if (data) {
    const { agentId: _, ...contextData } = data;
    for (const [key, value] of Object.entries(contextData)) {
      runtimeContext.set(key, value);
    }
  }

  const stream = await agent.stream(messages, {
    runtimeContext,
    format: "aisdk",
  });
  return stream.toUIMessageStreamResponse();
}

Comment on lines 4 to +21
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing error handling for async operations.

Per coding guidelines, use try...catch blocks for handling errors in asynchronous operations. The route lacks error handling for JSON parsing, agent retrieval, and streaming failures.

 export async function POST(req: Request) {
+  try {
     const { messages, data } = await req.json();
     const myAgent = mastra.getAgent("weatherAgent");
 
     const runtimeContext = new RuntimeContext();
 
     if (data) {
       for (const [key, value] of Object.entries(data)) {
         runtimeContext.set(key, value);
       }
     }
 
     const stream = await myAgent.stream(messages, {
       runtimeContext,
       format: "aisdk",
     });
     return stream.toUIMessageStreamResponse();
+  } catch (error) {
+    console.error("Chat API error:", error);
+    return new Response(
+      JSON.stringify({ error: "Failed to process chat request" }),
+      { status: 500, headers: { "Content-Type": "application/json" } }
+    );
+  }
 }
🤖 Prompt for AI Agents
In app/api/chat/route.ts around lines 4 to 21, the POST handler performs
multiple asynchronous operations (req.json(), mastra.getAgent, agent.stream,
stream.toUIMessageStreamResponse) without error handling; wrap the body in a
try...catch, validate and guard req.json() output, check that myAgent exists
before using it, await and catch failures from myAgent.stream and
stream.toUIMessageStreamResponse, log the caught error for diagnostics, and
return an appropriate HTTP Response (e.g., JSON error message with a 400 or 500
status as appropriate) from the catch block so failures are surfaced to clients
instead of crashing the server.

Comment on lines +1 to +21
Copy link

Choose a reason for hiding this comment

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

logic: Removed all dynamic agent selection, error handling, validation, and the GET endpoint. The previous implementation supported any agent via agentId parameter and had proper error handling. Now it only works with one hardcoded agent. Is this intentional? This breaks existing functionality for multi-agent chat.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/api/chat/route.ts
Line: 1:21

Comment:
**logic:** Removed all dynamic agent selection, error handling, validation, and the GET endpoint. The previous implementation supported any agent via `agentId` parameter and had proper error handling. Now it only works with one hardcoded agent. Is this intentional? This breaks existing functionality for multi-agent chat.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 4 to +21
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The simplified API removed error handling that was present in the previous implementation. Consider wrapping the stream call in a try-catch block to handle potential errors gracefully and return appropriate error responses to the client.

Copilot uses AI. Check for mistakes.
93 changes: 44 additions & 49 deletions lib/client-stream-to-ai-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
createUIMessageStreamResponse,
} from "ai";
import { toAISdkFormat } from "@mastra/ai-sdk";
import type { Mastra } from "@mastra/core/mastra";
import type { AgentExecutionOptions } from "@mastra/core/agent";
import type { MastraModelOutput } from "@mastra/core/stream";

export interface StreamToAISdkOptions {
Expand All @@ -16,34 +18,34 @@ export interface AgentStreamOptions {
format?: "aisdk" | "mastra";
threadId?: string;
resourceId?: string;
memory?: {
thread?: string | { id: string; resourceId?: string };
resource?: string;
options?: {
lastMessages?: number;
semanticRecall?: boolean;
workingMemory?: { enabled?: boolean };
};
};
memory?: AgentExecutionOptions["memory"];
maxSteps?: number;
}

type MastraAgent = {
stream: (
messages: unknown,
options?: {
format?: string;
threadId?: string;
resourceId?: string;
memory?: AgentStreamOptions["memory"];
maxSteps?: number;
}
) => Promise<MastraModelOutput & { toUIMessageStreamResponse?: () => Response }>;
};
type StreamResult = MastraModelOutput & { toUIMessageStreamResponse?: () => Response };

function isReadableStream<T>(value: unknown): value is ReadableStream<T> {
return (
typeof value === "object" &&
value !== null &&
typeof (value as ReadableStream<T>).getReader === "function"
);
}

type MastraInstance = {
getAgent: (id: string) => MastraAgent;
} & Record<string, any>;
async function* asyncIterableFromReadableStream<T>(
stream: ReadableStream<T>
): AsyncIterable<T> {
const reader = stream.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield value;
}
} finally {
reader.releaseLock();
}
}

/**
* Creates a streaming Response for Next.js API routes using server-side Mastra agent.
Expand All @@ -55,7 +57,7 @@ type MastraInstance = {
* ```ts
* // app/api/chat/route.ts
* import { mastra } from "@/src/mastra";
* import { createAgentStreamResponse } from "@/lib/client-stream-to-ai-sdk";
* import { createAgentStreamResponse } from "@/lib/server/agent-stream";
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The documentation references an incorrect import path @/lib/server/agent-stream, but this function is exported from @/lib/client-stream-to-ai-sdk. The actual usage in app/api/chat/route.ts doesn't use this function at all. Update the example to match the actual implementation pattern.

Suggested change
* import { createAgentStreamResponse } from "@/lib/server/agent-stream";
* import { createAgentStreamResponse } from "@/lib/client-stream-to-ai-sdk";

Copilot uses AI. Check for mistakes.
*
* export async function POST(req: Request) {
* const { messages, agentId, threadId, resourceId, memory } = await req.json();
Expand All @@ -70,7 +72,7 @@ type MastraInstance = {
* @see https://mastra.ai/docs/frameworks/agentic-uis/ai-sdk
*/
export async function createAgentStreamResponse(
mastra: MastraInstance,
mastra: Mastra,
agentId: string,
messages: unknown,
options?: AgentStreamOptions
Expand All @@ -85,34 +87,27 @@ export async function createAgentStreamResponse(
maxSteps: options?.maxSteps,
};

// Preferred: Use built-in AI SDK format
if (streamOptions.format === "aisdk") {
const stream = await agent.stream(messages, streamOptions);
if (stream.toUIMessageStreamResponse) {
return stream.toUIMessageStreamResponse();
}
// Call agent.stream once and reuse the result
const stream = await agent.stream(messages, streamOptions) as StreamResult;

// Preferred: Use built-in AI SDK format when available
if (streamOptions.format === "aisdk" && stream.toUIMessageStreamResponse) {
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The code calls agent.stream() once and stores the result, which is good. However, when format === "aisdk" but toUIMessageStreamResponse is not available, the code falls through to the manual transformation that may attempt to process an already-consumed stream. Consider checking if the stream can be reused or restructuring to avoid potential stream consumption issues.

Suggested change
if (streamOptions.format === "aisdk" && stream.toUIMessageStreamResponse) {
if (streamOptions.format === "aisdk" && typeof stream.toUIMessageStreamResponse === "function") {

Copilot uses AI. Check for mistakes.
return stream.toUIMessageStreamResponse();
}

// Fallback: Manual transformation with toAISdkFormat
const stream = await agent.stream(messages, {
threadId: options?.threadId,
resourceId: options?.resourceId,
memory: options?.memory,
maxSteps: options?.maxSteps,
});

// Handles both ReadableStream and AsyncIterable return types
const uiMessageStream = createUIMessageStream({
execute: async ({ writer }) => {
const aiSdkStream = toAISdkFormat(stream, { from: "agent" });
const reader = aiSdkStream.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
writer.write(value);
}
} finally {
reader.releaseLock();
const aiSdkResult = toAISdkFormat(stream, { from: "agent" });

// Handle both ReadableStream and AsyncIterable
const iterable: AsyncIterable<unknown> = isReadableStream(aiSdkResult)
? asyncIterableFromReadableStream(aiSdkResult)
: aiSdkResult;

for await (const value of iterable) {
writer.write(value as Parameters<typeof writer.write>[0]);
}
Comment on lines +109 to 111
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Type assertion is acceptable but consider stronger typing.

The cast value as Parameters<typeof writer.write>[0] works but is somewhat opaque. Consider defining an explicit type for the stream chunk if this pattern is used elsewhere.

🤖 Prompt for AI Agents
In lib/client-stream-to-ai-sdk.ts around lines 109 to 111, the loop casts each
iterated value with an opaque assertion (value as Parameters<typeof
writer.write>[0]); define an explicit chunk type (e.g., type StreamChunk = ...
or export an interface) that matches writer.write's parameter and use that type
for the iterable (for await (const value of iterable as
AsyncIterable<StreamChunk>)) and for writer.write(value) so the cast is removed
and the types are clear and reusable across the codebase.

},
});
Expand Down
10 changes: 7 additions & 3 deletions lib/mastra-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { MastraClient } from "@mastra/client-js";

/**
* Client-side Mastra SDK instance for frontend use.
* Use this in React components to interact with the Mastra API.
*
* For server-side streaming in API routes, import createAgentStreamResponse
* directly from "@/lib/client-stream-to-ai-sdk" instead.
*/
Comment on lines +3 to +9
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The comment directs users to import from @/lib/client-stream-to-ai-sdk, but this creates a confusing API surface. The previous approach of re-exporting from a single entry point was cleaner. Consider either: (1) restoring the exports for convenience, or (2) creating a separate @/lib/server directory to make the client/server distinction clearer.

Copilot uses AI. Check for mistakes.
export const mastraClient = new MastraClient({
baseUrl: process.env.NEXT_PUBLIC_MASTRA_API_URL || "http://localhost:4111",
retries: 3,
Expand All @@ -9,6 +16,3 @@ export const mastraClient = new MastraClient({
headers: {},
credentials: "same-origin",
});

export { createAgentStreamResponse } from "./client-stream-to-ai-sdk";
export type { StreamToAISdkOptions } from "./client-stream-to-ai-sdk";
Loading