Skip to main content
LuaVoice declares how the agent talks on a call: which speech-to-text, model and text-to-speech engines run the voice session, how turns and interruptions are detected, which tools are call-only, and what code runs when a call starts, after each turn, and when it ends. Register instances on LuaAgent under voices; the prompt comes from the agent’s persona (persona.voice), not from the voice. Starting calls from code is Voice. Verified against lua-cli 3.33.0.
This page does not list provider models or voice ids. The catalog is in the admin dashboard; lua voice list prints the voices in your compiled project.

Quick example

A cascaded voice (speech-to-text, model, text-to-speech) with a greeting and the three hooks:
src/voices/SupportLine.ts

Functions

defineVoice()

Returns a LuaVoice; identical to new LuaVoice(config). Keep the config a plain object literal: the compiler reads it from source, and a config built elsewhere or cast with as is not detected.
ErrorsLuaVoice requires a non-empty \name` (used as the server-side identifier).whenname` is missing or blank.

Engines

llm, stt and tts accept any of these forms. The compiler normalizes them; you never write a kind. The class forms come from the lua-cli/voice subpath, which re-exports the deepgram, elevenlabs, openai, google, xai and inference namespaces so your project depends on lua-cli alone. Google’s realtime class is google.beta.realtime.RealtimeModel. A class form for any other provider (new openai.LLM(…), new cartesia.TTS(…)) fails lua compile; use the descriptor string.
src/voices/SupportLinePlugin.ts
A cascaded llm requires both stt and tts; lua compile fails with Voice must declare an \stt` model …orVoice must declare a `tts` model …when one is missing. A realtimellmhandles audio itself: omitstt(setting it is refused at push withstt cannot be set with a realtime llm …), and omit ttsfor full realtime or keep it for half-cascade, where the realtime model emits text andttsspeaks it.pronunciationsneeds atts` step and is refused on full realtime.
src/voices/RealtimeLine.ts
The @ts-expect-error line is required because the published LuaVoiceConfig type declares stt and tts as required; lua compile and lua push accept their omission.

Configuration

string
required
Server-side identifier, and the name lua voice --voice and ctx.voice.handoff() address. 1 to 64 characters of a-zA-Z0-9_-. The LuaVoiceConfig type marks it optional; the constructor throws without it.
string | object
required
The model that drives the conversation. A descriptor string, inference.LLM, or a realtime model.
string | object
required
Speech-to-text engine. Required for a cascaded llm; must be omitted with a realtime llm.
string | object
required
Text-to-speech engine. Required for a cascaded llm; optional with a realtime llm.
string
Shown in the compiled manifest and admin listings.
string
Spoken when the voice session starts, after onEnter. An empty string means no greeting.
string
BCP-47 code such as en or pt-BR, or multi for multilingual transcription. Applies to descriptor-route and Deepgram plugin speech-to-text.
'multilingual' | 'english' | 'vad' | 'stt' | 'manual'
How the end of the caller’s turn is detected. multilingual and english use a turn-detector model; vad uses voice activity alone; stt uses the transcriber’s end-of-speech; manual leaves it to your code.
string
Voice activity detection engine. silero is the only supported value; omit for the platform default.
object
Silero tuning: minSpeechDuration (ms, 0–5000), minSilenceDuration (ms, 0–5000), prefixPaddingDuration (ms, 0–2000), activationThreshold (0–1). Unknown keys are rejected.
object
enabled, mode ('adaptive' or 'vad'), falseInterruptionTimeout (seconds, ≥ 0), resumeFalseInterruption, minDelay and maxDelay (seconds, minDelay ≤ maxDelay).
boolean
Start generating the reply while the caller is still speaking.
number
Sequential tool calls allowed per turn; integer 1–20.
number
Seconds of silence (≥ 0) before the caller is treated as away.
boolean
Background noise cancellation. Off unless set; billed separately.
object
ambient and thinking clips. Each is a clip name ('office-ambience', 'keyboard-typing', 'keyboard-typing-2'), a { source, volume?, probability? } object (both 0–1), or an array of those for a probabilistic mix.
number
Output volume, integer 0–100. Omit to pass the provider’s level through.
Record<string, string>
Whole-word, case-insensitive replacements applied before synthesis, for example { HVAC: 'H V A C' }. Cascaded path only.
boolean
When true, the voice session history is written to Data under the key call:<sessionId> after the call ends.
string
1–200 characters spoken once when a tool call throws, times out, or returns an unsupported result, before the model’s own recovery.
string[]
Tool names withheld from this voice: platform tools (searchKnowledgeBase, searchWeb, the send* family), MCP and device tools, and your skill tools. Names that match nothing are ignored with a warning.
Array<LuaTool | LuaVoiceTool>
Call-only tools, in addition to the agent’s skills. See Voice tools.
(ctx: LuaVoiceHookContext) => Promise<void>
Runs after the voice session connects and before the greeting. Set ctx.session.userdata here.
(turnCtx: LuaVoiceTurnContext, message: { content: string }) => Promise<void>
Runs after the caller finishes a turn and before the model is called. turnCtx.addMessage() adds context the model sees on this turn.
(ctx: LuaVoiceHookContext) => Promise<void>
Runs when the voice session is closing; ctx.duration is set.

