Skip to main content

Core Concepts

Understand the architecture, event system, and design patterns of the Estuary Web SDK.

Architecture

The EstuaryClient is the main entry point. It composes several internal modules:

┌─────────────────────────────────────────────────────────┐
│ EstuaryClient │
│ │
│ ┌─────────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ SocketManager │ │ VoiceManager │ │ MemoryClient│ │
│ │ (WebSocket) │ │ (Mic + Audio)│ │ (REST API) │ │
│ └────────┬────────┘ └──────┬───────┘ └─────┬─────┘ │
│ │ │ │ │
└───────────┼──────────────────┼────────────────┼─────────┘
│ │ │
▼ ▼ ▼
Socket.IO v4 Microphone / REST /api/
WebSocket LiveKit WebRTC endpoints
│ │ │
└──────────┬───────┘ │
▼ │
┌────────────────┐ │
│ Estuary Server │◄───────────────┘
└────────────────┘
  • SocketManager handles the WebSocket connection, authentication, reconnection logic, and event routing.
  • VoiceManager captures microphone audio and streams it to the server. Two implementations exist: WebSocket-based and LiveKit-based.
  • MemoryClient provides access to the memory REST API for querying facts, knowledge graphs, and conversation history.

Event-Driven Design

The SDK uses a typed EventEmitter pattern. Every event has a defined callback signature through the EstuaryEventMap interface, giving you full TypeScript autocompletion and type safety.

// Events are fully typed
client.on('botResponse', (response) => {
// response is typed as BotResponse
console.log(response.text);
});

client.on('connected', (session) => {
// session is typed as SessionInfo
console.log(session.sessionId);
});

You can subscribe with on(), unsubscribe with off(), and use once() for one-time listeners:

// Listen until removed
const handler = (response: BotResponse) => { /* ... */ };
client.on('botResponse', handler);
client.off('botResponse', handler);

// Listen once
client.once('connected', (session) => {
console.log('First connection:', session.sessionId);
});

Connection Lifecycle

The client tracks its connection through five states:

┌──────────────┐
│ Disconnected │
└──────┬───────┘
│ connect()

┌──────────────┐ ┌───────┐
│ Connecting │────────►│ Error │
└──────┬───────┘ └───────┘
│ authenticated ▲
▼ │
┌──────────────┐ │
│ Connected │──────────────┘
└──────┬───────┘ connection lost


┌──────────────┐
│ Reconnecting │ (if autoReconnect is true)
└──────────────┘
import { ConnectionState } from '@estuary-ai/sdk';

client.on('connectionStateChanged', (state) => {
switch (state) {
case ConnectionState.Connecting:
showSpinner();
break;
case ConnectionState.Connected:
hideSpinner();
break;
case ConnectionState.Reconnecting:
showReconnectingBanner();
break;
case ConnectionState.Error:
showErrorMessage();
break;
}
});

When autoReconnect is enabled (the default), the client automatically attempts to reconnect on disconnect with linear backoff (delay × attemptNumber, e.g. 2s, 4s, 6s, 8s, 10s with the default 2000ms base delay), up to maxReconnectAttempts (default: 5).

Error Handling

All SDK errors are instances of EstuaryError, which extends the standard Error class with a typed code property:

import { EstuaryError, ErrorCode } from '@estuary-ai/sdk';

client.on('error', (err) => {
if (err instanceof EstuaryError) {
switch (err.code) {
case ErrorCode.AUTH_FAILED:
console.error('Invalid API key');
break;
case ErrorCode.QUOTA_EXCEEDED:
console.error('Usage limit reached');
break;
case ErrorCode.VOICE_NOT_SUPPORTED:
console.error('Voice not available in this environment');
break;
default:
console.error('Error:', err.message);
}
}
});

Methods like connect(), startVoice(), and toggleMute() throw EstuaryError synchronously or reject with one. Always wrap connect() in a try/catch:

try {
await client.connect();
} catch (err) {
if (err instanceof EstuaryError && err.code === ErrorCode.AUTH_FAILED) {
// Handle invalid credentials
}
}

Configuration

The EstuaryConfig object controls all client behavior. Only serverUrl, apiKey, characterId, and playerId are required:

const client = new EstuaryClient({
// Required
serverUrl: 'https://api.estuary-ai.com',
apiKey: 'est_your_api_key',
characterId: 'your-character-uuid',
playerId: 'user-123',

// Optional
voiceTransport: 'auto', // 'websocket' | 'livekit' | 'auto'
audioSampleRate: 16000, // Hz
autoReconnect: true,
maxReconnectAttempts: 5,
reconnectDelayMs: 2000, // ms
debug: false,
realtimeMemory: false, // Enable real-time memory extraction events
suppressMicDuringPlayback: false, // Mute mic while bot audio plays (software AEC)
autoInterruptOnSpeech: true, // Interrupt bot audio when user starts speaking
});

See the Configuration Reference for details on every option.

Next Steps