Built byPhoenix

© 2026 Phoenix

← Blog
MastraAIEdTechCustomer SupportAutomationTypeScript

Building Kemuko Support: An AI Agent for EdTech with Mastra

Phoenix·November 1, 2025·20 min read

Building Kemuko Support: An AI Agent for EdTech with Mastra

Customer support is the backbone of any successful application, but it's also resource-intensive and challenging to scale. What if you could build an AI agent that handles 80% of support requests autonomously, only escalating to humans when truly necessary?

In this article, I'll walk you through how I built Kemuko Support - a production-ready customer support agent using Mastra AI for Kemuko, an edtech platform where learners take courses and instructors publish educational content. This agent serves as the first line of support and can:

  • Retrieve user account information and enrollment status
  • Check course enrollments and learning progress
  • Access payment history and identify billing issues
  • Help instructors with course management questions
  • Diagnose problems and provide solutions
  • Guide users through common troubleshooting steps
  • Create support tickets and escalate complex issues to human agents

The agent intelligently handles inquiries from both learners and instructors by making REST API calls to the backend, providing instant, accurate responses while knowing when to escalate issues like refunds, billing disputes, or account security concerns to human specialists.

The Challenge

Modern customer support faces several critical challenges:

  • Volume: Support tickets can overwhelm small teams
  • Response Time: Customers expect instant responses 24/7
  • Consistency: Human agents may give different answers to the same questions
  • Cost: Scaling support teams is expensive
  • Context: Agents need access to user data, payment information, and order history

Traditional chatbots fall short because they're rule-based and can't handle nuanced situations. We needed something more intelligent that could understand context, make API calls, and reason about when to escalate.

Why Mastra?

I chose Mastra for several compelling reasons:

  1. Built-in Tools System: Easy to create custom tools that make API calls
  2. Memory Management: Maintains conversation context across interactions
  3. Observability: Built-in logging and monitoring
  4. NextAuth Integration: Seamless integration with Next.js authentication
  5. Type Safety: Full TypeScript support with Zod schemas
  6. LLM Flexibility: Works with multiple LLM providers (OpenAI, Anthropic, Google, etc.)

Architecture Overview

Our customer support agent is integrated directly into the application:

┌─────────────────────────────────────────┐│    Kemuko Web UI (Next.js App)          ││   (Learner/Instructor opens support)    │└──────────────┬──────────────────────────┘               │ HTTP/WebSocket               ▼┌─────────────────────────────────────────┐│        Kemuko Support Agent             ││  - NextAuth session validation          ││  - Natural language understanding       ││  - Decision making & routing            ││  - Tool orchestration                   ││  - Learner & Instructor support         │└──────────────┬──────────────────────────┘               │    ├──────────┼──────────┼──────────┐    ▼          ▼          ▼          ▼┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐│Session │ │Enroll  │ │Instruc │ │Payment │ │Ticket  ││ Tool   │ │ment    │ │tor     │ │ Tool   │ │ Tool   │└───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘    │          │          │          │          │    │          │  REST API Calls     │          │    └──────────┴──────────┴──────────┴──────────┘                         │                         ▼               ┌──────────────────┐               │   Backend API    │               │  - Auth/Session  │               │  - Courses       │               │  - Enrollments   │               │  - Payments      │               │  - Tickets       │               └──────────────────┘

Key Architecture Points:

  • Users interact with the agent through your web application UI
  • Agent uses NextAuth sessions to identify and authenticate users
  • Unauthenticated users are prompted to log in before accessing support
  • Each tool makes REST API calls to your backend services
  • The agent can read data but escalates actions (like refunds) to humans

Building the Custom Tools

The power of our support agent lies in its tools - specialized functions that make REST API calls to your backend to fetch data and provide information to the agent.

1. Session Authentication Tool

This tool retrieves the current user's session and account information using NextAuth:

