diff --git a/README.md b/README.md
index 2ee43eb..4d0eee3 100644
--- a/README.md
+++ b/README.md
@@ -2,64 +2,43 @@

-Essential utilities that extend and improve the Vercel AI SDK experience. State management, debugging tools, and structured artifact streaming - everything you need to build production-ready AI applications beyond simple chat interfaces.
+Essential utilities for building production-ready AI applications with Vercel AI SDK. State management, debugging, structured streaming, intelligent agents, and caching - everything you need beyond basic chat interfaces.
## Packages
-### ๐๏ธ [@ai-sdk-tools/store](./packages/store)
-AI chat state that scales with your application. Eliminates prop drilling within your chat components, ensuring better performance and cleaner architecture.
+### [@ai-sdk-tools/store](./packages/store)
+AI chat state management that eliminates prop drilling. Clean architecture and better performance for chat components.
```bash
npm i @ai-sdk-tools/store
```
-### ๐ง [@ai-sdk-tools/devtools](./packages/devtools)
-Development tools for debugging AI applications. A development-only debugging tool that integrates directly into your codebase, just like react-query-devtools.
+### [@ai-sdk-tools/devtools](./packages/devtools)
+Development tools for debugging AI applications. Inspect tool calls, messages, and execution flow directly in your app.
```bash
npm i @ai-sdk-tools/devtools
```
-### ๐ฆ [@ai-sdk-tools/artifacts](./packages/artifacts)
-Advanced streaming interfaces for AI applications. Create structured, type-safe artifacts that stream real-time updates from AI tools to React components. Perfect for dashboards, analytics, documents, and interactive experiences beyond chat.
+### [@ai-sdk-tools/artifacts](./packages/artifacts)
+Stream structured, type-safe artifacts from AI tools to React components. Build dashboards, analytics, and interactive experiences beyond chat.
```bash
npm i @ai-sdk-tools/artifacts @ai-sdk-tools/store
```
-## Quick Example
-
-Build advanced AI interfaces with structured streaming:
-
-```tsx
-// Define an artifact
-const BurnRate = artifact('burn-rate', z.object({
- title: z.string(),
- data: z.array(z.object({
- month: z.string(),
- burnRate: z.number()
- }))
-}));
-
-// Stream from AI tool
-const analysis = BurnRate.stream({ title: 'Q4 Analysis' });
-await analysis.update({ data: [{ month: '2024-01', burnRate: 50000 }] });
-await analysis.complete({ title: 'Q4 Analysis Complete' });
-
-// Consume in React
-function Dashboard() {
- const { data, status, progress } = useArtifact(BurnRate);
-
- return (
-
-
{data?.title}
- {status === 'loading' &&
Loading... {progress * 100}%
}
- {data?.data.map(item => (
-
{item.month}: ${item.burnRate}
- ))}
-
- );
-}
+### [@ai-sdk-tools/agents](./packages/agents)
+Multi-agent orchestration with automatic handoffs and routing. Build intelligent workflows with specialized agents for any AI provider.
+
+```bash
+npm i @ai-sdk-tools/agents ai zod
+```
+
+### [@ai-sdk-tools/cache](./packages/cache)
+Universal caching for AI SDK tools. Cache expensive operations with zero configuration - works with regular tools, streaming, and artifacts.
+
+```bash
+npm i @ai-sdk-tools/cache
```
## Getting Started
@@ -74,4 +53,4 @@ Visit our [website](https://ai-sdk-tools.dev) to explore interactive demos and d
## License
-MIT
\ No newline at end of file
+MIT
diff --git a/apps/example/src/ai/README.md b/apps/example/src/ai/README.md
deleted file mode 100644
index e3bab76..0000000
--- a/apps/example/src/ai/README.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# AI Module Structure
-
-This directory contains all AI-related functionality organized by type.
-
-## Directory Structure
-
-```
-src/ai/
-โโโ artifacts/ # AI SDK Artifacts
-โ โโโ burn-rate.ts # Burn rate analysis artifact
-โ โโโ index.ts # Export all artifacts
-โโโ tools/ # AI SDK Tools
-โ โโโ burn-rate.ts # Burn rate analysis tool
-โ โโโ index.ts # Export all tools
-โโโ index.ts # Main export file
-โโโ README.md # This file
-```
-
-## Artifacts (`/artifacts`)
-
-Artifacts define the data schemas and streaming behavior for AI-generated content. They are used to create interactive, real-time updates in the UI.
-
-### Example: Burn Rate Artifact
-
-```typescript
-import { BurnRateArtifact } from "@/ai/artifacts";
-
-// Use in components
-const burnRateData = useArtifact(BurnRateArtifact);
-```
-
-## Tools (`/tools`)
-
-Tools define the AI SDK tool implementations that can be called by the AI model. They contain the business logic and return structured data.
-
-### Example: Burn Rate Tool
-
-```typescript
-import { analyzeBurnRateTool } from "@/ai/tools";
-
-// Use in API routes
-const result = streamText({
- model: openai("gpt-4o"),
- tools: {
- analyzeBurnRate: analyzeBurnRateTool,
- },
-});
-```
-
-## Adding New AI Features
-
-1. **Create Artifact**: Define the data schema in `/artifacts/feature-name.ts`
-2. **Create Tool**: Implement the tool logic in `/tools/feature-name.ts`
-3. **Export**: Add exports to the respective `index.ts` files
-4. **Use**: Import from `@/ai` in your components and API routes
-
-## Best Practices
-
-- Keep artifacts focused on data structure and streaming behavior
-- Keep tools focused on business logic and AI integration
-- Use descriptive names for both artifacts and tools
-- Export everything through index files for clean imports
-- Document complex logic and data structures
diff --git a/apps/example/src/ai/agents/agents/index.ts b/apps/example/src/ai/agents/agents/index.ts
deleted file mode 100644
index ec83424..0000000
--- a/apps/example/src/ai/agents/agents/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Financial Specialist Agents
- *
- * Domain-specific agents for financial operations
- */
-
-export { invoicesAgent } from "./invoices-agent";
-export { reportsAgent } from "./reports-agent";
-export { transactionsAgent } from "./transactions-agent";
-
-// Future agents to add:
-// - Customers Agent
-// - Time Tracking Agent
-// - Forecasting Agent
-// - Accounts Agent
-// - Operations Agent
diff --git a/apps/example/src/ai/agents/agents/invoices-agent.ts b/apps/example/src/ai/agents/agents/invoices-agent.ts
deleted file mode 100644
index fce0b86..0000000
--- a/apps/example/src/ai/agents/agents/invoices-agent.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { openai } from "@ai-sdk/openai";
-import { Agent } from "@ai-sdk-tools/agents";
-import { getInvoiceTool, listInvoicesTool } from "../tools/invoices";
-
-/**
- * Invoices Specialist Agent
- *
- * Handles invoice queries and management
- */
-export const invoicesAgent = new Agent({
- name: "Invoices Specialist",
- model: openai("gpt-4o-mini"),
- instructions: `You are an invoices specialist. You help users:
- - Find and filter invoices by customer, date, status, or search query
- - Get details of specific invoices
- - Understand invoice status and payment information
-
-Common invoice statuses:
-- draft: Invoice is being prepared, not sent yet
-- unpaid: Invoice has been sent but not paid
-- paid: Invoice has been paid in full
-- overdue: Invoice is past due date and unpaid
-- canceled: Invoice has been canceled
-
-Be helpful in suggesting filters and helping users track their invoices.
-When discussing payment status, be clear about due dates and amounts.`,
- tools: {
- listInvoices: listInvoicesTool,
- getInvoice: getInvoiceTool,
- },
- maxTurns: 5,
-});
diff --git a/apps/example/src/ai/agents/agents/reports-agent.ts b/apps/example/src/ai/agents/agents/reports-agent.ts
deleted file mode 100644
index 139cbf5..0000000
--- a/apps/example/src/ai/agents/agents/reports-agent.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { openai } from "@ai-sdk/openai";
-import { Agent } from "@ai-sdk-tools/agents";
-import {
- burnRateMetricsTool,
- profitLossTool,
- revenueDashboardTool,
- runwayMetricsTool,
- spendingMetricsTool,
-} from "../tools/reports";
-
-/**
- * Financial Reports Specialist Agent
- *
- * Handles all financial reporting and metrics analysis
- */
-export const reportsAgent = new Agent({
- name: "Financial Reports Specialist",
- model: openai("gpt-4o-mini"),
- instructions: `You are a financial reports specialist. You help users access and understand:
- - P&L (Profit & Loss) statements
- - Runway calculations
- - Revenue metrics and analysis
- - Burn rate analysis
- - Spending breakdowns and analysis
-
-You have access to these specific tools:
-- revenueDashboard: Generate comprehensive revenue dashboard with charts and metrics
-- profitMetrics: Get P&L (profit & loss) metrics
-- runwayMetrics: Calculate runway (months of cash remaining)
-- burnRateMetrics: Analyze burn rate and cash consumption
-- spendingMetrics: Get spending analysis with category breakdowns
-
-If asked about data you don't have access to (like balance sheets or tax summaries), politely explain what you CAN provide instead.
-
-Provide clear, concise summaries of financial data. Always format dates properly and clarify currency when relevant.
-
-When users ask about date ranges, help them with common periods:
-- "Q1 2024" = January 1 - March 31, 2024
-- "last quarter" = previous 3 months
-- "this month" = current month
-- "YTD" = year to date (Jan 1 to today)
-- "2024" = January 1 - December 31, 2024
-
-Always express monetary values with the appropriate currency symbol or code.`,
- tools: {
- revenueDashboard: revenueDashboardTool,
- profitMetrics: profitLossTool,
- runwayMetrics: runwayMetricsTool,
- burnRateMetrics: burnRateMetricsTool,
- spendingMetrics: spendingMetricsTool,
- },
- maxTurns: 8,
-});
diff --git a/apps/example/src/ai/agents/agents/transactions-agent.ts b/apps/example/src/ai/agents/agents/transactions-agent.ts
deleted file mode 100644
index b2b92f0..0000000
--- a/apps/example/src/ai/agents/agents/transactions-agent.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { openai } from "@ai-sdk/openai";
-import { Agent } from "@ai-sdk-tools/agents";
-import {
- getTransactionTool,
- listTransactionsTool,
-} from "../tools/transactions";
-
-/**
- * Transactions Specialist Agent
- *
- * Handles transaction queries and searches
- */
-export const transactionsAgent = new Agent({
- name: "Transactions Specialist",
- model: openai("gpt-4o-mini"),
- instructions: `You are a transactions specialist. You help users:
- - Find and filter transactions by date, type, category, status, or search query
- - Get details of specific transactions
- - Understand transaction patterns and data
-
-Be helpful in suggesting useful filters when users ask about transactions.
-
-Common transaction statuses:
-- pending: Transaction is not yet finalized
-- completed: Transaction is complete
-- posted: Transaction is posted to account
-- archived: Transaction is archived
-- excluded: Transaction is excluded from reports
-
-Transaction types:
-- income: Money coming in
-- expense: Money going out
-
-Explain the data clearly and help users find what they're looking for.`,
- tools: {
- listTransactions: listTransactionsTool,
- getTransaction: getTransactionTool,
- },
- maxTurns: 5,
-});
diff --git a/apps/example/src/ai/agents/analytics.ts b/apps/example/src/ai/agents/analytics.ts
new file mode 100644
index 0000000..fbc9ef5
--- /dev/null
+++ b/apps/example/src/ai/agents/analytics.ts
@@ -0,0 +1,67 @@
+/**
+ * Analytics Specialist Agent
+ *
+ * Analytics & forecasting specialist with business intelligence tools
+ */
+
+import { openai } from "@ai-sdk/openai";
+import {
+ businessHealthScoreTool,
+ cashFlowForecastTool,
+ cashFlowStressTestTool,
+} from "../tools/analytics";
+import { createAgent, formatContextForLLM } from "./shared";
+
+export const analyticsAgent = createAgent({
+ name: "analytics",
+ model: openai("gpt-4o-mini"),
+ instructions: (
+ ctx,
+ ) => `You are an analytics & forecasting specialist with access to business intelligence tools for ${ctx.companyName}.
+
+CRITICAL RULES:
+1. ALWAYS use your tools to run analysis - NEVER ask user for data
+2. Call tools IMMEDIATELY when asked for forecasts, health scores, or stress tests
+3. Present analytics clearly with key insights highlighted
+4. Answer ONLY what was asked - don't provide extra analysis unless requested
+
+TOOL SELECTION:
+- "health" or "healthy" queries โ Use businessHealth tool (gives consolidated score)
+- "forecast" or "prediction" โ Use cashFlowForecast tool
+- "stress test" or "what if" โ Use stressTest tool
+- DO NOT call multiple detailed tools (revenue, P&L, etc.) - use businessHealth for overview
+
+PRESENTATION STYLE:
+- Reference ${ctx.companyName} when providing insights
+- Use clear trend labels (Increasing, Decreasing, Stable)
+- Use clear status labels (Healthy, Warning, Critical)
+- Include confidence levels when forecasting (e.g., "High confidence", "Moderate risk")
+- End with 2-3 actionable focus areas (not a laundry list)
+- Keep responses concise - quality over quantity
+
+${formatContextForLLM(ctx)}`,
+ tools: {
+ businessHealth: businessHealthScoreTool,
+ cashFlowForecast: cashFlowForecastTool,
+ stressTest: cashFlowStressTestTool,
+ },
+ matchOn: [
+ "forecast",
+ "prediction",
+ "predict",
+ "stress test",
+ "what if",
+ "scenario",
+ "health score",
+ "business health",
+ "healthy",
+ "health",
+ "analyze",
+ "analysis",
+ "future",
+ "projection",
+ /forecast/i,
+ /health.*score/i,
+ ],
+ maxTurns: 5,
+});
diff --git a/apps/example/src/ai/agents/customers.ts b/apps/example/src/ai/agents/customers.ts
new file mode 100644
index 0000000..50284d1
--- /dev/null
+++ b/apps/example/src/ai/agents/customers.ts
@@ -0,0 +1,36 @@
+import { openai } from "@ai-sdk/openai";
+import {
+ createCustomerTool,
+ customerProfitabilityTool,
+ getCustomerTool,
+ updateCustomerTool,
+} from "../tools/customers";
+import { createAgent, formatContextForLLM } from "./shared";
+
+export const customersAgent = createAgent({
+ name: "customers",
+ model: openai("gpt-4o-mini"),
+ instructions: (
+ ctx,
+ ) => `You are a customer management specialist for ${ctx.companyName}.
+
+CRITICAL RULES:
+1. ALWAYS use tools to get/create/update customer data
+2. Present customer information clearly with key details
+3. Highlight profitability insights when analyzing customers
+
+${formatContextForLLM(ctx)}`,
+ tools: {
+ getCustomer: getCustomerTool,
+ createCustomer: createCustomerTool,
+ updateCustomer: updateCustomerTool,
+ profitabilityAnalysis: customerProfitabilityTool,
+ },
+ matchOn: [
+ "customer",
+ "client",
+ "customer profitability",
+ "customer analysis",
+ ],
+ maxTurns: 5,
+});
diff --git a/apps/example/src/ai/agents/general.ts b/apps/example/src/ai/agents/general.ts
new file mode 100644
index 0000000..fef8666
--- /dev/null
+++ b/apps/example/src/ai/agents/general.ts
@@ -0,0 +1,46 @@
+import { openai } from "@ai-sdk/openai";
+import { createAgent, formatContextForLLM } from "./shared";
+
+export const generalAgent = createAgent({
+ name: "general",
+ model: openai("gpt-4o-mini"),
+ instructions: (ctx) => `You are a general assistant for ${ctx.companyName}.
+
+YOUR ROLE:
+- Handle general conversation (greetings, thanks, casual chat)
+- Answer questions about what you can do and your capabilities
+- Handle ambiguous or unclear requests by asking clarifying questions
+- Provide helpful information about the available specialists
+
+AVAILABLE SPECIALISTS:
+- **reports**: Financial metrics (revenue, P&L, burn rate, runway, etc.)
+- **transactions**: Transaction history and details
+- **invoices**: Invoice management
+- **timeTracking**: Time tracking and timers
+- **customers**: Customer management and profitability
+- **analytics**: Forecasting and business intelligence
+- **operations**: Inbox, documents, balances, data export
+
+STYLE:
+- Be friendly and helpful
+- Reference ${ctx.companyName} when relevant
+- If the user asks for something specific, suggest the right specialist
+- Keep responses concise but complete
+
+${formatContextForLLM(ctx)}`,
+ matchOn: [
+ "hello",
+ "hi",
+ "hey",
+ "thanks",
+ "thank you",
+ "what can you do",
+ "previous question",
+ "last question",
+ "help",
+ "how does this work",
+ "what are you",
+ "who are you",
+ ],
+ maxTurns: 5,
+});
diff --git a/apps/example/src/ai/agents/index.ts b/apps/example/src/ai/agents/index.ts
index 0f3865c..4cda103 100644
--- a/apps/example/src/ai/agents/index.ts
+++ b/apps/example/src/ai/agents/index.ts
@@ -1,17 +1,13 @@
-/**
- * Financial Agents - Main Entry Point
- *
- * Modular agent system for comprehensive financial operations
- */
+export { analyticsAgent } from "./analytics";
+export { customersAgent } from "./customers";
+export { generalAgent } from "./general";
+export { invoicesAgent } from "./invoices";
+export { operationsAgent } from "./operations";
+export { reportsAgent } from "./reports";
-// Individual specialist agents (for advanced usage)
-export {
- invoicesAgent,
- reportsAgent,
- transactionsAgent,
-} from "./agents";
-// Main orchestrator (primary export)
-export { orchestratorAgent } from "./orchestrator";
+// Shared utilities
+export { timeTrackingAgent } from "./time-tracking";
+export { transactionsAgent } from "./transactions";
-// Re-export types and utilities for external use
-export type * from "./types/filters";
+// Triage agent (main entry point)
+export { triageAgent } from "./triage";
diff --git a/apps/example/src/ai/agents/invoices.ts b/apps/example/src/ai/agents/invoices.ts
new file mode 100644
index 0000000..045e4dc
--- /dev/null
+++ b/apps/example/src/ai/agents/invoices.ts
@@ -0,0 +1,39 @@
+import { openai } from "@ai-sdk/openai";
+import {
+ createInvoiceTool,
+ getInvoiceTool,
+ listInvoicesTool,
+ updateInvoiceTool,
+} from "../tools/invoices";
+import { createAgent, formatContextForLLM } from "./shared";
+
+export const invoicesAgent = createAgent({
+ name: "invoices",
+ model: openai("gpt-4o-mini"),
+ instructions: (
+ ctx,
+ ) => `You are an invoice management specialist for ${ctx.companyName}.
+
+CRITICAL RULES:
+1. ALWAYS use tools to get/create/update invoice data
+2. Present invoice information clearly with key details (amount, status, due date)
+3. Use clear status labels (Paid, Overdue, Pending)
+
+${formatContextForLLM(ctx)}`,
+ tools: {
+ listInvoices: listInvoicesTool,
+ getInvoice: getInvoiceTool,
+ createInvoice: createInvoiceTool,
+ updateInvoice: updateInvoiceTool,
+ },
+ matchOn: [
+ "invoice",
+ "bill",
+ "create invoice",
+ "send invoice",
+ "unpaid invoice",
+ "paid invoice",
+ /create.*invoice/i,
+ ],
+ maxTurns: 5,
+});
diff --git a/apps/example/src/ai/agents/operations.ts b/apps/example/src/ai/agents/operations.ts
new file mode 100644
index 0000000..7ca2ac8
--- /dev/null
+++ b/apps/example/src/ai/agents/operations.ts
@@ -0,0 +1,31 @@
+import { openai } from "@ai-sdk/openai";
+import {
+ exportDataTool,
+ getBalancesTool,
+ listDocumentsTool,
+ listInboxItemsTool,
+} from "../tools/operations";
+import { createAgent, formatContextForLLM } from "./shared";
+
+export const operationsAgent = createAgent({
+ name: "operations",
+ model: openai("gpt-4o-mini"),
+ instructions: (
+ ctx,
+ ) => `You are an operations specialist for ${ctx.companyName}.
+
+CRITICAL RULES:
+1. ALWAYS use tools to get inbox items, documents, balances, or export data
+2. Present information clearly with counts and summaries
+3. Organize multiple items in clear lists or tables
+
+${formatContextForLLM(ctx)}`,
+ tools: {
+ listInbox: listInboxItemsTool,
+ getBalances: getBalancesTool,
+ listDocuments: listDocumentsTool,
+ exportData: exportDataTool,
+ },
+ matchOn: ["inbox", "document", "export", "balance", "account balance"],
+ maxTurns: 5,
+});
diff --git a/apps/example/src/ai/agents/orchestrator.ts b/apps/example/src/ai/agents/orchestrator.ts
deleted file mode 100644
index 403e8f8..0000000
--- a/apps/example/src/ai/agents/orchestrator.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { openai } from "@ai-sdk/openai";
-import { Agent } from "@ai-sdk-tools/agents";
-import { invoicesAgent, reportsAgent, transactionsAgent } from "./agents";
-
-/**
- * Financial Assistant Orchestrator
- *
- * Main entry point that routes user queries to appropriate specialist agents
- */
-export const orchestratorAgent = Agent.create({
- name: "Financial Assistant",
- model: openai("gpt-4o-mini"),
- instructions: `You are a financial assistant that routes requests to specialist agents.
-
-**Financial Reports Specialist** handles:
-- Revenue metrics
-- Profit & Loss (P&L)
-- Runway calculations
-- Burn rate analysis
-- Spending analysis
-
-**Transactions Specialist** handles:
-- Listing transactions with filters
-- Getting transaction details
-
-**Invoices Specialist** handles:
-- Listing invoices with filters
-- Getting invoice details
-
-Route to the appropriate specialist. If a query needs multiple specialists, route to the first one with context about what else is needed.`,
- handoffs: [reportsAgent, transactionsAgent, invoicesAgent],
- maxTurns: 3,
- temperature: 0,
-});
diff --git a/apps/example/src/ai/agents/reports.ts b/apps/example/src/ai/agents/reports.ts
new file mode 100644
index 0000000..cd40517
--- /dev/null
+++ b/apps/example/src/ai/agents/reports.ts
@@ -0,0 +1,77 @@
+import { openai } from "@ai-sdk/openai";
+import {
+ balanceSheetTool,
+ burnRateMetricsTool,
+ cashFlowTool,
+ expensesTool,
+ profitLossTool,
+ revenueDashboardTool,
+ runwayMetricsTool,
+ spendingMetricsTool,
+ taxSummaryTool,
+} from "../tools/reports";
+import { createAgent, formatContextForLLM } from "./shared";
+
+export const reportsAgent = createAgent({
+ name: "reports",
+ model: openai("gpt-4o-mini"),
+ instructions: (
+ ctx,
+ ) => `You are a financial reports specialist with access to live financial data.
+
+YOUR SCOPE: Provide specific financial reports (revenue, P&L, cash flow, etc.)
+NOT YOUR SCOPE: Business health analysis, forecasting (those go to analytics specialist)
+
+CRITICAL RULES:
+1. ALWAYS use your tools to get data - NEVER ask the user for information you can retrieve
+2. Call tools IMMEDIATELY when asked for financial metrics
+3. Present results clearly after retrieving data
+4. For date ranges: "Q1 2024" = 2024-01-01 to 2024-03-31, "2024" = 2024-01-01 to 2024-12-31
+5. Answer ONLY what was asked - don't provide extra reports unless requested
+
+TOOL SELECTION GUIDE:
+- "runway" or "how long can we last" โ Use runway tool
+- "burn rate" or "monthly burn" โ Use burnRate tool
+- "revenue" or "income" โ Use revenue tool
+- "P&L" or "profit" or "loss" โ Use profitLoss tool
+- "cash flow" โ Use cashFlow tool
+- "balance sheet" or "assets/liabilities" โ Use balanceSheet tool
+- "expenses" or "spending breakdown" โ Use expenses tool
+- "tax" โ Use taxSummary tool
+
+PRESENTATION STYLE:
+- Reference the company name (${ctx.companyName}) when providing insights
+- Use clear sections with headers for multiple metrics
+- Include status indicators (e.g., "Status: Healthy", "Warning", "Critical")
+- End with a brief key insight or takeaway when relevant
+- Be concise but complete - no unnecessary fluff
+
+${formatContextForLLM(ctx)}`,
+ tools: {
+ revenue: revenueDashboardTool,
+ profitLoss: profitLossTool,
+ cashFlow: cashFlowTool,
+ balanceSheet: balanceSheetTool,
+ expenses: expensesTool,
+ burnRate: burnRateMetricsTool,
+ runway: runwayMetricsTool,
+ spending: spendingMetricsTool,
+ taxSummary: taxSummaryTool,
+ },
+ matchOn: [
+ "revenue",
+ "profit",
+ "loss",
+ "p&l",
+ "runway",
+ "burn rate",
+ "expenses",
+ "spending",
+ "balance sheet",
+ "tax",
+ "financial report",
+ /burn.?rate/i,
+ /profit.*loss/i,
+ ],
+ maxTurns: 5,
+});
diff --git a/apps/example/src/ai/agents/shared.ts b/apps/example/src/ai/agents/shared.ts
new file mode 100644
index 0000000..417797b
--- /dev/null
+++ b/apps/example/src/ai/agents/shared.ts
@@ -0,0 +1,83 @@
+/**
+ * Shared Agent Configuration
+ *
+ * Dynamic context and utilities used across all agents
+ */
+
+import type { AgentConfig } from "@ai-sdk-tools/agents";
+import { Agent } from "@ai-sdk-tools/agents";
+
+/**
+ * Application context passed to agents
+ * Built dynamically per-request with current date/time
+ */
+export interface AppContext {
+ userId: string;
+ fullName: string;
+ email: string;
+ teamId: string;
+ companyName: string;
+ baseCurrency: string;
+ locale: string;
+ currentDate: string;
+ currentTime: string;
+ currentDateTime: string;
+ timezone: string;
+ // Allow additional properties to satisfy Record constraint
+ [key: string]: unknown;
+}
+
+/**
+ * Build application context dynamically
+ * Ensures current date/time on every request
+ */
+export function buildAppContext(params: {
+ userId: string;
+ fullName: string;
+ email: string;
+ teamId: string;
+ companyName: string;
+ baseCurrency?: string;
+ locale?: string;
+ timezone?: string;
+}): AppContext {
+ const now = new Date();
+ return {
+ userId: params.userId,
+ fullName: params.fullName,
+ email: params.email,
+ teamId: params.teamId,
+ companyName: params.companyName,
+ baseCurrency: params.baseCurrency || "USD",
+ locale: params.locale || "en-US",
+ currentDate: now.toISOString().split("T")[0],
+ currentTime: now.toTimeString().split(" ")[0],
+ currentDateTime: now.toISOString(),
+ timezone:
+ params.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
+ };
+}
+
+/**
+ * Format context for LLM system prompts
+ * Auto-injected by agent instructions functions
+ */
+export function formatContextForLLM(context: AppContext): string {
+ return `
+CURRENT CONTEXT:
+- Date: ${context.currentDate} ${context.currentTime} (${context.timezone})
+- User: ${context.fullName} (${context.email})
+- Company: ${context.companyName}
+- Currency: ${context.baseCurrency}
+- Locale: ${context.locale}
+
+Important: Use the current date/time above for any time-sensitive operations.
+`;
+}
+
+/**
+ * Create a typed agent with AppContext pre-applied
+ * This enables automatic type inference for the context parameter
+ */
+export const createAgent = (config: AgentConfig) =>
+ Agent.create(config);
diff --git a/apps/example/src/ai/agents/time-tracking.ts b/apps/example/src/ai/agents/time-tracking.ts
new file mode 100644
index 0000000..9f1f8e0
--- /dev/null
+++ b/apps/example/src/ai/agents/time-tracking.ts
@@ -0,0 +1,45 @@
+import { openai } from "@ai-sdk/openai";
+import {
+ createTimeEntryTool,
+ deleteTimeEntryTool,
+ getTimeEntriesTool,
+ getTrackerProjectsTool,
+ startTimerTool,
+ stopTimerTool,
+ updateTimeEntryTool,
+} from "../tools/tracker";
+import { createAgent, formatContextForLLM } from "./shared";
+
+export const timeTrackingAgent = createAgent({
+ name: "timeTracking",
+ model: openai("gpt-4o-mini"),
+ instructions: (
+ ctx,
+ ) => `You are a time tracking specialist for ${ctx.companyName}.
+
+CRITICAL RULES:
+1. ALWAYS use tools to get/create/update time entries and timers
+2. Present time data clearly (duration, project, date)
+3. Summarize totals when showing multiple entries
+
+${formatContextForLLM(ctx)}`,
+ tools: {
+ startTimer: startTimerTool,
+ stopTimer: stopTimerTool,
+ getTimeEntries: getTimeEntriesTool,
+ createTimeEntry: createTimeEntryTool,
+ updateTimeEntry: updateTimeEntryTool,
+ deleteTimeEntry: deleteTimeEntryTool,
+ getProjects: getTrackerProjectsTool,
+ },
+ matchOn: [
+ "timer",
+ "time entry",
+ "time tracking",
+ "hours",
+ "tracked time",
+ "start timer",
+ "stop timer",
+ ],
+ maxTurns: 5,
+});
diff --git a/apps/example/src/ai/agents/transactions.ts b/apps/example/src/ai/agents/transactions.ts
new file mode 100644
index 0000000..862c784
--- /dev/null
+++ b/apps/example/src/ai/agents/transactions.ts
@@ -0,0 +1,42 @@
+import { openai } from "@ai-sdk/openai";
+import {
+ getTransactionTool,
+ listTransactionsTool,
+} from "../tools/transactions";
+import { createAgent, formatContextForLLM } from "./shared";
+
+export const transactionsAgent = createAgent({
+ name: "transactions",
+ model: openai("gpt-4o-mini"),
+ instructions: (
+ ctx,
+ ) => `You are a transactions specialist with access to live transaction data for ${ctx.companyName}.
+
+CRITICAL RULES:
+1. ALWAYS use your tools to get data - NEVER ask the user for transaction details
+2. Call tools IMMEDIATELY when asked about transactions
+3. For "largest transactions", use sort and limit filters
+4. Present transaction data clearly in tables or lists
+
+PRESENTATION STYLE:
+- Reference ${ctx.companyName} when relevant
+- Use clear formatting (tables/lists) for multiple transactions
+- Highlight key insights (e.g., "Largest expense: Marketing at 5,000 SEK")
+- Be concise and data-focused
+
+${formatContextForLLM(ctx)}`,
+ tools: {
+ listTransactions: listTransactionsTool,
+ getTransaction: getTransactionTool,
+ },
+ matchOn: [
+ "transaction",
+ "payment",
+ "transfer",
+ "purchase",
+ "last transaction",
+ "recent transaction",
+ "latest transaction",
+ ],
+ maxTurns: 5,
+});
diff --git a/apps/example/src/ai/agents/triage.ts b/apps/example/src/ai/agents/triage.ts
new file mode 100644
index 0000000..42369eb
--- /dev/null
+++ b/apps/example/src/ai/agents/triage.ts
@@ -0,0 +1,69 @@
+import { openai } from "@ai-sdk/openai";
+import { analyticsAgent } from "./analytics";
+import { customersAgent } from "./customers";
+import { generalAgent } from "./general";
+import { invoicesAgent } from "./invoices";
+import { operationsAgent } from "./operations";
+import { reportsAgent } from "./reports";
+import { createAgent, formatContextForLLM } from "./shared";
+import { timeTrackingAgent } from "./time-tracking";
+import { transactionsAgent } from "./transactions";
+
+export const triageAgent = createAgent({
+ name: "triage",
+ model: openai("gpt-4o-mini"),
+ instructions: (ctx) => `Route user requests to the appropriate agent:
+
+**reports**: Financial metrics and reports
+ - Revenue, P&L, expenses, spending
+ - Burn rate, runway (how long money will last)
+ - Cash flow, balance sheet, tax summary
+
+**transactions**: Transaction queries
+ - List transactions, search transactions
+ - Get specific transaction details
+
+**invoices**: Invoice management
+ - Create, update, list invoices
+
+**timeTracking**: Time tracking
+ - Start/stop timers, time entries
+
+**customers**: Customer management
+ - Get/create/update customers, profitability analysis
+
+**analytics**: Advanced forecasting & analysis
+ - Business health score
+ - Cash flow forecasting (future predictions)
+ - Stress testing scenarios
+
+**operations**: Operations
+ - Inbox, balances, documents, exports
+
+**general**: General queries and conversation
+ - Greetings, thanks, casual conversation
+ - "What can you do?", "How does this work?"
+ - Memory queries: "What did I just ask?", "What did we discuss?"
+ - Ambiguous or unclear requests
+ - Default for anything that doesn't fit other specialists
+
+ROUTING RULES:
+- "runway" = reports (not analytics)
+- "forecast" = analytics (not reports)
+- "what did I just ask" or memory queries = general
+- Greetings, thanks, casual chat = general
+- When uncertain = general (as default)
+- Route to ONE specialist at a time
+
+${formatContextForLLM(ctx)}`,
+ handoffs: [
+ reportsAgent,
+ analyticsAgent,
+ transactionsAgent,
+ invoicesAgent,
+ timeTrackingAgent,
+ customersAgent,
+ operationsAgent,
+ generalAgent,
+ ],
+});
diff --git a/apps/example/src/ai/agents/utils/README.md b/apps/example/src/ai/agents/utils/README.md
deleted file mode 100644
index 928d90d..0000000
--- a/apps/example/src/ai/agents/utils/README.md
+++ /dev/null
@@ -1,150 +0,0 @@
-# Utilities
-
-Shared utility functions used across the agents system.
-
-## ๐ Files
-
-### `date-helpers.ts`
-Date and time utilities for financial operations:
-- `getStartOfMonth()` / `getEndOfMonth()`
-- `getStartOfYear()` / `getEndOfYear()`
-- `getDaysAgo(days)` - Get date N days ago
-- `formatDateRange(from, to)` - Format display
-- `isValidISODate(dateString)` - Validation
-
-### `filter-builders.ts`
-Helper functions for building filter descriptions:
-- `buildFilterDescription(filters)` - Human-readable filter text
-- `cleanFilters(filters)` - Remove undefined/null values
-
-### `fake-data.ts` โจ
-Realistic fake data generators using `@faker-js/faker`:
-
-#### Financial Reports
-- `generateRevenueMetrics(params)` - Revenue analysis
-- `generateProfitLossMetrics(params)` - P&L statements
-- `generateRunwayMetrics(params)` - Runway calculations
-- `generateBurnRateMetrics(params)` - Burn rate tracking
-- `generateSpendingMetrics(params)` - Spending breakdown
-
-#### Transactions
-- `generateTransactions(params)` - List of transactions
-- `generateTransaction(id)` - Single transaction details
-
-#### Invoices
-- `generateInvoices(params)` - List of invoices
-- `generateInvoice(id)` - Single invoice details
-
-## ๐ฒ Fake Data Features
-
-### Realistic Financial Data
-- **Proper calculations:** Revenue = profit + expenses
-- **Realistic ranges:** Amounts between $50-$500K
-- **Trends:** Growth percentages, burn rate efficiency
-- **Breakdowns:** Categories, merchants, tags
-
-### Smart Defaults
-- Currency defaults to USD
-- Dates respect provided ranges
-- Page sizes honor limits
-- Status distribution matches reality
-
-### Consistent Structures
-All fake data matches expected schemas:
-- Proper date formats (ISO 8601)
-- Complete customer objects
-- Detailed line items
-- Realistic metadata
-
-## ๐ก Usage Examples
-
-### Generate Revenue Metrics
-```typescript
-import { generateRevenueMetrics } from './fake-data';
-
-const data = generateRevenueMetrics({
- from: '2024-01-01',
- to: '2024-03-31',
- currency: 'USD'
-});
-// Returns: { period, currency, total, breakdown, growth }
-```
-
-### Generate Transactions
-```typescript
-import { generateTransactions } from './fake-data';
-
-const data = generateTransactions({
- pageSize: 20,
- start: '2024-01-01',
- end: '2024-12-31',
- type: 'expense'
-});
-// Returns: { data: [...], pagination: {...} }
-```
-
-### Generate Invoice
-```typescript
-import { generateInvoice } from './fake-data';
-
-const invoice = generateInvoice('INV-1234');
-// Returns complete invoice with customer, line items, totals
-```
-
-## ๐ Replacing with Real Data
-
-When you're ready to use real database data, simply replace the fake data generators in the tool files:
-
-```typescript
-// Before (fake data):
-import { generateRevenueMetrics } from '../../utils/fake-data';
-execute: async ({ from, to, currency }) => {
- return generateRevenueMetrics({ from, to, currency });
-}
-
-// After (real data):
-import { db } from '@/lib/database';
-execute: async ({ from, to, currency }) => {
- return await db.getRevenueMetrics({ from, to, currency });
-}
-```
-
-## ๐จ Customizing Fake Data
-
-To customize the fake data:
-
-1. **Edit generators** in `fake-data.ts`
-2. **Adjust ranges** - Change min/max values
-3. **Add fields** - Include additional data
-4. **Change distributions** - Modify status/type probabilities
-
-Example:
-```typescript
-// Increase revenue range
-const totalRevenue = faker.number.float({
- min: 100000, // was 50000
- max: 1000000, // was 500000
- fractionDigits: 2,
-});
-```
-
-## ๐งช Testing
-
-Fake data is perfect for:
-- โ Development without database
-- โ Testing agent behavior
-- โ Demos and presentations
-- โ Integration tests
-- โ UI component development
-
-## ๐ฆ Dependencies
-
-- `@faker-js/faker` (v10+) - Fake data generation
-- No other external dependencies
-
----
-
-**Status:** โ Complete
-**Generators:** 9 financial data generators
-**Coverage:** Reports, Transactions, Invoices
-
diff --git a/apps/example/src/ai/context.ts b/apps/example/src/ai/context.ts
deleted file mode 100644
index 39d02b8..0000000
--- a/apps/example/src/ai/context.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { type BaseContext, createTypedContext } from "@ai-sdk-tools/artifacts";
-
-// Define custom context type with database and user info (mirrors real app)
-interface ChatContext extends BaseContext {
- userId: string;
- fullName: string;
- db: any; // Mock database
- user: {
- teamId: string;
- baseCurrency: string;
- locale: string;
- fullName: string;
- };
-}
-
-// Create typed context helpers
-const { setContext, getContext } = createTypedContext();
-
-// Helper function to get current user context (can be used in tools)
-export function getCurrentUser() {
- const context = getContext();
- return {
- id: context.userId,
- fullName: context.fullName,
- };
-}
-
-export { setContext, getContext };
diff --git a/apps/example/src/ai/index.ts b/apps/example/src/ai/index.ts
index b309da0..6448040 100644
--- a/apps/example/src/ai/index.ts
+++ b/apps/example/src/ai/index.ts
@@ -1,2 +1,11 @@
+// Agents
+export * from "./agents";
+// Artifacts
export * from "./artifacts";
-export * from "./tools";
+
+// Types
+export * from "./types/filters";
+
+// Utils
+export * from "./utils/date-helpers";
+export * from "./utils/filter-builders";
diff --git a/apps/example/src/ai/agents/tools/analytics/business-health-score.ts b/apps/example/src/ai/tools/analytics/business-health-score.ts
similarity index 88%
rename from apps/example/src/ai/agents/tools/analytics/business-health-score.ts
rename to apps/example/src/ai/tools/analytics/business-health-score.ts
index be609c3..d9c986c 100644
--- a/apps/example/src/ai/agents/tools/analytics/business-health-score.ts
+++ b/apps/example/src/ai/tools/analytics/business-health-score.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateBusinessHealthScore } from "../../utils/fake-data";
+import { generateBusinessHealthScore } from "@/ai/utils/fake-data";
export const businessHealthScoreTool = tool({
description: `Calculate comprehensive business health score based on key metrics.
diff --git a/apps/example/src/ai/agents/tools/analytics/cash-flow-forecast.ts b/apps/example/src/ai/tools/analytics/cash-flow-forecast.ts
similarity index 85%
rename from apps/example/src/ai/agents/tools/analytics/cash-flow-forecast.ts
rename to apps/example/src/ai/tools/analytics/cash-flow-forecast.ts
index f3a5f69..c0b7cb0 100644
--- a/apps/example/src/ai/agents/tools/analytics/cash-flow-forecast.ts
+++ b/apps/example/src/ai/tools/analytics/cash-flow-forecast.ts
@@ -1,7 +1,7 @@
import { tool } from "ai";
import { z } from "zod";
-import { currencyFilterSchema } from "../../types/filters";
-import { generateCashFlowForecast } from "../../utils/fake-data";
+import { currencyFilterSchema } from "@/ai/types/filters";
+import { generateCashFlowForecast } from "@/ai/utils/fake-data";
export const cashFlowForecastTool = tool({
description: `Forecast future cash flow based on historical data and unpaid invoices.
diff --git a/apps/example/src/ai/agents/tools/analytics/cash-flow-stress-test.ts b/apps/example/src/ai/tools/analytics/cash-flow-stress-test.ts
similarity index 91%
rename from apps/example/src/ai/agents/tools/analytics/cash-flow-stress-test.ts
rename to apps/example/src/ai/tools/analytics/cash-flow-stress-test.ts
index 841da39..ee46984 100644
--- a/apps/example/src/ai/agents/tools/analytics/cash-flow-stress-test.ts
+++ b/apps/example/src/ai/tools/analytics/cash-flow-stress-test.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateCashFlowStressTest } from "../../utils/fake-data";
+import { generateCashFlowStressTest } from "@/ai/utils/fake-data";
export const cashFlowStressTestTool = tool({
description: `Perform cash flow stress testing under various scenarios.
diff --git a/apps/example/src/ai/agents/tools/analytics/index.ts b/apps/example/src/ai/tools/analytics/index.ts
similarity index 63%
rename from apps/example/src/ai/agents/tools/analytics/index.ts
rename to apps/example/src/ai/tools/analytics/index.ts
index 41d75e3..22230b4 100644
--- a/apps/example/src/ai/agents/tools/analytics/index.ts
+++ b/apps/example/src/ai/tools/analytics/index.ts
@@ -1,9 +1,3 @@
-/**
- * Analytics & Forecasting Tools
- *
- * Advanced analytics, forecasting, and business intelligence tools
- */
-
export { businessHealthScoreTool } from "./business-health-score";
export { cashFlowForecastTool } from "./cash-flow-forecast";
export { cashFlowStressTestTool } from "./cash-flow-stress-test";
diff --git a/apps/example/src/ai/tools/burn-rate.ts b/apps/example/src/ai/tools/burn-rate.ts
deleted file mode 100644
index 02975e4..0000000
--- a/apps/example/src/ai/tools/burn-rate.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { tool } from "ai";
-import { z } from "zod";
-import { cachedUpstash } from "@/lib/cache";
-import { BurnRateArtifact } from "@/ai/artifacts/burn-rate";
-import { delay } from "@/lib/delay";
-
-export const analyzeBurnRateTool = tool({
- description:
- "Analyze company burn rate with interactive chart data and insights. Use this when users ask about burn rate analysis, financial health, runway calculations, or expense tracking.",
- inputSchema: z.object({
- companyName: z.string().describe("Name of the company to analyze"),
- monthlyData: z
- .array(
- z.object({
- month: z.string().describe("Month in YYYY-MM format"),
- revenue: z.number().describe("Monthly revenue"),
- expenses: z.number().describe("Monthly expenses"),
- cashBalance: z.number().describe("Cash balance at end of month"),
- }),
- )
- .describe("Array of monthly financial data"),
- }),
- execute: async function* ({ companyName, monthlyData }) {
- const analysis = BurnRateArtifact.stream({
- stage: "loading",
- title: `${companyName} Burn Rate Analysis`,
- chartData: [],
- progress: 0,
- });
-
- // Yield initial progress
- yield { text: `Starting burn rate analysis for ${companyName}...` };
-
- await delay(500);
-
- // Step 2: Processing - generate chart data
- analysis.progress = 0.1;
- await analysis.update({ stage: "processing" });
-
- yield { text: `Processing financial data...` };
-
- for (const [index, month] of monthlyData.entries()) {
- const burnRate = month.expenses - month.revenue;
- const runway = burnRate > 0 ? month.cashBalance / burnRate : Infinity;
-
- await analysis.update({
- chartData: [
- ...analysis.data.chartData,
- {
- month: month.month,
- revenue: month.revenue,
- expenses: month.expenses,
- burnRate,
- runway: Math.min(runway, 24), // Cap at 24 months for display
- },
- ],
- progress: ((index + 1) / monthlyData.length) * 0.7, // 70% for data processing
- });
-
- await delay(200); // Simulate processing time
- }
-
- await delay(300);
-
- // Step 3: Analyzing - generate insights
- await analysis.update({ stage: "analyzing" });
- analysis.progress = 0.9;
-
- const avgBurnRate =
- analysis.data.chartData.reduce((sum, d) => sum + d.burnRate, 0) /
- analysis.data.chartData.length;
- const avgRunway =
- analysis.data.chartData.reduce((sum, d) => sum + d.runway, 0) /
- analysis.data.chartData.length;
-
- // Determine trend
- const firstHalf = analysis.data.chartData.slice(
- 0,
- Math.floor(analysis.data.chartData.length / 2),
- );
- const secondHalf = analysis.data.chartData.slice(
- Math.floor(analysis.data.chartData.length / 2),
- );
- const firstAvg =
- firstHalf.reduce((sum, d) => sum + d.burnRate, 0) / firstHalf.length;
- const secondAvg =
- secondHalf.reduce((sum, d) => sum + d.burnRate, 0) / secondHalf.length;
-
- const trend =
- secondAvg < firstAvg
- ? ("improving" as const)
- : secondAvg > firstAvg
- ? ("declining" as const)
- : ("stable" as const);
-
- // Generate alerts and recommendations
- const alerts: string[] = [];
- const recommendations: string[] = [];
-
- if (avgRunway < 6) {
- alerts.push("Critical: Average runway below 6 months");
- recommendations.push("Consider immediate cost reduction or fundraising");
- } else if (avgRunway < 12) {
- alerts.push("Warning: Average runway below 12 months");
- recommendations.push("Plan fundraising or revenue optimization");
- }
-
- if (trend === "declining") {
- alerts.push("Burn rate trend is worsening");
- recommendations.push(
- "Review expense categories for optimization opportunities",
- );
- }
-
- if (avgBurnRate < 0) {
- recommendations.push("Great! You're generating positive cash flow");
- }
-
- await delay(400);
-
- // Step 4: Complete with summary (including user context)
- const finalData = {
- title: `${companyName} Burn Rate Analysis`,
- stage: "complete" as const,
- currency: "USD",
- chartData: analysis.data.chartData,
- progress: 1,
- summary: {
- currentBurnRate: avgBurnRate,
- averageRunway: avgRunway,
- trend,
- alerts,
- recommendations,
- },
- };
-
- // Complete the artifact with final data - this should call artifacts.getWriter()
- await analysis.complete(finalData);
-
- // Final yield with completion
- yield {
- text: `Completed burn rate analysis for ${companyName}. Average burn rate: $${avgBurnRate.toLocaleString()}/month. Runway: ${avgRunway.toFixed(1)} months. Trend: ${trend}.`,
- forceStop: true,
- };
- },
-});
-
-// Create cached version - uses pre-configured cache
-export const cachedAnalyzeBurnRateTool = cachedUpstash(analyzeBurnRateTool);
diff --git a/apps/example/src/ai/tools/complex-burn-rate.ts b/apps/example/src/ai/tools/complex-burn-rate.ts
deleted file mode 100644
index 1e28d9f..0000000
--- a/apps/example/src/ai/tools/complex-burn-rate.ts
+++ /dev/null
@@ -1,293 +0,0 @@
-import { openai } from "@ai-sdk/openai";
-import { cachedUpstash } from "@/lib/cache";
-import { getBurnRate, getRunway, getSpending } from "@/lib/mock-db-queries";
-import { formatAmount, safeValue, generateFollowupQuestions } from "@/lib/mock-utils";
-import { generateText, smoothStream, streamText, tool } from "ai";
-import {
- eachMonthOfInterval,
- endOfMonth,
- format,
- startOfMonth,
-} from "date-fns";
-import { BurnRateArtifact } from "@/ai/artifacts/burn-rate";
-import { followupQuestionsArtifact } from "@/ai/artifacts/followup-questions";
-import { getContext } from "@/ai/context";
-import { getBurnRateSchema } from "./schema";
-
-export const complexBurnRateAnalysis = tool({
- description:
- "Generate comprehensive burn rate analysis with interactive visualizations, spending trends, runway projections, and actionable insights. This mirrors the real-world tool to test complex streaming and caching patterns.",
- inputSchema: getBurnRateSchema.omit({ showCanvas: true }),
- execute: async function* ({ from, to, currency }) {
- try {
- const context = getContext();
-
- // Always create canvas for analysis tool
- const analysis = BurnRateArtifact.stream({
- stage: "loading",
- title: "Complex Burn Rate Analysis",
- currency: currency ?? context.user.baseCurrency ?? "USD",
- chartData: [],
- progress: 0,
- });
-
- // Generate a contextual initial message based on the analysis request
- const initialMessageStream = streamText({
- model: openai("gpt-4o-mini"),
- temperature: 0.2,
- system: `You are a financial assistant generating a brief initial message for a burn rate analysis.
-
-The user has requested a burn rate analysis for the period ${from} to ${to}. Create a message that:
-- Acknowledges the specific time period being analyzed
-- Explains what you're currently doing (gathering financial data)
-- Mentions the specific insights they'll receive (monthly burn rate, cash runway, expense breakdown)
-- Uses a warm, personal tone while staying professional
-- Uses the user's first name (${safeValue(context?.user.fullName?.split(" ")[0]) || "there"}) when appropriate
-- Shows genuine interest in their financial well-being
-- Avoids generic phrases like "Got it! Let's dive into..." or "Thanks for reaching out"
-- Keep it concise (1-2 sentences max)
-
-Example format: "I'm analyzing your burn rate data for [period] to show your monthly spending patterns, cash runway, and expense breakdown."`,
- messages: [
- {
- role: "user",
- content: `Generate a brief initial message for a burn rate analysis request for the period ${from} to ${to}.`,
- },
- ],
- experimental_transform: smoothStream({ chunking: "word" }),
- });
-
- let completeMessage = "";
- for await (const chunk of initialMessageStream.textStream) {
- completeMessage += chunk;
- // Yield the accumulated text so far for streaming effect
- yield { text: completeMessage };
- }
-
- // Add line breaks to prepare for the detailed analysis
- completeMessage += "\n";
-
- // Yield to continue processing while showing loading step
- yield { text: completeMessage };
-
- // Run all database queries in parallel for maximum performance
- const [burnRateData, runway, spendingData] = await Promise.all([
- getBurnRate(context.db, {
- teamId: context.user.teamId,
- from,
- to,
- currency: currency ?? undefined,
- }),
- getRunway(context.db, {
- teamId: context.user.teamId,
- from,
- to,
- currency: currency ?? undefined,
- }),
- getSpending(context.db, {
- teamId: context.user.teamId,
- from,
- to,
- currency: currency ?? undefined,
- }),
- ]);
-
- console.log("Database results:", { burnRateData: burnRateData.length, runway, spendingData: spendingData.length });
-
- // Early return if no data
- if (burnRateData.length === 0) {
- await analysis.update({
- stage: "complete",
- summary: {
- currentBurnRate: 0,
- averageRunway: 0,
- trend: "stable" as const,
- alerts: ["No data available"],
- recommendations: ["Check date range selection"],
- },
- });
-
- yield { text: completeMessage + "\n\nNo burn rate data available for the selected period." };
- return {
- currentMonthlyBurn: 0,
- runway: 0,
- topCategory: "No data",
- summary: "No data available",
- };
- }
-
- // Calculate basic metrics from burn rate data
- const currentMonthlyBurn = burnRateData[burnRateData.length - 1]?.value || 0;
- const averageBurnRate = Math.round(
- burnRateData.reduce((sum, item) => sum + item.value, 0) / burnRateData.length
- );
-
- // Generate monthly chart data
- const fromDate = startOfMonth(new Date(from));
- const toDate = endOfMonth(new Date(to));
- const monthSeries = eachMonthOfInterval({
- start: fromDate,
- end: toDate,
- });
-
- const monthlyData = monthSeries.map((month, index) => {
- const currentBurn = burnRateData[index]?.value || 0;
- return {
- month: format(month, "MMM"),
- revenue: Math.floor(Math.random() * 30000) + 20000,
- expenses: currentBurn + Math.floor(Math.random() * 10000),
- burnRate: currentBurn,
- runway: Math.max(1, runway - index),
- };
- });
-
- // Update with chart data
- await analysis.update({
- stage: "processing",
- chartData: monthlyData,
- progress: 0.4,
- });
-
- yield { text: completeMessage + "\n\nProcessing financial data and generating insights..." };
-
- // Get the highest spending category
- const highestCategory = spendingData[0] || {
- name: "Uncategorized",
- slug: "uncategorized",
- amount: 0,
- percentage: 0,
- };
-
- // Calculate burn rate change
- const burnRateStartValue = burnRateData[0]?.value || 0;
- const burnRateEndValue = currentMonthlyBurn;
- const burnRateChangePercentage = burnRateStartValue > 0
- ? Math.round(((burnRateEndValue - burnRateStartValue) / burnRateStartValue) * 100)
- : 0;
- const burnRateChangePeriod = `${burnRateData.length} months`;
-
- // Update with metrics
- await analysis.update({
- stage: "analyzing",
- progress: 0.7,
- summary: {
- currentBurnRate: currentMonthlyBurn,
- averageRunway: runway,
- trend: burnRateChangePercentage > 5 ? "declining" as const :
- burnRateChangePercentage < -5 ? "improving" as const : "stable" as const,
- alerts: runway < 6 ? ["Critical: Low runway"] : [],
- recommendations: ["Monitor spending trends", "Plan for future growth"],
- },
- });
-
- const targetCurrency = currency ?? context.user.baseCurrency ?? "USD";
-
- // Generate AI summary
- const analysisResult = await generateText({
- model: openai("gpt-4o-mini"),
- messages: [
- {
- role: "user",
- content: `Analyze this burn rate data:
-
-Monthly Burn: ${formatAmount({ amount: currentMonthlyBurn, currency: targetCurrency, locale: context.user.locale })}
-Runway: ${runway} months
-Change: ${burnRateChangePercentage}% over ${burnRateChangePeriod}
-Top Category: ${highestCategory.name} (${highestCategory.percentage}%)
-
-Provide a concise 2-sentence summary and 2-3 brief recommendations.`,
- },
- ],
- });
-
- const responseText = analysisResult.text;
- const lines = responseText.split("\n").filter((line) => line.trim().length > 0);
- const summaryText = lines[0] || `Current monthly burn: ${formatAmount({ amount: currentMonthlyBurn, currency: targetCurrency, locale: context.user.locale })} with ${runway}-month runway.`;
- const recommendations = lines.slice(1, 4).map((line) => line.replace(/^[-โข*]\s*/, "").trim()).filter((line) => line.length > 0);
-
- // Final update
- await analysis.complete({
- stage: "complete",
- title: "Complex Burn Rate Analysis",
- chartData: monthlyData,
- progress: 1,
- summary: {
- currentBurnRate: currentMonthlyBurn,
- averageRunway: runway,
- trend: burnRateChangePercentage > 5 ? "declining" as const :
- burnRateChangePercentage < -5 ? "improving" as const : "stable" as const,
- alerts: runway < 6 ? ["Critical: Low runway"] : [],
- recommendations,
- },
- });
-
- // Stream the detailed analysis
- const burnRateAnalysisData = {
- currentMonthlyBurn: formatAmount({ amount: currentMonthlyBurn, currency: targetCurrency, locale: context.user.locale }),
- runway,
- topCategory: highestCategory.name,
- topCategoryPercentage: highestCategory.percentage,
- burnRateChange: burnRateChangePercentage,
- burnRateChangePeriod,
- runwayStatus: runway >= 12 ? "healthy" : runway >= 6 ? "concerning" : "critical",
- };
-
- const responseStream = streamText({
- model: openai("gpt-4o-mini"),
- system: `Generate a detailed burn rate analysis using the provided data. Format it with clear sections for Monthly Burn Rate, Cash Runway, Expense Breakdown, and Trends.`,
- messages: [
- {
- role: "user",
- content: `Generate analysis: ${JSON.stringify(burnRateAnalysisData)}`,
- },
- ],
- experimental_transform: smoothStream({ chunking: "word" }),
- });
-
- // Yield the streamed response
- let analysisText = "";
- for await (const chunk of responseStream.textStream) {
- analysisText += chunk;
- yield { text: completeMessage + analysisText };
- }
-
- completeMessage += analysisText;
-
- // Generate follow-up questions
- const burnRateFollowupQuestions = await generateFollowupQuestions(
- "complexBurnRateAnalysis",
- completeMessage,
- );
-
- // Stream follow-up questions artifact
- const followupStream = followupQuestionsArtifact.stream({
- questions: burnRateFollowupQuestions,
- context: "burn_rate_analysis",
- timestamp: new Date().toISOString(),
- });
-
- await followupStream.complete();
-
- // Final yield
- yield {
- text: completeMessage,
- forceStop: true,
- };
-
- return {
- success: true,
- currentMonthlyBurn: formatAmount({ amount: currentMonthlyBurn, currency: targetCurrency, locale: context.user.locale }),
- runway,
- topCategory: highestCategory.name,
- summary: summaryText,
- analysisComplete: true,
- };
- } catch (error) {
- console.error("Complex burn rate analysis error:", error);
- throw error;
- }
- },
-});
-
-// Create cached version - uses pre-configured cache
-export const complexBurnRateAnalysisTool = cachedUpstash(complexBurnRateAnalysis);
diff --git a/apps/example/src/ai/agents/tools/customers/create-customer.ts b/apps/example/src/ai/tools/customers/create-customer.ts
similarity index 73%
rename from apps/example/src/ai/agents/tools/customers/create-customer.ts
rename to apps/example/src/ai/tools/customers/create-customer.ts
index dbb1f7c..8c0adff 100644
--- a/apps/example/src/ai/agents/tools/customers/create-customer.ts
+++ b/apps/example/src/ai/tools/customers/create-customer.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateCreatedCustomer } from "../../utils/fake-data";
+import { generateCreatedCustomer } from "@/ai/utils/fake-data";
export const createCustomerTool = tool({
description: `Create a new customer record.`,
@@ -10,9 +10,11 @@ export const createCustomerTool = tool({
email: z.string().email().describe("Customer email"),
phone: z.string().optional().describe("Phone number"),
address: z.string().optional().describe("Billing address"),
- tags: z.array(z.string()).optional().describe("Customer tags for categorization"),
+ tags: z
+ .array(z.string())
+ .optional()
+ .describe("Customer tags for categorization"),
}),
execute: async (params) => generateCreatedCustomer(params),
});
-
diff --git a/apps/example/src/ai/agents/tools/customers/get-customer.ts b/apps/example/src/ai/tools/customers/get-customer.ts
similarity index 86%
rename from apps/example/src/ai/agents/tools/customers/get-customer.ts
rename to apps/example/src/ai/tools/customers/get-customer.ts
index 96f4454..de6b411 100644
--- a/apps/example/src/ai/agents/tools/customers/get-customer.ts
+++ b/apps/example/src/ai/tools/customers/get-customer.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateCustomer } from "../../utils/fake-data";
+import { generateCustomer } from "@/ai/utils/fake-data";
export const getCustomerTool = tool({
description: `Get customer details by ID.
diff --git a/apps/example/src/ai/agents/tools/customers/index.ts b/apps/example/src/ai/tools/customers/index.ts
similarity index 75%
rename from apps/example/src/ai/agents/tools/customers/index.ts
rename to apps/example/src/ai/tools/customers/index.ts
index 89241e8..640ae70 100644
--- a/apps/example/src/ai/agents/tools/customers/index.ts
+++ b/apps/example/src/ai/tools/customers/index.ts
@@ -1,9 +1,3 @@
-/**
- * Customer Tools
- *
- * Tools for customer management and analysis
- */
-
export { createCustomerTool } from "./create-customer";
export { getCustomerTool } from "./get-customer";
export { customerProfitabilityTool } from "./profitability-analysis";
diff --git a/apps/example/src/ai/agents/tools/customers/profitability-analysis.ts b/apps/example/src/ai/tools/customers/profitability-analysis.ts
similarity index 78%
rename from apps/example/src/ai/agents/tools/customers/profitability-analysis.ts
rename to apps/example/src/ai/tools/customers/profitability-analysis.ts
index 481efdd..720a153 100644
--- a/apps/example/src/ai/agents/tools/customers/profitability-analysis.ts
+++ b/apps/example/src/ai/tools/customers/profitability-analysis.ts
@@ -1,7 +1,7 @@
import { tool } from "ai";
import { z } from "zod";
-import { dateRangeSchema } from "../../types/filters";
-import { generateCustomerProfitability } from "../../utils/fake-data";
+import { dateRangeSchema } from "@/ai/types/filters";
+import { generateCustomerProfitability } from "@/ai/utils/fake-data";
export const customerProfitabilityTool = tool({
description: `Analyze customer profitability using revenue, costs, and tags.
diff --git a/apps/example/src/ai/agents/tools/customers/update-customer.ts b/apps/example/src/ai/tools/customers/update-customer.ts
similarity index 90%
rename from apps/example/src/ai/agents/tools/customers/update-customer.ts
rename to apps/example/src/ai/tools/customers/update-customer.ts
index 908c4d4..29d31fc 100644
--- a/apps/example/src/ai/agents/tools/customers/update-customer.ts
+++ b/apps/example/src/ai/tools/customers/update-customer.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateUpdatedCustomer } from "../../utils/fake-data";
+import { generateUpdatedCustomer } from "@/ai/utils/fake-data";
export const updateCustomerTool = tool({
description: `Update customer information.`,
diff --git a/apps/example/src/ai/tools/financial-tools.ts b/apps/example/src/ai/tools/financial-tools.ts
deleted file mode 100644
index 605121d..0000000
--- a/apps/example/src/ai/tools/financial-tools.ts
+++ /dev/null
@@ -1,238 +0,0 @@
-import { tool } from "ai";
-import { z } from "zod";
-
-// Mock transaction data
-const mockTransactions = [
- {
- id: "tx001",
- date: "2024-01-15",
- amount: 1250.0,
- category: "Software",
- vendor: "Adobe Inc",
- type: "expense",
- },
- {
- id: "tx002",
- date: "2024-01-16",
- amount: 3500.0,
- category: "Marketing",
- vendor: "Google Ads",
- type: "expense",
- },
- {
- id: "tx003",
- date: "2024-01-17",
- amount: 15000.0,
- category: "Revenue",
- vendor: "Client ABC",
- type: "income",
- },
- {
- id: "tx004",
- date: "2024-01-18",
- amount: 800.0,
- category: "Office",
- vendor: "WeWork",
- type: "expense",
- },
- {
- id: "tx005",
- date: "2024-01-19",
- amount: 2200.0,
- category: "Payroll",
- vendor: "ADP",
- type: "expense",
- },
- {
- id: "tx006",
- date: "2024-01-20",
- amount: 12000.0,
- category: "Revenue",
- vendor: "Client XYZ",
- type: "income",
- },
- {
- id: "tx007",
- date: "2024-01-21",
- amount: 450.0,
- category: "Travel",
- vendor: "Delta Airlines",
- type: "expense",
- },
- {
- id: "tx008",
- date: "2024-01-22",
- amount: 1800.0,
- category: "Equipment",
- vendor: "Apple Store",
- type: "expense",
- },
-];
-
-export const queryTransactionsTool = tool({
- description: "Query and analyze transaction data",
- inputSchema: z.object({
- filters: z.object({
- category: z.string().optional().describe("Filter by category"),
- type: z
- .enum(["income", "expense", "all"])
- .optional()
- .describe("Filter by transaction type"),
- dateFrom: z.string().optional().describe("Start date (YYYY-MM-DD)"),
- dateTo: z.string().optional().describe("End date (YYYY-MM-DD)"),
- minAmount: z.number().optional().describe("Minimum amount"),
- maxAmount: z.number().optional().describe("Maximum amount"),
- }),
- analysis: z
- .enum(["summary", "totals", "trends", "categories"])
- .describe("Type of analysis to perform"),
- }),
- execute: async ({ filters, analysis }) => {
- let filteredTransactions = mockTransactions;
-
- // Apply filters
- if (filters.category) {
- filteredTransactions = filteredTransactions.filter((t) =>
- t.category.toLowerCase().includes(filters.category?.toLowerCase()),
- );
- }
- if (filters.type && filters.type !== "all") {
- filteredTransactions = filteredTransactions.filter(
- (t) => t.type === filters.type,
- );
- }
- if (filters.minAmount) {
- filteredTransactions = filteredTransactions.filter(
- (t) => t.amount >= filters.minAmount!,
- );
- }
- if (filters.maxAmount) {
- filteredTransactions = filteredTransactions.filter(
- (t) => t.amount <= filters.maxAmount!,
- );
- }
-
- // Perform analysis
- switch (analysis) {
- case "summary":
- return {
- totalTransactions: filteredTransactions.length,
- totalIncome: filteredTransactions
- .filter((t) => t.type === "income")
- .reduce((sum, t) => sum + t.amount, 0),
- totalExpenses: filteredTransactions
- .filter((t) => t.type === "expense")
- .reduce((sum, t) => sum + t.amount, 0),
- transactions: filteredTransactions,
- };
-
- case "totals": {
- const income = filteredTransactions
- .filter((t) => t.type === "income")
- .reduce((sum, t) => sum + t.amount, 0);
- const expenses = filteredTransactions
- .filter((t) => t.type === "expense")
- .reduce((sum, t) => sum + t.amount, 0);
- return {
- totalIncome: income,
- totalExpenses: expenses,
- netIncome: income - expenses,
- count: filteredTransactions.length,
- };
- }
-
- case "categories": {
- const categoryTotals = filteredTransactions.reduce(
- (acc, t) => {
- acc[t.category] = (acc[t.category] || 0) + t.amount;
- return acc;
- },
- {} as Record,
- );
- return { categoryBreakdown: categoryTotals };
- }
-
- case "trends":
- return {
- message:
- "Trend analysis shows consistent revenue growth with controlled expense management",
- transactions: filteredTransactions.slice(0, 5), // Recent transactions
- };
-
- default:
- return { transactions: filteredTransactions };
- }
- },
-});
-
-export const generateChartTool = tool({
- description: "Generate chart data for visualizations",
- inputSchema: z.object({
- chartType: z
- .enum(["bar", "line", "pie", "area"])
- .describe("Type of chart to generate"),
- dataType: z
- .enum(["revenue", "expenses", "categories", "trends"])
- .describe("What data to chart"),
- period: z
- .enum(["daily", "weekly", "monthly"])
- .optional()
- .describe("Time period for the chart"),
- }),
- execute: async ({ chartType, dataType, period = "daily" }) => {
- // Generate mock chart data based on our transactions
- switch (dataType) {
- case "revenue":
- return {
- chartType,
- data: [
- { label: "Jan Week 1", value: 15000 },
- { label: "Jan Week 2", value: 12000 },
- { label: "Jan Week 3", value: 18000 },
- { label: "Jan Week 4", value: 22000 },
- ],
- title: "Revenue Trends",
- };
-
- case "expenses":
- return {
- chartType,
- data: [
- { label: "Software", value: 1250 },
- { label: "Marketing", value: 3500 },
- { label: "Office", value: 800 },
- { label: "Payroll", value: 2200 },
- { label: "Travel", value: 450 },
- { label: "Equipment", value: 1800 },
- ],
- title: "Expense Breakdown",
- };
-
- case "categories": {
- const categoryData = mockTransactions.reduce(
- (acc, t) => {
- acc[t.category] = (acc[t.category] || 0) + t.amount;
- return acc;
- },
- {} as Record,
- );
-
- return {
- chartType,
- data: Object.entries(categoryData).map(([label, value]) => ({
- label,
- value,
- })),
- title: "Spending by Category",
- };
- }
-
- default:
- return {
- chartType,
- data: [],
- title: "Chart Data",
- };
- }
- },
-});
diff --git a/apps/example/src/ai/tools/index.ts b/apps/example/src/ai/tools/index.ts
deleted file mode 100644
index ffe0b51..0000000
--- a/apps/example/src/ai/tools/index.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { analyzeBurnRateTool, cachedAnalyzeBurnRateTool } from "./burn-rate";
-import { complexBurnRateAnalysis, complexBurnRateAnalysisTool } from "./complex-burn-rate";
-
-// Export production tools
-export const tools = {
- analyzeBurnRate: cachedAnalyzeBurnRateTool, // Simple cached version
- complexAnalysis: complexBurnRateAnalysisTool, // Complex cached version (mirrors real app)
- complexAnalysisUncached: complexBurnRateAnalysis, // Uncached for comparison
-};
-
-// Also export original for comparison
-export const originalTools = {
- analyzeBurnRate: analyzeBurnRateTool,
-};
diff --git a/apps/example/src/ai/agents/tools/invoices/create-invoice.ts b/apps/example/src/ai/tools/invoices/create-invoice.ts
similarity index 95%
rename from apps/example/src/ai/agents/tools/invoices/create-invoice.ts
rename to apps/example/src/ai/tools/invoices/create-invoice.ts
index bede15a..5d89068 100644
--- a/apps/example/src/ai/agents/tools/invoices/create-invoice.ts
+++ b/apps/example/src/ai/tools/invoices/create-invoice.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateCreatedInvoice } from "../../utils/fake-data";
+import { generateCreatedInvoice } from "@/ai/utils/fake-data";
/**
* Create Invoice Tool
diff --git a/apps/example/src/ai/agents/tools/invoices/get-invoice.ts b/apps/example/src/ai/tools/invoices/get-invoice.ts
similarity index 91%
rename from apps/example/src/ai/agents/tools/invoices/get-invoice.ts
rename to apps/example/src/ai/tools/invoices/get-invoice.ts
index 76bd453..241d661 100644
--- a/apps/example/src/ai/agents/tools/invoices/get-invoice.ts
+++ b/apps/example/src/ai/tools/invoices/get-invoice.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateInvoice } from "../../utils/fake-data";
+import { generateInvoice } from "@/ai/utils/fake-data";
/**
* Get Invoice Tool
diff --git a/apps/example/src/ai/agents/tools/invoices/index.ts b/apps/example/src/ai/tools/invoices/index.ts
similarity index 74%
rename from apps/example/src/ai/agents/tools/invoices/index.ts
rename to apps/example/src/ai/tools/invoices/index.ts
index 240bd45..2edd962 100644
--- a/apps/example/src/ai/agents/tools/invoices/index.ts
+++ b/apps/example/src/ai/tools/invoices/index.ts
@@ -1,9 +1,3 @@
-/**
- * Invoice Tools
- *
- * Tools for querying and managing invoices
- */
-
export { createInvoiceTool } from "./create-invoice";
export { getInvoiceTool } from "./get-invoice";
export { listInvoicesTool } from "./list-invoices";
diff --git a/apps/example/src/ai/agents/tools/invoices/list-invoices.ts b/apps/example/src/ai/tools/invoices/list-invoices.ts
similarity index 95%
rename from apps/example/src/ai/agents/tools/invoices/list-invoices.ts
rename to apps/example/src/ai/tools/invoices/list-invoices.ts
index 3b45f0a..984cf39 100644
--- a/apps/example/src/ai/agents/tools/invoices/list-invoices.ts
+++ b/apps/example/src/ai/tools/invoices/list-invoices.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateInvoices } from "../../utils/fake-data";
+import { generateInvoices } from "@/ai/utils/fake-data";
/**
* List Invoices Tool
diff --git a/apps/example/src/ai/agents/tools/invoices/update-invoice.ts b/apps/example/src/ai/tools/invoices/update-invoice.ts
similarity index 95%
rename from apps/example/src/ai/agents/tools/invoices/update-invoice.ts
rename to apps/example/src/ai/tools/invoices/update-invoice.ts
index 8f641da..1922a07 100644
--- a/apps/example/src/ai/agents/tools/invoices/update-invoice.ts
+++ b/apps/example/src/ai/tools/invoices/update-invoice.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateUpdatedInvoice } from "../../utils/fake-data";
+import { generateUpdatedInvoice } from "@/ai/utils/fake-data";
/**
* Update Invoice Tool
diff --git a/apps/example/src/ai/agents/tools/operations/export-data.ts b/apps/example/src/ai/tools/operations/export-data.ts
similarity index 83%
rename from apps/example/src/ai/agents/tools/operations/export-data.ts
rename to apps/example/src/ai/tools/operations/export-data.ts
index 34ab13e..c048b25 100644
--- a/apps/example/src/ai/agents/tools/operations/export-data.ts
+++ b/apps/example/src/ai/tools/operations/export-data.ts
@@ -1,7 +1,7 @@
import { tool } from "ai";
import { z } from "zod";
-import { dateRangeSchema } from "../../types/filters";
-import { generateDataExport } from "../../utils/fake-data";
+import { dateRangeSchema } from "@/ai/types/filters";
+import { generateDataExport } from "@/ai/utils/fake-data";
export const exportDataTool = tool({
description: `Export financial data in various formats.
diff --git a/apps/example/src/ai/agents/tools/operations/get-balances.ts b/apps/example/src/ai/tools/operations/get-balances.ts
similarity index 90%
rename from apps/example/src/ai/agents/tools/operations/get-balances.ts
rename to apps/example/src/ai/tools/operations/get-balances.ts
index a83906d..9e7555e 100644
--- a/apps/example/src/ai/agents/tools/operations/get-balances.ts
+++ b/apps/example/src/ai/tools/operations/get-balances.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateBalances } from "../../utils/fake-data";
+import { generateBalances } from "@/ai/utils/fake-data";
export const getBalancesTool = tool({
description: `Get account balances across all accounts or by specific account.
diff --git a/apps/example/src/ai/agents/tools/operations/index.ts b/apps/example/src/ai/tools/operations/index.ts
similarity index 69%
rename from apps/example/src/ai/agents/tools/operations/index.ts
rename to apps/example/src/ai/tools/operations/index.ts
index be99f41..253b2af 100644
--- a/apps/example/src/ai/agents/tools/operations/index.ts
+++ b/apps/example/src/ai/tools/operations/index.ts
@@ -1,9 +1,3 @@
-/**
- * Operations Tools
- *
- * Tools for inbox, balances, documents, and data export
- */
-
export { exportDataTool } from "./export-data";
export { getBalancesTool } from "./get-balances";
export { listDocumentsTool } from "./list-documents";
diff --git a/apps/example/src/ai/agents/tools/operations/list-documents.ts b/apps/example/src/ai/tools/operations/list-documents.ts
similarity index 89%
rename from apps/example/src/ai/agents/tools/operations/list-documents.ts
rename to apps/example/src/ai/tools/operations/list-documents.ts
index 36257ae..4b54fae 100644
--- a/apps/example/src/ai/agents/tools/operations/list-documents.ts
+++ b/apps/example/src/ai/tools/operations/list-documents.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateDocuments } from "../../utils/fake-data";
+import { generateDocuments } from "@/ai/utils/fake-data";
export const listDocumentsTool = tool({
description: `List stored documents with filtering and search.
diff --git a/apps/example/src/ai/agents/tools/operations/list-inbox.ts b/apps/example/src/ai/tools/operations/list-inbox.ts
similarity index 89%
rename from apps/example/src/ai/agents/tools/operations/list-inbox.ts
rename to apps/example/src/ai/tools/operations/list-inbox.ts
index 206aaac..a7e9add 100644
--- a/apps/example/src/ai/agents/tools/operations/list-inbox.ts
+++ b/apps/example/src/ai/tools/operations/list-inbox.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateInboxItems } from "../../utils/fake-data";
+import { generateInboxItems } from "@/ai/utils/fake-data";
export const listInboxItemsTool = tool({
description: `List items in the inbox (receipts, documents awaiting processing).
diff --git a/apps/example/src/ai/agents/tools/reports/balance-sheet.ts b/apps/example/src/ai/tools/reports/balance-sheet.ts
similarity index 74%
rename from apps/example/src/ai/agents/tools/reports/balance-sheet.ts
rename to apps/example/src/ai/tools/reports/balance-sheet.ts
index 20c4be7..fbe553f 100644
--- a/apps/example/src/ai/agents/tools/reports/balance-sheet.ts
+++ b/apps/example/src/ai/tools/reports/balance-sheet.ts
@@ -1,9 +1,10 @@
+import { getWriter } from "@ai-sdk-tools/artifacts";
import { tool } from "ai";
import { z } from "zod";
import { BalanceSheetArtifact } from "@/ai/artifacts/balance-sheet";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateBalanceSheet } from "@/ai/utils/fake-data";
import { delay } from "@/lib/delay";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateBalanceSheet } from "../../utils/fake-data";
/**
* Balance Sheet Tool
@@ -37,63 +38,71 @@ Capabilities:
.describe("Use interactive artifact visualization"),
}),
- execute: async function* ({ from, to, currency, categories, useArtifact }) {
+ execute: async function* (
+ { from, to, currency, categories, useArtifact },
+ executionOptions,
+ ) {
+ const writer = getWriter(executionOptions);
+
if (!useArtifact) {
// Legacy mode - return raw data
return generateBalanceSheet({ from, to, currency, categories });
}
// Artifact mode - stream the balance sheet with visualization
- const analysis = BalanceSheetArtifact.stream({
- stage: "loading",
- title: `Balance Sheet as of ${to}`,
- asOfDate: to,
- currency: currency || "USD",
- progress: 0,
- assets: {
- currentAssets: {
- cash: 0,
- accountsReceivable: 0,
- inventory: 0,
- prepaidExpenses: 0,
- total: 0,
+ const analysis = BalanceSheetArtifact.stream(
+ {
+ stage: "loading",
+ title: `Balance Sheet as of ${to}`,
+ asOfDate: to,
+ currency: currency || "USD",
+ progress: 0,
+ assets: {
+ currentAssets: {
+ cash: 0,
+ accountsReceivable: 0,
+ inventory: 0,
+ prepaidExpenses: 0,
+ total: 0,
+ },
+ nonCurrentAssets: {
+ propertyPlantEquipment: 0,
+ intangibleAssets: 0,
+ investments: 0,
+ total: 0,
+ },
+ totalAssets: 0,
},
- nonCurrentAssets: {
- propertyPlantEquipment: 0,
- intangibleAssets: 0,
- investments: 0,
- total: 0,
+ liabilities: {
+ currentLiabilities: {
+ accountsPayable: 0,
+ shortTermDebt: 0,
+ accruedExpenses: 0,
+ total: 0,
+ },
+ nonCurrentLiabilities: {
+ longTermDebt: 0,
+ deferredRevenue: 0,
+ otherLiabilities: 0,
+ total: 0,
+ },
+ totalLiabilities: 0,
},
- totalAssets: 0,
- },
- liabilities: {
- currentLiabilities: {
- accountsPayable: 0,
- shortTermDebt: 0,
- accruedExpenses: 0,
- total: 0,
+ equity: {
+ commonStock: 0,
+ retainedEarnings: 0,
+ additionalPaidInCapital: 0,
+ totalEquity: 0,
},
- nonCurrentLiabilities: {
- longTermDebt: 0,
- deferredRevenue: 0,
- otherLiabilities: 0,
- total: 0,
+ ratios: {
+ currentRatio: 0,
+ quickRatio: 0,
+ debtToEquity: 0,
+ workingCapital: 0,
},
- totalLiabilities: 0,
},
- equity: {
- commonStock: 0,
- retainedEarnings: 0,
- additionalPaidInCapital: 0,
- totalEquity: 0,
- },
- ratios: {
- currentRatio: 0,
- quickRatio: 0,
- debtToEquity: 0,
- workingCapital: 0,
- },
- });
+ writer,
+ );
yield { text: `Generating balance sheet for ${to}...` };
await delay(300);
diff --git a/apps/example/src/ai/agents/tools/reports/burn-rate.ts b/apps/example/src/ai/tools/reports/burn-rate.ts
similarity index 80%
rename from apps/example/src/ai/agents/tools/reports/burn-rate.ts
rename to apps/example/src/ai/tools/reports/burn-rate.ts
index 24c8421..1e7c9c5 100644
--- a/apps/example/src/ai/agents/tools/reports/burn-rate.ts
+++ b/apps/example/src/ai/tools/reports/burn-rate.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateBurnRateMetrics } from "../../utils/fake-data";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateBurnRateMetrics } from "@/ai/utils/fake-data";
/**
* Burn Rate Metrics Tool
diff --git a/apps/example/src/ai/agents/tools/reports/cash-flow.ts b/apps/example/src/ai/tools/reports/cash-flow.ts
similarity index 85%
rename from apps/example/src/ai/agents/tools/reports/cash-flow.ts
rename to apps/example/src/ai/tools/reports/cash-flow.ts
index 73f4121..ea428a5 100644
--- a/apps/example/src/ai/agents/tools/reports/cash-flow.ts
+++ b/apps/example/src/ai/tools/reports/cash-flow.ts
@@ -1,7 +1,7 @@
import { tool } from "ai";
import { z } from "zod";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateCashFlowMetrics } from "../../utils/fake-data";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateCashFlowMetrics } from "@/ai/utils/fake-data";
/**
* Cash Flow Analysis Tool
diff --git a/apps/example/src/ai/agents/tools/reports/expenses.ts b/apps/example/src/ai/tools/reports/expenses.ts
similarity index 86%
rename from apps/example/src/ai/agents/tools/reports/expenses.ts
rename to apps/example/src/ai/tools/reports/expenses.ts
index 0a51549..ee53a72 100644
--- a/apps/example/src/ai/agents/tools/reports/expenses.ts
+++ b/apps/example/src/ai/tools/reports/expenses.ts
@@ -1,7 +1,7 @@
import { tool } from "ai";
import { z } from "zod";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateExpensesMetrics } from "../../utils/fake-data";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateExpensesMetrics } from "@/ai/utils/fake-data";
/**
* Expenses Analysis Tool
diff --git a/apps/example/src/ai/agents/tools/reports/index.ts b/apps/example/src/ai/tools/reports/index.ts
similarity index 80%
rename from apps/example/src/ai/agents/tools/reports/index.ts
rename to apps/example/src/ai/tools/reports/index.ts
index 33e3907..db572ee 100644
--- a/apps/example/src/ai/agents/tools/reports/index.ts
+++ b/apps/example/src/ai/tools/reports/index.ts
@@ -1,9 +1,3 @@
-/**
- * Financial Reports Tools
- *
- * Collection of tools for generating various financial reports
- */
-
export { balanceSheetTool } from "./balance-sheet";
export { burnRateMetricsTool } from "./burn-rate";
export { cashFlowTool } from "./cash-flow";
diff --git a/apps/example/src/ai/agents/tools/reports/profit-loss.ts b/apps/example/src/ai/tools/reports/profit-loss.ts
similarity index 80%
rename from apps/example/src/ai/agents/tools/reports/profit-loss.ts
rename to apps/example/src/ai/tools/reports/profit-loss.ts
index 0a7cdd6..26272a8 100644
--- a/apps/example/src/ai/agents/tools/reports/profit-loss.ts
+++ b/apps/example/src/ai/tools/reports/profit-loss.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateProfitLossMetrics } from "../../utils/fake-data";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateProfitLossMetrics } from "@/ai/utils/fake-data";
/**
* Profit & Loss (P&L) Tool
diff --git a/apps/example/src/ai/agents/tools/reports/revenue.ts b/apps/example/src/ai/tools/reports/revenue.ts
similarity index 97%
rename from apps/example/src/ai/agents/tools/reports/revenue.ts
rename to apps/example/src/ai/tools/reports/revenue.ts
index b17eac7..1afbf4c 100644
--- a/apps/example/src/ai/agents/tools/reports/revenue.ts
+++ b/apps/example/src/ai/tools/reports/revenue.ts
@@ -1,9 +1,9 @@
import { tool } from "ai";
import { z } from "zod";
import { RevenueArtifact } from "@/ai/artifacts/revenue";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateRevenueMetrics } from "@/ai/utils/fake-data";
import { delay } from "@/lib/delay";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateRevenueMetrics } from "../../utils/fake-data";
/**
* Revenue Dashboard Tool
diff --git a/apps/example/src/ai/agents/tools/reports/runway.ts b/apps/example/src/ai/tools/reports/runway.ts
similarity index 81%
rename from apps/example/src/ai/agents/tools/reports/runway.ts
rename to apps/example/src/ai/tools/reports/runway.ts
index fdec0f1..c88ba70 100644
--- a/apps/example/src/ai/agents/tools/reports/runway.ts
+++ b/apps/example/src/ai/tools/reports/runway.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateRunwayMetrics } from "../../utils/fake-data";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateRunwayMetrics } from "@/ai/utils/fake-data";
/**
* Runway Metrics Tool
diff --git a/apps/example/src/ai/agents/tools/reports/spending.ts b/apps/example/src/ai/tools/reports/spending.ts
similarity index 87%
rename from apps/example/src/ai/agents/tools/reports/spending.ts
rename to apps/example/src/ai/tools/reports/spending.ts
index 599b2ae..dcddad0 100644
--- a/apps/example/src/ai/agents/tools/reports/spending.ts
+++ b/apps/example/src/ai/tools/reports/spending.ts
@@ -1,7 +1,7 @@
import { tool } from "ai";
import { z } from "zod";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateSpendingMetrics } from "../../utils/fake-data";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateSpendingMetrics } from "@/ai/utils/fake-data";
/**
* Spending Analysis Tool
diff --git a/apps/example/src/ai/agents/tools/reports/tax-summary.ts b/apps/example/src/ai/tools/reports/tax-summary.ts
similarity index 86%
rename from apps/example/src/ai/agents/tools/reports/tax-summary.ts
rename to apps/example/src/ai/tools/reports/tax-summary.ts
index 5170808..b6f8982 100644
--- a/apps/example/src/ai/agents/tools/reports/tax-summary.ts
+++ b/apps/example/src/ai/tools/reports/tax-summary.ts
@@ -1,7 +1,7 @@
import { tool } from "ai";
import { z } from "zod";
-import { currencyFilterSchema, dateRangeSchema } from "../../types/filters";
-import { generateTaxSummary } from "../../utils/fake-data";
+import { currencyFilterSchema, dateRangeSchema } from "@/ai/types/filters";
+import { generateTaxSummary } from "@/ai/utils/fake-data";
/**
* Tax Summary Tool
diff --git a/apps/example/src/ai/tools/schema.ts b/apps/example/src/ai/tools/schema.ts
deleted file mode 100644
index 5ca96e6..0000000
--- a/apps/example/src/ai/tools/schema.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { z } from "zod";
-
-export const getBurnRateSchema = z.object({
- from: z.string().describe("Start date in YYYY-MM-DD format"),
- to: z.string().describe("End date in YYYY-MM-DD format"),
- currency: z.string().optional().describe("Currency code (USD, EUR, SEK, etc.)"),
- showCanvas: z.boolean().optional().describe("Whether to show the analysis canvas"),
-});
diff --git a/apps/example/src/ai/agents/tools/tracker/create-time-entry.ts b/apps/example/src/ai/tools/tracker/create-time-entry.ts
similarity index 91%
rename from apps/example/src/ai/agents/tools/tracker/create-time-entry.ts
rename to apps/example/src/ai/tools/tracker/create-time-entry.ts
index 050b3fe..62ec981 100644
--- a/apps/example/src/ai/agents/tools/tracker/create-time-entry.ts
+++ b/apps/example/src/ai/tools/tracker/create-time-entry.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateCreatedTimeEntry } from "../../utils/fake-data";
+import { generateCreatedTimeEntry } from "@/ai/utils/fake-data";
export const createTimeEntryTool = tool({
description: `Create a manual time entry (not from timer).
diff --git a/apps/example/src/ai/agents/tools/tracker/delete-time-entry.ts b/apps/example/src/ai/tools/tracker/delete-time-entry.ts
similarity index 82%
rename from apps/example/src/ai/agents/tools/tracker/delete-time-entry.ts
rename to apps/example/src/ai/tools/tracker/delete-time-entry.ts
index 6dd70e5..e08fc7f 100644
--- a/apps/example/src/ai/agents/tools/tracker/delete-time-entry.ts
+++ b/apps/example/src/ai/tools/tracker/delete-time-entry.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateDeletedTimeEntry } from "../../utils/fake-data";
+import { generateDeletedTimeEntry } from "@/ai/utils/fake-data";
export const deleteTimeEntryTool = tool({
description: `Delete a time tracking entry.`,
diff --git a/apps/example/src/ai/agents/tools/tracker/get-time-entries.ts b/apps/example/src/ai/tools/tracker/get-time-entries.ts
similarity index 90%
rename from apps/example/src/ai/agents/tools/tracker/get-time-entries.ts
rename to apps/example/src/ai/tools/tracker/get-time-entries.ts
index 482f181..5bb1365 100644
--- a/apps/example/src/ai/agents/tools/tracker/get-time-entries.ts
+++ b/apps/example/src/ai/tools/tracker/get-time-entries.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateTimeEntries } from "../../utils/fake-data";
+import { generateTimeEntries } from "@/ai/utils/fake-data";
export const getTimeEntriesTool = tool({
description: `Get time tracking entries with filtering options.
diff --git a/apps/example/src/ai/agents/tools/tracker/get-tracker-projects.ts b/apps/example/src/ai/tools/tracker/get-tracker-projects.ts
similarity index 87%
rename from apps/example/src/ai/agents/tools/tracker/get-tracker-projects.ts
rename to apps/example/src/ai/tools/tracker/get-tracker-projects.ts
index 5f8df28..15f5d78 100644
--- a/apps/example/src/ai/agents/tools/tracker/get-tracker-projects.ts
+++ b/apps/example/src/ai/tools/tracker/get-tracker-projects.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateTrackerProjects } from "../../utils/fake-data";
+import { generateTrackerProjects } from "@/ai/utils/fake-data";
export const getTrackerProjectsTool = tool({
description: `Get list of time tracking projects.
diff --git a/apps/example/src/ai/agents/tools/tracker/index.ts b/apps/example/src/ai/tools/tracker/index.ts
similarity index 81%
rename from apps/example/src/ai/agents/tools/tracker/index.ts
rename to apps/example/src/ai/tools/tracker/index.ts
index 059581b..76489ad 100644
--- a/apps/example/src/ai/agents/tools/tracker/index.ts
+++ b/apps/example/src/ai/tools/tracker/index.ts
@@ -1,9 +1,3 @@
-/**
- * Time Tracker Tools
- *
- * Tools for time tracking, projects, and time entries
- */
-
export { createTimeEntryTool } from "./create-time-entry";
export { deleteTimeEntryTool } from "./delete-time-entry";
export { getTimeEntriesTool } from "./get-time-entries";
diff --git a/apps/example/src/ai/agents/tools/tracker/start-timer.ts b/apps/example/src/ai/tools/tracker/start-timer.ts
similarity index 92%
rename from apps/example/src/ai/agents/tools/tracker/start-timer.ts
rename to apps/example/src/ai/tools/tracker/start-timer.ts
index edb7aa1..f619f69 100644
--- a/apps/example/src/ai/agents/tools/tracker/start-timer.ts
+++ b/apps/example/src/ai/tools/tracker/start-timer.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateStartedTimer } from "../../utils/fake-data";
+import { generateStartedTimer } from "@/ai/utils/fake-data";
/**
* Start Timer Tool
diff --git a/apps/example/src/ai/agents/tools/tracker/stop-timer.ts b/apps/example/src/ai/tools/tracker/stop-timer.ts
similarity index 89%
rename from apps/example/src/ai/agents/tools/tracker/stop-timer.ts
rename to apps/example/src/ai/tools/tracker/stop-timer.ts
index 4b3758e..9307ebe 100644
--- a/apps/example/src/ai/agents/tools/tracker/stop-timer.ts
+++ b/apps/example/src/ai/tools/tracker/stop-timer.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateStoppedTimer } from "../../utils/fake-data";
+import { generateStoppedTimer } from "@/ai/utils/fake-data";
export const stopTimerTool = tool({
description: `Stop the currently running timer or a specific timer entry.`,
diff --git a/apps/example/src/ai/agents/tools/tracker/update-time-entry.ts b/apps/example/src/ai/tools/tracker/update-time-entry.ts
similarity index 89%
rename from apps/example/src/ai/agents/tools/tracker/update-time-entry.ts
rename to apps/example/src/ai/tools/tracker/update-time-entry.ts
index 3ff486f..68d4994 100644
--- a/apps/example/src/ai/agents/tools/tracker/update-time-entry.ts
+++ b/apps/example/src/ai/tools/tracker/update-time-entry.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateUpdatedTimeEntry } from "../../utils/fake-data";
+import { generateUpdatedTimeEntry } from "@/ai/utils/fake-data";
export const updateTimeEntryTool = tool({
description: `Update an existing time entry.`,
diff --git a/apps/example/src/ai/agents/tools/transactions/get-transaction.ts b/apps/example/src/ai/tools/transactions/get-transaction.ts
similarity index 90%
rename from apps/example/src/ai/agents/tools/transactions/get-transaction.ts
rename to apps/example/src/ai/tools/transactions/get-transaction.ts
index 94c3a4b..a77260e 100644
--- a/apps/example/src/ai/agents/tools/transactions/get-transaction.ts
+++ b/apps/example/src/ai/tools/transactions/get-transaction.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateTransaction } from "../../utils/fake-data";
+import { generateTransaction } from "@/ai/utils/fake-data";
/**
* Get Transaction Tool
diff --git a/apps/example/src/ai/agents/tools/transactions/index.ts b/apps/example/src/ai/tools/transactions/index.ts
similarity index 58%
rename from apps/example/src/ai/agents/tools/transactions/index.ts
rename to apps/example/src/ai/tools/transactions/index.ts
index 1b74816..84a8676 100644
--- a/apps/example/src/ai/agents/tools/transactions/index.ts
+++ b/apps/example/src/ai/tools/transactions/index.ts
@@ -1,8 +1,2 @@
-/**
- * Transaction Tools
- *
- * Tools for querying and managing transactions
- */
-
export { getTransactionTool } from "./get-transaction";
export { listTransactionsTool } from "./list-transactions";
diff --git a/apps/example/src/ai/agents/tools/transactions/list-transactions.ts b/apps/example/src/ai/tools/transactions/list-transactions.ts
similarity index 96%
rename from apps/example/src/ai/agents/tools/transactions/list-transactions.ts
rename to apps/example/src/ai/tools/transactions/list-transactions.ts
index 28c1f62..a62265c 100644
--- a/apps/example/src/ai/agents/tools/transactions/list-transactions.ts
+++ b/apps/example/src/ai/tools/transactions/list-transactions.ts
@@ -1,6 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
-import { generateTransactions } from "../../utils/fake-data";
+import { generateTransactions } from "@/ai/utils/fake-data";
/**
* List Transactions Tool
diff --git a/apps/example/src/ai/agents/types/filters.ts b/apps/example/src/ai/types/filters.ts
similarity index 100%
rename from apps/example/src/ai/agents/types/filters.ts
rename to apps/example/src/ai/types/filters.ts
diff --git a/apps/example/src/ai/agents/utils/date-helpers.ts b/apps/example/src/ai/utils/date-helpers.ts
similarity index 100%
rename from apps/example/src/ai/agents/utils/date-helpers.ts
rename to apps/example/src/ai/utils/date-helpers.ts
diff --git a/apps/example/src/ai/agents/utils/fake-data.ts b/apps/example/src/ai/utils/fake-data.ts
similarity index 100%
rename from apps/example/src/ai/agents/utils/fake-data.ts
rename to apps/example/src/ai/utils/fake-data.ts
diff --git a/apps/example/src/ai/agents/utils/filter-builders.ts b/apps/example/src/ai/utils/filter-builders.ts
similarity index 100%
rename from apps/example/src/ai/agents/utils/filter-builders.ts
rename to apps/example/src/ai/utils/filter-builders.ts
diff --git a/apps/example/src/app/api/chat/route.ts b/apps/example/src/app/api/chat/route.ts
index 36b9bce..293efbf 100644
--- a/apps/example/src/app/api/chat/route.ts
+++ b/apps/example/src/app/api/chat/route.ts
@@ -1,711 +1,65 @@
-// NOTE: This is what will become @ai-sdk-tools/agents, make it work, make it right, make it good.
-// https://ai-sdk-tools.dev/agents
-
-import { openai } from "@ai-sdk/openai";
-import {
- Experimental_Agent as Agent,
- convertToModelMessages,
- createUIMessageStream,
- createUIMessageStreamResponse,
- smoothStream,
- tool,
-} from "ai";
-import { z } from "zod";
-import {
- businessHealthScoreTool,
- cashFlowForecastTool,
- cashFlowStressTestTool,
-} from "@/ai/agents/tools/analytics";
-import {
- createCustomerTool,
- customerProfitabilityTool,
- getCustomerTool,
- updateCustomerTool,
-} from "@/ai/agents/tools/customers";
-import {
- createInvoiceTool,
- getInvoiceTool,
- listInvoicesTool,
- updateInvoiceTool,
-} from "@/ai/agents/tools/invoices";
-import {
- exportDataTool,
- getBalancesTool,
- listDocumentsTool,
- listInboxItemsTool,
-} from "@/ai/agents/tools/operations";
-import {
- balanceSheetTool,
- burnRateMetricsTool,
- cashFlowTool,
- expensesTool,
- profitLossTool,
- revenueDashboardTool,
- runwayMetricsTool,
- spendingMetricsTool,
- taxSummaryTool,
-} from "@/ai/agents/tools/reports";
-import {
- createTimeEntryTool,
- deleteTimeEntryTool,
- getTimeEntriesTool,
- getTrackerProjectsTool,
- startTimerTool,
- stopTimerTool,
- updateTimeEntryTool,
-} from "@/ai/agents/tools/tracker";
-import {
- getTransactionTool,
- listTransactionsTool,
-} from "@/ai/agents/tools/transactions";
-import { setContext } from "@/ai/context";
+import type { AgentEvent } from "@ai-sdk-tools/agents";
+import { convertToModelMessages, smoothStream } from "ai";
+import type { NextRequest } from "next/server";
+import { buildAppContext } from "@/ai/agents/shared";
+import { triageAgent } from "@/ai/agents/triage";
import { checkRateLimit, getClientIP } from "@/lib/rate-limiter";
-import type { AgentUIMessage } from "@/types/agents";
-import { classifyIntent, RECOMMENDED_PROMPT_PREFIX } from "./routing";
-
-/**
- * Multi-Agent Financial Assistant
- *
- * Architecture:
- * - HYBRID ROUTING: Programmatic classifier for 90% of cases, LLM fallback for complex queries
- * - Specialists execute tools and present results
- * - Sends real-time status updates via transient data parts
- */
-
-/**
- * Extract text from a UI message (handles various AI SDK message formats)
- */
-function extractMessageText(message: unknown): string {
- if (!message || typeof message !== "object") return "";
- const msg = message as { content?: unknown; parts?: unknown[] };
-
- // String content
- if (typeof msg.content === "string") return msg.content;
-
- // Find text in parts array
- const findTextPart = (items?: unknown[]) =>
- items?.find(
- (item): item is { text: string } =>
- typeof item === "object" &&
- item !== null &&
- "type" in item &&
- item.type === "text",
- )?.text || "";
-
- return findTextPart(msg.parts) || findTextPart(msg.content as unknown[]);
-}
-
-// Configuration
-const CONFIG = {
- orchestration: {
- maxRounds: 5,
- contextWindow: 5, // Number of recent messages for specialists
- },
- agents: {
- maxSteps: 5,
- },
-} as const;
-
-/**
- * Agent Context
- * TODO: Later pass as parameter to orchestrator.run({ context })
- */
-const AGENT_CONTEXT = {
- companyName: "Acme Inc.",
- date: new Date().toISOString().split("T")[0], // YYYY-MM-DD
- fullName: "John Doe",
- registeredCountry: "United States",
-} as const;
-
-/**
- * Format context for system prompts
- */
-function getContextPrompt(): string {
- return `
-CONTEXT:
-- Company: ${AGENT_CONTEXT.companyName}
-- Date: ${AGENT_CONTEXT.date}
-- User: ${AGENT_CONTEXT.fullName}
-- Country: ${AGENT_CONTEXT.registeredCountry}
-`.trim();
-}
-
-// Specialist Agents
-const specialists = {
- reports: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are a financial reports specialist with access to live financial data.
-
-YOUR SCOPE: Provide specific financial reports (revenue, P&L, cash flow, etc.)
-NOT YOUR SCOPE: Business health analysis, forecasting (those go to analytics specialist)
-
-CRITICAL RULES:
-1. ALWAYS use your tools to get data - NEVER ask the user for information you can retrieve
-2. Call tools IMMEDIATELY when asked for financial metrics
-3. Present results clearly after retrieving data
-4. For date ranges: "Q1 2024" = 2024-01-01 to 2024-03-31, "2024" = 2024-01-01 to 2024-12-31
-5. Answer ONLY what was asked - don't provide extra reports unless requested
-
-TOOL SELECTION GUIDE:
-- "runway" or "how long can we last" โ Use runway tool
-- "burn rate" or "monthly burn" โ Use burnRate tool
-- "revenue" or "income" โ Use revenue tool
-- "P&L" or "profit" or "loss" โ Use profitLoss tool
-- "cash flow" โ Use cashFlow tool
-- "balance sheet" or "assets/liabilities" โ Use balanceSheet tool
-- "expenses" or "spending breakdown" โ Use expenses tool
-- "tax" โ Use taxSummary tool
-
-PRESENTATION STYLE:
-- Reference the company name (${AGENT_CONTEXT.companyName}) when providing insights
-- Use clear sections with headers for multiple metrics
-- Include status indicators (e.g., "Status: Healthy", "Warning", "Critical")
-- End with a brief key insight or takeaway when relevant
-- Be concise but complete - no unnecessary fluff`,
- tools: {
- revenue: revenueDashboardTool,
- profitLoss: profitLossTool,
- cashFlow: cashFlowTool,
- balanceSheet: balanceSheetTool,
- expenses: expensesTool,
- burnRate: burnRateMetricsTool,
- runway: runwayMetricsTool,
- spending: spendingMetricsTool,
- taxSummary: taxSummaryTool,
- },
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-
- transactions: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are a transactions specialist with access to live transaction data for ${AGENT_CONTEXT.companyName}.
-
-CRITICAL RULES:
-1. ALWAYS use your tools to get data - NEVER ask the user for transaction details
-2. Call tools IMMEDIATELY when asked about transactions
-3. For "largest transactions", use sort and limit filters
-4. Present transaction data clearly in tables or lists
-
-PRESENTATION STYLE:
-- Reference ${AGENT_CONTEXT.companyName} when relevant
-- Use clear formatting (tables/lists) for multiple transactions
-- Highlight key insights (e.g., "Largest expense: Marketing at 5,000 SEK")
-- Be concise and data-focused`,
- tools: {
- listTransactions: listTransactionsTool,
- getTransaction: getTransactionTool,
- },
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-
- invoices: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are an invoice management specialist for ${AGENT_CONTEXT.companyName}.
-
-CRITICAL RULES:
-1. ALWAYS use tools to get/create/update invoice data
-2. Present invoice information clearly with key details (amount, status, due date)
-3. Use clear status labels (Paid, Overdue, Pending)`,
- tools: {
- listInvoices: listInvoicesTool,
- getInvoice: getInvoiceTool,
- createInvoice: createInvoiceTool,
- updateInvoice: updateInvoiceTool,
- },
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-
- timeTracking: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are a time tracking specialist for ${AGENT_CONTEXT.companyName}.
-
-CRITICAL RULES:
-1. ALWAYS use tools to get/create/update time entries and timers
-2. Present time data clearly (duration, project, date)
-3. Summarize totals when showing multiple entries`,
- tools: {
- startTimer: startTimerTool,
- stopTimer: stopTimerTool,
- getTimeEntries: getTimeEntriesTool,
- createTimeEntry: createTimeEntryTool,
- updateTimeEntry: updateTimeEntryTool,
- deleteTimeEntry: deleteTimeEntryTool,
- getProjects: getTrackerProjectsTool,
- },
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-
- customers: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are a customer management specialist for ${AGENT_CONTEXT.companyName}.
-
-CRITICAL RULES:
-1. ALWAYS use tools to get/create/update customer data
-2. Present customer information clearly with key details
-3. Highlight profitability insights when analyzing customers`,
- tools: {
- getCustomer: getCustomerTool,
- createCustomer: createCustomerTool,
- updateCustomer: updateCustomerTool,
- profitabilityAnalysis: customerProfitabilityTool,
- },
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-
- analytics: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are an analytics & forecasting specialist with access to business intelligence tools for ${AGENT_CONTEXT.companyName}.
-
-CRITICAL RULES:
-1. ALWAYS use your tools to run analysis - NEVER ask user for data
-2. Call tools IMMEDIATELY when asked for forecasts, health scores, or stress tests
-3. Present analytics clearly with key insights highlighted
-4. Answer ONLY what was asked - don't provide extra analysis unless requested
-
-TOOL SELECTION:
-- "health" or "healthy" queries โ Use businessHealth tool (gives consolidated score)
-- "forecast" or "prediction" โ Use cashFlowForecast tool
-- "stress test" or "what if" โ Use stressTest tool
-- DO NOT call multiple detailed tools (revenue, P&L, etc.) - use businessHealth for overview
-
-PRESENTATION STYLE:
-- Reference ${AGENT_CONTEXT.companyName} when providing insights
-- Use clear trend labels (Increasing, Decreasing, Stable)
-- Use clear status labels (Healthy, Warning, Critical)
-- Include confidence levels when forecasting (e.g., "High confidence", "Moderate risk")
-- End with 2-3 actionable focus areas (not a laundry list)
-- Keep responses concise - quality over quantity`,
- tools: {
- businessHealth: businessHealthScoreTool,
- cashFlowForecast: cashFlowForecastTool,
- stressTest: cashFlowStressTestTool,
- },
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-
- operations: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are an operations specialist for ${AGENT_CONTEXT.companyName}.
-
-CRITICAL RULES:
-1. ALWAYS use tools to get inbox items, documents, balances, or export data
-2. Present information clearly with counts and summaries
-3. Organize multiple items in clear lists or tables`,
- tools: {
- listInbox: listInboxItemsTool,
- getBalances: getBalancesTool,
- listDocuments: listDocumentsTool,
- exportData: exportDataTool,
- },
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-
- research: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are a research specialist with access to real-time web search for ${AGENT_CONTEXT.companyName}.
-
-YOUR ROLE:
-- Search the web for current information, news, and market data
-- Find competitor information and industry insights
-- Look up real-time data (exchange rates, stock prices, etc.)
-- Research business tools, services, and best practices
-- Verify facts and cross-reference information
-
-CRITICAL RULES:
-1. ALWAYS use web_search when asked to find external information
-2. Synthesize findings into actionable insights
-3. Cite or reference sources when relevant
-4. Distinguish between fact and opinion
-5. Flag information gaps or uncertainties
-
-SCOPE:
-โ External information (news, competitors, markets, tools)
-โ Internal company data (use appropriate specialists: reports, transactions, etc.)
-
-PRESENTATION STYLE:
-- Provide clear summaries with key findings first
-- Use bullet points for multiple findings
-- Include context on how findings relate to ${AGENT_CONTEXT.companyName}
-- Be concise but comprehensive
-- Recommend next steps when relevant`,
- tools: {
- web_search: openai.tools.webSearch({
- searchContextSize: "high",
- }),
- },
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-
- general: new Agent({
- model: openai("gpt-4o-mini"),
- system: `${getContextPrompt()}
-
-You are a general assistant for ${AGENT_CONTEXT.companyName}.
-
-YOUR ROLE:
-- Handle general conversation (greetings, thanks, casual chat)
-- Answer questions about what you can do and your capabilities
-- Handle ambiguous or unclear requests by asking clarifying questions
-- Provide helpful information about the available specialists
-
-AVAILABLE SPECIALISTS:
-- **reports**: Financial metrics (revenue, P&L, burn rate, runway, etc.)
-- **transactions**: Transaction history and details
-- **invoices**: Invoice management
-- **timeTracking**: Time tracking and timers
-- **customers**: Customer management and profitability
-- **analytics**: Forecasting and business intelligence
-- **operations**: Inbox, documents, balances, data export
-- **research**: Web search for external information, news, competitors, market data
-
-STYLE:
-- Be friendly and helpful
-- Reference ${AGENT_CONTEXT.companyName} when relevant
-- If the user asks for something specific, suggest the right specialist
-- Keep responses concise but complete`,
- stopWhen: ({ steps }) => steps.length >= CONFIG.agents.maxSteps,
- }),
-};
-
-// Orchestrator
-const orchestrator = new Agent({
- model: openai("gpt-4o-mini"),
- toolChoice: "required",
- system: `${getContextPrompt()}
-
-
-${RECOMMENDED_PROMPT_PREFIX}
-
-Route user requests to the appropriate agent:
-
-**reports**: Financial metrics and reports
- - Revenue, P&L, expenses, spending
- - Burn rate, runway (how long money will last)
- - Cash flow, balance sheet, tax summary
-
-**transactions**: Transaction queries
- - List transactions, search transactions
- - Get specific transaction details
-
-**invoices**: Invoice management
- - Create, update, list invoices
-
-**timeTracking**: Time tracking
- - Start/stop timers, time entries
-**customers**: Customer management
- - Get/create/update customers, profitability analysis
+export async function POST(request: NextRequest) {
+ const ip = getClientIP(request);
+ const { success, remaining } = await checkRateLimit(ip);
-**analytics**: Advanced forecasting & analysis
- - Business health score
- - Cash flow forecasting (future predictions)
- - Stress testing scenarios
-
-**operations**: Operations
- - Inbox, balances, documents, exports
-
-**research**: Web search and external information
- - Latest news, market trends, competitor research
- - Real-time data lookup (exchange rates, stock prices, etc.)
- - Industry insights, business tools research
- - Any external information not in our internal systems
-
-**general**: General queries and conversation
- - Greetings, thanks, casual conversation
- - "What can you do?", "How does this work?"
- - Memory queries: "What did I just ask?", "What did we discuss?"
- - Ambiguous or unclear requests
- - Default for anything that doesn't fit other specialists
-
-ROUTING RULES:
-- "runway" = reports (not analytics)
-- "forecast" = analytics (not reports)
-- "search", "look up", "find", "latest news" = research (not reports)
-- "what did I just ask" or memory queries = general
-- Greetings, thanks, casual chat = general
-- When uncertain = general (as default)
-- Route to ONE specialist at a time`,
- tools: {
- handoff: tool({
- description: "Hand off to a specialist agent",
- inputSchema: z.object({
- agent: z.enum([
- "reports",
- "transactions",
- "invoices",
- "timeTracking",
- "customers",
- "analytics",
- "operations",
- "research",
- "general",
- ]),
- reason: z.string().optional(),
+ if (!success) {
+ return new Response(
+ JSON.stringify({
+ error: "Rate limit exceeded. Please try again later.",
+ remaining,
}),
- execute: async ({ agent, reason }) => ({ agent, reason }),
- }),
- },
-});
-
-type HandoffData = { agent: keyof typeof specialists; reason?: string };
-
-export async function POST(request: Request) {
- // Rate limiting: 5 messages per IP per day
- const clientIP = getClientIP(request);
- const { success, limit, remaining, reset } = await checkRateLimit(clientIP);
+ {
+ status: 429,
+ headers: { "Content-Type": "application/json" },
+ },
+ );
+ }
+ // Parse request body
const { messages } = await request.json();
- const conversationMessages = convertToModelMessages(messages).slice(-8); // Only keep last 8 messages
-
- const response = createUIMessageStreamResponse({
- experimental_transform: smoothStream(),
- stream: createUIMessageStream({
- execute: async ({ writer }) => {
- // Handle rate limiting in the stream
- if (!success) {
- writer.write({
- type: "data-rate-limit",
- data: {
- code: "RATE_LIMIT_EXCEEDED",
- limit,
- remaining,
- reset: new Date(reset).toISOString(),
- },
- transient: true,
- });
-
- return;
- }
-
- // Send rate limit info as data part
- writer.write({
- type: "data-rate-limit",
- data: {
- code: "RATE_LIMIT_OK",
- limit,
- remaining,
- reset: new Date(reset).toISOString(),
- },
- transient: true,
- });
-
- // Set up artifact context with writer
- setContext({
- writer,
- userId: "demo-user",
- fullName: "Demo User",
- db: null,
- user: {
- teamId: "demo-team",
- baseCurrency: "USD",
- locale: "en-US",
- fullName: "Demo User",
- },
- });
-
- // HYBRID ROUTING: Try programmatic classification first
- const lastMessage = messages[messages.length - 1];
- const lastUserMessage = extractMessageText(lastMessage);
-
- console.log("[Route] Extracted content:", lastUserMessage);
- const programmaticRoute = classifyIntent(lastUserMessage);
-
- let currentAgent:
- | typeof orchestrator
- | (typeof specialists)[keyof typeof specialists];
-
- if (programmaticRoute && programmaticRoute in specialists) {
- // Fast path: Direct to specialist (90% of cases)
- currentAgent = specialists[programmaticRoute];
- console.log(`[Routing] Programmatic โ ${programmaticRoute}`);
- } else {
- // Fallback: Use orchestrator for complex/ambiguous queries (10% of cases)
- currentAgent = orchestrator;
- console.log("[Routing] Fallback โ orchestrator (ambiguous query)");
- }
-
- let round = 0;
- const usedSpecialists = new Set();
-
- // If we used programmatic routing, mark specialist as used
- if (programmaticRoute && programmaticRoute in specialists) {
- usedSpecialists.add(programmaticRoute);
- }
-
- // Agent name lookup - WeakMap for O(1) performance
- type AgentName =
- | "orchestrator"
- | "reports"
- | "transactions"
- | "invoices"
- | "timeTracking"
- | "customers"
- | "analytics"
- | "operations"
- | "research"
- | "general";
- const agentNames = new WeakMap