@estuary-ai/sdk 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -2
- package/dist/index.d.mts +4 -0
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{websocket-voice-A4CK3UTM.mjs → websocket-voice-HYHCIYEW.mjs} +17 -4
- package/dist/websocket-voice-HYHCIYEW.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/websocket-voice-A4CK3UTM.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -74,6 +74,50 @@ await client.startVoice();
|
|
|
74
74
|
client.toggleMute();
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### Interrupts
|
|
78
|
+
|
|
79
|
+
Interrupt the bot's current response (stops audio playback and generation):
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
client.interrupt(); // interrupt current response
|
|
83
|
+
client.interrupt('msg_abc123'); // interrupt a specific message
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Vision / Camera
|
|
87
|
+
|
|
88
|
+
Send images for vision processing. The server may also request captures via the `cameraCaptureRequest` event.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Send a camera image proactively
|
|
92
|
+
client.sendCameraImage(base64Image, 'image/jpeg');
|
|
93
|
+
|
|
94
|
+
// Respond to a server-initiated capture request
|
|
95
|
+
client.on('cameraCaptureRequest', (request) => {
|
|
96
|
+
const image = captureFrame(); // your capture logic
|
|
97
|
+
client.sendCameraImage(image, 'image/jpeg', request.requestId, request.text);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Character Actions
|
|
102
|
+
|
|
103
|
+
Bot responses can include inline action tags (e.g., `<action name="wave" target="user"/>`). The SDK automatically parses these, strips them from `botResponse.text`, and emits `characterAction` events:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
client.on('characterAction', (action) => {
|
|
107
|
+
console.log(action.name); // e.g., "wave"
|
|
108
|
+
console.log(action.params); // e.g., { target: "user" }
|
|
109
|
+
console.log(action.messageId); // originating message
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For non-streaming contexts, use the `parseActions` utility:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { parseActions } from '@estuary-ai/sdk';
|
|
117
|
+
|
|
118
|
+
const { actions, cleanText } = parseActions(rawBotText);
|
|
119
|
+
```
|
|
120
|
+
|
|
77
121
|
### Memory & Knowledge Graph
|
|
78
122
|
|
|
79
123
|
```typescript
|
|
@@ -81,6 +125,9 @@ const memories = await client.memory.getMemories({ status: 'active', limit: 50 }
|
|
|
81
125
|
const facts = await client.memory.getCoreFacts();
|
|
82
126
|
const graph = await client.memory.getGraph({ includeEntities: true });
|
|
83
127
|
const results = await client.memory.search('favorite food');
|
|
128
|
+
const timeline = await client.memory.getTimeline({ groupBy: 'week' });
|
|
129
|
+
const stats = await client.memory.getStats();
|
|
130
|
+
await client.memory.deleteAll(true); // pass true to confirm
|
|
84
131
|
```
|
|
85
132
|
|
|
86
133
|
### Real-Time Memory Extraction
|
|
@@ -109,17 +156,69 @@ await client.connect();
|
|
|
109
156
|
## Events
|
|
110
157
|
|
|
111
158
|
```typescript
|
|
159
|
+
// Connection
|
|
112
160
|
client.on('connected', (session) => { /* authenticated */ });
|
|
113
161
|
client.on('disconnected', (reason) => { /* lost connection */ });
|
|
114
|
-
client.on('
|
|
162
|
+
client.on('reconnecting', (attempt) => { /* reconnect attempt number */ });
|
|
163
|
+
client.on('connectionStateChanged', (state) => { /* ConnectionState enum */ });
|
|
164
|
+
client.on('authError', (error) => { /* authentication failed */ });
|
|
165
|
+
|
|
166
|
+
// Conversation
|
|
167
|
+
client.on('botResponse', (response) => { /* streaming text (actions auto-stripped) */ });
|
|
115
168
|
client.on('botVoice', (voice) => { /* audio chunk */ });
|
|
116
|
-
client.on('sttResponse', (stt) => { /* speech-to-text */ });
|
|
169
|
+
client.on('sttResponse', (stt) => { /* speech-to-text transcript */ });
|
|
117
170
|
client.on('interrupt', (data) => { /* response interrupted */ });
|
|
171
|
+
client.on('characterAction', (action) => { /* parsed action from bot response */ });
|
|
172
|
+
client.on('cameraCaptureRequest', (request) => { /* server requests a camera image */ });
|
|
173
|
+
|
|
174
|
+
// Voice
|
|
175
|
+
client.on('voiceStarted', () => { /* voice session began */ });
|
|
176
|
+
client.on('voiceStopped', () => { /* voice session ended */ });
|
|
177
|
+
client.on('livekitConnected', (room) => { /* joined LiveKit room */ });
|
|
178
|
+
client.on('livekitDisconnected', () => { /* left LiveKit room */ });
|
|
179
|
+
|
|
180
|
+
// Audio playback
|
|
181
|
+
client.on('audioPlaybackStarted', (messageId) => { /* bot audio started playing */ });
|
|
182
|
+
client.on('audioPlaybackComplete', (messageId) => { /* bot audio finished playing */ });
|
|
183
|
+
|
|
184
|
+
// Memory
|
|
118
185
|
client.on('memoryUpdated', (event) => { /* real-time memory extraction */ });
|
|
186
|
+
|
|
187
|
+
// Errors & limits
|
|
119
188
|
client.on('error', (error) => { /* EstuaryError */ });
|
|
120
189
|
client.on('quotaExceeded', (data) => { /* rate limited */ });
|
|
121
190
|
```
|
|
122
191
|
|
|
192
|
+
## Error Handling
|
|
193
|
+
|
|
194
|
+
Errors are instances of `EstuaryError` with a typed `code` field:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { EstuaryError, ErrorCode } from '@estuary-ai/sdk';
|
|
198
|
+
|
|
199
|
+
client.on('error', (error) => {
|
|
200
|
+
if (error instanceof EstuaryError) {
|
|
201
|
+
switch (error.code) {
|
|
202
|
+
case ErrorCode.NOT_CONNECTED:
|
|
203
|
+
case ErrorCode.CONNECTION_FAILED:
|
|
204
|
+
case ErrorCode.CONNECTION_TIMEOUT:
|
|
205
|
+
// connection issues
|
|
206
|
+
break;
|
|
207
|
+
case ErrorCode.AUTH_FAILED:
|
|
208
|
+
// bad API key or character ID
|
|
209
|
+
break;
|
|
210
|
+
case ErrorCode.MICROPHONE_DENIED:
|
|
211
|
+
// user denied mic permission
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
client.on('authError', (message) => {
|
|
218
|
+
console.error('Authentication failed:', message);
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
123
222
|
## Configuration
|
|
124
223
|
|
|
125
224
|
```typescript
|
|
@@ -135,9 +234,46 @@ interface EstuaryConfig {
|
|
|
135
234
|
debug?: boolean; // Default: false
|
|
136
235
|
voiceTransport?: 'websocket' | 'livekit' | 'auto'; // Default: 'auto'
|
|
137
236
|
realtimeMemory?: boolean; // Enable real-time memory extraction events. Default: false
|
|
237
|
+
suppressMicDuringPlayback?: boolean; // Mute mic while bot audio plays (software AEC). Default: false
|
|
138
238
|
}
|
|
139
239
|
```
|
|
140
240
|
|
|
241
|
+
## Exports
|
|
242
|
+
|
|
243
|
+
Key exports for TypeScript users:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// Client
|
|
247
|
+
import { EstuaryClient } from '@estuary-ai/sdk';
|
|
248
|
+
|
|
249
|
+
// Errors
|
|
250
|
+
import { EstuaryError, ErrorCode } from '@estuary-ai/sdk';
|
|
251
|
+
|
|
252
|
+
// Enums
|
|
253
|
+
import { ConnectionState } from '@estuary-ai/sdk';
|
|
254
|
+
|
|
255
|
+
// Utilities
|
|
256
|
+
import { parseActions } from '@estuary-ai/sdk';
|
|
257
|
+
|
|
258
|
+
// Types (import type)
|
|
259
|
+
import type {
|
|
260
|
+
EstuaryConfig,
|
|
261
|
+
SessionInfo,
|
|
262
|
+
BotResponse,
|
|
263
|
+
BotVoice,
|
|
264
|
+
SttResponse,
|
|
265
|
+
InterruptData,
|
|
266
|
+
CameraCaptureRequest,
|
|
267
|
+
CharacterAction,
|
|
268
|
+
QuotaExceededData,
|
|
269
|
+
MemoryData,
|
|
270
|
+
MemoryUpdatedEvent,
|
|
271
|
+
EstuaryEventMap,
|
|
272
|
+
ParsedAction,
|
|
273
|
+
MemoryClient,
|
|
274
|
+
} from '@estuary-ai/sdk';
|
|
275
|
+
```
|
|
276
|
+
|
|
141
277
|
## Requirements
|
|
142
278
|
|
|
143
279
|
- Node.js 18+ or modern browser
|
package/dist/index.d.mts
CHANGED
|
@@ -21,6 +21,8 @@ interface EstuaryConfig {
|
|
|
21
21
|
voiceTransport?: VoiceTransport;
|
|
22
22
|
/** Enable real-time memory extraction after each response (default: false) */
|
|
23
23
|
realtimeMemory?: boolean;
|
|
24
|
+
/** Suppress mic during TTS playback (software AEC fallback, disables barge-in). Default: false */
|
|
25
|
+
suppressMicDuringPlayback?: boolean;
|
|
24
26
|
}
|
|
25
27
|
type VoiceTransport = 'websocket' | 'livekit' | 'auto';
|
|
26
28
|
declare enum ConnectionState {
|
|
@@ -130,6 +132,8 @@ interface VoiceManager {
|
|
|
130
132
|
start(): Promise<void>;
|
|
131
133
|
stop(): Promise<void>;
|
|
132
134
|
toggleMute(): void;
|
|
135
|
+
/** Suppress audio sending (software AEC). No-op if not supported. */
|
|
136
|
+
setSuppressed?(suppressed: boolean): void;
|
|
133
137
|
readonly isMuted: boolean;
|
|
134
138
|
readonly isActive: boolean;
|
|
135
139
|
dispose(): void;
|
package/dist/index.js
CHANGED
|
@@ -4889,6 +4889,7 @@ var init_websocket_voice = __esm({
|
|
|
4889
4889
|
scriptProcessor = null;
|
|
4890
4890
|
sourceNode = null;
|
|
4891
4891
|
_isMuted = false;
|
|
4892
|
+
_isSuppressed = false;
|
|
4892
4893
|
_isActive = false;
|
|
4893
4894
|
constructor(socketManager, sampleRate, logger) {
|
|
4894
4895
|
this.socketManager = socketManager;
|
|
@@ -4911,7 +4912,13 @@ var init_websocket_voice = __esm({
|
|
|
4911
4912
|
let stream;
|
|
4912
4913
|
try {
|
|
4913
4914
|
stream = await navigator.mediaDevices.getUserMedia({
|
|
4914
|
-
audio: {
|
|
4915
|
+
audio: {
|
|
4916
|
+
sampleRate: this.sampleRate,
|
|
4917
|
+
channelCount: 1,
|
|
4918
|
+
echoCancellation: true,
|
|
4919
|
+
noiseSuppression: true,
|
|
4920
|
+
autoGainControl: true
|
|
4921
|
+
}
|
|
4915
4922
|
});
|
|
4916
4923
|
} catch (err) {
|
|
4917
4924
|
throw new exports.EstuaryError(
|
|
@@ -4928,7 +4935,7 @@ var init_websocket_voice = __esm({
|
|
|
4928
4935
|
const nativeRate = this.audioContext.sampleRate;
|
|
4929
4936
|
const targetRate = this.sampleRate;
|
|
4930
4937
|
this.scriptProcessor.onaudioprocess = (event) => {
|
|
4931
|
-
if (this._isMuted) return;
|
|
4938
|
+
if (this._isMuted || this._isSuppressed) return;
|
|
4932
4939
|
const inputData = event.inputBuffer.getChannelData(0);
|
|
4933
4940
|
let pcmFloat;
|
|
4934
4941
|
if (nativeRate !== targetRate) {
|
|
@@ -4958,6 +4965,7 @@ var init_websocket_voice = __esm({
|
|
|
4958
4965
|
this.cleanup();
|
|
4959
4966
|
this._isActive = false;
|
|
4960
4967
|
this._isMuted = false;
|
|
4968
|
+
this._isSuppressed = false;
|
|
4961
4969
|
this.logger.debug("WebSocket voice stopped");
|
|
4962
4970
|
}
|
|
4963
4971
|
toggleMute() {
|
|
@@ -4968,10 +4976,15 @@ var init_websocket_voice = __esm({
|
|
|
4968
4976
|
}
|
|
4969
4977
|
this.logger.debug("Mute toggled:", this._isMuted);
|
|
4970
4978
|
}
|
|
4979
|
+
setSuppressed(suppressed) {
|
|
4980
|
+
this._isSuppressed = suppressed;
|
|
4981
|
+
this.logger.debug("Audio suppression:", suppressed ? "on" : "off");
|
|
4982
|
+
}
|
|
4971
4983
|
dispose() {
|
|
4972
4984
|
this.cleanup();
|
|
4973
4985
|
this._isActive = false;
|
|
4974
4986
|
this._isMuted = false;
|
|
4987
|
+
this._isSuppressed = false;
|
|
4975
4988
|
}
|
|
4976
4989
|
cleanup() {
|
|
4977
4990
|
if (this.scriptProcessor) {
|
|
@@ -9418,6 +9431,9 @@ var EstuaryClient = class extends TypedEventEmitter {
|
|
|
9418
9431
|
this.ensureConnected();
|
|
9419
9432
|
this.socketManager.emitEvent("client_interrupt", { message_id: messageId });
|
|
9420
9433
|
this.audioPlayer?.clear();
|
|
9434
|
+
if (this.config.suppressMicDuringPlayback) {
|
|
9435
|
+
this.voiceManager?.setSuppressed?.(false);
|
|
9436
|
+
}
|
|
9421
9437
|
}
|
|
9422
9438
|
/** Send a camera image for vision processing */
|
|
9423
9439
|
sendCameraImage(imageBase64, mimeType, requestId, text) {
|
|
@@ -9456,9 +9472,15 @@ var EstuaryClient = class extends TypedEventEmitter {
|
|
|
9456
9472
|
this.audioPlayer = new AudioPlayer(sampleRate, (event) => {
|
|
9457
9473
|
if (event.type === "started") {
|
|
9458
9474
|
this.emit("audioPlaybackStarted", event.messageId);
|
|
9475
|
+
if (this.config.suppressMicDuringPlayback) {
|
|
9476
|
+
this.voiceManager?.setSuppressed?.(true);
|
|
9477
|
+
}
|
|
9459
9478
|
} else if (event.type === "complete") {
|
|
9460
9479
|
this.emit("audioPlaybackComplete", event.messageId);
|
|
9461
9480
|
this.notifyAudioPlaybackComplete(event.messageId);
|
|
9481
|
+
if (this.config.suppressMicDuringPlayback) {
|
|
9482
|
+
this.voiceManager?.setSuppressed?.(false);
|
|
9483
|
+
}
|
|
9462
9484
|
}
|
|
9463
9485
|
});
|
|
9464
9486
|
}
|
|
@@ -9513,6 +9535,9 @@ var EstuaryClient = class extends TypedEventEmitter {
|
|
|
9513
9535
|
this.socketManager.on("interrupt", (data) => {
|
|
9514
9536
|
this.audioPlayer?.clear();
|
|
9515
9537
|
this.actionParsers.clear();
|
|
9538
|
+
if (this.config.suppressMicDuringPlayback) {
|
|
9539
|
+
this.voiceManager?.setSuppressed?.(false);
|
|
9540
|
+
}
|
|
9516
9541
|
this.emit("interrupt", data);
|
|
9517
9542
|
});
|
|
9518
9543
|
this.socketManager.on("error", (error) => this.emit("error", error));
|