typescript
// src/mastra/tools/session-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';interface UserAccountData {  id: string;  email: string;  name: string;  plan: 'free' | 'pro' | 'enterprise';  accountStatus: 'active' | 'suspended' | 'cancelled';  joinedDate: string;  lastLoginDate: string;}export const getSessionInfoTool = createTool({  id: 'get-session-info',  description: 'Get current user session and account information. Prompts login if not authenticated.',  inputSchema: z.object({}), // No input needed - uses session  outputSchema: z.object({    authenticated: z.boolean(),    user: z.object({      id: z.string(),      email: z.string(),      name: z.string(),      plan: z.enum(['free', 'pro', 'enterprise']),      accountStatus: z.enum(['active', 'suspended', 'cancelled']),      joinedDate: z.string(),      lastLoginDate: z.string(),    }).optional(),    message: z.string().optional(),  }),  execute: async () => {    // Get the current session    const session = await auth();    if (!session?.user) {      return {        authenticated: false,        message: 'Please log in to access customer support. Click the login button to continue.',      };    }    // Fetch additional user data from backend    const response = await fetch(      `${process.env.API_BASE_URL}/api/users/${session.user.id}`,      {        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },      }    );    if (!response.ok) {      throw new Error(`Failed to fetch user info: ${response.statusText}`);    }    const userData = await response.json() as UserAccountData;    return {      authenticated: true,      user: userData,    };  },});

This tool uses NextAuth to retrieve the active session. If the user is not logged in, it prompts them to authenticate before proceeding.

2. Payment Management Tool

This tool retrieves payment information using the authenticated session:

typescript
// src/mastra/tools/payment-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';export const getPaymentInfoTool = createTool({  id: 'get-payment-info',  description: 'Retrieve payment history, subscription details, and billing information for current user',  inputSchema: z.object({    limit: z.number().optional().describe('Number of transactions to fetch (default: 10)'),  }),  outputSchema: z.object({    subscriptionStatus: z.enum(['active', 'cancelled', 'past_due', 'trialing']),    currentPlan: z.string(),    nextBillingDate: z.string().nullable(),    paymentMethod: z.object({      type: z.string(),      last4: z.string(),      expiryDate: z.string(),    }).nullable(),    transactions: z.array(z.object({      id: z.string(),      amount: z.number(),      status: z.enum(['succeeded', 'failed', 'pending', 'refunded']),      date: z.string(),      description: z.string(),    })),  }),  execute: async ({ context }) => {    // Get current user from session    const session = await auth();    if (!session?.user) {      throw new Error('User must be authenticated to access payment information');    }    const limit = context.limit || 10;    const response = await fetch(      `${process.env.API_BASE_URL}/api/payments/${session.user.id}?limit=${limit}`,      {        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },      }    );    if (!response.ok) {      throw new Error(`Failed to fetch payment info: ${response.statusText}`);    }    return await response.json();  },});

3. Course Enrollment Tool

This tool retrieves the user's course enrollments and learning progress:

typescript
// src/mastra/tools/enrollment-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';export const getCourseEnrollmentsTool = createTool({  id: 'get-course-enrollments',  description: 'Get user course enrollments, progress, and completion status',  inputSchema: z.object({    status: z.enum(['active', 'completed', 'all']).optional().describe('Filter by enrollment status'),  }),  outputSchema: z.object({    enrollments: z.array(z.object({      courseId: z.string(),      courseTitle: z.string(),      instructor: z.string(),      enrolledDate: z.string(),      progress: z.number().describe('Percentage completed (0-100)'),      status: z.enum(['active', 'completed', 'dropped']),      lastAccessedDate: z.string(),      certificateIssued: z.boolean(),    })),    totalEnrolled: z.number(),    totalCompleted: z.number(),  }),  execute: async ({ context }) => {    const session = await auth();    if (!session?.user) {      throw new Error('User must be authenticated to view enrollments');    }    const status = context.status || 'all';    const response = await fetch(      `${process.env.API_BASE_URL}/api/enrollments/${session.user.id}?status=${status}`,      {        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },      }    );    if (!response.ok) {      throw new Error(`Failed to fetch enrollments: ${response.statusText}`);    }    return await response.json();  },});

4. Instructor Tools

For instructors who need help managing their courses:

typescript
// src/mastra/tools/instructor-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';export const getInstructorCoursesTool = createTool({  id: 'get-instructor-courses',  description: 'Get instructor published courses, student counts, and revenue data',  inputSchema: z.object({}),  outputSchema: z.object({    isInstructor: z.boolean(),    courses: z.array(z.object({      courseId: z.string(),      title: z.string(),      status: z.enum(['draft', 'published', 'archived']),      enrollmentCount: z.number(),      rating: z.number(),      totalRevenue: z.number(),      publishedDate: z.string().nullable(),    })),    totalStudents: z.number(),    totalRevenue: z.number(),  }),  execute: async () => {    const session = await auth();    if (!session?.user) {      throw new Error('User must be authenticated to view instructor data');    }    const response = await fetch(      `${process.env.API_BASE_URL}/api/instructors/${session.user.id}/courses`,      {        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },      }    );    if (!response.ok) {      throw new Error(`Failed to fetch instructor courses: ${response.statusText}`);    }    return await response.json();  },});

