> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Customer Support Agent

> Support automation with Zendesk API + Lua vector search

## Overview

AI-powered customer support that integrates with **Zendesk** for ticketing and **Lua Data API** for knowledge base search.

**What it does:**

* Search knowledge base with semantic search
* Create support tickets in Zendesk
* Check ticket status
* Answer common questions
* Escalate to human agents

**APIs used:** Zendesk API (external) + Lua Data API (vector search)

## Complete Implementation

### src/index.ts

```typescript theme={null}
import { LuaAgent, LuaSkill, LuaWebhook, LuaJob } from "lua-cli";
import {
  SearchKnowledgeBaseTool,
  CreateTicketTool,
  GetTicketStatusTool,
  UpdateTicketTool
} from "./tools/SupportTools";

// Support skill
const supportSkill = new LuaSkill({
  name: "customer-support",
  description: "AI-powered customer support with ticketing and knowledge base",
  context: `
    This skill provides customer support.
    
    - search_knowledge_base: Use first to find answers in documentation
    - create_ticket: Create Zendesk ticket if knowledge base can't help
    - get_ticket_status: Check status of existing tickets
    - update_ticket: Add information to existing tickets
    
    Always search knowledge base before creating tickets.
    Be empathetic and professional.
    Escalate complex issues to human agents.
  `,
  tools: [
    new SearchKnowledgeBaseTool(),
    new CreateTicketTool(),
    new GetTicketStatusTool(),
    new UpdateTicketTool()
  ]
});

// Zendesk webhook for ticket updates
const zendeskWebhook = new LuaWebhook({
  name: 'zendesk-webhook',
  description: 'Handle Zendesk ticket update events',
  secret: env('ZENDESK_WEBHOOK_SECRET'),
  execute: async (event) => {
    if (event.type === 'ticket.solved') {
      // Get user ID from ticket metadata
      const userId = event.data.custom_fields?.lua_user_id;
      
      if (userId) {
        const user = await User.get(userId);
        await user.send([{
          type: 'text',
          text: `✅ Your support ticket #${event.data.id} has been resolved! We hope we were able to help.`
        }]);
      }
    }
    return { received: true };
  }
});

// Daily follow-up job for open tickets
const ticketFollowUpJob = new LuaJob({
  name: 'ticket-followup',
  description: 'Send follow-up messages for open tickets',
  schedule: {
    type: 'cron',
    pattern: '0 10 * * *'  // Daily at 10 AM
  },
  execute: async (job) => {
    const tickets = await Data.search('support_tickets', 'status:open', 50);
    const user = await job.user();
    
    if (tickets.length > 0) {
      await user.send([{
        type: 'text',
        text: `You have ${tickets.length} open support ticket(s). Need any updates?`
      }]);
    }
  }
});

// Configure agent
export const agent = new LuaAgent({
  name: "support-agent",
  
  persona: `You are Alex, a customer support specialist.
  
Your role:
- Help customers find answers to their questions
- Search the knowledge base for solutions
- Create support tickets when needed
- Track and update existing tickets
- Provide empathetic and professional support

Communication style:
- Patient and empathetic
- Clear and professional
- Solution-oriented
- Reassuring and supportive

Workflow:
1. First, search the knowledge base for answers
2. If no solution found, offer to create a ticket
3. For existing tickets, help track status
4. For urgent issues, escalate to priority support

Best practices:
- Always search knowledge base first
- Gather all details before creating tickets
- Set realistic expectations for response times
- Thank customers for their patience
- Confirm resolution before closing

When to escalate:
- Billing disputes over $500
- Account security issues
- Legal or compliance matters
- VIP customer requests`,

  
  skills: [supportSkill],
  webhooks: [zendeskWebhook],
  jobs: [ticketFollowUpJob]
});
```

<Note>
  This demo uses `LuaAgent` with webhooks for Zendesk events and scheduled jobs for ticket follow-ups.
</Note>

### src/tools/SupportTools.ts

```typescript theme={null}
import { LuaTool, Data, env } from "lua-cli";
import { z } from "zod";

