> ## 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.

# Lua API

> Access request-level runtime information in your code

## Overview

The `Lua` API provides access to request-level runtime information. Use it in your tools, tool conditions, preprocessors, and postprocessors to access context about the current request.

```typescript theme={null}
import { Lua } from 'lua-cli';

// Access the current channel
const channel = Lua.request.channel;

if (channel === 'whatsapp') {
  // WhatsApp-specific logic
}
```

## Import

```typescript theme={null}
import { Lua, Channel } from 'lua-cli';
```

## Availability

The `Lua` API is available in:

| Context                     | Available |
| --------------------------- | --------- |
| **Tool `execute`**          | ✅ Yes     |
| **Tool `condition`**        | ✅ Yes     |
| **Preprocessor `execute`**  | ✅ Yes     |
| **Postprocessor `execute`** | ✅ Yes     |

## Lua.request

The `request` object contains information about the current request context.

### channel

The channel through which the current request originated.

```typescript theme={null}
Lua.request.channel: Channel
```

**Type:** `Channel`

```typescript theme={null}
type Channel =
  | 'web'         // Chat widget on a website
  | 'whatsapp'    // WhatsApp integration
  | 'facebook'    // Facebook Messenger integration
  | 'instagram'   // Instagram integration
  | 'slack'       // Slack integration
  | 'teams'       // Microsoft Teams integration
  | 'front'       // Front integration
  | 'messagebird' // MessageBird integration
  | 'api'         // Direct HTTP API call
  | 'dev'         // Local development / CLI testing
  | 'email'       // Email integration
  | string;       // Any other channel
```

**Example - Channel-specific behavior:**

```typescript theme={null}
import { Lua } from 'lua-cli';

const channel = Lua.request.channel;

switch (channel) {
  case 'whatsapp':
    // Keep responses concise for mobile
    return { format: 'brief' };
  case 'web':
    // Can use rich formatting
    return { format: 'rich' };
  case 'api':
    // Return structured data
    return { format: 'json' };
  default:
    return { format: 'standard' };
}
```

### webhook

The webhook object contains information about the incoming webhook request from channel integrations. This provides access to the original data sent by the channel provider (WhatsApp, Slack, Teams, etc.).

```typescript theme={null}
Lua.request.webhook: { payload: any } | undefined
```

**Type:** `{ payload: any } | undefined`

The webhook object is only available when the request originated from a webhook-based channel. For direct API calls or the web chat widget, this will be `undefined`.

#### webhook.payload

The raw, unmodified webhook payload from the channel provider.

**Available for channels:**

* `whatsapp` - Full WhatsApp Cloud API webhook payload
* `slack` - Slack Events API payload
* `teams` - Microsoft Teams activity object
* `front` - Front webhook body
* `facebook` - Facebook Messenger webhook event
* `instagram` - Instagram Messaging webhook event
* `messagebird` - MessageBird webhook body
* `email` - JMAP-aligned parsed email (headers + envelope; not raw MIME — see note below)

**Example - Accessing WhatsApp webhook data:**

```typescript theme={null}
import { Lua } from 'lua-cli';

const webhook = Lua.request.webhook;

if (Lua.request.channel === 'whatsapp' && webhook) {
  // Access WhatsApp-specific data
  const entry = webhook.payload.entry?.[0];
  const value = entry?.changes?.[0]?.value;
  const messageId = value?.messages?.[0]?.id;
  const phoneNumberId = value?.metadata?.phone_number_id;
  
  return {
    messageId,
    phoneNumberId,
    raw: webhook.payload,
  };
}
```

**Example - Slack event data:**

```typescript theme={null}
import { Lua } from 'lua-cli';

const webhook = Lua.request.webhook;

if (Lua.request.channel === 'slack' && webhook) {
  const event = webhook.payload.event;
  const teamId = webhook.payload.team_id;
  const channelId = event?.channel;
  
  return {
    teamId,
    channelId,
    eventType: event?.type,
  };
}
```

**Example - Teams activity:**

```typescript theme={null}
import { Lua } from 'lua-cli';

const webhook = Lua.request.webhook;

if (Lua.request.channel === 'teams' && webhook) {
  const activity = webhook.payload;
  const conversationId = activity.conversation?.id;
  const tenantId = activity.conversation?.tenantId;
  const serviceUrl = activity.serviceUrl;
  
  return {
    conversationId,
    tenantId,
    serviceUrl,
  };
}
```

**Example - Email metadata:**

```typescript theme={null}
import { Lua } from 'lua-cli';

const webhook = Lua.request.webhook;

if (Lua.request.channel === 'email' && webhook) {
  const {
    messageId,        // bare ID, no <angle brackets>
    inReplyTo,        // bare ID, or null
    references,       // array of bare IDs
    subject,
    from,             // [{ address, name }]
    to,
    cc,
    date,             // ISO 8601 string
    headerLines,      // [{ key, line }] — full ordered RFC 5322 header list
  } = webhook.payload;

  const isReply = !!inReplyTo;
  const threadRoot = references?.[0] ?? messageId;

  return { messageId, isReply, threadRoot, subject };
}
```

<Note>
  The `email` channel's payload differs from JSON-over-HTTP channels (WhatsApp, Slack, etc.). Email arrives over SMTP as RFC 5322 MIME — there is no JSON envelope from a provider to forward. Lua parses the message and exposes it as a [JMAP](https://datatracker.ietf.org/doc/html/rfc8621#section-4)-aligned object: typed common headers plus a `headerLines` array preserving the full RFC 5322 header order.
</Note>

