EstuaryClient
The main class for interacting with the Estuary platform. Manages the WebSocket connection, voice sessions, and provides access to the memory API.
import { EstuaryClient } from '@estuary-ai/sdk';
const client = new EstuaryClient(config);
Constructor
new EstuaryClient(config)
Creates a new client instance. Either apiKey or sessionToken must be provided in the config.
| Parameter | Type | Description |
|---|---|---|
config | EstuaryConfig | Client configuration |
Throws: EstuaryError with code AUTH_FAILED if neither apiKey nor sessionToken is provided.
const client = new EstuaryClient({
serverUrl: 'https://api.estuary-ai.com',
apiKey: 'est_your_api_key',
characterId: 'your-character-uuid',
playerId: 'user-123',
});
Static Methods
EstuaryClient.openShare(serverUrl, shareId)
Opens a permanent share link. Calls the backend's share endpoint to mint a fresh session token and resolve character metadata. Use the returned fields to construct an EstuaryClient with sessionToken auth.
| Parameter | Type | Description |
|---|---|---|
serverUrl | string | Base URL of the Estuary server |
shareId | string | The share link identifier |
Returns: Promise<ShareOpenResponse>
Throws: EstuaryError with code REST_ERROR if the share open request fails.
import { EstuaryClient } from '@estuary-ai/sdk';
const share = await EstuaryClient.openShare(
'https://api.estuary-ai.com',
'share-abc-123',
);
const client = new EstuaryClient({
serverUrl: share.serverUrl,
sessionToken: share.sessionToken,
characterId: share.characterId,
playerId: share.playerId,
});
await client.connect();
See ShareOpenResponse for the full response shape.
Connection
connect()
Connects to the Estuary server and authenticates with the configured apiKey (or sessionToken). Returns a promise that resolves with session info on success.
Returns: Promise<SessionInfo>
Throws: EstuaryError with code CONNECTION_FAILED or AUTH_FAILED
const session = await client.connect();
console.log('Session ID:', session.sessionId);
disconnect()
Disconnects from the server. Stops any active voice session, clears audio playback, and cleans up resources. Awaits stopVoice() internally, so it returns a promise.
Returns: Promise<void>
await client.disconnect();
isConnected
Whether the client is currently connected and authenticated.
Type: boolean (read-only)
if (client.isConnected) {
client.sendText('Hello!');
}
connectionState
The current connection state.
Type: ConnectionState (read-only)
console.log(client.connectionState); // 'connected', 'connecting', etc.
Text
sendText(text, textOnly?)
Sends a text message to the character.
| Parameter | Type | Default | Description |
|---|---|---|---|
text | string | -- | The message text |
textOnly | boolean | true | If true, suppresses voice response (text only). Pass false to receive voice audio. |
Throws: EstuaryError with code NOT_CONNECTED
client.sendText('Hello!'); // text only (default)
client.sendText('Tell me a story', false); // also stream voice audio
sayLine(text, textOnly?)
Scripts the character to say a specific prewritten line via TTS. Skips the LLM entirely -- the text is fed directly to TTS and saved to chat history as a normal assistant message. Any in-progress response is interrupted before the line is spoken.
| Parameter | Type | Default | Description |
|---|---|---|---|
text | string | -- | The line to speak. Empty/whitespace strings are ignored. |
textOnly | boolean | false | If true, only botResponse is emitted (no audio). |
Throws: EstuaryError with code NOT_CONNECTED
client.sayLine('Welcome back, traveler.');
client.sayLine('System message', true); // text only, no TTS
The response uses the same botResponse and botVoice events as a normal reply; isInterjection will be false.
interrupt(messageId?)
Interrupts the current bot response and clears queued audio.
| Parameter | Type | Description |
|---|---|---|
messageId | string (optional) | Specific message to interrupt |
Throws: EstuaryError with code NOT_CONNECTED
client.interrupt();
client.interrupt('msg-123');
Voice
startVoice()
Starts a voice session and requests microphone permission. With voiceMode: 'continuous' (default), audio streams immediately. With 'push_to_talk', the microphone is prepared with transmission off until a press.
The voice transport used depends on the voiceTransport config option:
'websocket'-- Streams PCM audio over WebSocket'livekit'-- Connects via LiveKit WebRTC (requireslivekit-client)'auto'(default) -- Uses LiveKit if available, otherwise WebSocket
Returns: Promise<void>
Throws:
EstuaryErrorwith codeNOT_CONNECTEDif not connectedEstuaryErrorwith codeVOICE_ALREADY_ACTIVEif voice is already startedEstuaryErrorwith codeVOICE_NOT_SUPPORTEDif no transport is availableEstuaryErrorwith codeMICROPHONE_DENIEDif microphone permission is denied
await client.startVoice();
stopVoice()
Stops the active voice session. Releases the microphone and cleans up the voice manager. No-op if voice is not active.
Returns: Promise<void>
await client.stopVoice();
beginPushToTalk()
Presses talk in a prepared voiceMode: 'push_to_talk' session. Await the promise
before prompting the user to speak. Pauses while held do not submit the turn.
Repeated presses while already held share the pending operation.
Returns: Promise<void>
Throws: NOT_CONNECTED before connect; VOICE_NOT_ACTIVE before startVoice();
VOICE_MODE_MISMATCH in continuous mode; CONNECTION_FAILED on a voice provider
failure; CONNECTION_TIMEOUT if readiness times out. Reconnect before retrying
a timed-out command.
endPushToTalk()
Releases talk and finalizes the turn, keeping voice prepared for another press. Safe to call while a press is pending, when already released, or after teardown. Bind this to release/cancel/blur events so the button cannot remain held. Await it before the next turn; a rapid LiveKit re-press can merge with the previous turn during the release grace period. It does not end the LiveKit call.
Returns: Promise<void>
Throws: VOICE_MODE_MISMATCH in continuous mode; connection errors if the
release signal fails. On a command timeout, reconnect before retrying.
isPushToTalkActive
Whether the talk button is held, including while press readiness is pending.
It resets on release, voice teardown, and disconnect. Reconnection does not resume
an old press. User mute and playback suppression can still block transmission
while this property is true; isMuted reports the separate user mute toggle.
Type: boolean (read-only)
See Push-to-talk for usage.
toggleMute()
Toggles the microphone mute state.
Throws: EstuaryError with code VOICE_NOT_ACTIVE if no voice session is active
Returns: void
client.toggleMute();
isMuted
Whether the microphone is currently muted.
Type: boolean (read-only)
console.log('Muted:', client.isMuted);
isVoiceActive
Whether a voice session is currently active.
Type: boolean (read-only)
if (client.isVoiceActive) {
await client.stopVoice();
}
Camera
sendCameraImage(imageBase64, mimeType, requestId?, text?)
Sends a camera image for vision-language model processing.
| Parameter | Type | Description |
|---|---|---|
imageBase64 | string | Base64-encoded image data |
mimeType | string | Image MIME type (e.g., 'image/jpeg') |
requestId | string (optional) | Request ID (if responding to a cameraCaptureRequest) |
text | string (optional) | Text to accompany the image |
Throws: EstuaryError with code NOT_CONNECTED
client.sendCameraImage(base64Data, 'image/jpeg', requestId, 'What is this?');
updatePreferences(preferences)
Updates session-level preferences.
| Parameter | Type | Description |
|---|---|---|
preferences | object | Preferences to update |
preferences.enableVisionAcknowledgment | boolean (optional) | Whether the character acknowledges before analyzing images |
Throws: EstuaryError with code NOT_CONNECTED
client.updatePreferences({ enableVisionAcknowledgment: false });
Audio
notifyAudioPlaybackComplete(messageId?)
Notifies the server that audio playback has completed for a message. This is called automatically:
- WebSocket transport: by the built-in
AudioPlayerwhen its drain callback fires. - LiveKit transport: when the bot's speaking state transitions from speaking to silent (via the LiveKit speaking-state callback).
You only need to call this manually if you implement a custom audio playback path (for example, intercepting botVoice events and rendering audio yourself).
| Parameter | Type | Description |
|---|---|---|
messageId | string (optional) | The message whose audio finished playing |
Throws: EstuaryError with code NOT_CONNECTED
client.notifyAudioPlaybackComplete('msg-123');
Character
getCharacter(characterId?)
Fetches character details (3D model URL, avatar, tagline, etc.) from the REST API.
| Parameter | Type | Description |
|---|---|---|
characterId | string (optional) | Character to fetch. Defaults to the characterId configured on the client. |
Returns: Promise<CharacterInfo> -- see CharacterInfo.
Throws: EstuaryError with code NOT_CONNECTED if the client was constructed with a sessionToken instead of an apiKey (REST is not available with session-token auth -- use a server-side proxy in that case).
const character = await client.getCharacter();
console.log('3D model URL:', character.modelUrl);
// Or explicitly fetch a different character
const other = await client.getCharacter('character-uuid-2');
Properties
memory
The memory API client for querying memories, knowledge graphs, and facts.
Type: MemoryClient (read-only)
Throws: EstuaryError with code NOT_CONNECTED when accessed if the client was constructed with a sessionToken (REST is not available with session-token auth).
const facts = await client.memory.getCoreFacts();
See Memory & Knowledge Graph for usage details.
session
The current session info. null if not connected.
Type: SessionInfo | null (read-only)
if (client.session) {
console.log('Conversation:', client.session.conversationId);
}
Event Methods
EstuaryClient extends TypedEventEmitter and provides typed event subscription:
on(event, listener)
Subscribe to an event. See Events and Types for the full event map.
client.on('botResponse', (response) => { /* ... */ });
off(event, listener)
Remove an event listener.
client.off('botResponse', handler);
once(event, listener)
Subscribe to an event for a single invocation.
client.once('connected', (session) => { /* ... */ });