ReadonlyaccountOptionalagentSet when the adapter has emitted its first agent audio chunk for the
current turn — gates timing-based barge-in. Concrete adapters expose
this so scenario.interrupt can wait for real speech before
firing the interruption. Optional: adapters without server-VAD-style
interrupt sequencing can leave it undefined.
Optional ReadonlyallowedReadonlyauthReadonlycapabilitiesDeclaration of what this adapter can and cannot do. Concrete subclasses MUST publish a non-default value; the base instance defaults to "nothing supported" so capability-gated steps fail safely when an adapter forgets to declare.
ReadonlyfetchReadonlyhttpOptionalnameOptional ReadonlyonReadonlyphoneOptionalpublicCalled by the server when an inbound webhook is rejected (caller filter or bad signature). Exposed for tests; production callers see the HTTP response and never look at this counter.
Hard cap on a single agent turn's audio. Prevents runaway loops if a transport never signals end-of-stream. 30s = a long sentence.
Tail silence: once the first agent chunk arrives, keep draining receiveAudio until no chunk shows up within this many seconds — that's how we detect the agent finished talking.
Seconds to wait for agent audio after sending user audio.
OptionalstreamingIncremental transcript text emitted while the agent speaks. Populated
by adapters that advertise capabilities.streamingTranscripts. Read
by scenario.interrupt when afterWords: N is set.
ReadonlyvalidateInternalTest seam: the running webhook server's bound HTTP base URL (e.g.
http://127.0.0.1:54321). Useful for tests that don't want a tunnel.
Throws if the adapter isn't connected.
Test seam: directly drive a media-stream loop over a provided socket.
Production code reaches the loop via the /twilio/stream route.
InternalInternalInternalInternalInternalDefault call() body, ported from Python VoiceAgentAdapter.call.
Threads the latest user-message audio through sendAudio, drains the agent response on tail silence, records one user and one agent segment into the executor state, and returns the merged assistant audio message. Subclasses may override for specialised flows but will usually inherit it.
Open the transport and prepare to exchange audio.
Close the transport and release resources.
Send a first-class interrupt signal to the agent under test.
Adapters that advertise capabilities.interruption === true override
this to send the transport-native interrupt (e.g. Twilio clear,
OpenAI Realtime response.cancel). The default raises
UnsupportedCapabilityError; callers (scenario.interrupt())
check capabilities.interruption and fall back to timing-based
barge-in when this returns false.
Whether the Media Stream transport is open (Gap #11).
Transmit DTMF tones to the telephony peer. Adapters that advertise
capabilities.dtmf MUST implement this; the default raises
UnsupportedCapabilityError so an adapter that forgot to ship
sendDtmf while claiming the capability fails loudly instead of
silently routing through a PCM fallback.
Abstract base for voice agents that exchange audio with the agent under test.
Subclasses must implement connect, disconnect, sendAudio, and receiveAudio. They must also publish an AdapterCapabilities instance as the capabilities field — declared once per concrete adapter, not per instance.
The default call implementation lives in defaultVoiceCall: it extracts audio from the latest user message, transmits via sendAudio, drains the agent response on tail silence, and records one user + one agent segment into the executor state. Subclasses can override
call()for specialised flows but will usually inherit it.