Hook contexts

Voice tools

LuaVoiceTool is a LuaTool whose execute receives a LuaVoiceToolCtx with the live call’s controls. A plain LuaTool placed in tools receives the same ctx and may carry flags under voice: { flags }.
src/voices/tools.ts
LuaVoiceToolConfig
string
required
Tool name the model calls.
string
required
When to call it.
ZodType
required
Input schema.
(input, ctx?: LuaVoiceToolCtx) => Promise<any>
required
Tool body.
() => Promise<boolean>
Offered to the model only when it resolves true.
ToolFlag[]
ToolFlag.IGNORE_ON_ENTER hides the tool during the first turn; ToolFlag.DISALLOW_INTERRUPTION stops caller speech from interrupting while it runs; ToolFlag.NONE. Stored on the instance as voice.flags.
LuaVoiceToolCtx — every member is optional, so call them with ?.. ctx is undefined when the same tool runs outside a voice session.
string
Identifier of this tool invocation.
Promise<void>
Speaks text to the caller, for example while a slow lookup runs.
Promise<void>
Ends the call. With { announce }, speaks it first and waits for playout.
unknown
Hands the call to another voice on the same agent by name. The receiving voice starts from its own greeting with no history; { context } is surfaced to it on its first turn. An unknown name is spoken as a fallback, not thrown.
Promise<void>
Transfers the caller to a phone number. mode: 'refer' (default) uses SIP REFER on the inbound leg; mode: 'bridge' dials the number into the room as a second participant, which works regardless of carrier REFER support. announce is spoken first.
void
Declared but not implemented in 3.33.0.

Instance properties

A LuaVoice exposes every config field as a read-only property: name, description, llm, stt, tts, vad, vadOptions, turnDetection, greeting, maxToolSteps, userAwayTimeout, preemptiveGeneration, interruption, sttLanguage, excludeTools, tools (a frozen array), onEnter, onUserTurnCompleted, onExit. The class has no methods.

Registration

List voices on the agent; a channel bound to the agent picks one by name, and falls back to the first entry. The voice-specific prompt is the voice branch of the agent’s persona.
src/index.ts
lua push voice --name <name> uploads the voice; lua voice --voice <name> opens a live test call from the browser, terminal or a phone. Binding a phone number is described under voice calls.

Testing

lua-cli/voice/test drives a LuaVoice offline with a scripted model, and lua voice test runs *.voice.test.ts files with Jest or Vitest.
src/voices/support-line.voice.test.ts
Promise<TestSession>
Starts a session. RunVoiceOptions: llm (scripted responses keyed by user input, or a model instance), mockTools (replacements for named tools), caller, channel ('phone' | 'meeting' | 'whatsapp' | 'webchat'), initialUserdata, handoffTargets (voices reachable by handoff), onTransferToHuman (a spy; no real transfer fires), dispatchedContext, sessionId.
interface
run(userInput) drives one turn and returns a RunResult; runMany(inputs) drives several; session is the underlying agent session; history the accumulated items; close() runs onExit.
void
Throws unless the turn called the tool; expectedArgs is an object or a predicate.
void
Throws unless an assistant message matches the substring, regex, or predicate.
void
Throws unless the turn handed off to the named voice.
Promise<boolean>
Asks a model whether response meets criterion. JudgeOptions: llm (required), systemInstruction, context. Returns false on disagreement and on any judge failure.
Promise<void>
Throwing form of judge.
The module also re-exports the llm and voice namespaces for typing scripted models and results.

Types

From 'lua-cli': LuaVoice, defineVoice, LuaVoiceConfig, LuaVoiceTool, LuaVoiceToolConfig, LuaVoiceToolCtx, LuaVoiceHookContext, LuaVoiceTurnContext, ToolFlag. From 'lua-cli/voice': the deepgram, elevenlabs, openai, google, xai and inference namespaces. From 'lua-cli/voice/test': runVoice, RunVoiceOptions, TestSession, expectCalledTool, expectContainsMessage, expectContainsHandoff, judge, expectJudge, JudgeOptions, llm, voice.

See also

  • Voice — phone numbers, voice definitions, widget voice, the catalog
  • Voice — placing calls and creating voice sessions from code
  • lua voice — live calls, list, test
  • Persona — the { base, voice, text } prompt shape
  • Voice calls — binding a phone number to a voice