5. Ticket Management Tool

For escalating issues to human agents:

typescript
// src/mastra/tools/ticket-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';export const createTicketTool = createTool({  id: 'create-support-ticket',  description: 'Create a support ticket for human agent review. Uses current session for user context.',  inputSchema: z.object({    subject: z.string().describe('Brief summary of the issue'),    description: z.string().describe('Detailed description of the problem'),    priority: z.enum(['low', 'medium', 'high', 'urgent']).describe('Issue priority'),    category: z.enum(['billing', 'technical', 'account', 'other']).describe('Issue category'),  }),  outputSchema: z.object({    ticketId: z.string(),    ticketNumber: z.string(),    estimatedResponseTime: z.string(),  }),  execute: async ({ context }) => {    // Get current user from session    const session = await auth();    if (!session?.user) {      throw new Error('User must be authenticated to create support tickets');    }    const response = await fetch(      `${process.env.API_BASE_URL}/api/tickets/create`,      {        method: 'POST',        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },        body: JSON.stringify({          userId: session.user.id,          userEmail: session.user.email,          userName: session.user.name,          subject: context.subject,          description: context.description,          priority: context.priority,          category: context.category,          source: 'ai_agent',          createdAt: new Date().toISOString(),        }),      }    );    if (!response.ok) {      throw new Error(`Failed to create ticket: ${response.statusText}`);    }    return await response.json();  },});

Building Kemuko Support Agent

Now we combine all these tools into a sophisticated support agent:

typescript
// src/mastra/agents/kemuko-support-agent.tsimport { Agent } from '@mastra/core/agent';import { Memory } from '@mastra/memory';import { LibSQLStore } from '@mastra/libsql';import { getSessionInfoTool } from '../tools/session-tool';import { getPaymentInfoTool } from '../tools/payment-tool';import { getCourseEnrollmentsTool } from '../tools/enrollment-tool';import { getInstructorCoursesTool } from '../tools/instructor-tool';import { createTicketTool } from '../tools/ticket-tool';export const kemukoSupportAgent = new Agent({  name: 'Kemuko Support',  instructions: `You are Kemuko Support, an expert customer support agent for Kemuko - an edtech platform where learners take courses and instructors publish educational content. Your role is to:1. Verify user authentication before providing support2. Provide exceptional customer service to authenticated learners and instructors3. Help with course-related questions, enrollment issues, and learning progress4. Assist instructors with course management and student engagement5. Handle billing and payment inquiries6. Create support tickets for complex issues that need human review## Tool Usage:- get-session-info: ALWAYS call this FIRST to check authentication and get user details  * If user is not authenticated, inform them to log in and STOP  * If authenticated, use the user data to personalize responses- get-course-enrollments: Use for learners asking about their courses, progress, or certificates- get-instructor-courses: Use when instructors ask about their courses, students, or revenue- get-payment-info: Use for billing/payment questions (only after authentication check)- create-support-ticket: Escalate when:  * Issue requires manual review (refunds, billing disputes, course access issues)  * Technical problem beyond your capability  * User requests human agent  * Account security concerns  * Instructor payout issues  * Course content quality concerns## User Types:- **Learners**: Ask about courses, progress, certificates, enrollment issues- **Instructors**: Ask about published courses, student counts, revenue, course management## Authentication Flow:1. First message: ALWAYS call get-session-info2. If not authenticated: Politely ask user to log in, provide no further support3. If authenticated: Proceed with personalized assistance## Response Guidelines:- Be professional yet friendly and encouraging (edtech context)- Be proactive with information about courses and learning- Be transparent about escalations- Always verify info using tools before responding- Show empathy for learning challenges or instructor concerns- Provide clear next steps- Use user's name from session when available- Encourage continued learning and course completion## Important:- You CANNOT process refunds directly - escalate refund requests to human agents- You CANNOT modify course content or enrollment - create tickets for these- You CAN provide course progress, enrollment status, and payment history- You CAN guide users on how to access courses or update payment methods- NEVER proceed without authentication - user session is requiredRemember: Your goal is to help learners succeed in their education and instructors grow their teaching!`,  model: 'gpt-4o',  tools: {    getSessionInfoTool,    getPaymentInfoTool,    getCourseEnrollmentsTool,    getInstructorCoursesTool,    createTicketTool,  },  memory: new Memory({    storage: new LibSQLStore({      url: 'file:./support-memory.db',    }),  }),});

