@devicai/ui 0.60.0 → 0.61.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.
Files changed (58) hide show
  1. package/README.md +4 -0
  2. package/dist/cjs/api/client.js +38 -5
  3. package/dist/cjs/api/client.js.map +1 -1
  4. package/dist/cjs/api/types.js.map +1 -1
  5. package/dist/cjs/components/ChatDrawer/ChatDrawer.js +9 -2
  6. package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
  7. package/dist/cjs/components/ChatDrawer/ChatMessages.js +2 -1
  8. package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
  9. package/dist/cjs/components/ChatDrawer/LiveVoicePanel.js +106 -0
  10. package/dist/cjs/components/ChatDrawer/LiveVoicePanel.js.map +1 -0
  11. package/dist/cjs/components/ChatDrawer/LiveVoicePrompter.js +148 -0
  12. package/dist/cjs/components/ChatDrawer/LiveVoicePrompter.js.map +1 -0
  13. package/dist/cjs/hooks/useDevicChat.js +68 -17
  14. package/dist/cjs/hooks/useDevicChat.js.map +1 -1
  15. package/dist/cjs/hooks/useDevicLiveVoice.js +73 -0
  16. package/dist/cjs/hooks/useDevicLiveVoice.js.map +1 -0
  17. package/dist/cjs/hooks/usePolling.js +1 -1
  18. package/dist/cjs/hooks/usePolling.js.map +1 -1
  19. package/dist/cjs/index.js +4 -0
  20. package/dist/cjs/index.js.map +1 -1
  21. package/dist/cjs/styles.css +1 -1
  22. package/dist/cjs/voice/LiveVoiceController.js +287 -0
  23. package/dist/cjs/voice/LiveVoiceController.js.map +1 -0
  24. package/dist/esm/api/client.d.ts +12 -3
  25. package/dist/esm/api/client.js +38 -5
  26. package/dist/esm/api/client.js.map +1 -1
  27. package/dist/esm/api/liveVoice.types.d.ts +49 -0
  28. package/dist/esm/api/types.d.ts +3 -0
  29. package/dist/esm/api/types.js.map +1 -1
  30. package/dist/esm/components/ChatDrawer/ChatDrawer.js +11 -4
  31. package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
  32. package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +6 -0
  33. package/dist/esm/components/ChatDrawer/ChatMessages.js +2 -1
  34. package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
  35. package/dist/esm/components/ChatDrawer/LiveVoicePanel.d.ts +21 -0
  36. package/dist/esm/components/ChatDrawer/LiveVoicePanel.js +102 -0
  37. package/dist/esm/components/ChatDrawer/LiveVoicePanel.js.map +1 -0
  38. package/dist/esm/components/ChatDrawer/LiveVoicePrompter.d.ts +12 -0
  39. package/dist/esm/components/ChatDrawer/LiveVoicePrompter.js +145 -0
  40. package/dist/esm/components/ChatDrawer/LiveVoicePrompter.js.map +1 -0
  41. package/dist/esm/hooks/index.d.ts +2 -0
  42. package/dist/esm/hooks/useDevicChat.d.ts +6 -0
  43. package/dist/esm/hooks/useDevicChat.js +68 -17
  44. package/dist/esm/hooks/useDevicChat.js.map +1 -1
  45. package/dist/esm/hooks/useDevicLiveVoice.d.ts +20 -0
  46. package/dist/esm/hooks/useDevicLiveVoice.js +71 -0
  47. package/dist/esm/hooks/useDevicLiveVoice.js.map +1 -0
  48. package/dist/esm/hooks/usePolling.d.ts +1 -1
  49. package/dist/esm/hooks/usePolling.js +1 -1
  50. package/dist/esm/hooks/usePolling.js.map +1 -1
  51. package/dist/esm/index.d.ts +6 -0
  52. package/dist/esm/index.js +2 -0
  53. package/dist/esm/index.js.map +1 -1
  54. package/dist/esm/styles.css +1 -1
  55. package/dist/esm/voice/LiveVoiceController.d.ts +38 -0
  56. package/dist/esm/voice/LiveVoiceController.js +284 -0
  57. package/dist/esm/voice/LiveVoiceController.js.map +1 -0
  58. package/package.json +1 -1
