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

# LuaTool

> Tool interface for one function the model can call, with a Zod input schema, an execute function, and an optional availability condition

`LuaTool` is the interface every [tool](/concepts/skills-and-tools) implements: a `name`, a `description` the model reads to decide when to call it, a Zod `inputSchema`, and an async `execute`. Tools live inside a [`LuaSkill`](/reference/sdk/luaskill); `lua compile` bundles each tool as its own artifact, and the runtime parses the model's arguments with `inputSchema` before `execute` runs. `LuaTool` is a type-only export; `ToolFlag` is a runtime enum.

*Verified against lua-cli 3.33.0.*

```ts theme={null}
import { LuaTool, ToolFlag } from 'lua-cli';
```

## Quick example

The class form the project scaffold uses.

```ts src/skills/tools/LookupOrderTool.ts theme={null}
import { LuaTool, Data } from 'lua-cli';
import { z } from 'zod';

export default class LookupOrderTool implements LuaTool {
  name = 'lookup_order';
  description = 'Look up an order by its number. Use when the customer asks about an order.';
  inputSchema = z.object({
    orderNumber: z.string().describe('Order number, for example A-1001'),
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const page = await Data.get('orders', { orderNumber: { $eq: input.orderNumber } }, 1, 1);
    const order = page.data[0];
    if (!order) return { found: false, orderNumber: input.orderNumber };
    return { found: true, ...order.data };
  }
}
```

Register it on a skill as `tools: [new LookupOrderTool()]`, with no constructor arguments; the compiler drops any arguments and warns `lua/constructor-args-dropped`. Run it with the tool name and its own fields as JSON.

```bash theme={null}
lua test skill --name lookup_order --input '{"orderNumber":"A-1001"}'
```

```text Output theme={null}
…
✅ Tool execution successful!

Tool returned: Object — fields: found, orderNumber
Output:
{ found: false, orderNumber: 'A-1001' }
✨ Tip: run `lua chat -e sandbox -m "test message"` to verify the tool integrates correctly with the agent.
```

## Members

Fields of `LuaTool<TInput extends ZodType = ZodType>`, in declaration order.

### name

<ParamField path="name" type="string" required>
  Identifier the model calls. Letters, digits, `-`, and `_` only; a skill throws when it contains anything else. Convention is snake\_case, and `lua compile` warns unless the name matches `^[a-z][a-z0-9-_]*$`. Also the value of `--name` for `lua test skill`.
</ParamField>

### description

<ParamField path="description" type="string" required>
  What the tool does and when to use it. Sent to the model together with the schema.
</ParamField>

### inputSchema

<ParamField path="inputSchema" type="ZodType" required>
  Zod schema for the arguments. The compiler converts it to JSON Schema for the model, and the runtime parses the arguments with it before `execute` runs. `.describe()` text reaches the model.
</ParamField>

### execute()

Runs the tool with validated arguments.

```ts theme={null}
execute(input: any, ctx?: LuaToolCtx): Promise<any>
```

In the class form, type `input` as `z.infer<typeof this.inputSchema>` to get the schema's static type.

<ParamField path="input" type="z.infer<typeof inputSchema>" required>
  The arguments, parsed by `inputSchema`.
</ParamField>

<ParamField path="ctx" type="LuaToolCtx">
  `undefined` outside a voice session. In a voice session it carries `toolCallId` and `voice.say(text)`, which speaks `text` to the caller while the tool keeps running.
</ParamField>

**Returns** — any JSON-serializable value; it is passed back to the model as the tool result. Throw an `Error` to fail the call.

**Errors** — none beyond what your code throws.

### condition()

Decides whether the tool is offered on a turn.

```ts theme={null}
condition?: () => Promise<boolean>
```

Evaluated before the tool is offered, with every runtime object available. `false` removes the tool from the model's list for that turn; the skill's context stays in the prompt, so the model can still mention what the tool does. A throw or a 30-second timeout disables the tool for that turn. To hide the whole skill, use [`LuaSkill.condition`](/reference/sdk/luaskill).

**Example**

```ts theme={null}
condition = async () => {
  const user = await User.get();
  return user?.data?.isPremium === true;
};
```

### voice

<ParamField path="voice" type="{ flags?: ToolFlag[] }">
  Flags applied when the tool is offered inside a voice session. Tools without it behave the same in chat and voice.
</ParamField>

## Authoring forms

Two shapes compile as a tool.

* Class form: `class X implements LuaTool`, or a class that extends one. The compiler walks the `extends` chain, so a shared base class works, and a field initializer on the subclass overrides the base. Register with `new X()`.
* Object form: a plain object with a string `name`, an `execute`, and at least one of `description`, `inputSchema`, or a `LuaTool` annotation (`: LuaTool`, `as LuaTool`, `satisfies LuaTool`). Register by reference.

```ts src/skills/tools/cancelOrder.ts theme={null}
import { LuaTool, Data } from 'lua-cli';
import { z } from 'zod';

const inputSchema = z.object({ orderNumber: z.string() });

export const cancelOrder: LuaTool<typeof inputSchema> = {
  name: 'cancel_order',
  description: 'Cancel an order the customer has confirmed by number.',
  inputSchema,
  execute: async (input: z.infer<typeof inputSchema>) => {
    const page = await Data.get('orders', { orderNumber: { $eq: input.orderNumber } }, 1, 1);
    const order = page.data[0];
    if (!order) return { cancelled: false, reason: 'not found' };
    await Data.update('orders', order.id, { ...order.data, status: 'cancelled' });
    return { cancelled: true, orderNumber: input.orderNumber };
  },
};
```

`lua compile` fails with `Tool must have an execute function` when `execute` is missing and warns `Tool should have an inputSchema for type safety` when `inputSchema` is. Voice-only tools use the `LuaVoiceTool` class instead; see [defineVoice](/reference/sdk/voice).

## Types

<ResponseField name="ToolFlag" type="enum">
  Values for `voice.flags`.

  <Expandable title="values">
    <ResponseField name="NONE" type="'none'">
      No special behavior.
    </ResponseField>

    <ResponseField name="IGNORE_ON_ENTER" type="'ignore_on_enter'">
      Hidden from the model during the first turn after the voice session starts.
    </ResponseField>

    <ResponseField name="DISALLOW_INTERRUPTION" type="'disallow_interruption'">
      The caller's speech doesn't interrupt the agent while this tool is executing.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="LuaToolCtx" type="interface">
  The second argument of `execute`. Not exported by name; derive it with `NonNullable<Parameters<LuaTool['execute']>[1]>`.

  <Expandable title="properties">
    <ResponseField name="toolCallId" type="string">
      The voice runtime's ID for this call. Voice only.
    </ResponseField>

    <ResponseField name="voice.say" type="(text: string) => Promise<void>">
      Speaks `text` to the caller while the tool runs. Voice only.
    </ResponseField>
  </Expandable>
</ResponseField>

## See also

* [LuaSkill](/reference/sdk/luaskill)
* [Add a tool](/build/add-a-tool)
* [About skills and tools](/concepts/skills-and-tools)
* [lua test](/reference/cli/test)
* [defineVoice](/reference/sdk/voice)