<Note>
  If your email channel is backed by an AgentMail inbox, `webhook.payload` follows AgentMail's native event shape (`message_id`, `thread_id`, `inbox_id`, …) rather than the JMAP shape above. Branch on the presence of `messageId` (Lua-native) vs `message_id` (AgentMail) until the shapes are normalized.
</Note>

## Complete Examples

### Conditional Tool Availability

Make a tool available only on certain channels:

```typescript theme={null}
import { LuaTool, Lua } from 'lua-cli';
import { z } from 'zod';

export default class WhatsAppOnlyTool implements LuaTool {
  name = 'send_whatsapp_template';
  description = 'Send a WhatsApp message template';

  inputSchema = z.object({
    templateId: z.string(),
  });

  // Tool only shows up on WhatsApp
  condition = async () => {
    return Lua.request.channel === 'whatsapp';
  };

  async execute(input: z.infer<typeof this.inputSchema>) {
    // Implementation
    return { success: true };
  }
}
```

### Channel-Aware Tool Logic

Adjust tool behavior based on channel:

```typescript theme={null}
import { LuaTool, Lua } from 'lua-cli';
import { z } from 'zod';

export default class GetSupportInfoTool implements LuaTool {
  name = 'get_support_info';
  description = 'Get customer support information';

  inputSchema = z.object({});

  async execute() {
    const channel = Lua.request.channel;

    const baseInfo = {
      email: 'support@example.com',
      hours: '9 AM - 5 PM EST',
    };

    // Add channel-specific info
    switch (channel) {
      case 'whatsapp':
        return {
          ...baseInfo,
          message: 'You can also reply here for quick support!',
        };
      case 'web':
        return {
          ...baseInfo,
          phone: '1-800-SUPPORT',
          liveChatUrl: 'https://example.com/chat',
        };
      default:
        return baseInfo;
    }
  }
}
```

### Preprocessor with Channel Context

Use channel in preprocessors:

```typescript theme={null}
import { PreProcessor, Lua } from 'lua-cli';

const channelGreeting = new PreProcessor({
  name: 'channel-greeting',
  description: 'Add channel-specific greeting context',
  priority: 1,
  execute: async (user, messages, channel) => {
    const currentChannel = Lua.request.channel;
    return { action: 'proceed' };
  },
});
```

### Postprocessor with Channel-Specific Formatting

Format responses based on channel:

```typescript theme={null}
import { PostProcessor, Lua } from 'lua-cli';

const formatForChannel = new PostProcessor({
  name: 'format-for-channel',
  description: 'Format response based on channel',
  execute: async (user, message, response, channel) => {
    const currentChannel = Lua.request.channel;

    // WhatsApp has character limits - truncate long responses
    if (currentChannel === 'whatsapp' && response.length > 4096) {
      return {
        modifiedResponse: response.substring(0, 4093) + '...',
      };
    }

    return { modifiedResponse: response };
  },
});
```

## Future Expansion

The `Lua` API will be expanded to include additional runtime information, such as:

* User profile data
* Session information
* More request metadata

<Note>
  This API is designed to grow. Check back for new properties as they become available.
</Note>

## Best Practices

<AccordionGroup>
  <Accordion title="✅ Import from lua-cli">
    Always import the `Lua` API from `lua-cli`:

    ```typescript theme={null}
    import { Lua } from 'lua-cli';

    const channel = Lua.request.channel;
    ```
  </Accordion>

  <Accordion title="✅ Handle Unknown Channels">
    The channel type includes `unknown` for unrecognized values:

    ```typescript theme={null}
    const channel = Lua.request.channel;

    if (channel === 'whatsapp' || channel === 'web') {
      // Known channel handling
    } else {
      // Fallback for unknown channels
    }
    ```
  </Accordion>

  <Accordion title="✅ Keep Channel Logic Simple">
    Use channel for minor adjustments, not completely different flows:

    ```typescript theme={null}
    // ✅ Good - Minor adjustments
    const maxLength = channel === 'whatsapp' ? 4096 : 10000;

    // ❌ Avoid - Completely different tools per channel
    // Instead, use tool conditions to show/hide tools
    ```
  </Accordion>

  <Accordion title="✅ Use Tool Conditions for Availability">
    For channel-specific tools, use the `condition` function:

    ```typescript theme={null}
    condition = async () => {
      return Lua.request.channel === 'whatsapp';
    };
    ```
  </Accordion>

  <Accordion title="✅ Check webhook Before Use">
    Always check if webhook exists before accessing it:

    ```typescript theme={null}
    const webhook = Lua.request.webhook;

    if (webhook) {
      // Safe to access webhook.payload properties
      const data = webhook.payload.entry?.[0]?.changes?.[0]?.value;
    }
    ```
  </Accordion>

  <Accordion title="✅ Combine Channel Check with webhook">
    For channel-specific webhook handling, always check both:

    ```typescript theme={null}
    if (Lua.request.channel === 'whatsapp' && Lua.request.webhook) {
      // WhatsApp-specific webhook handling
      const payload = Lua.request.webhook.payload;
    }
    ```
  </Accordion>
</AccordionGroup>

## Related

<CardGroup cols={2}>
  <Card title="Skills & Tools" href="/concepts/skills-and-tools" icon="wrench">
    Learn about creating tools with conditions
  </Card>

  <Card title="Preprocessors" href="/overview/preprocessors" icon="filter">
    Intercept and modify incoming messages
  </Card>

  <Card title="Postprocessors" href="/overview/postprocessors" icon="wand-magic-sparkles">
    Transform agent responses
  </Card>

  <Card title="Channels" href="/channels/introduction" icon="comments">
    Overview of available channels
  </Card>
</CardGroup>