@@ -0,0 +1,287 @@
1
+ 'use strict';
2
+
3
+ const initialVoiceSnapshot = () => ({ state: 'idle', muted: false, seconds: 0, transcript: [] });
4
+ /** No browser globals at import/constructor time; loaded only by start(). */
5
+ class LiveVoiceController {
6
+ constructor(client, assistantId, context, changed, created) {
7
+ this.client = client;
8
+ this.assistantId = assistantId;
9
+ this.context = context;
10
+ this.changed = changed;
11
+ this.created = created;
12
+ this.snapshot = initialVoiceSnapshot();
13
+ this.generation = 0;
14
+ this.attempts = 0;
15
+ this.disposeTransport = () => { };
16
+ this.disposed = false;
17
+ }
18
+ update(patch) {
19
+ this.snapshot = { ...this.snapshot, ...patch };
20
+ if (!this.disposed)
21
+ this.changed(this.snapshot);
22
+ }
23
+ mute(muted) {
24
+ this.snapshot.input?.getAudioTracks().forEach(track => { track.enabled = !muted && this.snapshot.state === 'connected'; });
25
+ this.update({ muted });
26
+ }
27
+ async play() {
28
+ try {
29
+ await this.audio?.play();
30
+ this.update({ playbackBlocked: false });
31
+ }
32
+ catch {
33
+ this.update({ playbackBlocked: true });
34
+ }
35
+ }
36
+ async start(chatUid, recovering = false) {
37
+ if (this.disposed || this.closing || (!recovering && !['idle', 'error'].includes(this.snapshot.state)))
38
+ return;
39
+ const generation = ++this.generation;
40
+ const current = () => generation === this.generation && !this.disposed;
41
+ if (!recovering)
42
+ this.attempts = 0;
43
+ this.update({ state: 'connecting', error: undefined, chatUid, sessionId: undefined,
44
+ seconds: 0, ...(recovering ? {} : { transcript: [], muted: false }) });
45
+ let media;
46
+ let peer;
47
+ let channel;
48
+ let audio;
49
+ let health;
50
+ let deadline;
51
+ let connectDeadline;
52
+ let backendReady = false;
53
+ let lastTranscript = 0;
54
+ let sessionId;
55
+ let connectionLostAt = 0;
56
+ let healthFailures = 0;
57
+ const cleanup = () => {
58
+ clearInterval(health);
59
+ clearTimeout(deadline);
60
+ clearTimeout(connectDeadline);
61
+ media?.getTracks().forEach(track => track.stop());
62
+ if (channel) {
63
+ channel.onclose = null;
64
+ channel.onmessage = null;
65
+ channel.close();
66
+ }
67
+ if (peer) {
68
+ peer.onconnectionstatechange = null;
69
+ peer.ontrack = null;
70
+ peer.close();
71
+ }
72
+ if (audio) {
73
+ audio.pause();
74
+ audio.srcObject = null;
75
+ }
76
+ this.audio = undefined;
77
+ this.update({ input: undefined, output: undefined });
78
+ };
79
+ this.disposeTransport = cleanup;
80
+ const ready = () => {
81
+ if (!current() || !backendReady || peer?.connectionState !== 'connected')
82
+ return;
83
+ clearTimeout(connectDeadline);
84
+ media?.getAudioTracks().forEach(track => { track.enabled = !this.snapshot.muted; });
85
+ this.update({ state: 'connected' });
86
+ };
87
+ const recover = async () => {
88
+ if (!current())
89
+ return;
90
+ ++this.generation;
91
+ const ticket = this.generation;
92
+ cleanup();
93
+ this.update({ state: 'reconnecting' });
94
+ try {
95
+ if (!sessionId || ++this.attempts > 3)
96
+ throw new Error('Voice connection interrupted. Please start again.');
97
+ await this.client.closeLiveSession(this.assistantId, sessionId);
98
+ // Never open another paid transport before the old one is confirmed closed.
99
+ for (let i = 0; i < 35 && ticket === this.generation && !this.disposed; i++) {
100
+ const state = await this.client.getLiveSessionStatus(this.assistantId, sessionId);
101
+ if (state.status === 'closed') {
102
+ if (ticket === this.generation && !this.disposed)
103
+ await this.start(this.snapshot.chatUid, true);
104
+ return;
105
+ }
106
+ await new Promise(resolve => setTimeout(resolve, 2000));
107
+ }
108
+ if (ticket === this.generation)
109
+ throw new Error('Could not confirm the previous voice session closed.');
110
+ }
111
+ catch (error) {
112
+ if (ticket === this.generation)
113
+ this.update({ state: 'error', error: error });
114
+ }
115
+ };
116
+ try {
117
+ if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia || typeof RTCPeerConnection === 'undefined')
118
+ throw new Error('Voice requires microphone access on HTTPS or localhost.');
119
+ media = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true }, video: false });
120
+ if (!current()) {
121
+ media.getTracks().forEach(track => track.stop());
122
+ return;
123
+ }
124
+ media.getAudioTracks().forEach(track => { track.enabled = false; });
125
+ this.update({ input: media });
126
+ peer = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });
127
+ audio = new Audio();
128
+ this.audio = audio;
129
+ audio.autoplay = true;
130
+ peer.ontrack = event => {
131
+ if (!current())
132
+ return;
133
+ const output = new MediaStream([event.track]);
134
+ audio.srcObject = output;
135
+ this.update({ output });
136
+ void this.play();
137
+ };
138
+ peer.onconnectionstatechange = () => {
139
+ ready();
140
+ if (peer?.connectionState === 'disconnected')
141
+ connectionLostAt || (connectionLostAt = Date.now());
142
+ else
143
+ connectionLostAt = 0;
144
+ if (peer?.connectionState === 'failed')
145
+ void recover();
146
+ };
147
+ media.getTracks().forEach(track => peer.addTrack(track, media));
148
+ channel = peer.createDataChannel('oai-events');
149
+ channel.onmessage = message => {
150
+ if (!current())
151
+ return;
152
+ try {
153
+ const event = JSON.parse(message.data);
154
+ if (event.type === 'session.started') {
155
+ backendReady = true;
156
+ ready();
157
+ }
158
+ if (['session.input_transcript.delta', 'session.output_transcript.delta'].includes(event.type) && typeof event.delta === 'string') {
159
+ lastTranscript = Date.now();
160
+ const role = event.type === 'session.input_transcript.delta' ? 'user' : 'assistant';
161
+ const turns = this.snapshot.transcript.slice(-3).map(turn => ({ ...turn }));
162
+ if (turns[turns.length - 1]?.role === role)
163
+ turns[turns.length - 1].text = (turns[turns.length - 1].text + event.delta).slice(-2000);
164
+ else
165
+ turns.push({ role, text: event.delta.slice(-2000) });
166
+ this.update({ transcript: turns });
167
+ }
168
+ if (event.type === 'session.closed' && sessionId) {
169
+ void this.client.getLiveSessionStatus(this.assistantId, sessionId).then(state => {
170
+ if (!current())
171
+ return;
172
+ if (state.restartRequested)
173
+ void recover();
174
+ else
175
+ void this.stop();
176
+ }).catch(() => { if (current())
177
+ void this.stop(); });
178
+ }
179
+ if (event.type === 'error') {
180
+ this.update({ error: new Error('Voice session failed.') });
181
+ void this.stop();
182
+ }
183
+ }
184
+ catch { /* Only lifecycle and spoken transcript events are accepted. */ }
185
+ };
186
+ channel.onclose = () => { if (current())
187
+ void recover(); };
188
+ connectDeadline = setTimeout(() => {
189
+ if (current()) {
190
+ this.update({ error: new Error('Voice connection timed out.') });
191
+ void this.stop();
192
+ }
193
+ }, 30000);
194
+ await peer.setLocalDescription(await peer.createOffer());
195
+ await new Promise((resolve, reject) => {
196
+ if (peer.iceGatheringState === 'complete')
197
+ return resolve();
198
+ const done = () => {
199
+ if (peer.iceGatheringState !== 'complete')
200
+ return;
201
+ clearTimeout(timer);
202
+ peer.removeEventListener('icegatheringstatechange', done);
203
+ resolve();
204
+ };
205
+ const timer = setTimeout(() => { peer.removeEventListener('icegatheringstatechange', done); reject(new Error('Could not prepare the audio connection.')); }, 10000);
206
+ peer.addEventListener('icegatheringstatechange', done);
207
+ });
208
+ if (!current())
209
+ return;
210
+ const result = await this.client.createLiveSession(this.assistantId, { ...this.context, chatUid, sdp: peer.localDescription.sdp });
211
+ sessionId = result.sessionId;
212
+ if (!current()) {
213
+ void this.client.closeLiveSession(this.assistantId, sessionId).catch(() => { });
214
+ return;
215
+ }
216
+ this.update({ sessionId, chatUid: result.chatUid });
217
+ this.created(result.chatUid);
218
+ deadline = setTimeout(() => { void this.stop(); }, Math.max(1, Math.min(6000, result.maxDurationSeconds || 6000)) * 1000);
219
+ await peer.setRemoteDescription({ type: 'answer', sdp: result.sdp });
220
+ let busy = false;
221
+ health = setInterval(async () => {
222
+ if (!current() || busy)
223
+ return;
224
+ busy = true;
225
+ try {
226
+ const state = await this.client.getLiveSessionStatus(this.assistantId, sessionId);
227
+ if (!current())
228
+ return;
229
+ healthFailures = 0;
230
+ this.update({ seconds: state.seconds });
231
+ if (state.interrupted || state.restartRequested || (connectionLostAt && Date.now() - connectionLostAt > 5000)) {
232
+ void recover();
233
+ return;
234
+ }
235
+ if (state.status === 'closed') {
236
+ void this.stop();
237
+ return;
238
+ }
239
+ backendReady = state.connected;
240
+ ready();
241
+ if (Date.now() - lastTranscript > 1500 && state.transcript?.length)
242
+ this.update({ transcript: state.transcript.slice(-4).filter(t => t.role === 'user' || t.role === 'assistant').map(t => ({ role: t.role, text: t.text.slice(-2000) })) });
243
+ }
244
+ catch {
245
+ if (current() && ++healthFailures >= 5) {
246
+ this.update({ error: new Error('Voice status unavailable. Session stopped.') });
247
+ void this.stop();
248
+ }
249
+ }
250
+ finally {
251
+ busy = false;
252
+ }
253
+ }, 1000);
254
+ }
255
+ catch (error) {
256
+ if (!current())
257
+ return;
258
+ this.update({ error: error instanceof Error ? error : new Error(String(error)) });
259
+ await this.stop();
260
+ }
261
+ }
262
+ stop() {
263
+ if (this.closing)
264
+ return this.closing;
265
+ ++this.generation;
266
+ this.disposeTransport();
267
+ const id = this.snapshot.sessionId;
268
+ this.update({ state: id ? 'closing' : (this.snapshot.error ? 'error' : 'idle') });
269
+ this.closing = (async () => {
270
+ if (id) {
271
+ try {
272
+ await this.client.closeLiveSession(this.assistantId, id);
273
+ }
274
+ catch (error) {
275
+ this.update({ error: error });
276
+ }
277
+ }
278
+ this.update({ state: this.snapshot.error ? 'error' : 'idle', sessionId: undefined });
279
+ })().finally(() => { this.closing = undefined; });
280
+ return this.closing;
281
+ }
282
+ dispose() { this.disposed = true; void this.stop(); }
283
+ }
284
+
285
+ exports.LiveVoiceController = LiveVoiceController;
286
+ exports.initialVoiceSnapshot = initialVoiceSnapshot;
287
+ //# sourceMappingURL=LiveVoiceController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveVoiceController.js","sources":["../../../../src/voice/LiveVoiceController.ts"],"sourcesContent":["import type { DevicApiClient } from '../api/client';\nimport type { LiveVoiceContext, LiveVoiceTurn } from '../api/liveVoice.types';\n\nexport type LiveVoiceState = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'closing' | 'error';\nexport interface LiveVoiceSnapshot {\n state: LiveVoiceState;\n chatUid?: string;\n sessionId?: string;\n muted: boolean;\n seconds: number;\n transcript: LiveVoiceTurn[];\n input?: MediaStream;\n output?: MediaStream;\n error?: Error;\n playbackBlocked?: boolean;\n}\nexport const initialVoiceSnapshot = (): LiveVoiceSnapshot => ({ state: 'idle', muted: false, seconds: 0, transcript: [] });\n\n/** No browser globals at import/constructor time; loaded only by start(). */\nexport class LiveVoiceController {\n private snapshot = initialVoiceSnapshot();\n private generation = 0;\n private attempts = 0;\n private disposeTransport: () => void = () => {};\n private audio?: HTMLAudioElement;\n private closing?: Promise<void>;\n private disposed = false;\n constructor(private client: DevicApiClient, private assistantId: string,\n private context: LiveVoiceContext, private changed: (value: LiveVoiceSnapshot) => void,\n private created: (chatUid: string) => void) {}\n\n private update(patch: Partial<LiveVoiceSnapshot>) {\n this.snapshot = { ...this.snapshot, ...patch };\n if (!this.disposed) this.changed(this.snapshot);\n }\n mute(muted: boolean) {\n this.snapshot.input?.getAudioTracks().forEach(track => { track.enabled = !muted && this.snapshot.state === 'connected'; });\n this.update({ muted });\n }\n async play() {\n try { await this.audio?.play(); this.update({ playbackBlocked: false }); }\n catch { this.update({ playbackBlocked: true }); }\n }\n async start(chatUid?: string, recovering = false): Promise<void> {\n if (this.disposed || this.closing || (!recovering && !['idle', 'error'].includes(this.snapshot.state))) return;\n const generation = ++this.generation;\n const current = () => generation === this.generation && !this.disposed;\n if (!recovering) this.attempts = 0;\n this.update({ state: 'connecting', error: undefined, chatUid, sessionId: undefined,\n seconds: 0, ...(recovering ? {} : { transcript: [], muted: false }) });\n let media: MediaStream | undefined;\n let peer: RTCPeerConnection | undefined;\n let channel: RTCDataChannel | undefined;\n let audio: HTMLAudioElement | undefined;\n let health: ReturnType<typeof setInterval> | undefined;\n let deadline: ReturnType<typeof setTimeout> | undefined;\n let connectDeadline: ReturnType<typeof setTimeout> | undefined;\n let backendReady = false;\n let lastTranscript = 0;\n let sessionId: string | undefined;\n let connectionLostAt = 0;\n let healthFailures = 0;\n const cleanup = () => {\n clearInterval(health); clearTimeout(deadline); clearTimeout(connectDeadline);\n media?.getTracks().forEach(track => track.stop());\n if (channel) { channel.onclose = null; channel.onmessage = null; channel.close(); }\n if (peer) { peer.onconnectionstatechange = null; peer.ontrack = null; peer.close(); }\n if (audio) { audio.pause(); audio.srcObject = null; }\n this.audio = undefined;\n this.update({ input: undefined, output: undefined });\n };\n this.disposeTransport = cleanup;\n const ready = () => {\n if (!current() || !backendReady || peer?.connectionState !== 'connected') return;\n clearTimeout(connectDeadline);\n media?.getAudioTracks().forEach(track => { track.enabled = !this.snapshot.muted; });\n this.update({ state: 'connected' });\n };\n const recover = async () => {\n if (!current()) return;\n ++this.generation;\n const ticket = this.generation;\n cleanup();\n this.update({ state: 'reconnecting' });\n try {\n if (!sessionId || ++this.attempts > 3) throw new Error('Voice connection interrupted. Please start again.');\n await this.client.closeLiveSession(this.assistantId, sessionId);\n // Never open another paid transport before the old one is confirmed closed.\n for (let i = 0; i < 35 && ticket === this.generation && !this.disposed; i++) {\n const state = await this.client.getLiveSessionStatus(this.assistantId, sessionId);\n if (state.status === 'closed') {\n if (ticket === this.generation && !this.disposed) await this.start(this.snapshot.chatUid, true);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 2000));\n }\n if (ticket === this.generation) throw new Error('Could not confirm the previous voice session closed.');\n } catch (error) {\n if (ticket === this.generation) this.update({ state: 'error', error: error as Error });\n }\n };\n try {\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia || typeof RTCPeerConnection === 'undefined')\n throw new Error('Voice requires microphone access on HTTPS or localhost.');\n media = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true }, video: false });\n if (!current()) { media.getTracks().forEach(track => track.stop()); return; }\n media.getAudioTracks().forEach(track => { track.enabled = false; });\n this.update({ input: media });\n peer = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });\n audio = new Audio(); this.audio = audio; audio.autoplay = true;\n peer.ontrack = event => {\n if (!current()) return;\n const output = new MediaStream([event.track]);\n audio!.srcObject = output;\n this.update({ output }); void this.play();\n };\n peer.onconnectionstatechange = () => {\n ready();\n if (peer?.connectionState === 'disconnected') connectionLostAt ||= Date.now();\n else connectionLostAt = 0;\n if (peer?.connectionState === 'failed') void recover();\n };\n media.getTracks().forEach(track => peer!.addTrack(track, media!));\n channel = peer.createDataChannel('oai-events');\n channel.onmessage = message => {\n if (!current()) return;\n try {\n const event = JSON.parse(message.data);\n if (event.type === 'session.started') { backendReady = true; ready(); }\n if (['session.input_transcript.delta', 'session.output_transcript.delta'].includes(event.type) && typeof event.delta === 'string') {\n lastTranscript = Date.now();\n const role = event.type === 'session.input_transcript.delta' ? 'user' : 'assistant';\n const turns = this.snapshot.transcript.slice(-3).map(turn => ({ ...turn }));\n if (turns[turns.length - 1]?.role === role) turns[turns.length - 1].text = (turns[turns.length - 1].text + event.delta).slice(-2000);\n else turns.push({ role, text: event.delta.slice(-2000) });\n this.update({ transcript: turns });\n }\n if (event.type === 'session.closed' && sessionId) {\n void this.client.getLiveSessionStatus(this.assistantId, sessionId).then(state => {\n if (!current()) return;\n if (state.restartRequested) void recover(); else void this.stop();\n }).catch(() => { if (current()) void this.stop(); });\n }\n if (event.type === 'error') { this.update({ error: new Error('Voice session failed.') }); void this.stop(); }\n } catch { /* Only lifecycle and spoken transcript events are accepted. */ }\n };\n channel.onclose = () => { if (current()) void recover(); };\n connectDeadline = setTimeout(() => {\n if (current()) { this.update({ error: new Error('Voice connection timed out.') }); void this.stop(); }\n }, 30000);\n await peer.setLocalDescription(await peer.createOffer());\n await new Promise<void>((resolve, reject) => {\n if (peer!.iceGatheringState === 'complete') return resolve();\n const done = () => {\n if (peer!.iceGatheringState !== 'complete') return;\n clearTimeout(timer); peer!.removeEventListener('icegatheringstatechange', done); resolve();\n };\n const timer = setTimeout(() => { peer!.removeEventListener('icegatheringstatechange', done); reject(new Error('Could not prepare the audio connection.')); }, 10000);\n peer!.addEventListener('icegatheringstatechange', done);\n });\n if (!current()) return;\n const result = await this.client.createLiveSession(this.assistantId, { ...this.context, chatUid, sdp: peer.localDescription!.sdp });\n sessionId = result.sessionId;\n if (!current()) { void this.client.closeLiveSession(this.assistantId, sessionId).catch(() => {}); return; }\n this.update({ sessionId, chatUid: result.chatUid }); this.created(result.chatUid);\n deadline = setTimeout(() => { void this.stop(); }, Math.max(1, Math.min(6000, result.maxDurationSeconds || 6000)) * 1000);\n await peer.setRemoteDescription({ type: 'answer', sdp: result.sdp });\n let busy = false;\n health = setInterval(async () => {\n if (!current() || busy) return;\n busy = true;\n try {\n const state = await this.client.getLiveSessionStatus(this.assistantId, sessionId!);\n if (!current()) return;\n healthFailures = 0;\n this.update({ seconds: state.seconds });\n if (state.interrupted || state.restartRequested || (connectionLostAt && Date.now() - connectionLostAt > 5000)) { void recover(); return; }\n if (state.status === 'closed') { void this.stop(); return; }\n backendReady = state.connected; ready();\n if (Date.now() - lastTranscript > 1500 && state.transcript?.length)\n this.update({ transcript: state.transcript.slice(-4).filter(t => t.role === 'user' || t.role === 'assistant').map(t => ({ role: t.role, text: t.text.slice(-2000) })) });\n } catch {\n if (current() && ++healthFailures >= 5) { this.update({ error: new Error('Voice status unavailable. Session stopped.')} ); void this.stop(); }\n } finally { busy = false; }\n }, 1000);\n } catch (error) {\n if (!current()) return;\n this.update({ error: error instanceof Error ? error : new Error(String(error)) });\n await this.stop();\n }\n }\n stop(): Promise<void> {\n if (this.closing) return this.closing;\n ++this.generation;\n this.disposeTransport();\n const id = this.snapshot.sessionId;\n this.update({ state: id ? 'closing' : (this.snapshot.error ? 'error' : 'idle') });\n this.closing = (async () => {\n if (id) {\n try { await this.client.closeLiveSession(this.assistantId, id); }\n catch (error) { this.update({ error: error as Error }); }\n }\n this.update({ state: this.snapshot.error ? 'error' : 'idle', sessionId: undefined });\n })().finally(() => { this.closing = undefined; });\n return this.closing;\n }\n dispose() { this.disposed = true; void this.stop(); }\n}\n"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { CreateLiveSessionRequest, LiveVoiceSession, LiveVoiceSessionStatus, LiveVoiceRecording, LiveVoiceUsage } from './liveVoice.types';
1
2
  import type { ProcessMessageDto, ChatMessage, AsyncResponse, RealtimeChatHistory, ChatHistory, AssistantSpecialization, ApiError, ToolCallResponse, ListConversationsResponse, FeedbackSubmission, FeedbackEntry, AgentThreadDto, AgentDto, WhisperTranscriptionResponse, TenantUsage, TenantUsageHistoryRow, TenantUsageHistoryQuery, CoreMemoryList, CoreMemoryEntry, Integration, TenantMcpAuthInput, TenantMcpConnectResult, TenantMcpListing, IntegrationAuthScheme, IntegrationSetupRequired, ModelInterfaceToolSchema, StopChatResponse } from "./types";
