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.
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 aLuaVoice; 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.
LuaVoice 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
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
@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
ALuaVoice 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 thevoice 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.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 codelua voice— live calls,list,test- Persona — the
{ base, voice, text }prompt shape - Voice calls — binding a phone number to a voice

