@tanstack/ai-client 0.6.0 → 0.7.1

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.
@@ -1,4 +1,4 @@
1
- import { aiEventClient } from "@tanstack/ai/event-client";
1
+ import { aiEventClient } from "@tanstack/ai-event-client";
2
2
  class ChatClientEventEmitter {
3
3
  constructor(clientId) {
4
4
  this.clientId = clientId;
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sources":["../../src/events.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai/event-client'\nimport type { ContentPart } from '@tanstack/ai'\nimport type { UIMessage } from './types'\n\n/**\n * Abstract base class for ChatClient event emission\n */\nexport abstract class ChatClientEventEmitter {\n protected clientId: string\n\n constructor(clientId: string) {\n this.clientId = clientId\n }\n\n /**\n * Protected abstract method for emitting events\n * Implementations should handle adding clientId and timestamp\n */\n protected abstract emitEvent(\n eventName: string,\n data?: Record<string, unknown>,\n ): void\n\n /**\n * Emit client created event\n */\n clientCreated(initialMessageCount: number): void {\n this.emitEvent('client:created', {\n initialMessageCount,\n })\n }\n\n /**\n * Emit loading state changed event\n */\n loadingChanged(isLoading: boolean): void {\n this.emitEvent('client:loading:changed', { isLoading })\n }\n\n /**\n * Emit error state changed event\n */\n errorChanged(error: string | null): void {\n this.emitEvent('client:error:changed', {\n error,\n })\n }\n\n /**\n * Emit text update events (combines processor and client events)\n */\n textUpdated(streamId: string, messageId: string, content: string): void {\n this.emitEvent('text:chunk:content', {\n streamId,\n messageId,\n content,\n })\n }\n\n /**\n * Emit tool call state change events (combines processor and client events)\n */\n toolCallStateChanged(\n streamId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n state: string,\n args: string,\n ): void {\n this.emitEvent('tools:call:updated', {\n streamId,\n messageId,\n toolCallId,\n toolName,\n state,\n arguments: args,\n })\n }\n\n /**\n * Emit tool result state change event\n */\n /**\n * Emit thinking update event\n */\n thinkingUpdated(\n streamId: string,\n messageId: string,\n content: string,\n delta?: string,\n ): void {\n this.emitEvent('text:chunk:thinking', {\n streamId,\n messageId,\n content,\n delta,\n })\n }\n\n /**\n * Emit approval requested event\n */\n approvalRequested(\n streamId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n input: unknown,\n approvalId: string,\n ): void {\n this.emitEvent('tools:approval:requested', {\n streamId,\n messageId,\n toolCallId,\n toolName,\n input,\n approvalId,\n })\n }\n\n /**\n * Emit message appended event\n */\n messageAppended(uiMessage: UIMessage, streamId?: string): void {\n const content = uiMessage.parts\n .filter((part) => part.type === 'text')\n .map((part) => part.content)\n .join(' ')\n\n this.emitEvent('text:message:created', {\n streamId,\n messageId: uiMessage.id,\n role: uiMessage.role,\n content,\n parts: uiMessage.parts,\n })\n }\n\n /**\n * Emit message sent event.\n * Supports both simple string content and multimodal content arrays.\n *\n * @param messageId - The ID of the sent message\n * @param content - The message content (string or array of ContentPart for multimodal)\n */\n messageSent(messageId: string, content: string | Array<ContentPart>): void {\n // For text content, extract it; for multimodal, provide the array\n const textContent =\n typeof content === 'string'\n ? content\n : content\n .filter((part) => part.type === 'text')\n .map((part) => (part as { type: 'text'; content: string }).content)\n .join('')\n\n this.emitEvent('text:message:created', {\n messageId,\n role: 'user',\n content: textContent,\n // Include full content for multimodal messages\n ...(Array.isArray(content) && { parts: content }),\n })\n\n this.emitEvent('text:message:user', {\n messageId,\n role: 'user',\n content: textContent,\n // Include full content for multimodal messages\n ...(Array.isArray(content) && { parts: content }),\n })\n }\n\n /**\n * Emit reloaded event\n */\n reloaded(fromMessageIndex: number): void {\n this.emitEvent('client:reloaded', {\n fromMessageIndex,\n })\n }\n /**\n * Emit stopped event\n */\n stopped(): void {\n this.emitEvent('client:stopped')\n }\n\n /**\n * Emit messages cleared event\n */\n messagesCleared(): void {\n this.emitEvent('client:messages:cleared')\n }\n\n /**\n * Emit tool result added event\n */\n toolResultAdded(\n toolCallId: string,\n toolName: string,\n output: unknown,\n state: string,\n ): void {\n this.emitEvent('tools:result:added', {\n toolCallId,\n toolName,\n output,\n state,\n })\n }\n\n /**\n * Emit tool approval responded event\n */\n toolApprovalResponded(\n approvalId: string,\n toolCallId: string,\n approved: boolean,\n ): void {\n this.emitEvent('tools:approval:responded', {\n approvalId,\n toolCallId,\n approved,\n })\n }\n}\n\n/**\n * Default implementation of ChatClientEventEmitter\n */\nexport class DefaultChatClientEventEmitter extends ChatClientEventEmitter {\n /**\n * Emit an event with automatic clientId and timestamp for client/tool events\n */\n protected emitEvent(eventName: string, data?: Record<string, any>): void {\n // For client:* and tool:* events, automatically add clientId and timestamp\n if (\n eventName.startsWith('client:') ||\n eventName.startsWith('tools:') ||\n eventName.startsWith('text:')\n ) {\n aiEventClient.emit(eventName as any, {\n ...data,\n clientId: this.clientId,\n source: 'client',\n timestamp: Date.now(),\n })\n } else {\n // For other events, just add timestamp\n aiEventClient.emit(eventName as any, {\n ...data,\n timestamp: Date.now(),\n })\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"events.js","sources":["../../src/events.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai-event-client'\nimport type { ContentPart } from '@tanstack/ai'\nimport type { UIMessage } from './types'\n\n/**\n * Abstract base class for ChatClient event emission\n */\nexport abstract class ChatClientEventEmitter {\n protected clientId: string\n\n constructor(clientId: string) {\n this.clientId = clientId\n }\n\n /**\n * Protected abstract method for emitting events\n * Implementations should handle adding clientId and timestamp\n */\n protected abstract emitEvent(\n eventName: string,\n data?: Record<string, unknown>,\n ): void\n\n /**\n * Emit client created event\n */\n clientCreated(initialMessageCount: number): void {\n this.emitEvent('client:created', {\n initialMessageCount,\n })\n }\n\n /**\n * Emit loading state changed event\n */\n loadingChanged(isLoading: boolean): void {\n this.emitEvent('client:loading:changed', { isLoading })\n }\n\n /**\n * Emit error state changed event\n */\n errorChanged(error: string | null): void {\n this.emitEvent('client:error:changed', {\n error,\n })\n }\n\n /**\n * Emit text update events (combines processor and client events)\n */\n textUpdated(streamId: string, messageId: string, content: string): void {\n this.emitEvent('text:chunk:content', {\n streamId,\n messageId,\n content,\n })\n }\n\n /**\n * Emit tool call state change events (combines processor and client events)\n */\n toolCallStateChanged(\n streamId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n state: string,\n args: string,\n ): void {\n this.emitEvent('tools:call:updated', {\n streamId,\n messageId,\n toolCallId,\n toolName,\n state,\n arguments: args,\n })\n }\n\n /**\n * Emit tool result state change event\n */\n /**\n * Emit thinking update event\n */\n thinkingUpdated(\n streamId: string,\n messageId: string,\n content: string,\n delta?: string,\n ): void {\n this.emitEvent('text:chunk:thinking', {\n streamId,\n messageId,\n content,\n delta,\n })\n }\n\n /**\n * Emit approval requested event\n */\n approvalRequested(\n streamId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n input: unknown,\n approvalId: string,\n ): void {\n this.emitEvent('tools:approval:requested', {\n streamId,\n messageId,\n toolCallId,\n toolName,\n input,\n approvalId,\n })\n }\n\n /**\n * Emit message appended event\n */\n messageAppended(uiMessage: UIMessage, streamId?: string): void {\n const content = uiMessage.parts\n .filter((part) => part.type === 'text')\n .map((part) => part.content)\n .join(' ')\n\n this.emitEvent('text:message:created', {\n streamId,\n messageId: uiMessage.id,\n role: uiMessage.role,\n content,\n parts: uiMessage.parts,\n })\n }\n\n /**\n * Emit message sent event.\n * Supports both simple string content and multimodal content arrays.\n *\n * @param messageId - The ID of the sent message\n * @param content - The message content (string or array of ContentPart for multimodal)\n */\n messageSent(messageId: string, content: string | Array<ContentPart>): void {\n // For text content, extract it; for multimodal, provide the array\n const textContent =\n typeof content === 'string'\n ? content\n : content\n .filter((part) => part.type === 'text')\n .map((part) => (part as { type: 'text'; content: string }).content)\n .join('')\n\n this.emitEvent('text:message:created', {\n messageId,\n role: 'user',\n content: textContent,\n // Include full content for multimodal messages\n ...(Array.isArray(content) && { parts: content }),\n })\n\n this.emitEvent('text:message:user', {\n messageId,\n role: 'user',\n content: textContent,\n // Include full content for multimodal messages\n ...(Array.isArray(content) && { parts: content }),\n })\n }\n\n /**\n * Emit reloaded event\n */\n reloaded(fromMessageIndex: number): void {\n this.emitEvent('client:reloaded', {\n fromMessageIndex,\n })\n }\n /**\n * Emit stopped event\n */\n stopped(): void {\n this.emitEvent('client:stopped')\n }\n\n /**\n * Emit messages cleared event\n */\n messagesCleared(): void {\n this.emitEvent('client:messages:cleared')\n }\n\n /**\n * Emit tool result added event\n */\n toolResultAdded(\n toolCallId: string,\n toolName: string,\n output: unknown,\n state: string,\n ): void {\n this.emitEvent('tools:result:added', {\n toolCallId,\n toolName,\n output,\n state,\n })\n }\n\n /**\n * Emit tool approval responded event\n */\n toolApprovalResponded(\n approvalId: string,\n toolCallId: string,\n approved: boolean,\n ): void {\n this.emitEvent('tools:approval:responded', {\n approvalId,\n toolCallId,\n approved,\n })\n }\n}\n\n/**\n * Default implementation of ChatClientEventEmitter\n */\nexport class DefaultChatClientEventEmitter extends ChatClientEventEmitter {\n /**\n * Emit an event with automatic clientId and timestamp for client/tool events\n */\n protected emitEvent(eventName: string, data?: Record<string, any>): void {\n // For client:* and tool:* events, automatically add clientId and timestamp\n if (\n eventName.startsWith('client:') ||\n eventName.startsWith('tools:') ||\n eventName.startsWith('text:')\n ) {\n aiEventClient.emit(eventName as any, {\n ...data,\n clientId: this.clientId,\n source: 'client',\n timestamp: Date.now(),\n })\n } else {\n // For other events, just add timestamp\n aiEventClient.emit(eventName as any, {\n ...data,\n timestamp: Date.now(),\n })\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  export { ChatClient } from './chat-client.js';
2
+ export { RealtimeClient } from './realtime-client.js';
2
3
  export { GenerationClient } from './generation-client.js';
3
4
  export { VideoGenerationClient } from './video-generation-client.js';
4
5
  export type { UIMessage, MessagePart, TextPart, ToolCallPart, ToolResultPart, ThinkingPart, ChatClientOptions, ChatRequestBody, InferChatMessages, ChatClientState, MultimodalContent, } from './types.js';
@@ -7,6 +8,7 @@ export { GENERATION_EVENTS } from './generation-types.js';
7
8
  export { clientTools, createChatClientOptions } from './types.js';
8
9
  export type { ExtractToolNames, ExtractToolInput, ExtractToolOutput, } from './tool-types.js';
9
10
  export type { AnyClientTool } from '@tanstack/ai';
11
+ export type { RealtimeAdapter, RealtimeConnection, RealtimeClientOptions, RealtimeClientState, RealtimeStateChangeCallback, } from './realtime-types.js';
10
12
  export { fetchServerSentEvents, fetchHttpStream, stream, rpcStream, type ConnectionAdapter, type FetchConnectionOptions, } from './connection-adapters.js';
11
13
  export { uiMessageToModelMessages, modelMessageToUIMessage, modelMessagesToUIMessages, convertMessagesToModelMessages, normalizeToUIMessage, generateMessageId, } from '@tanstack/ai';
12
14
  export { StreamProcessor, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, parsePartialJSON, PartialJSONParser, defaultJSONParser, type ChunkStrategy, type StreamProcessorOptions, type StreamProcessorEvents, type InternalToolCallState, type ToolCallState, type ToolResultState, type JSONParser, type ChunkRecording, type ProcessorResult, type ProcessorState, } from '@tanstack/ai';
package/dist/esm/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ChatClient } from "./chat-client.js";
2
+ import { RealtimeClient } from "./realtime-client.js";
2
3
  import { GenerationClient } from "./generation-client.js";
3
4
  import { VideoGenerationClient } from "./video-generation-client.js";
4
5
  import { GENERATION_EVENTS } from "./generation-types.js";
@@ -14,6 +15,7 @@ export {
14
15
  ImmediateStrategy,
15
16
  PartialJSONParser,
16
17
  PunctuationStrategy,
18
+ RealtimeClient,
17
19
  StreamProcessor,
18
20
  VideoGenerationClient,
19
21
  WordBoundaryStrategy,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
@@ -0,0 +1,98 @@
1
+ import { AudioVisualization, RealtimeMessage, RealtimeMode, RealtimeStatus } from '@tanstack/ai';
2
+ import { RealtimeClientOptions, RealtimeStateChangeCallback } from './realtime-types.js';
3
+ /**
4
+ * Client for managing realtime voice conversations.
5
+ *
6
+ * Handles connection lifecycle, audio I/O, message state,
7
+ * and tool execution for realtime voice-to-voice AI interactions.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { RealtimeClient } from '@tanstack/ai-client'
12
+ * import { openaiRealtime } from '@tanstack/ai-openai'
13
+ *
14
+ * const client = new RealtimeClient({
15
+ * getToken: () => fetch('/api/realtime-token').then(r => r.json()),
16
+ * adapter: openaiRealtime(),
17
+ * tools: [myTool.client(handler)],
18
+ * onMessage: (msg) => console.log('Message:', msg),
19
+ * })
20
+ *
21
+ * await client.connect()
22
+ * ```
23
+ */
24
+ export declare class RealtimeClient {
25
+ private options;
26
+ private connection;
27
+ private token;
28
+ private tokenRefreshTimeout;
29
+ private clientTools;
30
+ private stateChangeCallbacks;
31
+ private unsubscribers;
32
+ private state;
33
+ constructor(options: RealtimeClientOptions);
34
+ /**
35
+ * Connect to the realtime session.
36
+ * Fetches a token and establishes the connection.
37
+ */
38
+ connect(): Promise<void>;
39
+ /**
40
+ * Disconnect from the realtime session.
41
+ */
42
+ disconnect(): Promise<void>;
43
+ /**
44
+ * Start listening for voice input.
45
+ * Only needed when vadMode is 'manual'.
46
+ */
47
+ startListening(): void;
48
+ /**
49
+ * Stop listening for voice input.
50
+ * Only needed when vadMode is 'manual'.
51
+ */
52
+ stopListening(): void;
53
+ /**
54
+ * Interrupt the current assistant response.
55
+ */
56
+ interrupt(): void;
57
+ /**
58
+ * Send a text message instead of voice.
59
+ */
60
+ sendText(text: string): void;
61
+ /**
62
+ * Send an image to the conversation.
63
+ * @param imageData - Base64-encoded image data or a URL
64
+ * @param mimeType - MIME type of the image (e.g., 'image/png', 'image/jpeg')
65
+ */
66
+ sendImage(imageData: string, mimeType: string): void;
67
+ /** Get current connection status */
68
+ get status(): RealtimeStatus;
69
+ /** Get current mode */
70
+ get mode(): RealtimeMode;
71
+ /** Get conversation messages */
72
+ get messages(): Array<RealtimeMessage>;
73
+ /** Get current error, if any */
74
+ get error(): Error | null;
75
+ /** Get pending user transcript (while user is speaking) */
76
+ get pendingUserTranscript(): string | null;
77
+ /** Get pending assistant transcript (while assistant is speaking) */
78
+ get pendingAssistantTranscript(): string | null;
79
+ /** Get audio visualization data */
80
+ get audio(): AudioVisualization | null;
81
+ /**
82
+ * Subscribe to state changes.
83
+ * @returns Unsubscribe function
84
+ */
85
+ onStateChange(callback: RealtimeStateChangeCallback): () => void;
86
+ /**
87
+ * Clean up resources.
88
+ * Call this when disposing of the client.
89
+ */
90
+ destroy(): void;
91
+ private updateState;
92
+ private addMessage;
93
+ private scheduleTokenRefresh;
94
+ private refreshToken;
95
+ private subscribeToConnectionEvents;
96
+ private applySessionConfig;
97
+ private generateId;
98
+ }
@@ -0,0 +1,384 @@
1
+ import { convertSchemaToJsonSchema } from "@tanstack/ai";
2
+ const TOKEN_REFRESH_BUFFER_MS = 6e4;
3
+ class RealtimeClient {
4
+ constructor(options) {
5
+ this.connection = null;
6
+ this.token = null;
7
+ this.tokenRefreshTimeout = null;
8
+ this.stateChangeCallbacks = /* @__PURE__ */ new Set();
9
+ this.unsubscribers = [];
10
+ this.state = {
11
+ status: "idle",
12
+ mode: "idle",
13
+ messages: [],
14
+ pendingUserTranscript: null,
15
+ pendingAssistantTranscript: null,
16
+ error: null
17
+ };
18
+ this.options = {
19
+ autoPlayback: true,
20
+ autoCapture: true,
21
+ vadMode: "server",
22
+ ...options
23
+ };
24
+ this.clientTools = /* @__PURE__ */ new Map();
25
+ if (options.tools) {
26
+ for (const tool of options.tools) {
27
+ this.clientTools.set(tool.name, tool);
28
+ }
29
+ }
30
+ }
31
+ // ============================================================================
32
+ // Connection Lifecycle
33
+ // ============================================================================
34
+ /**
35
+ * Connect to the realtime session.
36
+ * Fetches a token and establishes the connection.
37
+ */
38
+ async connect() {
39
+ if (this.state.status === "connected") {
40
+ return;
41
+ }
42
+ this.updateState({ status: "connecting", error: null });
43
+ try {
44
+ this.token = await this.options.getToken();
45
+ this.scheduleTokenRefresh();
46
+ const toolsList = this.clientTools.size > 0 ? Array.from(this.clientTools.values()) : void 0;
47
+ this.connection = await this.options.adapter.connect(
48
+ this.token,
49
+ toolsList
50
+ );
51
+ this.subscribeToConnectionEvents();
52
+ this.applySessionConfig();
53
+ if (this.options.autoCapture) {
54
+ await this.connection.startAudioCapture();
55
+ }
56
+ this.updateState({ status: "connected", mode: "listening" });
57
+ this.options.onConnect?.();
58
+ } catch (error) {
59
+ const err = error instanceof Error ? error : new Error(String(error));
60
+ this.updateState({ status: "error", error: err });
61
+ this.options.onError?.(err);
62
+ throw err;
63
+ }
64
+ }
65
+ /**
66
+ * Disconnect from the realtime session.
67
+ */
68
+ async disconnect() {
69
+ if (this.tokenRefreshTimeout) {
70
+ clearTimeout(this.tokenRefreshTimeout);
71
+ this.tokenRefreshTimeout = null;
72
+ }
73
+ for (const unsub of this.unsubscribers) {
74
+ unsub();
75
+ }
76
+ this.unsubscribers = [];
77
+ if (this.connection) {
78
+ await this.connection.disconnect();
79
+ this.connection = null;
80
+ }
81
+ this.token = null;
82
+ this.updateState({
83
+ status: "idle",
84
+ mode: "idle",
85
+ pendingUserTranscript: null,
86
+ pendingAssistantTranscript: null
87
+ });
88
+ this.options.onDisconnect?.();
89
+ }
90
+ // ============================================================================
91
+ // Voice Control
92
+ // ============================================================================
93
+ /**
94
+ * Start listening for voice input.
95
+ * Only needed when vadMode is 'manual'.
96
+ */
97
+ startListening() {
98
+ if (!this.connection || this.state.status !== "connected") {
99
+ return;
100
+ }
101
+ this.connection.startAudioCapture();
102
+ this.updateState({ mode: "listening" });
103
+ }
104
+ /**
105
+ * Stop listening for voice input.
106
+ * Only needed when vadMode is 'manual'.
107
+ */
108
+ stopListening() {
109
+ if (!this.connection) {
110
+ return;
111
+ }
112
+ this.connection.stopAudioCapture();
113
+ this.updateState({ mode: "idle" });
114
+ }
115
+ /**
116
+ * Interrupt the current assistant response.
117
+ */
118
+ interrupt() {
119
+ if (!this.connection) {
120
+ return;
121
+ }
122
+ this.connection.interrupt();
123
+ }
124
+ // ============================================================================
125
+ // Text Input
126
+ // ============================================================================
127
+ /**
128
+ * Send a text message instead of voice.
129
+ */
130
+ sendText(text) {
131
+ if (!this.connection || this.state.status !== "connected") {
132
+ return;
133
+ }
134
+ const userMessage = {
135
+ id: this.generateId(),
136
+ role: "user",
137
+ timestamp: Date.now(),
138
+ parts: [{ type: "text", content: text }]
139
+ };
140
+ this.addMessage(userMessage);
141
+ this.connection.sendText(text);
142
+ }
143
+ /**
144
+ * Send an image to the conversation.
145
+ * @param imageData - Base64-encoded image data or a URL
146
+ * @param mimeType - MIME type of the image (e.g., 'image/png', 'image/jpeg')
147
+ */
148
+ sendImage(imageData, mimeType) {
149
+ if (!this.connection || this.state.status !== "connected") {
150
+ return;
151
+ }
152
+ const userMessage = {
153
+ id: this.generateId(),
154
+ role: "user",
155
+ timestamp: Date.now(),
156
+ parts: [{ type: "image", data: imageData, mimeType }]
157
+ };
158
+ this.addMessage(userMessage);
159
+ this.connection.sendImage(imageData, mimeType);
160
+ }
161
+ // ============================================================================
162
+ // State Access
163
+ // ============================================================================
164
+ /** Get current connection status */
165
+ get status() {
166
+ return this.state.status;
167
+ }
168
+ /** Get current mode */
169
+ get mode() {
170
+ return this.state.mode;
171
+ }
172
+ /** Get conversation messages */
173
+ get messages() {
174
+ return this.state.messages;
175
+ }
176
+ /** Get current error, if any */
177
+ get error() {
178
+ return this.state.error;
179
+ }
180
+ /** Get pending user transcript (while user is speaking) */
181
+ get pendingUserTranscript() {
182
+ return this.state.pendingUserTranscript;
183
+ }
184
+ /** Get pending assistant transcript (while assistant is speaking) */
185
+ get pendingAssistantTranscript() {
186
+ return this.state.pendingAssistantTranscript;
187
+ }
188
+ /** Get audio visualization data */
189
+ get audio() {
190
+ return this.connection?.getAudioVisualization() ?? null;
191
+ }
192
+ // ============================================================================
193
+ // State Subscription
194
+ // ============================================================================
195
+ /**
196
+ * Subscribe to state changes.
197
+ * @returns Unsubscribe function
198
+ */
199
+ onStateChange(callback) {
200
+ this.stateChangeCallbacks.add(callback);
201
+ return () => {
202
+ this.stateChangeCallbacks.delete(callback);
203
+ };
204
+ }
205
+ // ============================================================================
206
+ // Cleanup
207
+ // ============================================================================
208
+ /**
209
+ * Clean up resources.
210
+ * Call this when disposing of the client.
211
+ */
212
+ destroy() {
213
+ this.disconnect();
214
+ this.stateChangeCallbacks.clear();
215
+ }
216
+ // ============================================================================
217
+ // Private Methods
218
+ // ============================================================================
219
+ updateState(updates) {
220
+ this.state = { ...this.state, ...updates };
221
+ for (const callback of this.stateChangeCallbacks) {
222
+ callback(this.state);
223
+ }
224
+ if ("status" in updates && updates.status !== void 0) {
225
+ this.options.onStatusChange?.(updates.status);
226
+ }
227
+ if ("mode" in updates && updates.mode !== void 0) {
228
+ this.options.onModeChange?.(updates.mode);
229
+ }
230
+ }
231
+ addMessage(message) {
232
+ this.updateState({
233
+ messages: [...this.state.messages, message]
234
+ });
235
+ this.options.onMessage?.(message);
236
+ }
237
+ scheduleTokenRefresh() {
238
+ if (!this.token) return;
239
+ const timeUntilExpiry = this.token.expiresAt - Date.now();
240
+ const refreshIn = Math.max(0, timeUntilExpiry - TOKEN_REFRESH_BUFFER_MS);
241
+ this.tokenRefreshTimeout = setTimeout(() => {
242
+ this.refreshToken();
243
+ }, refreshIn);
244
+ }
245
+ async refreshToken() {
246
+ try {
247
+ this.token = await this.options.getToken();
248
+ this.scheduleTokenRefresh();
249
+ } catch (error) {
250
+ const err = error instanceof Error ? error : new Error(String(error));
251
+ this.updateState({ error: err });
252
+ this.options.onError?.(err);
253
+ }
254
+ }
255
+ subscribeToConnectionEvents() {
256
+ if (!this.connection) return;
257
+ this.unsubscribers.push(
258
+ this.connection.on("status_change", ({ status }) => {
259
+ this.updateState({ status });
260
+ })
261
+ );
262
+ this.unsubscribers.push(
263
+ this.connection.on("mode_change", ({ mode }) => {
264
+ this.updateState({ mode });
265
+ })
266
+ );
267
+ this.unsubscribers.push(
268
+ this.connection.on("transcript", ({ role, transcript, isFinal }) => {
269
+ if (role === "user") {
270
+ this.updateState({
271
+ pendingUserTranscript: isFinal ? null : transcript
272
+ });
273
+ if (isFinal && transcript) {
274
+ this.addMessage({
275
+ id: this.generateId(),
276
+ role: "user",
277
+ timestamp: Date.now(),
278
+ parts: [{ type: "audio", transcript, durationMs: 0 }]
279
+ });
280
+ }
281
+ } else {
282
+ this.updateState({
283
+ pendingAssistantTranscript: isFinal ? null : transcript
284
+ });
285
+ }
286
+ })
287
+ );
288
+ this.unsubscribers.push(
289
+ this.connection.on(
290
+ "tool_call",
291
+ async ({ toolCallId, toolName, input }) => {
292
+ const tool = this.clientTools.get(toolName);
293
+ if (tool?.execute) {
294
+ try {
295
+ const output = await tool.execute(input);
296
+ this.connection?.sendToolResult(
297
+ toolCallId,
298
+ typeof output === "string" ? output : JSON.stringify(output)
299
+ );
300
+ } catch (error) {
301
+ const errMsg = error instanceof Error ? error.message : String(error);
302
+ this.connection?.sendToolResult(
303
+ toolCallId,
304
+ JSON.stringify({ error: errMsg })
305
+ );
306
+ }
307
+ }
308
+ }
309
+ )
310
+ );
311
+ this.unsubscribers.push(
312
+ this.connection.on("message_complete", ({ message }) => {
313
+ const existingIndex = this.state.messages.findIndex(
314
+ (m) => m.id === message.id
315
+ );
316
+ if (existingIndex >= 0) {
317
+ const newMessages = [...this.state.messages];
318
+ newMessages[existingIndex] = message;
319
+ this.updateState({ messages: newMessages });
320
+ } else {
321
+ this.addMessage(message);
322
+ }
323
+ })
324
+ );
325
+ this.unsubscribers.push(
326
+ this.connection.on("interrupted", ({ messageId }) => {
327
+ if (messageId) {
328
+ const newMessages = this.state.messages.map(
329
+ (m) => m.id === messageId ? { ...m, interrupted: true } : m
330
+ );
331
+ this.updateState({ messages: newMessages });
332
+ }
333
+ this.updateState({
334
+ mode: "listening",
335
+ pendingAssistantTranscript: null
336
+ });
337
+ this.options.onInterrupted?.();
338
+ })
339
+ );
340
+ this.unsubscribers.push(
341
+ this.connection.on("error", ({ error }) => {
342
+ this.updateState({ error });
343
+ this.options.onError?.(error);
344
+ })
345
+ );
346
+ }
347
+ applySessionConfig() {
348
+ if (!this.connection) return;
349
+ const {
350
+ instructions,
351
+ voice,
352
+ vadMode,
353
+ tools,
354
+ outputModalities,
355
+ temperature,
356
+ maxOutputTokens,
357
+ semanticEagerness
358
+ } = this.options;
359
+ const hasConfig = instructions || voice || vadMode || tools && tools.length > 0 || outputModalities || temperature !== void 0 || maxOutputTokens !== void 0 || semanticEagerness;
360
+ if (!hasConfig) return;
361
+ const toolsConfig = tools ? Array.from(this.clientTools.values()).map((t) => ({
362
+ name: t.name,
363
+ description: t.description,
364
+ inputSchema: t.inputSchema ? convertSchemaToJsonSchema(t.inputSchema) : void 0
365
+ })) : void 0;
366
+ this.connection.updateSession({
367
+ instructions,
368
+ voice,
369
+ vadMode,
370
+ tools: toolsConfig,
371
+ outputModalities,
372
+ temperature,
373
+ maxOutputTokens,
374
+ semanticEagerness
375
+ });
376
+ }
377
+ generateId() {
378
+ return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`;
379
+ }
380
+ }
381
+ export {
382
+ RealtimeClient
383
+ };
384
+ //# sourceMappingURL=realtime-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-client.js","sources":["../../src/realtime-client.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from '@tanstack/ai'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeMessage,\n RealtimeMode,\n RealtimeStatus,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type {\n RealtimeClientOptions,\n RealtimeClientState,\n RealtimeConnection,\n RealtimeStateChangeCallback,\n} from './realtime-types'\n\n// Token refresh buffer - refresh 1 minute before expiry\nconst TOKEN_REFRESH_BUFFER_MS = 60_000\n\n/**\n * Client for managing realtime voice conversations.\n *\n * Handles connection lifecycle, audio I/O, message state,\n * and tool execution for realtime voice-to-voice AI interactions.\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { openaiRealtime } from '@tanstack/ai-openai'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: openaiRealtime(),\n * tools: [myTool.client(handler)],\n * onMessage: (msg) => console.log('Message:', msg),\n * })\n *\n * await client.connect()\n * ```\n */\nexport class RealtimeClient {\n private options: RealtimeClientOptions\n private connection: RealtimeConnection | null = null\n private token: RealtimeToken | null = null\n private tokenRefreshTimeout: ReturnType<typeof setTimeout> | null = null\n private clientTools: Map<string, AnyClientTool>\n private stateChangeCallbacks: Set<RealtimeStateChangeCallback> = new Set()\n private unsubscribers: Array<() => void> = []\n\n private state: RealtimeClientState = {\n status: 'idle',\n mode: 'idle',\n messages: [],\n pendingUserTranscript: null,\n pendingAssistantTranscript: null,\n error: null,\n }\n\n constructor(options: RealtimeClientOptions) {\n this.options = {\n autoPlayback: true,\n autoCapture: true,\n vadMode: 'server',\n ...options,\n }\n\n // Build client tools map\n this.clientTools = new Map()\n if (options.tools) {\n for (const tool of options.tools) {\n this.clientTools.set(tool.name, tool)\n }\n }\n }\n\n // ============================================================================\n // Connection Lifecycle\n // ============================================================================\n\n /**\n * Connect to the realtime session.\n * Fetches a token and establishes the connection.\n */\n async connect(): Promise<void> {\n if (this.state.status === 'connected') {\n return\n }\n\n this.updateState({ status: 'connecting', error: null })\n\n try {\n // Fetch token from server\n this.token = await this.options.getToken()\n\n // Schedule token refresh\n this.scheduleTokenRefresh()\n\n // Connect via adapter (pass tools for providers like ElevenLabs that need them at connect time)\n const toolsList =\n this.clientTools.size > 0\n ? Array.from(this.clientTools.values())\n : undefined\n this.connection = await this.options.adapter.connect(\n this.token,\n toolsList,\n )\n\n // Subscribe to connection events\n this.subscribeToConnectionEvents()\n\n // Auto-configure session with client-provided settings\n this.applySessionConfig()\n\n // Start audio capture if configured\n if (this.options.autoCapture) {\n await this.connection.startAudioCapture()\n }\n\n this.updateState({ status: 'connected', mode: 'listening' })\n this.options.onConnect?.()\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n this.updateState({ status: 'error', error: err })\n this.options.onError?.(err)\n throw err\n }\n }\n\n /**\n * Disconnect from the realtime session.\n */\n async disconnect(): Promise<void> {\n if (this.tokenRefreshTimeout) {\n clearTimeout(this.tokenRefreshTimeout)\n this.tokenRefreshTimeout = null\n }\n\n // Unsubscribe from all events\n for (const unsub of this.unsubscribers) {\n unsub()\n }\n this.unsubscribers = []\n\n if (this.connection) {\n await this.connection.disconnect()\n this.connection = null\n }\n\n this.token = null\n this.updateState({\n status: 'idle',\n mode: 'idle',\n pendingUserTranscript: null,\n pendingAssistantTranscript: null,\n })\n this.options.onDisconnect?.()\n }\n\n // ============================================================================\n // Voice Control\n // ============================================================================\n\n /**\n * Start listening for voice input.\n * Only needed when vadMode is 'manual'.\n */\n startListening(): void {\n if (!this.connection || this.state.status !== 'connected') {\n return\n }\n this.connection.startAudioCapture()\n this.updateState({ mode: 'listening' })\n }\n\n /**\n * Stop listening for voice input.\n * Only needed when vadMode is 'manual'.\n */\n stopListening(): void {\n if (!this.connection) {\n return\n }\n this.connection.stopAudioCapture()\n this.updateState({ mode: 'idle' })\n }\n\n /**\n * Interrupt the current assistant response.\n */\n interrupt(): void {\n if (!this.connection) {\n return\n }\n this.connection.interrupt()\n }\n\n // ============================================================================\n // Text Input\n // ============================================================================\n\n /**\n * Send a text message instead of voice.\n */\n sendText(text: string): void {\n if (!this.connection || this.state.status !== 'connected') {\n return\n }\n\n // Add user message\n const userMessage: RealtimeMessage = {\n id: this.generateId(),\n role: 'user',\n timestamp: Date.now(),\n parts: [{ type: 'text', content: text }],\n }\n this.addMessage(userMessage)\n\n // Send to provider\n this.connection.sendText(text)\n }\n\n /**\n * Send an image to the conversation.\n * @param imageData - Base64-encoded image data or a URL\n * @param mimeType - MIME type of the image (e.g., 'image/png', 'image/jpeg')\n */\n sendImage(imageData: string, mimeType: string): void {\n if (!this.connection || this.state.status !== 'connected') {\n return\n }\n\n // Add user message with image part\n const userMessage: RealtimeMessage = {\n id: this.generateId(),\n role: 'user',\n timestamp: Date.now(),\n parts: [{ type: 'image', data: imageData, mimeType }],\n }\n this.addMessage(userMessage)\n\n // Send to provider\n this.connection.sendImage(imageData, mimeType)\n }\n\n // ============================================================================\n // State Access\n // ============================================================================\n\n /** Get current connection status */\n get status(): RealtimeStatus {\n return this.state.status\n }\n\n /** Get current mode */\n get mode(): RealtimeMode {\n return this.state.mode\n }\n\n /** Get conversation messages */\n get messages(): Array<RealtimeMessage> {\n return this.state.messages\n }\n\n /** Get current error, if any */\n get error(): Error | null {\n return this.state.error\n }\n\n /** Get pending user transcript (while user is speaking) */\n get pendingUserTranscript(): string | null {\n return this.state.pendingUserTranscript\n }\n\n /** Get pending assistant transcript (while assistant is speaking) */\n get pendingAssistantTranscript(): string | null {\n return this.state.pendingAssistantTranscript\n }\n\n /** Get audio visualization data */\n get audio(): AudioVisualization | null {\n return this.connection?.getAudioVisualization() ?? null\n }\n\n // ============================================================================\n // State Subscription\n // ============================================================================\n\n /**\n * Subscribe to state changes.\n * @returns Unsubscribe function\n */\n onStateChange(callback: RealtimeStateChangeCallback): () => void {\n this.stateChangeCallbacks.add(callback)\n return () => {\n this.stateChangeCallbacks.delete(callback)\n }\n }\n\n // ============================================================================\n // Cleanup\n // ============================================================================\n\n /**\n * Clean up resources.\n * Call this when disposing of the client.\n */\n destroy(): void {\n this.disconnect()\n this.stateChangeCallbacks.clear()\n }\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n private updateState(updates: Partial<RealtimeClientState>): void {\n this.state = { ...this.state, ...updates }\n\n // Notify callbacks\n for (const callback of this.stateChangeCallbacks) {\n callback(this.state)\n }\n\n // Notify specific callbacks\n if ('status' in updates && updates.status !== undefined) {\n this.options.onStatusChange?.(updates.status)\n }\n if ('mode' in updates && updates.mode !== undefined) {\n this.options.onModeChange?.(updates.mode)\n }\n }\n\n private addMessage(message: RealtimeMessage): void {\n this.updateState({\n messages: [...this.state.messages, message],\n })\n this.options.onMessage?.(message)\n }\n\n private scheduleTokenRefresh(): void {\n if (!this.token) return\n\n const timeUntilExpiry = this.token.expiresAt - Date.now()\n const refreshIn = Math.max(0, timeUntilExpiry - TOKEN_REFRESH_BUFFER_MS)\n\n this.tokenRefreshTimeout = setTimeout(() => {\n this.refreshToken()\n }, refreshIn)\n }\n\n private async refreshToken(): Promise<void> {\n try {\n this.token = await this.options.getToken()\n this.scheduleTokenRefresh()\n // Note: Some providers may require reconnection with new token\n // This is handled by the adapter implementation\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n this.updateState({ error: err })\n this.options.onError?.(err)\n }\n }\n\n private subscribeToConnectionEvents(): void {\n if (!this.connection) return\n\n // Status changes\n this.unsubscribers.push(\n this.connection.on('status_change', ({ status }) => {\n this.updateState({ status })\n }),\n )\n\n // Mode changes\n this.unsubscribers.push(\n this.connection.on('mode_change', ({ mode }) => {\n this.updateState({ mode })\n }),\n )\n\n // Transcripts (streaming)\n // User transcripts are added as messages when final (no separate message_complete for user input)\n // Assistant transcripts are streamed, final message comes via message_complete\n this.unsubscribers.push(\n this.connection.on('transcript', ({ role, transcript, isFinal }) => {\n if (role === 'user') {\n this.updateState({\n pendingUserTranscript: isFinal ? null : transcript,\n })\n // Add user message when transcript is finalized\n if (isFinal && transcript) {\n this.addMessage({\n id: this.generateId(),\n role: 'user',\n timestamp: Date.now(),\n parts: [{ type: 'audio', transcript, durationMs: 0 }],\n })\n }\n } else {\n // Assistant transcripts - just update pending, message_complete handles final\n this.updateState({\n pendingAssistantTranscript: isFinal ? null : transcript,\n })\n }\n }),\n )\n\n // Tool calls\n this.unsubscribers.push(\n this.connection.on(\n 'tool_call',\n async ({ toolCallId, toolName, input }) => {\n const tool = this.clientTools.get(toolName)\n if (tool?.execute) {\n try {\n const output = await tool.execute(input)\n this.connection?.sendToolResult(\n toolCallId,\n typeof output === 'string' ? output : JSON.stringify(output),\n )\n } catch (error) {\n const errMsg =\n error instanceof Error ? error.message : String(error)\n this.connection?.sendToolResult(\n toolCallId,\n JSON.stringify({ error: errMsg }),\n )\n }\n }\n },\n ),\n )\n\n // Message complete\n this.unsubscribers.push(\n this.connection.on('message_complete', ({ message }) => {\n // Replace pending message with final version if needed\n const existingIndex = this.state.messages.findIndex(\n (m) => m.id === message.id,\n )\n if (existingIndex >= 0) {\n const newMessages = [...this.state.messages]\n newMessages[existingIndex] = message\n this.updateState({ messages: newMessages })\n } else {\n this.addMessage(message)\n }\n }),\n )\n\n // Interruption\n this.unsubscribers.push(\n this.connection.on('interrupted', ({ messageId }) => {\n if (messageId) {\n const newMessages = this.state.messages.map((m) =>\n m.id === messageId ? { ...m, interrupted: true } : m,\n )\n this.updateState({ messages: newMessages })\n }\n this.updateState({\n mode: 'listening',\n pendingAssistantTranscript: null,\n })\n this.options.onInterrupted?.()\n }),\n )\n\n // Errors\n this.unsubscribers.push(\n this.connection.on('error', ({ error }) => {\n this.updateState({ error })\n this.options.onError?.(error)\n }),\n )\n }\n\n private applySessionConfig(): void {\n if (!this.connection) return\n\n const {\n instructions,\n voice,\n vadMode,\n tools,\n outputModalities,\n temperature,\n maxOutputTokens,\n semanticEagerness,\n } = this.options\n const hasConfig =\n instructions ||\n voice ||\n vadMode ||\n (tools && tools.length > 0) ||\n outputModalities ||\n temperature !== undefined ||\n maxOutputTokens !== undefined ||\n semanticEagerness\n if (!hasConfig) return\n\n const toolsConfig = tools\n ? Array.from(this.clientTools.values()).map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema\n ? convertSchemaToJsonSchema(t.inputSchema)\n : undefined,\n }))\n : undefined\n\n this.connection.updateSession({\n instructions,\n voice,\n vadMode,\n tools: toolsConfig,\n outputModalities,\n temperature,\n maxOutputTokens,\n semanticEagerness,\n })\n }\n\n private generateId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n}\n"],"names":[],"mappings":"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"}