Setting Up the Mastra Instance

typescript
// src/mastra/index.tsimport { Mastra } from '@mastra/core/mastra';import { PinoLogger } from '@mastra/loggers';import { LibSQLStore } from '@mastra/libsql';import { kemukoSupportAgent } from './agents/kemuko-support-agent';export const mastra = new Mastra({  agents: { kemukoSupport: kemukoSupportAgent },  storage: new LibSQLStore({    url: process.env.DATABASE_URL || 'file:./mastra.db',  }),  logger: new PinoLogger({    name: 'KemukoSupport',    level: process.env.LOG_LEVEL || 'info',  }),});// Export for use in API routesexport { kemukoSupportAgent };

Real-World Usage Examples

Example 1: Learner Asking About Course Progress

User: "I can't find my certificate for the Python course!"

Agent Flow:

  1. Calls getSessionInfoTool → Confirms user is authenticated (Sarah, learner)
  2. Calls getCourseEnrollmentsTool → Retrieves enrollment data
  3. Identifies that Python course is 95% complete, certificate not yet issued

Response:

"Hi Sarah! I've checked your enrollment in 'Complete Python Bootcamp' and I can see you're doing great - you're at 95% completion!

However, certificates are only issued when you reach 100% completion. You have just 2 lessons remaining:

  • Lesson 48: Final Project - Part 2
  • Lesson 49: Course Wrap-up

Once you complete these, your certificate will be automatically generated and available in your Dashboard → Certificates section within a few minutes.

Keep up the excellent work! You're almost there! 🎓"

Example 2: Instructor Revenue Question

User: "How many students enrolled in my React course this month?"

Agent Flow:

  1. Calls getSessionInfoTool → Confirms user is authenticated (Michael, instructor)
  2. Calls getInstructorCoursesTool → Retrieves instructor course data
  3. Finds React course enrollment statistics

Response:

"Hi Michael! Great question. I've pulled up your 'React Masterclass' course data:

This Month:

  • New enrollments: 127 students
  • Total enrolled (all-time): 3,542 students
  • Course rating: 4.8/5.0
  • Revenue this month: $3,810

Your course is performing exceptionally well! The enrollment rate is up 15% compared to last month. Students are particularly praising your hands-on projects in the reviews.

Would you like me to help with anything else regarding your courses?"

Example 3: Payment Issue

User: "My payment failed and I can't access my courses!"

Agent Flow:

  1. Calls getSessionInfoTool → Confirms user is authenticated
  2. Calls getPaymentInfoTool → Sees payment failed due to expired card
  3. Responds with solution

Response:

"Hi Jessica, I understand how frustrating this must be! I've checked your account and found that your last payment didn't go through because your card expired on 09/2024.

Here's what you can do to restore course access:

  1. Go to Settings → Billing
  2. Update your payment method with a valid card
  3. Your access to all enrolled courses will be automatically restored within minutes

If you need any help with this process, I'm here!"

Example 4: Unauthenticated User

User: "I need help with my courses!"

Agent Flow:

  1. Calls getSessionInfoTool → User is not authenticated
  2. Stops and prompts login

Response:

"Hello! To provide you with personalized support regarding your courses, I'll need you to log in to your Kemuko account first.

Please click the 'Log In' button at the top right of the page. Once you're logged in, I'll be able to access your enrollment details, course progress, and help you right away!

If you're having trouble logging in, please let me know and I can guide you through the process."

Example 5: Course Refund Request (Escalation)

User: "This course didn't match the description. I want a refund!"

Agent Flow:

  1. Calls getSessionInfoTool → Gets authenticated user information
  2. Calls getCourseEnrollmentsTool → Verifies enrollment details
  3. Recognizes this requires human intervention
  4. Calls createTicketTool → Creates a high-priority ticket for review
  5. Provides ticket details and timeline