2
3
  /**
3
4
  * A tenant session, as the integrator's backend hands it over.
@@ -82,9 +83,17 @@ export declare class DevicApiClient {
82
83
  * Make an authenticated request to the API
83
84
  */
84
85
  private request;
85
- /**
86
- * Get all assistant specializations
87
- */
86
+ private liveRequest;
87
+ createLiveSession(assistantId: string, body: CreateLiveSessionRequest): Promise<LiveVoiceSession>;
88
+ getLiveSessionStatus(assistantId: string, sessionId: string): Promise<LiveVoiceSessionStatus>;
89
+ closeLiveSession(assistantId: string, sessionId: string): Promise<{
90
+ closing: boolean;
91
+ chatUid: string;
92
+ }>;
93
+ getLiveRecordings(assistantId: string, chatUid: string, offset?: number, limit?: number): Promise<LiveVoiceRecording[]>;
94
+ getLiveRecordingAudio(assistantId: string, chatUid: string, sessionId: string): Promise<Blob>;
95
+ getLiveVoiceUsage(assistantId: string, chatUid: string): Promise<LiveVoiceUsage>;
96
+ /** Get all assistant specializations. */
88
97
  getAssistants(external?: boolean): Promise<AssistantSpecialization[]>;
89
98
  /**
90
99
  * Get a specific assistant specialization
@@ -115,7 +115,7 @@ class DevicApiClient {
115
115
  /**
116
116
  * Make an authenticated request to the API
117
117
  */