// 1. Search Knowledge Base (Lua Data with Vector Search)
export class SearchKnowledgeBaseTool implements LuaTool {
  name = "search_knowledge_base";
  description = "Search help articles and documentation";
  
  inputSchema = z.object({
    query: z.string().describe("User's question or search query")
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    // Use Lua's vector search for semantic matching
    const results = await Data.search(
      'help_articles',
      input.query,
      5,
      0.7
    );
    
    if (results.length === 0) {
      return {
        articles: [],
        message: "No articles found. Would you like to create a support ticket?"
      };
    }
    
    return {
      articles: results.map(entry => ({
        id: entry.id,
        title: entry.title,
        content: entry.content.substring(0, 300) + '...',
        category: entry.category,
        relevance: `${Math.round(entry.score * 100)}% match`,
        url: entry.url
      })),
      count: results.length,
      message: `Found ${results.length} helpful articles`
    };
  }
}

// 2. Create Zendesk Ticket (External API)
export class CreateTicketTool implements LuaTool {
  name = "create_ticket";
  description = "Create a support ticket in Zendesk";
  
  inputSchema = z.object({
    subject: z.string().describe("Ticket subject/summary"),
    description: z.string().describe("Detailed description of the issue"),
    priority: z.enum(['low', 'normal', 'high', 'urgent']).default('normal'),
    customerEmail: z.string().email().describe("Customer's email address"),
    customerName: z.string().describe("Customer's name")
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const zendeskKey = env('ZENDESK_API_KEY');
    const zendeskSubdomain = env('ZENDESK_SUBDOMAIN');
    
    if (!zendeskKey || !zendeskSubdomain) {
      throw new Error('Zendesk API credentials not configured');
    }
    
    // Create ticket in Zendesk
    const response = await fetch(
      `https://${zendeskSubdomain}.zendesk.com/api/v2/tickets.json`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Basic ${Buffer.from(`${input.customerEmail}/token:${zendeskKey}`).toString('base64')}`
        },
        body: JSON.stringify({
          ticket: {
            subject: input.subject,
            description: input.description,
            priority: input.priority,
            requester: {
              name: input.customerName,
              email: input.customerEmail
            },
            tags: ['ai_created', 'chat']
          }
        })
      }
    );
    
    if (!response.ok) {
      throw new Error(`Failed to create ticket: ${response.statusText}`);
    }
    
    const data = await response.json();
    
    // Also log ticket in Lua Data for our records
    await Data.create('support_tickets', {
      zendeskId: data.ticket.id,
      subject: input.subject,
      customerEmail: input.customerEmail,
      priority: input.priority,
      createdAt: new Date().toISOString()
    }, `${input.subject} ${input.description}`);
    
    return {
      success: true,
      ticketId: data.ticket.id,
      ticketUrl: data.ticket.url,
      message: `Ticket #${data.ticket.id} created. We'll respond within 24 hours.`
    };
  }
}

// 3. Get Ticket Status (External API)
export class GetTicketStatusTool implements LuaTool {
  name = "get_ticket_status";
  description = "Check the status of a support ticket";
  