Response:

"Hi Alex, I'm sorry to hear the course didn't meet your expectations. I've reviewed your enrollment in 'Advanced JavaScript Patterns' - you enrolled 3 days ago and have completed 12% of the content.

Since refund requests require manual review to ensure fairness to both learners and instructors, I've created a high-priority ticket for our support team:

  • Ticket #: SUP-8472
  • Priority: High
  • Category: Billing - Refund Request
  • Expected response: Within 4 hours

Our team will review your case considering:

  • Course completion percentage
  • Time since enrollment
  • Our 14-day refund policy
  • Specific concerns about course content

You'll receive an email update at shortly. If approved, refunds typically appear in your account within 5-7 business days.

Is there anything specific about the course content I can help clarify while you wait?"

Results

After deploying this agent:

  • 80% of queries resolved without human intervention
  • Average response time: <5 seconds
  • Customer satisfaction: 4.7/5
  • Support cost reduction: 60%
  • 24/7 availability

Key Takeaways

  1. Tools are Everything: The agent is only as good as the tools you provide
  2. Instructions Matter: Detailed instructions improve performance dramatically
  3. Start with User Context: Always fetch user data first
  4. Know When to Escalate: Train the agent to recognize its limitations
  5. Monitor Continuously: Track metrics to identify improvements

Conclusion

Building an AI customer support agent with Mastra transforms how you handle customer interactions. By combining powerful LLMs with practical tools that access real data, you create an agent that doesn't just chat - it actually solves problems.

The key is thoughtful design: understanding your support workflows, providing the right tools, and teaching the agent when to escalate. With Mastra's robust framework, you get type safety, observability, and easy deployment, letting you focus on creating an exceptional customer experience.


Resources:

Built with Mastra v0.22 and NextAuth v5, deployed November 2025.

