@estuary-ai/sdk 0.4.1 → 0.6.0

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 estuary.ai
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 estuary.ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -46,6 +46,37 @@ client.sendText('What do you remember about me?');
46
46
  client.sendText('Just respond in text', true); // textOnly mode
47
47
  ```
48
48
 
49
+ ### Scripted Lines
50
+
51
+ Make the character speak exact, prewritten lines (straight to TTS, skipping the LLM):
52
+
53
+ ```typescript
54
+ client.sayLine('Welcome back, traveler.'); // TTS audio
55
+ client.sayLine('System: connection restored.', true); // text only, no audio
56
+ ```
57
+
58
+ For a sequence, use `playScript()` — it paces the lines so each finishes before the next is
59
+ sent. (`say_line` interrupts any in-progress speech, so firing several at once would make each
60
+ line cut off the previous one.)
61
+
62
+ ```typescript
63
+ const script = client.playScript(
64
+ ['Welcome to my shop, traveler!', 'I have wares, if you have coin.', 'Come back anytime.'],
65
+ { textOnly: false, lineGapMs: 250 },
66
+ );
67
+
68
+ client.on('scriptLineStarted', ({ index, text }) => console.log(`line ${index}: ${text}`));
69
+ await script.done; // resolves when all lines have been spoken
70
+
71
+ script.pause();
72
+ script.resume();
73
+ script.next(); // skip ahead
74
+ script.stop(); // halt + interrupt
75
+ ```
76
+
77
+ `sayLines(lines, opts)` is a convenience alias of `playScript(lines, opts)`. Lines may be plain
78
+ strings or per-line overrides: `{ text: '(a silent stage direction)', textOnly: true }`.
79
+
49
80
  ### Voice (WebSocket)
50
81
 
51
82
  ```typescript
@@ -182,6 +213,10 @@ client.on('interrupt', (data) => { /* response interrupted */ });
182
213
  client.on('characterAction', (action) => { /* parsed action from bot response */ });
183
214
  client.on('cameraCaptureRequest', (request) => { /* server requests a camera image */ });
184
215
 
216
+ // Scripted lines (playScript / sayLines)
217
+ client.on('scriptLineStarted', ({ index, text, messageId }) => { /* a scripted line began */ });
218
+ client.on('scriptComplete', ({ reason }) => { /* 'finished' | 'stopped' | 'disconnected' | 'interrupted' */ });
219
+
185
220
  // Voice
186
221
  client.on('voiceStarted', () => { /* voice session began */ });
187
222
  client.on('voiceStopped', () => { /* voice session ended */ });
package/dist/index.d.mts CHANGED
@@ -91,6 +91,17 @@ interface QuotaExceededData {
91
91
  remaining: number;
92
92
  tier: string;
93
93
  }