118
- async request(endpoint, options = {}, isRetry = false) {
118
+ async request(endpoint, options = {}, isRetry = false, binary = false) {
119
119
  const url = `${this.config.baseUrl}${endpoint}`;
120
120
  const credential = await this.authorization();
121
121
  const headers = {
@@ -133,7 +133,7 @@ class DevicApiClient {
133
133
  // turns that into a pause instead of a broken conversation.
134
134
  if (response.status === 401 && this.config.getTenantSession && !isRetry) {
135
135
  if (await this.recoverSession(credential)) {
136
- return this.request(endpoint, options, true);
136
+ return this.request(endpoint, options, true, binary);
137
137
  }
138
138
  }
139
139
  if (!response.ok) {
@@ -156,6 +156,10 @@ class DevicApiClient {
156
156
  }
157
157
  throw new DevicApiError(errorData);
158
158
  }
159
+ if (binary)
160
+ return await response.blob();
161
+ if (response.status === 204)
162
+ return undefined;
159
163
  // Handle responses that may have a wrapper structure
160
164
  const data = await response.json();
161
165
  // If the response has a data property, extract it (common wrapper pattern)
@@ -164,9 +168,38 @@ class DevicApiClient {
164
168
  }
165
169
  return data;
166
170
  }
167
- /**
168
- * Get all assistant specializations
169
- */
171
+ async liveRequest(path, options = {}) {
172
+ const controller = new AbortController();
173
+ const timeout = setTimeout(() => controller.abort(), 25000);
174
+ try {
175
+ return await this.request(path, { ...options, signal: controller.signal });
176
+ }
177
+ finally {
178
+ clearTimeout(timeout);
179
+ }
180
+ }
181
+ createLiveSession(assistantId, body) {
182
+ // No network-error retry: an ambiguous response could hide a paid session.
183
+ return this.liveRequest(`/api/v1/assistants/${encodeURIComponent(assistantId)}/live/sessions`, {
184
+ method: 'POST', body: JSON.stringify(body),
185
+ });
186
+ }
187
+ getLiveSessionStatus(assistantId, sessionId) {
188
+ return this.liveRequest(`/api/v1/assistants/${encodeURIComponent(assistantId)}/live/sessions/${encodeURIComponent(sessionId)}`);
189
+ }
190
+ closeLiveSession(assistantId, sessionId) {
191
+ return this.liveRequest(`/api/v1/assistants/${encodeURIComponent(assistantId)}/live/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE', keepalive: true });
192
+ }
193
+ getLiveRecordings(assistantId, chatUid, offset = 0, limit = 100) {
194
+ return this.request(`/api/v1/assistants/${encodeURIComponent(assistantId)}/chats/${encodeURIComponent(chatUid)}/recordings?offset=${offset}&limit=${limit}`);
195
+ }
196
+ getLiveRecordingAudio(assistantId, chatUid, sessionId) {
197
+ return this.request(`/api/v1/assistants/${encodeURIComponent(assistantId)}/chats/${encodeURIComponent(chatUid)}/recordings/${encodeURIComponent(sessionId)}/audio`, {}, false, true);
198
+ }
199
+ getLiveVoiceUsage(assistantId, chatUid) {
200
+ return this.request(`/api/v1/assistants/${encodeURIComponent(assistantId)}/chats/${encodeURIComponent(chatUid)}/live-usage`);
201
+ }
202
+ /** Get all assistant specializations. */
170
203
  async getAssistants(external = false) {
171
204
  const query = external ? "?external=true" : "";
172
205
  return this.request(`/api/v1/assistants${query}`);