  inputSchema = z.object({
    ticketId: z.string().describe("Ticket ID")
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const zendeskKey = env('ZENDESK_API_KEY');
    const zendeskSubdomain = env('ZENDESK_SUBDOMAIN');
    
    // Get ticket from Zendesk
    const response = await fetch(
      `https://${zendeskSubdomain}.zendesk.com/api/v2/tickets/${input.ticketId}.json`,
      {
        headers: {
          'Authorization': `Basic ${Buffer.from(`${env('ZENDESK_EMAIL')}/token:${zendeskKey}`).toString('base64')}`
        }
      }
    );
    
    if (!response.ok) {
      throw new Error(`Ticket not found: ${input.ticketId}`);
    }
    
    const data = await response.json();
    const ticket = data.ticket;
    
    return {
      ticketId: ticket.id,
      subject: ticket.subject,
      status: ticket.status,
      priority: ticket.priority,
      createdAt: new Date(ticket.created_at).toLocaleDateString(),
      updatedAt: new Date(ticket.updated_at).toLocaleDateString(),
      assignee: ticket.assignee_id ? 'Assigned to support agent' : 'Not yet assigned',
      message: this.getStatusMessage(ticket.status)
    };
  }
  
  private getStatusMessage(status: string): string {
    const messages = {
      new: "Your ticket is in queue and will be reviewed shortly",
      open: "Our team is currently working on your ticket",
      pending: "Waiting for your response",
      solved: "This ticket has been resolved",
      closed: "This ticket is closed"
    };
    return messages[status] || "Status unknown";
  }
}

// 4. Update Ticket (External API)
export class UpdateTicketTool implements LuaTool {
  name = "update_ticket";
  description = "Add a comment or update to an existing ticket";
  
  inputSchema = z.object({
    ticketId: z.string(),
    comment: z.string().describe("Additional information or update")
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const zendeskKey = env('ZENDESK_API_KEY');
    const zendeskSubdomain = env('ZENDESK_SUBDOMAIN');
    
    // Add comment to ticket
    const response = await fetch(
      `https://${zendeskSubdomain}.zendesk.com/api/v2/tickets/${input.ticketId}.json`,
      {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Basic ${Buffer.from(`${env('ZENDESK_EMAIL')}/token:${zendeskKey}`).toString('base64')}`
        },
        body: JSON.stringify({
          ticket: {
            comment: {
              body: input.comment,
              public: true,
              author_id: 'end-user'
            }
          }
        })
      }
    );
    
    if (!response.ok) {
      throw new Error('Failed to update ticket');
    }
    
    return {
      success: true,
      ticketId: input.ticketId,
      message: "Your comment has been added to the ticket"
    };
  }
}
```

## Environment Setup

```bash theme={null}
# .env
ZENDESK_API_KEY=your_zendesk_api_key
ZENDESK_SUBDOMAIN=your_company
ZENDESK_EMAIL=support@yourcompany.com
```

## Seed Knowledge Base

```typescript theme={null}
// scripts/seed-knowledge-base.ts
import { Data } from "lua-cli";

const articles = [
  {
    title: "How to reset your password",
    content: "To reset your password: 1) Click 'Forgot Password' 2) Enter your email 3) Check your inbox for reset link 4) Create new password",
    category: "Account",
    url: "/help/reset-password"
  },
  {
    title: "Shipping and delivery times",
    content: "Standard shipping takes 5-7 business days. Express shipping takes 2-3 business days. Free shipping on orders over $50.",
    category: "Shipping",
    url: "/help/shipping"
  },
  {
    title: "Return policy",
    content: "Returns accepted within 30 days of purchase. Items must be unused with original packaging. Refunds processed within 5-10 business days.",
    category: "Returns",
    url: "/help/returns"
  }
];

async function seedKnowledgeBase() {
  for (const article of articles) {
    const searchText = `${article.title} ${article.content} ${article.category}`;
    await Data.create('help_articles', article, searchText);
  }
  console.log('✅ Knowledge base seeded');
}

seedKnowledgeBase();
```

## Key Features

<CardGroup cols={2}>
  <Card title="External API" icon="cloud">
    Integrates with Zendesk ticketing
  </Card>

  <Card title="Vector Search" icon="magnifying-glass">
    AI-powered knowledge base search
  </Card>

  <Card title="Hybrid Approach" icon="shuffle">
    Best of both worlds
  </Card>

  <Card title="Smart Escalation" icon="arrow-up">
    Search first, create ticket only if needed
  </Card>
</CardGroup>

## Next Demo

<Card title="Hotel Booking Agent" icon="hotel" href="/demos/hotel-booking">
  See reservation management with Lua Data API
</Card>