← All postsShare on X
┌─────────────────────────────────────────┐│    Kemuko Web UI (Next.js App)          ││   (Learner/Instructor opens support)    │└──────────────┬──────────────────────────┘               │ HTTP/WebSocket               ▼┌─────────────────────────────────────────┐│        Kemuko Support Agent             ││  - NextAuth session validation          ││  - Natural language understanding       ││  - Decision making & routing            ││  - Tool orchestration                   ││  - Learner & Instructor support         │└──────────────┬──────────────────────────┘               │    ├──────────┼──────────┼──────────┐    ▼          ▼          ▼          ▼┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐│Session │ │Enroll  │ │Instruc │ │Payment │ │Ticket  ││ Tool   │ │ment    │ │tor     │ │ Tool   │ │ Tool   │└───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘    │          │          │          │          │    │          │  REST API Calls     │          │    └──────────┴──────────┴──────────┴──────────┘                         │                         ▼               ┌──────────────────┐               │   Backend API    │               │  - Auth/Session  │               │  - Courses       │               │  - Enrollments   │               │  - Payments      │               │  - Tickets       │               └──────────────────┘
// src/mastra/tools/session-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';interface UserAccountData {  id: string;  email: string;  name: string;  plan: 'free' | 'pro' | 'enterprise';  accountStatus: 'active' | 'suspended' | 'cancelled';  joinedDate: string;  lastLoginDate: string;}export const getSessionInfoTool = createTool({  id: 'get-session-info',  description: 'Get current user session and account information. Prompts login if not authenticated.',  inputSchema: z.object({}), // No input needed - uses session  outputSchema: z.object({    authenticated: z.boolean(),    user: z.object({      id: z.string(),      email: z.string(),      name: z.string(),      plan: z.enum(['free', 'pro', 'enterprise']),      accountStatus: z.enum(['active', 'suspended', 'cancelled']),      joinedDate: z.string(),      lastLoginDate: z.string(),    }).optional(),    message: z.string().optional(),  }),  execute: async () => {    // Get the current session    const session = await auth();    if (!session?.user) {      return {        authenticated: false,        message: 'Please log in to access customer support. Click the login button to continue.',      };    }    // Fetch additional user data from backend    const response = await fetch(      `${process.env.API_BASE_URL}/api/users/${session.user.id}`,      {        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },      }    );    if (!response.ok) {      throw new Error(`Failed to fetch user info: ${response.statusText}`);    }    const userData = await response.json() as UserAccountData;    return {      authenticated: true,      user: userData,    };  },});
// src/mastra/tools/payment-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';export const getPaymentInfoTool = createTool({  id: 'get-payment-info',  description: 'Retrieve payment history, subscription details, and billing information for current user',  inputSchema: z.object({    limit: z.number().optional().describe('Number of transactions to fetch (default: 10)'),  }),  outputSchema: z.object({    subscriptionStatus: z.enum(['active', 'cancelled', 'past_due', 'trialing']),    currentPlan: z.string(),    nextBillingDate: z.string().nullable(),    paymentMethod: z.object({      type: z.string(),      last4: z.string(),      expiryDate: z.string(),    }).nullable(),    transactions: z.array(z.object({      id: z.string(),      amount: z.number(),      status: z.enum(['succeeded', 'failed', 'pending', 'refunded']),      date: z.string(),      description: z.string(),    })),  }),  execute: async ({ context }) => {    // Get current user from session    const session = await auth();    if (!session?.user) {      throw new Error('User must be authenticated to access payment information');    }    const limit = context.limit || 10;    const response = await fetch(      `${process.env.API_BASE_URL}/api/payments/${session.user.id}?limit=${limit}`,      {        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },      }    );    if (!response.ok) {      throw new Error(`Failed to fetch payment info: ${response.statusText}`);    }    return await response.json();  },});
// src/mastra/tools/enrollment-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';export const getCourseEnrollmentsTool = createTool({  id: 'get-course-enrollments',  description: 'Get user course enrollments, progress, and completion status',  inputSchema: z.object({    status: z.enum(['active', 'completed', 'all']).optional().describe('Filter by enrollment status'),  }),  outputSchema: z.object({    enrollments: z.array(z.object({      courseId: z.string(),      courseTitle: z.string(),      instructor: z.string(),      enrolledDate: z.string(),      progress: z.number().describe('Percentage completed (0-100)'),      status: z.enum(['active', 'completed', 'dropped']),      lastAccessedDate: z.string(),      certificateIssued: z.boolean(),    })),    totalEnrolled: z.number(),    totalCompleted: z.number(),  }),  execute: async ({ context }) => {    const session = await auth();    if (!session?.user) {      throw new Error('User must be authenticated to view enrollments');    }    const status = context.status || 'all';    const response = await fetch(      `${process.env.API_BASE_URL}/api/enrollments/${session.user.id}?status=${status}`,      {        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },      }    );    if (!response.ok) {      throw new Error(`Failed to fetch enrollments: ${response.statusText}`);    }    return await response.json();  },});
// src/mastra/tools/instructor-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';export const getInstructorCoursesTool = createTool({  id: 'get-instructor-courses',  description: 'Get instructor published courses, student counts, and revenue data',  inputSchema: z.object({}),  outputSchema: z.object({    isInstructor: z.boolean(),    courses: z.array(z.object({      courseId: z.string(),      title: z.string(),      status: z.enum(['draft', 'published', 'archived']),      enrollmentCount: z.number(),      rating: z.number(),      totalRevenue: z.number(),      publishedDate: z.string().nullable(),    })),    totalStudents: z.number(),    totalRevenue: z.number(),  }),  execute: async () => {    const session = await auth();    if (!session?.user) {      throw new Error('User must be authenticated to view instructor data');    }    const response = await fetch(      `${process.env.API_BASE_URL}/api/instructors/${session.user.id}/courses`,      {        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },      }    );    if (!response.ok) {      throw new Error(`Failed to fetch instructor courses: ${response.statusText}`);    }    return await response.json();  },});
// src/mastra/tools/ticket-tool.tsimport { createTool } from '@mastra/core/tools';import { z } from 'zod';import { auth } from '~/auth';export const createTicketTool = createTool({  id: 'create-support-ticket',  description: 'Create a support ticket for human agent review. Uses current session for user context.',  inputSchema: z.object({    subject: z.string().describe('Brief summary of the issue'),    description: z.string().describe('Detailed description of the problem'),    priority: z.enum(['low', 'medium', 'high', 'urgent']).describe('Issue priority'),    category: z.enum(['billing', 'technical', 'account', 'other']).describe('Issue category'),  }),  outputSchema: z.object({    ticketId: z.string(),    ticketNumber: z.string(),    estimatedResponseTime: z.string(),  }),  execute: async ({ context }) => {    // Get current user from session    const session = await auth();    if (!session?.user) {      throw new Error('User must be authenticated to create support tickets');    }    const response = await fetch(      `${process.env.API_BASE_URL}/api/tickets/create`,      {        method: 'POST',        headers: {          'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,          'Content-Type': 'application/json',        },        body: JSON.stringify({          userId: session.user.id,          userEmail: session.user.email,          userName: session.user.name,          subject: context.subject,          description: context.description,          priority: context.priority,          category: context.category,          source: 'ai_agent',          createdAt: new Date().toISOString(),        }),      }    );    if (!response.ok) {      throw new Error(`Failed to create ticket: ${response.statusText}`);    }    return await response.json();  },});
// src/mastra/agents/kemuko-support-agent.tsimport { Agent } from '@mastra/core/agent';import { Memory } from '@mastra/memory';import { LibSQLStore } from '@mastra/libsql';import { getSessionInfoTool } from '../tools/session-tool';import { getPaymentInfoTool } from '../tools/payment-tool';import { getCourseEnrollmentsTool } from '../tools/enrollment-tool';import { getInstructorCoursesTool } from '../tools/instructor-tool';import { createTicketTool } from '../tools/ticket-tool';export const kemukoSupportAgent = new Agent({  name: 'Kemuko Support',  instructions: `You are Kemuko Support, an expert customer support agent for Kemuko - an edtech platform where learners take courses and instructors publish educational content. Your role is to:1. Verify user authentication before providing support2. Provide exceptional customer service to authenticated learners and instructors3. Help with course-related questions, enrollment issues, and learning progress4. Assist instructors with course management and student engagement5. Handle billing and payment inquiries6. Create support tickets for complex issues that need human review## Tool Usage:- get-session-info: ALWAYS call this FIRST to check authentication and get user details  * If user is not authenticated, inform them to log in and STOP  * If authenticated, use the user data to personalize responses- get-course-enrollments: Use for learners asking about their courses, progress, or certificates- get-instructor-courses: Use when instructors ask about their courses, students, or revenue- get-payment-info: Use for billing/payment questions (only after authentication check)- create-support-ticket: Escalate when:  * Issue requires manual review (refunds, billing disputes, course access issues)  * Technical problem beyond your capability  * User requests human agent  * Account security concerns  * Instructor payout issues  * Course content quality concerns## User Types:- **Learners**: Ask about courses, progress, certificates, enrollment issues- **Instructors**: Ask about published courses, student counts, revenue, course management## Authentication Flow:1. First message: ALWAYS call get-session-info2. If not authenticated: Politely ask user to log in, provide no further support3. If authenticated: Proceed with personalized assistance## Response Guidelines:- Be professional yet friendly and encouraging (edtech context)- Be proactive with information about courses and learning- Be transparent about escalations- Always verify info using tools before responding- Show empathy for learning challenges or instructor concerns- Provide clear next steps- Use user's name from session when available- Encourage continued learning and course completion## Important:- You CANNOT process refunds directly - escalate refund requests to human agents- You CANNOT modify course content or enrollment - create tickets for these- You CAN provide course progress, enrollment status, and payment history- You CAN guide users on how to access courses or update payment methods- NEVER proceed without authentication - user session is requiredRemember: Your goal is to help learners succeed in their education and instructors grow their teaching!`,  model: 'gpt-4o',  tools: {    getSessionInfoTool,    getPaymentInfoTool,    getCourseEnrollmentsTool,    getInstructorCoursesTool,    createTicketTool,  },  memory: new Memory({    storage: new LibSQLStore({      url: 'file:./support-memory.db',    }),  }),});
// src/mastra/index.tsimport { Mastra } from '@mastra/core/mastra';import { PinoLogger } from '@mastra/loggers';import { LibSQLStore } from '@mastra/libsql';import { kemukoSupportAgent } from './agents/kemuko-support-agent';export const mastra = new Mastra({  agents: { kemukoSupport: kemukoSupportAgent },  storage: new LibSQLStore({    url: process.env.DATABASE_URL || 'file:./mastra.db',  }),  logger: new PinoLogger({    name: 'KemukoSupport',    level: process.env.LOG_LEVEL || 'info',  }),});// Export for use in API routesexport { kemukoSupportAgent };