@fabric-harness/sdk 1.9.0 → 1.11.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.
@@ -0,0 +1,362 @@
1
+ import { CostLimitExceededError } from './cost-budget.js';
2
+ import { applyEstimatedCost } from './model-pricing.js';
3
+ import { toOpenAITool } from './model.js';
4
+ const DEFAULT_BASE = 'wss://api.openai.com/v1/realtime';
5
+ /**
6
+ * OpenAI Realtime voice provider. Connects via WebSocket; emits
7
+ * `audio_delta` / `text_delta` / `transcript` / `tool_call` / `response_done`
8
+ * events. No transitive dependency on `ws` — uses the global `WebSocket`
9
+ * available on Node 22+ and browsers.
10
+ *
11
+ * ```ts
12
+ * const provider = new OpenAIRealtimeVoiceProvider({ apiKey });
13
+ * const session = await provider.connect({
14
+ * instructions: 'You are a friendly intake agent.',
15
+ * voice: 'alloy',
16
+ * tools: [submitField],
17
+ * });
18
+ * for await (const event of session.events()) {
19
+ * if (event.type === 'audio_delta') speaker.write(event.audio);
20
+ * if (event.type === 'tool_call') {
21
+ * const output = await runTool(event.name, event.input);
22
+ * await session.submitToolResult({ id: event.id, output });
23
+ * }
24
+ * if (event.type === 'response_done') break;
25
+ * }
26
+ * ```
27
+ */
28
+ export class OpenAIRealtimeVoiceProvider {
29
+ name;
30
+ apiKey;
31
+ model;
32
+ baseUrl;
33
+ extraHeaders;
34
+ constructor(options) {
35
+ if (!options.apiKey)
36
+ throw new Error('OpenAIRealtimeVoiceProvider: apiKey is required.');
37
+ this.apiKey = options.apiKey;
38
+ this.model = options.model ?? 'gpt-4o-realtime-preview';
39
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE;
40
+ this.name = options.name ?? 'openai-realtime';
41
+ this.extraHeaders = options.headers ?? {};
42
+ }
43
+ async connect(options = {}) {
44
+ const url = `${this.baseUrl}?model=${encodeURIComponent(this.model)}`;
45
+ const ws = await openWebSocket(url, this.apiKey, this.extraHeaders);
46
+ return new RealtimeVoiceSession(ws, this.model, options);
47
+ }
48
+ }
49
+ async function openWebSocket(url, apiKey, headers) {
50
+ const WebSocketImpl = globalThis.WebSocket;
51
+ if (!WebSocketImpl) {
52
+ throw new Error('OpenAIRealtimeVoiceProvider: global WebSocket not available. Use Node 22+ or polyfill.');
53
+ }
54
+ // Node's WebSocket accepts `headers` via the second argument's options bag
55
+ // (non-standard but widely supported); browsers ignore options.
56
+ const ws = new WebSocketImpl(url, {
57
+ headers: {
58
+ Authorization: `Bearer ${apiKey}`,
59
+ 'OpenAI-Beta': 'realtime=v1',
60
+ ...headers,
61
+ },
62
+ });
63
+ // Wait for the connection to open so subsequent `send` calls succeed.
64
+ await new Promise((resolve, reject) => {
65
+ const onOpen = () => { cleanup(); resolve(); };
66
+ const onError = (ev) => { cleanup(); reject(toError(ev, 'WebSocket open failed')); };
67
+ const cleanup = () => {
68
+ // No removeEventListener on the structural type; rely on once-firing semantics.
69
+ };
70
+ ws.addEventListener('open', onOpen);
71
+ ws.addEventListener('error', onError);
72
+ });
73
+ return ws;
74
+ }
75
+ function toError(value, fallback) {
76
+ if (value instanceof Error)
77
+ return value;
78
+ if (value && typeof value === 'object' && 'message' in value) {
79
+ return new Error(String(value.message));
80
+ }
81
+ return new Error(fallback);
82
+ }
83
+ /** Base64-encode a Uint8Array. Works on Node and browsers. */
84
+ function bytesToBase64(bytes) {
85
+ if (typeof Buffer !== 'undefined')
86
+ return Buffer.from(bytes).toString('base64');
87
+ let binary = '';
88
+ for (let i = 0; i < bytes.byteLength; i += 1)
89
+ binary += String.fromCharCode(bytes[i]);
90
+ return btoa(binary);
91
+ }
92
+ /** Decode a base64 string to a Uint8Array. */
93
+ function base64ToBytes(value) {
94
+ if (typeof Buffer !== 'undefined')
95
+ return new Uint8Array(Buffer.from(value, 'base64'));
96
+ const bin = atob(value);
97
+ const out = new Uint8Array(bin.length);
98
+ for (let i = 0; i < bin.length; i += 1)
99
+ out[i] = bin.charCodeAt(i);
100
+ return out;
101
+ }
102
+ class RealtimeVoiceSession {
103
+ ws;
104
+ modelRef;
105
+ queue = [];
106
+ waiters = [];
107
+ closed = false;
108
+ modelHint;
109
+ costBudget;
110
+ requestCostLimitApproval;
111
+ constructor(ws, model, options) {
112
+ this.ws = ws;
113
+ this.modelRef = `openai/${model}`;
114
+ this.costBudget = options.costBudget;
115
+ this.requestCostLimitApproval = options.requestCostLimitApproval;
116
+ ws.addEventListener('message', (ev) => this.onMessage(ev.data));
117
+ ws.addEventListener('close', () => this.fail({ type: 'session_open' }, true));
118
+ ws.addEventListener('error', (ev) => this.push({ type: 'error', message: toError(ev, 'WebSocket error').message }));
119
+ // Send the initial session.update.
120
+ const audioFormat = options.audioFormat ?? 'pcm16';
121
+ const sessionUpdate = {
122
+ type: 'session.update',
123
+ session: {
124
+ ...(options.instructions ? { instructions: options.instructions } : {}),
125
+ ...(options.voice ? { voice: options.voice } : {}),
126
+ input_audio_format: audioFormat,
127
+ output_audio_format: audioFormat,
128
+ turn_detection: options.turnDetection === 'none' ? null : { type: 'server_vad' },
129
+ ...(options.tools && options.tools.length > 0 ? { tools: options.tools.map(toolToRealtime) } : {}),
130
+ },
131
+ };
132
+ ws.send(JSON.stringify(sessionUpdate));
133
+ this.push({ type: 'session_open' });
134
+ if (options.signal) {
135
+ if (options.signal.aborted)
136
+ void this.close();
137
+ else
138
+ options.signal.addEventListener('abort', () => void this.close(), { once: true });
139
+ }
140
+ }
141
+ async sendAudio(frame) {
142
+ if (this.closed)
143
+ throw new Error('Voice session closed');
144
+ this.ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: bytesToBase64(frame) }));
145
+ }
146
+ async sendText(text) {
147
+ if (this.closed)
148
+ throw new Error('Voice session closed');
149
+ this.ws.send(JSON.stringify({
150
+ type: 'conversation.item.create',
151
+ item: { type: 'message', role: 'user', content: [{ type: 'input_text', text }] },
152
+ }));
153
+ this.ws.send(JSON.stringify({ type: 'response.create' }));
154
+ }
155
+ async submitToolResult(input) {
156
+ if (this.closed)
157
+ throw new Error('Voice session closed');
158
+ this.ws.send(JSON.stringify({
159
+ type: 'conversation.item.create',
160
+ item: {
161
+ type: 'function_call_output',
162
+ call_id: input.id,
163
+ output: typeof input.output === 'string' ? input.output : JSON.stringify(input.output),
164
+ },
165
+ }));
166
+ this.ws.send(JSON.stringify({ type: 'response.create' }));
167
+ }
168
+ async cancelResponse() {
169
+ if (this.closed)
170
+ return;
171
+ this.ws.send(JSON.stringify({ type: 'response.cancel' }));
172
+ }
173
+ events() {
174
+ return {
175
+ [Symbol.asyncIterator]: () => ({
176
+ next: () => this.next(),
177
+ return: async () => {
178
+ await this.close();
179
+ return { value: undefined, done: true };
180
+ },
181
+ }),
182
+ };
183
+ }
184
+ async close() {
185
+ if (this.closed)
186
+ return;
187
+ this.closed = true;
188
+ try {
189
+ this.ws.close();
190
+ }
191
+ catch { }
192
+ while (this.waiters.length > 0) {
193
+ const waiter = this.waiters.shift();
194
+ waiter?.({ value: undefined, done: true });
195
+ }
196
+ }
197
+ next() {
198
+ if (this.queue.length > 0) {
199
+ return Promise.resolve({ value: this.queue.shift(), done: false });
200
+ }
201
+ if (this.closed)
202
+ return Promise.resolve({ value: undefined, done: true });
203
+ return new Promise((resolve) => this.waiters.push(resolve));
204
+ }
205
+ push(event) {
206
+ if (this.closed)
207
+ return;
208
+ const waiter = this.waiters.shift();
209
+ if (waiter)
210
+ waiter({ value: event, done: false });
211
+ else
212
+ this.queue.push(event);
213
+ }
214
+ fail(_event, _close) {
215
+ void this.close();
216
+ }
217
+ async handleCostBudget(callCost) {
218
+ if (!this.costBudget)
219
+ return;
220
+ const handle = async (violation) => {
221
+ this.push({ type: 'cost_limit', scope: violation.scope, observedUsd: violation.observedUsd, limitUsd: violation.limitUsd, ...(violation.scopeKey ? { scopeKey: violation.scopeKey } : {}) });
222
+ const mode = this.costBudget.onExceed();
223
+ if (mode === 'approve' && this.requestCostLimitApproval) {
224
+ const approved = await this.requestCostLimitApproval(violation);
225
+ if (approved)
226
+ return;
227
+ }
228
+ this.push({ type: 'error', message: new CostLimitExceededError(violation).message });
229
+ void this.close();
230
+ };
231
+ const local = this.costBudget.observe(callCost);
232
+ if (local) {
233
+ await handle(local);
234
+ return;
235
+ }
236
+ const remote = await this.costBudget.observeScopeAsync(callCost);
237
+ if (remote)
238
+ await handle(remote);
239
+ }
240
+ onMessage(raw) {
241
+ let payload;
242
+ try {
243
+ const text = typeof raw === 'string' ? raw : (raw && typeof raw === 'object' && 'toString' in raw)
244
+ ? String(raw)
245
+ : '';
246
+ payload = JSON.parse(text);
247
+ }
248
+ catch {
249
+ return;
250
+ }
251
+ const type = String(payload.type ?? '');
252
+ switch (type) {
253
+ case 'session.created':
254
+ case 'session.updated': {
255
+ const session = payload.session;
256
+ if (session?.model)
257
+ this.modelHint = session.model;
258
+ return;
259
+ }
260
+ case 'response.audio.delta': {
261
+ const audio = typeof payload.delta === 'string' ? base64ToBytes(payload.delta) : undefined;
262
+ if (audio)
263
+ this.push({ type: 'audio_delta', audio });
264
+ return;
265
+ }
266
+ case 'response.audio_transcript.delta': {
267
+ const delta = typeof payload.delta === 'string' ? payload.delta : '';
268
+ if (delta)
269
+ this.push({ type: 'text_delta', delta, role: 'assistant' });
270
+ return;
271
+ }
272
+ case 'response.audio_transcript.done': {
273
+ const text = typeof payload.transcript === 'string' ? payload.transcript : '';
274
+ if (text)
275
+ this.push({ type: 'transcript', text, role: 'assistant' });
276
+ return;
277
+ }
278
+ case 'conversation.item.input_audio_transcription.completed': {
279
+ const text = typeof payload.transcript === 'string' ? payload.transcript : '';
280
+ if (text)
281
+ this.push({ type: 'transcript', text, role: 'user' });
282
+ return;
283
+ }
284
+ case 'response.function_call_arguments.done': {
285
+ const id = String(payload.call_id ?? '');
286
+ const name = String(payload.name ?? '');
287
+ const args = typeof payload.arguments === 'string' ? safeJson(payload.arguments) : payload.arguments;
288
+ if (id && name)
289
+ this.push({ type: 'tool_call', id, name, input: args });
290
+ return;
291
+ }
292
+ case 'response.done': {
293
+ const usage = parseRealtimeUsage(payload);
294
+ const modelKey = this.modelHint ? `openai/${this.modelHint}` : this.modelRef;
295
+ if (usage)
296
+ applyEstimatedCost(modelKey, usage);
297
+ this.push({ type: 'response_done', ...(usage ? { usage } : {}) });
298
+ if (usage && this.costBudget?.enabled() && typeof usage.costUsd === 'number' && usage.costUsd > 0) {
299
+ // Don't block onMessage — handle the budget check on a microtask.
300
+ void this.handleCostBudget(usage.costUsd);
301
+ }
302
+ return;
303
+ }
304
+ case 'error': {
305
+ const message = typeof payload.error?.message === 'string'
306
+ ? String(payload.error.message)
307
+ : 'Realtime error';
308
+ this.push({ type: 'error', message });
309
+ return;
310
+ }
311
+ default:
312
+ return;
313
+ }
314
+ }
315
+ }
316
+ function safeJson(value) {
317
+ try {
318
+ return JSON.parse(value);
319
+ }
320
+ catch {
321
+ return value;
322
+ }
323
+ }
324
+ function toolToRealtime(tool) {
325
+ // toOpenAITool returns { type: 'function', function: {...} }.
326
+ // Realtime wants { type: 'function', name, description, parameters }.
327
+ const wrapped = toOpenAITool({
328
+ name: tool.name,
329
+ ...(tool.description !== undefined ? { description: tool.description } : {}),
330
+ ...(tool.inputSchema !== undefined ? { inputSchema: tool.inputSchema } : {}),
331
+ });
332
+ const fn = wrapped.function ?? {};
333
+ return {
334
+ type: 'function',
335
+ name: fn.name ?? tool.name,
336
+ description: fn.description ?? '',
337
+ parameters: fn.parameters ?? { type: 'object', properties: {}, required: [], additionalProperties: false },
338
+ };
339
+ }
340
+ function parseRealtimeUsage(payload) {
341
+ const responseObj = payload.response;
342
+ const usage = (responseObj?.usage ?? payload.usage);
343
+ if (!usage)
344
+ return undefined;
345
+ const inputDetails = usage.input_token_details;
346
+ const outputDetails = usage.output_token_details;
347
+ const result = {};
348
+ if (typeof usage.input_tokens === 'number')
349
+ result.inputTokens = usage.input_tokens;
350
+ if (typeof usage.output_tokens === 'number')
351
+ result.outputTokens = usage.output_tokens;
352
+ if (typeof usage.total_tokens === 'number')
353
+ result.totalTokens = usage.total_tokens;
354
+ if (typeof inputDetails?.audio_tokens === 'number')
355
+ result.audioInputTokens = inputDetails.audio_tokens;
356
+ if (typeof outputDetails?.audio_tokens === 'number')
357
+ result.audioOutputTokens = outputDetails.audio_tokens;
358
+ if (typeof inputDetails?.cached_tokens === 'number')
359
+ result.cachedInputTokens = inputDetails.cached_tokens;
360
+ return result;
361
+ }
362
+ //# sourceMappingURL=voice-openai-realtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-openai-realtime.js","sourceRoot":"","sources":["../src/voice-openai-realtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAsB1C,MAAM,YAAY,GAAG,kCAAkC,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,2BAA2B;IAC7B,IAAI,CAAS;IACL,MAAM,CAAS;IACf,KAAK,CAAS;IACd,OAAO,CAAS;IAChB,YAAY,CAAyB;IAEtD,YAAY,OAA2C;QACrD,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;QACzF,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,yBAAyB,CAAC;QACxD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,YAAY,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,iBAAiB,CAAC;QAC9C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAA+B,EAAE;QAC7C,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,UAAU,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACpE,OAAO,IAAI,oBAAoB,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;CACF;AAaD,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,MAAc,EAAE,OAA+B;IACvF,MAAM,aAAa,GAChB,UAAwF,CAAC,SAAS,CAAC;IACtG,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAC;IAC5G,CAAC;IACD,2EAA2E;IAC3E,gEAAgE;IAChE,MAAM,EAAE,GAAG,IAAI,aAAa,CAAC,GAAG,EAAE;QAChC,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,MAAM,EAAE;YACjC,aAAa,EAAE,aAAa;YAC5B,GAAG,OAAO;SACX;KACF,CAAC,CAAC;IACH,sEAAsE;IACtE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,CAAC,EAAW,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9F,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,gFAAgF;QAClF,CAAC,CAAC;QACF,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACpC,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,OAAO,CAAC,KAAc,EAAE,QAAgB;IAC/C,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,SAAS,IAAK,KAAiC,EAAE,CAAC;QAC1F,OAAO,IAAI,KAAK,CAAC,MAAM,CAAE,KAA8B,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,8DAA8D;AAC9D,SAAS,aAAa,CAAC,KAAiB;IACtC,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChF,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IACvF,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;AAED,8CAA8C;AAC9C,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACnE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,oBAAoB;IACP,EAAE,CAAoB;IACtB,QAAQ,CAAS;IACjB,KAAK,GAAiB,EAAE,CAAC;IACzB,OAAO,GAAuD,EAAE,CAAC;IAC1E,MAAM,GAAG,KAAK,CAAC;IACf,SAAS,CAAqB;IACrB,UAAU,CAAgC;IAC1C,wBAAwB,CAA4D;IAErG,YAAY,EAAqB,EAAE,KAAa,EAAE,OAA4B;QAC5E,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,QAAQ,GAAG,UAAU,KAAK,EAAE,CAAC;QAClC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC;QAEjE,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAChE,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9E,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,iBAAiB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAEpH,mCAAmC;QACnC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC;QACnD,MAAM,aAAa,GAA4B;YAC7C,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE;gBACP,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClD,kBAAkB,EAAE,WAAW;gBAC/B,mBAAmB,EAAE,WAAW;gBAChC,cAAc,EAAE,OAAO,CAAC,aAAa,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE;gBAChF,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnG;SACF,CAAC;QACF,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;QAEpC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO;gBAAE,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;;gBACzC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,KAAiB;QAC/B,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACzD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,2BAA2B,EAAE,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACnG,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACzD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;YAC1B,IAAI,EAAE,0BAA0B;YAChC,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE;SACjF,CAAC,CAAC,CAAC;QACJ,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,KAA2B;QAChD,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACzD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;YAC1B,IAAI,EAAE,0BAA0B;YAChC,IAAI,EAAE;gBACJ,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,KAAK,CAAC,EAAE;gBACjB,MAAM,EAAE,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;aACvF;SACF,CAAC,CAAC,CAAC;QACJ,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM;QACJ,OAAO;YACL,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,GAA8B,EAAE,CAAC,CAAC;gBACxD,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE;gBACvB,MAAM,EAAE,KAAK,IAAI,EAAE;oBACjB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;oBACnB,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;gBAC1C,CAAC;aACF,CAAC;SACH,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACpC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,SAAkC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAkC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACnG,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,CAAC;IAEO,IAAI,CAAC,KAAiB;QAC5B,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACpC,IAAI,MAAM;YAAE,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;;YAC7C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAEO,IAAI,CAAC,MAAkB,EAAE,MAAe;QAC9C,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAC,QAAgB;QAC7C,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO;QAC7B,MAAM,MAAM,GAAG,KAAK,EAAE,SAAoH,EAAE,EAAE;YAC5I,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAC7L,MAAM,IAAI,GAAG,IAAI,CAAC,UAAW,CAAC,QAAQ,EAAE,CAAC;YACzC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC;gBACxD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAAC,SAAS,CAAC,CAAC;gBAChE,IAAI,QAAQ;oBAAE,OAAO;YACvB,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,sBAAsB,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YACrF,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAI,MAAM;YAAE,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAEO,SAAS,CAAC,GAAY;QAC5B,IAAI,OAAgC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,UAAU,IAAK,GAAc,CAAC;gBAC5G,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;gBACb,CAAC,CAAC,EAAE,CAAC;YACP,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACxC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,iBAAiB,CAAC;YACvB,KAAK,iBAAiB,CAAC,CAAC,CAAC;gBACvB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAyC,CAAC;gBAClE,IAAI,OAAO,EAAE,KAAK;oBAAE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC;gBACnD,OAAO;YACT,CAAC;YACD,KAAK,sBAAsB,CAAC,CAAC,CAAC;gBAC5B,MAAM,KAAK,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC3F,IAAI,KAAK;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;gBACrD,OAAO;YACT,CAAC;YACD,KAAK,iCAAiC,CAAC,CAAC,CAAC;gBACvC,MAAM,KAAK,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrE,IAAI,KAAK;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;gBACvE,OAAO;YACT,CAAC;YACD,KAAK,gCAAgC,CAAC,CAAC,CAAC;gBACtC,MAAM,IAAI,GAAG,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9E,IAAI,IAAI;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;gBACrE,OAAO;YACT,CAAC;YACD,KAAK,uDAAuD,CAAC,CAAC,CAAC;gBAC7D,MAAM,IAAI,GAAG,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9E,IAAI,IAAI;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;gBAChE,OAAO;YACT,CAAC;YACD,KAAK,uCAAuC,CAAC,CAAC,CAAC;gBAC7C,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;gBACzC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBACxC,MAAM,IAAI,GAAG,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;gBACrG,IAAI,EAAE,IAAI,IAAI;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACxE,OAAO;YACT,CAAC;YACD,KAAK,eAAe,CAAC,CAAC,CAAC;gBACrB,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;gBAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAC7E,IAAI,KAAK;oBAAE,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBAC/C,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBAClE,IAAI,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;oBAClG,kEAAkE;oBAClE,KAAK,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC5C,CAAC;gBACD,OAAO;YACT,CAAC;YACD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,OAAO,GAAG,OAAQ,OAAO,CAAC,KAA+B,EAAE,OAAO,KAAK,QAAQ;oBACnF,CAAC,CAAC,MAAM,CAAE,OAAO,CAAC,KAA6B,CAAC,OAAO,CAAC;oBACxD,CAAC,CAAC,gBAAgB,CAAC;gBACrB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD;gBACE,OAAO;QACX,CAAC;IACH,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,KAAa;IAC7B,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AAC3D,CAAC;AAED,SAAS,cAAc,CAAC,IAAa;IACnC,8DAA8D;IAC9D,sEAAsE;IACtE,MAAM,OAAO,GAAG,YAAY,CAAC;QAC3B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7E,CAAiF,CAAC;IACnF,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;IAClC,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI;QAC1B,WAAW,EAAE,EAAE,CAAC,WAAW,IAAI,EAAE;QACjC,UAAU,EAAE,EAAE,CAAC,UAAU,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,oBAAoB,EAAE,KAAK,EAAE;KAC3G,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAgC;IAC1D,MAAM,WAAW,GAAG,OAAO,CAAC,QAA+C,CAAC;IAC5E,MAAM,KAAK,GAAG,CAAC,WAAW,EAAE,KAAK,IAAI,OAAO,CAAC,KAAK,CAAwC,CAAC;IAC3F,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,YAAY,GAAG,KAAK,CAAC,mBAA0D,CAAC;IACtF,MAAM,aAAa,GAAG,KAAK,CAAC,oBAA2D,CAAC;IACxF,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,IAAI,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;QAAE,MAAM,CAAC,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC;IACpF,IAAI,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ;QAAE,MAAM,CAAC,YAAY,GAAG,KAAK,CAAC,aAAa,CAAC;IACvF,IAAI,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;QAAE,MAAM,CAAC,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC;IACpF,IAAI,OAAO,YAAY,EAAE,YAAY,KAAK,QAAQ;QAAE,MAAM,CAAC,gBAAgB,GAAG,YAAY,CAAC,YAAY,CAAC;IACxG,IAAI,OAAO,aAAa,EAAE,YAAY,KAAK,QAAQ;QAAE,MAAM,CAAC,iBAAiB,GAAG,aAAa,CAAC,YAAY,CAAC;IAC3G,IAAI,OAAO,YAAY,EAAE,aAAa,KAAK,QAAQ;QAAE,MAAM,CAAC,iBAAiB,GAAG,YAAY,CAAC,aAAa,CAAC;IAC3G,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,77 @@
1
+ import type { ModelUsage } from './model.js';
2
+ import type { VoiceAudioFormat } from './voice.js';
3
+ /**
4
+ * Lightweight WebSocket client for the `fh server` `WS /sessions/:id/voice`
5
+ * endpoint. Server-side bridge owns the provider connection and API keys;
6
+ * this client just streams audio + control messages over WS.
7
+ *
8
+ * Works in browsers and on Node 22+ (uses the global `WebSocket`).
9
+ */
10
+ export interface VoiceWsClientOptions {
11
+ /** WS URL — e.g. `ws://localhost:9111/sessions/abc/voice`. */
12
+ url: string;
13
+ /** Bearer token, sent as `?token=` query param. */
14
+ authToken?: string;
15
+ /** Tenant id, sent as `?tenant=` query param. */
16
+ tenantId?: string;
17
+ /** Realtime voice timbre id (alloy, nova, verse, ...). */
18
+ voice?: string;
19
+ /** Initial instructions / persona. */
20
+ instructions?: string;
21
+ /** Audio format hint. Default `pcm16` (server-side default). */
22
+ audioFormat?: VoiceAudioFormat;
23
+ /** Realtime model id — overrides server default. */
24
+ model?: string;
25
+ /** Called for every event from the server. */
26
+ onEvent?: (event: VoiceWsClientEvent) => void;
27
+ /** Called once when the bridge confirms the session. */
28
+ onSessionOpen?: (sessionId: string) => void;
29
+ /** Called on errors (transport or server-side). */
30
+ onError?: (error: Error) => void;
31
+ /** Called when the socket closes. */
32
+ onClose?: () => void;
33
+ }
34
+ export type VoiceWsClientEvent = {
35
+ type: 'session_open';
36
+ sessionId: string;
37
+ } | {
38
+ type: 'audio';
39
+ audio: Uint8Array;
40
+ } | {
41
+ type: 'text_delta';
42
+ delta: string;
43
+ role: 'assistant' | 'user';
44
+ } | {
45
+ type: 'transcript';
46
+ text: string;
47
+ role: 'assistant' | 'user';
48
+ } | {
49
+ type: 'tool_call';
50
+ id: string;
51
+ name: string;
52
+ input: unknown;
53
+ } | {
54
+ type: 'response_done';
55
+ usage?: ModelUsage;
56
+ } | {
57
+ type: 'cost_limit';
58
+ scope: string;
59
+ observedUsd: number;
60
+ limitUsd: number;
61
+ scopeKey?: string;
62
+ } | {
63
+ type: 'error';
64
+ message: string;
65
+ };
66
+ export interface VoiceWsClientHandle {
67
+ sendAudio(frame: Uint8Array): void;
68
+ sendText(text: string): void;
69
+ submitToolResult(input: {
70
+ id: string;
71
+ output: unknown;
72
+ }): void;
73
+ cancel(): void;
74
+ close(): void;
75
+ }
76
+ export declare function connectFabricVoice(options: VoiceWsClientOptions): VoiceWsClientHandle;
77
+ //# sourceMappingURL=voice-ws-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-ws-client.d.ts","sourceRoot":"","sources":["../src/voice-ws-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;GAMG;AAEH,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8CAA8C;IAC9C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C,wDAAwD;IACxD,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mDAAmD;IACnD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,CAAC,EAAE,UAAU,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/F;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,CAAC,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/D,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CAkDrF"}
@@ -0,0 +1,115 @@
1
+ export function connectFabricVoice(options) {
2
+ const url = appendQueryParams(options.url, {
3
+ ...(options.authToken ? { token: options.authToken } : {}),
4
+ ...(options.tenantId ? { tenant: options.tenantId } : {}),
5
+ ...(options.voice ? { voice: options.voice } : {}),
6
+ ...(options.instructions ? { instructions: options.instructions } : {}),
7
+ ...(options.audioFormat ? { audioFormat: options.audioFormat } : {}),
8
+ ...(options.model ? { model: options.model } : {}),
9
+ });
10
+ const WebSocketImpl = typeof globalThis.WebSocket === 'function'
11
+ ? globalThis.WebSocket
12
+ : undefined;
13
+ if (!WebSocketImpl) {
14
+ throw new Error('connectFabricVoice: global WebSocket not available. On Node use Node 22+ or polyfill.');
15
+ }
16
+ const ws = new WebSocketImpl(url);
17
+ ws.addEventListener('message', (ev) => {
18
+ const data = typeof ev.data === 'string' ? ev.data : '';
19
+ if (!data)
20
+ return;
21
+ let parsed;
22
+ try {
23
+ parsed = JSON.parse(data);
24
+ }
25
+ catch {
26
+ options.onError?.(new Error(`Invalid JSON from voice bridge: ${data.slice(0, 120)}`));
27
+ return;
28
+ }
29
+ const event = decodeServerMessage(parsed);
30
+ if (!event)
31
+ return;
32
+ options.onEvent?.(event);
33
+ if (event.type === 'session_open')
34
+ options.onSessionOpen?.(event.sessionId);
35
+ if (event.type === 'error')
36
+ options.onError?.(new Error(event.message));
37
+ });
38
+ ws.addEventListener('error', () => options.onError?.(new Error('WebSocket error')));
39
+ ws.addEventListener('close', () => options.onClose?.());
40
+ const send = (payload) => {
41
+ try {
42
+ if (ws.readyState === ws.OPEN)
43
+ ws.send(JSON.stringify(payload));
44
+ }
45
+ catch (error) {
46
+ options.onError?.(error instanceof Error ? error : new Error(String(error)));
47
+ }
48
+ };
49
+ return {
50
+ sendAudio(frame) { send({ type: 'audio', audio: bytesToBase64(frame) }); },
51
+ sendText(text) { send({ type: 'text', text }); },
52
+ submitToolResult(input) { send({ type: 'tool_result', id: input.id, output: input.output }); },
53
+ cancel() { send({ type: 'cancel' }); },
54
+ close() { try {
55
+ ws.close();
56
+ }
57
+ catch { } },
58
+ };
59
+ }
60
+ function decodeServerMessage(parsed) {
61
+ const type = String(parsed.type ?? '');
62
+ switch (type) {
63
+ case 'session_open':
64
+ return { type: 'session_open', sessionId: String(parsed.sessionId ?? '') };
65
+ case 'audio':
66
+ return typeof parsed.audio === 'string' ? { type: 'audio', audio: base64ToBytes(parsed.audio) } : undefined;
67
+ case 'text_delta':
68
+ return { type: 'text_delta', delta: String(parsed.delta ?? ''), role: (parsed.role === 'user' ? 'user' : 'assistant') };
69
+ case 'transcript':
70
+ return { type: 'transcript', text: String(parsed.text ?? ''), role: (parsed.role === 'user' ? 'user' : 'assistant') };
71
+ case 'tool_call':
72
+ return { type: 'tool_call', id: String(parsed.id ?? ''), name: String(parsed.name ?? ''), input: parsed.input };
73
+ case 'response_done':
74
+ return { type: 'response_done', ...(parsed.usage ? { usage: parsed.usage } : {}) };
75
+ case 'cost_limit':
76
+ return {
77
+ type: 'cost_limit',
78
+ scope: String(parsed.scope ?? ''),
79
+ observedUsd: Number(parsed.observedUsd ?? 0),
80
+ limitUsd: Number(parsed.limitUsd ?? 0),
81
+ ...(typeof parsed.scopeKey === 'string' ? { scopeKey: parsed.scopeKey } : {}),
82
+ };
83
+ case 'error':
84
+ return { type: 'error', message: String(parsed.message ?? 'Voice bridge error') };
85
+ case 'pong':
86
+ case 'ack':
87
+ return undefined;
88
+ default:
89
+ return undefined;
90
+ }
91
+ }
92
+ function appendQueryParams(url, params) {
93
+ const qs = new URLSearchParams(params).toString();
94
+ if (!qs)
95
+ return url;
96
+ return url.includes('?') ? `${url}&${qs}` : `${url}?${qs}`;
97
+ }
98
+ function bytesToBase64(bytes) {
99
+ if (typeof Buffer !== 'undefined')
100
+ return Buffer.from(bytes).toString('base64');
101
+ let binary = '';
102
+ for (let i = 0; i < bytes.byteLength; i += 1)
103
+ binary += String.fromCharCode(bytes[i]);
104
+ return btoa(binary);
105
+ }
106
+ function base64ToBytes(value) {
107
+ if (typeof Buffer !== 'undefined')
108
+ return new Uint8Array(Buffer.from(value, 'base64'));
109
+ const bin = atob(value);
110
+ const out = new Uint8Array(bin.length);
111
+ for (let i = 0; i < bin.length; i += 1)
112
+ out[i] = bin.charCodeAt(i);
113
+ return out;
114
+ }
115
+ //# sourceMappingURL=voice-ws-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-ws-client.js","sourceRoot":"","sources":["../src/voice-ws-client.ts"],"names":[],"mappings":"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"}