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
- Text Chat -- Send messages and handle streaming responses
- Voice (WebSocket) -- Add voice input and output
- API Reference -- Full reference for all classes, events, and types