94
+ /**
95
+ * Emitted when the server ends the session due to inactivity (no conversation
96
+ * activity for the server's idle timeout). The server disconnects the socket
97
+ * right after; the SDK does not auto-reconnect from this — call connect()
98
+ * again on explicit user intent to resume.
99
+ */
100
+ interface SessionTimeoutData {
101
+ reason: string;
102
+ idleSeconds: number;
103
+ timeoutSeconds: number;
104
+ }
94
105
  interface LiveKitTokenResponse {
95
106
  token: string;
96
107
  url: string;
@@ -156,6 +167,46 @@ interface CharacterAction {
156
167
  /** Message ID of the bot response that contained this action */
157
168
  messageId: string;
158
169
  }
170
+ /** A scripted line: plain text (uses the script's default textOnly) or an explicit override. */
171
+ type ScriptLine = string | {
172
+ text: string;
173
+ textOnly?: boolean;
174
+ };
175
+ interface ScriptOptions {
176
+ /** Default for plain-string lines: false = TTS audio (default), true = text-only. */
177
+ textOnly?: boolean;
178
+ /** Pause inserted after each line completes, in ms (default 0). */
179
+ lineGapMs?: number;
180
+ /** Begin speaking immediately on creation (default true). If false, call play(). */
181
+ autoStart?: boolean;
182
+ /** Repeat from the first line after the last (default false). */
183
+ loop?: boolean;
184
+ /** Force-advance a line if no completion signal arrives within this many ms (default 30000). */
185
+ lineTimeoutMs?: number;
186
+ }
187
+ type ScriptEndReason = 'finished' | 'stopped' | 'disconnected' | 'interrupted';
188
+ type ScriptState = 'idle' | 'playing' | 'paused' | 'done';
189
+ interface ScriptLineStartedInfo {
190
+ index: number;
191
+ text: string;
192
+ messageId: string;
193
+ }
194
+ /** Handle returned by EstuaryClient.playScript() / sayLines(). */
195
+ interface ScriptController {
196
+ readonly length: number;
197
+ /** Index of the current / most-recently-started line (-1 before the first line starts). */
198
+ readonly index: number;
199
+ readonly state: ScriptState;
200
+ /** Resolves (never rejects) when the script ends, with the reason. Awaitable. */
201
+ readonly done: Promise<{
202
+ reason: ScriptEndReason;
203
+ }>;
204
+ play(): void;
205
+ pause(): void;
206
+ resume(): void;
207
+ next(): void;
208
+ stop(): void;
209
+ }
159
210
  type EstuaryEventMap = {
160
211
  connected: (session: SessionInfo) => void;
161
212
  disconnected: (reason: string) => void;
@@ -168,6 +219,7 @@ type EstuaryEventMap = {
168
219
  error: (error: Error) => void;
169
220
  authError: (error: string) => void;
170
221
  quotaExceeded: (data: QuotaExceededData) => void;
222
+ sessionTimeout: (data: SessionTimeoutData) => void;
171
223
  cameraCaptureRequest: (request: CameraCaptureRequest) => void;
172
224
  characterAction: (action: CharacterAction) => void;
173
225
  voiceStarted: () => void;
@@ -179,6 +231,10 @@ type EstuaryEventMap = {
179
231
  /** Bot audio level 0.0–1.0, emitted during playback for both transports. */
180
232
  botAudioLevel: (level: number) => void;
181
233
  memoryUpdated: (event: MemoryUpdatedEvent) => void;
234
+ scriptLineStarted: (info: ScriptLineStartedInfo) => void;
235
+ scriptComplete: (info: {
236
+ reason: ScriptEndReason;
237
+ }) => void;
182
238
  };
183
239
  interface VoiceManager {
184
240
  start(): Promise<void>;
@@ -360,6 +416,7 @@ declare class EstuaryClient extends TypedEventEmitter<EstuaryEventMap> {
360
416
  private _hasAutoInterrupted;
361
417
  private _autoInterruptGraceTimer;
362
418
  private _isLiveKitSpeaking;
419
+ private _activeScript;
363
420
  constructor(config: EstuaryConfig);
364
421
  /** Memory API client for querying memories, graphs, and facts */
365
422
  get memory(): MemoryClient;
@@ -385,6 +442,16 @@ declare class EstuaryClient extends TypedEventEmitter<EstuaryEventMap> {
385
442
  sendText(text: string, textOnly?: boolean): void;
386
443
  /** Script the character to say a specific prewritten line. Defaults to TTS enabled (textOnly=false). */
387
444
  sayLine(text: string, textOnly?: boolean): void;
445
+ /**
446
+ * Script a sequence of prewritten lines. Lines are paced so each finishes before the next
447
+ * is sent — required because say_line interrupts any in-progress response server-side, so
448
+ * unpaced lines would stomp each other. Returns a controller (play/pause/resume/next/stop +
449
+ * an awaitable `done`). Starting a new script stops any currently-active one.
450
+ */
451
+ playScript(lines: ScriptLine[], opts?: ScriptOptions): ScriptController;
452
+ /** Convenience alias of playScript() for fire-and-forget scripted sequences. */
453
+ sayLines(lines: ScriptLine[], opts?: ScriptOptions): ScriptController;
454
+ private createScriptHost;
388
455
  /** Interrupt the current bot response */
389
456
  interrupt(messageId?: string): void;
390
457
  /** Send a camera image for vision processing */
@@ -399,6 +466,13 @@ declare class EstuaryClient extends TypedEventEmitter<EstuaryEventMap> {
399
466
  startVoice(): Promise<void>;
400
467
  /** Stop voice input */
401
468
  stopVoice(): Promise<void>;
469
+ /**
470
+ * Tear down voice state without requiring an active manager — used when the
471
+ * server ends the session out from under us. Unlike stopVoice(), this also
472
+ * disposes a manager whose transport already died (LiveKit room deleted →
473
+ * isActive is false but the local mic track was never released).
474
+ */
475
+ private releaseVoice;
402
476
  /** Toggle microphone mute */
403
477
  toggleMute(): void;
404
478
  /** Whether the microphone is muted */
@@ -470,4 +544,4 @@ declare class CharacterClient {
470
544
  dispose(): void;
471
545
  }
472
546
 
473
- export { type BotResponse, type BotVoice, type CameraCaptureRequest, type CharacterAction, CharacterClient, type CharacterInfo, ConnectionState, type CoreFact, type CoreFactsResponse, ErrorCode, EstuaryClient, type EstuaryConfig, EstuaryError, type EstuaryEventMap, type InterruptData, type LiveKitTokenResponse, MemoryClient, type MemoryData, type MemoryGraphEdge, type MemoryGraphNode, type MemoryGraphOptions, type MemoryGraphResponse, type MemoryListOptions, type MemoryListResponse, type MemorySearchOptions, type MemorySearchResponse, type MemoryStatsResponse, type MemoryTimelineOptions, type MemoryTimelineResponse, type MemoryUpdatedEvent, type ParsedAction, type QuotaExceededData, type SessionCapabilities, type SessionInfo, type ShareOpenResponse, type SttResponse, type VoiceManager, type VoiceTransport, parseActions };
547
+ export { type BotResponse, type BotVoice, type CameraCaptureRequest, type CharacterAction, CharacterClient, type CharacterInfo, ConnectionState, type CoreFact, type CoreFactsResponse, ErrorCode, EstuaryClient, type EstuaryConfig, EstuaryError, type EstuaryEventMap, type InterruptData, type LiveKitTokenResponse, MemoryClient, type MemoryData, type MemoryGraphEdge, type MemoryGraphNode, type MemoryGraphOptions, type MemoryGraphResponse, type MemoryListOptions, type MemoryListResponse, type MemorySearchOptions, type MemorySearchResponse, type MemoryStatsResponse, type MemoryTimelineOptions, type MemoryTimelineResponse, type MemoryUpdatedEvent, type ParsedAction, type QuotaExceededData, type ScriptController, type ScriptEndReason, type ScriptLine, type ScriptLineStartedInfo, type ScriptOptions, type ScriptState, type SessionCapabilities, type SessionInfo, type ShareOpenResponse, type SttResponse, type VoiceManager, type VoiceTransport, parseActions };
package/dist/index.d.ts CHANGED
@@ -91,6 +91,17 @@ interface QuotaExceededData {
91
91
  remaining: number;
92
92
  tier: string;
93
93
  }
94
+ /**
95
+ * Emitted when the server ends the session due to inactivity (no conversation
96
+ * activity for the server's idle timeout). The server disconnects the socket
97
+ * right after; the SDK does not auto-reconnect from this — call connect()
98
+ * again on explicit user intent to resume.
99
+ */
100
+ interface SessionTimeoutData {
101
+ reason: string;
102
+ idleSeconds: number;
103
+ timeoutSeconds: number;
104
+ }
94
105
  interface LiveKitTokenResponse {
95
106
  token: string;
96
107
  url: string;
@@ -156,6 +167,46 @@ interface CharacterAction {
156
167
  /** Message ID of the bot response that contained this action */
157
168
  messageId: string;
158
169
  }
170
+ /** A scripted line: plain text (uses the script's default textOnly) or an explicit override. */
171
+ type ScriptLine = string | {
172
+ text: string;
173
+ textOnly?: boolean;
174
+ };
175
+ interface ScriptOptions {
176
+ /** Default for plain-string lines: false = TTS audio (default), true = text-only. */
177
+ textOnly?: boolean;
178
+ /** Pause inserted after each line completes, in ms (default 0). */
179
+ lineGapMs?: number;
180
+ /** Begin speaking immediately on creation (default true). If false, call play(). */
181
+ autoStart?: boolean;
182
+ /** Repeat from the first line after the last (default false). */
183
+ loop?: boolean;
184
+ /** Force-advance a line if no completion signal arrives within this many ms (default 30000). */
185
+ lineTimeoutMs?: number;
186
+ }
187
+ type ScriptEndReason = 'finished' | 'stopped' | 'disconnected' | 'interrupted';
188
+ type ScriptState = 'idle' | 'playing' | 'paused' | 'done';
189
+ interface ScriptLineStartedInfo {
190
+ index: number;
191
+ text: string;
192
+ messageId: string;
193
+ }
194
+ /** Handle returned by EstuaryClient.playScript() / sayLines(). */
195
+ interface ScriptController {
196
+ readonly length: number;
197
+ /** Index of the current / most-recently-started line (-1 before the first line starts). */
198
+ readonly index: number;
199
+ readonly state: ScriptState;
200
+ /** Resolves (never rejects) when the script ends, with the reason. Awaitable. */
201
+ readonly done: Promise<{
202
+ reason: ScriptEndReason;
203
+ }>;
204
+ play(): void;
205
+ pause(): void;
206
+ resume(): void;
207
+ next(): void;
208
+ stop(): void;
209
+ }
159
210
  type EstuaryEventMap = {
160
211
  connected: (session: SessionInfo) => void;
161
212
  disconnected: (reason: string) => void;
@@ -168,6 +219,7 @@ type EstuaryEventMap = {
168
219
  error: (error: Error) => void;
169
220
  authError: (error: string) => void;
170
221
  quotaExceeded: (data: QuotaExceededData) => void;
222
+ sessionTimeout: (data: SessionTimeoutData) => void;
171
223
  cameraCaptureRequest: (request: CameraCaptureRequest) => void;
172
224
  characterAction: (action: CharacterAction) => void;
173
225
  voiceStarted: () => void;
@@ -179,6 +231,10 @@ type EstuaryEventMap = {
179
231
  /** Bot audio level 0.0–1.0, emitted during playback for both transports. */
180
232
  botAudioLevel: (level: number) => void;
181
233
  memoryUpdated: (event: MemoryUpdatedEvent) => void;
234
+ scriptLineStarted: (info: ScriptLineStartedInfo) => void;
235
+ scriptComplete: (info: {
236
+ reason: ScriptEndReason;
237
+ }) => void;
182
238
  };
183
239
  interface VoiceManager {
184
240
  start(): Promise<void>;
@@ -360,6 +416,7 @@ declare class EstuaryClient extends TypedEventEmitter<EstuaryEventMap> {
360
416
  private _hasAutoInterrupted;
361
417
  private _autoInterruptGraceTimer;
362
418
  private _isLiveKitSpeaking;
419
+ private _activeScript;
363
420
  constructor(config: EstuaryConfig);
364
421
  /** Memory API client for querying memories, graphs, and facts */
365
422
  get memory(): MemoryClient;
@@ -385,6 +442,16 @@ declare class EstuaryClient extends TypedEventEmitter<EstuaryEventMap> {
385
442
  sendText(text: string, textOnly?: boolean): void;
386
443
  /** Script the character to say a specific prewritten line. Defaults to TTS enabled (textOnly=false). */
387
444
  sayLine(text: string, textOnly?: boolean): void;
445
+ /**
446
+ * Script a sequence of prewritten lines. Lines are paced so each finishes before the next
447
+ * is sent — required because say_line interrupts any in-progress response server-side, so
448
+ * unpaced lines would stomp each other. Returns a controller (play/pause/resume/next/stop +
449
+ * an awaitable `done`). Starting a new script stops any currently-active one.
450
+ */
451
+ playScript(lines: ScriptLine[], opts?: ScriptOptions): ScriptController;
452
+ /** Convenience alias of playScript() for fire-and-forget scripted sequences. */
453
+ sayLines(lines: ScriptLine[], opts?: ScriptOptions): ScriptController;
454
+ private createScriptHost;
388
455
  /** Interrupt the current bot response */
389
456
  interrupt(messageId?: string): void;
390
457
  /** Send a camera image for vision processing */
@@ -399,6 +466,13 @@ declare class EstuaryClient extends TypedEventEmitter<EstuaryEventMap> {
399
466
  startVoice(): Promise<void>;
400
467
  /** Stop voice input */
401
468
  stopVoice(): Promise<void>;
469
+ /**
470
+ * Tear down voice state without requiring an active manager — used when the
471
+ * server ends the session out from under us. Unlike stopVoice(), this also
472
+ * disposes a manager whose transport already died (LiveKit room deleted →
473
+ * isActive is false but the local mic track was never released).
474
+ */
475
+ private releaseVoice;
402
476
  /** Toggle microphone mute */
403
477
  toggleMute(): void;
404
478
  /** Whether the microphone is muted */
@@ -470,4 +544,4 @@ declare class CharacterClient {
470
544
  dispose(): void;
471
545
  }
472
546
 
473
- export { type BotResponse, type BotVoice, type CameraCaptureRequest, type CharacterAction, CharacterClient, type CharacterInfo, ConnectionState, type CoreFact, type CoreFactsResponse, ErrorCode, EstuaryClient, type EstuaryConfig, EstuaryError, type EstuaryEventMap, type InterruptData, type LiveKitTokenResponse, MemoryClient, type MemoryData, type MemoryGraphEdge, type MemoryGraphNode, type MemoryGraphOptions, type MemoryGraphResponse, type MemoryListOptions, type MemoryListResponse, type MemorySearchOptions, type MemorySearchResponse, type MemoryStatsResponse, type MemoryTimelineOptions, type MemoryTimelineResponse, type MemoryUpdatedEvent, type ParsedAction, type QuotaExceededData, type SessionCapabilities, type SessionInfo, type ShareOpenResponse, type SttResponse, type VoiceManager, type VoiceTransport, parseActions };
547
+ export { type BotResponse, type BotVoice, type CameraCaptureRequest, type CharacterAction, CharacterClient, type CharacterInfo, ConnectionState, type CoreFact, type CoreFactsResponse, ErrorCode, EstuaryClient, type EstuaryConfig, EstuaryError, type EstuaryEventMap, type InterruptData, type LiveKitTokenResponse, MemoryClient, type MemoryData, type MemoryGraphEdge, type MemoryGraphNode, type MemoryGraphOptions, type MemoryGraphResponse, type MemoryListOptions, type MemoryListResponse, type MemorySearchOptions, type MemorySearchResponse, type MemoryStatsResponse, type MemoryTimelineOptions, type MemoryTimelineResponse, type MemoryUpdatedEvent, type ParsedAction, type QuotaExceededData, type ScriptController, type ScriptEndReason, type ScriptLine, type ScriptLineStartedInfo, type ScriptOptions, type ScriptState, type SessionCapabilities, type SessionInfo, type ShareOpenResponse, type SttResponse, type VoiceManager, type VoiceTransport, parseActions };