@flowingspring/dsh-voco 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -18,9 +18,9 @@ window.__ModuleLoader__.load({
18
18
  document.head.appendChild(tag);
19
19
  }
20
20
  var VoiceControl_module_css_default = {
21
+ "voice-pulse": "PapC8q_voice-pulse",
21
22
  "button": "PapC8q_button",
22
- "active": "PapC8q_active",
23
- "voice-pulse": "PapC8q_voice-pulse"
23
+ "active": "PapC8q_active"
24
24
  };
25
25
  //#endregion
26
26
  //#region ../ui-voice/src/client/VoiceControl.tsx
@@ -89,17 +89,17 @@ window.__ModuleLoader__.load({
89
89
  document.head.appendChild(tag);
90
90
  }
91
91
  var VoiceHistoryAction_module_css_default = {
92
- "empty": "hhHVeG_empty",
93
- "close": "hhHVeG_close",
94
92
  "title": "hhHVeG_title",
95
- "header": "hhHVeG_header",
96
- "trigger": "hhHVeG_trigger",
97
- "row": "hhHVeG_row",
98
- "list": "hhHVeG_list",
99
93
  "panel": "hhHVeG_panel",
94
+ "row": "hhHVeG_row",
95
+ "trigger": "hhHVeG_trigger",
96
+ "wave": "hhHVeG_wave",
97
+ "header": "hhHVeG_header",
98
+ "empty": "hhHVeG_empty",
100
99
  "rowText": "hhHVeG_rowText",
101
- "meta": "hhHVeG_meta",
102
- "wave": "hhHVeG_wave"
100
+ "close": "hhHVeG_close",
101
+ "list": "hhHVeG_list",
102
+ "meta": "hhHVeG_meta"
103
103
  };
104
104
  //#endregion
105
105
  //#region ../ui-voice/src/client/VoiceHistoryAction.tsx
@@ -211,23 +211,23 @@ window.__ModuleLoader__.load({
211
211
  document.head.appendChild(tag);
212
212
  }
213
213
  var VoiceNodeViews_module_css_default = {
214
- "taskDot": "KSbXkW_taskDot",
215
- "taskCard": "KSbXkW_taskCard",
216
- "miniWave": "KSbXkW_miniWave",
217
214
  "taskTitle": "KSbXkW_taskTitle",
218
- "taskStatus": "KSbXkW_taskStatus",
219
- "taskInput": "KSbXkW_taskInput",
220
- "taskUpdate": "KSbXkW_taskUpdate",
221
215
  "bubble": "KSbXkW_bubble",
216
+ "taskInput": "KSbXkW_taskInput",
217
+ "taskStatus": "KSbXkW_taskStatus",
222
218
  "taskDetails": "KSbXkW_taskDetails",
219
+ "taskDot": "KSbXkW_taskDot",
223
220
  "taskActions": "KSbXkW_taskActions",
224
- "meta": "KSbXkW_meta",
225
- "voiceBadge": "KSbXkW_voiceBadge",
226
- "taskCancel": "KSbXkW_taskCancel",
221
+ "taskCard": "KSbXkW_taskCard",
227
222
  "taskSummary": "KSbXkW_taskSummary",
223
+ "miniWave": "KSbXkW_miniWave",
224
+ "utterance": "KSbXkW_utterance",
225
+ "meta": "KSbXkW_meta",
228
226
  "taskLink": "KSbXkW_taskLink",
229
- "taskChevron": "KSbXkW_taskChevron",
230
- "utterance": "KSbXkW_utterance"
227
+ "taskCancel": "KSbXkW_taskCancel",
228
+ "voiceBadge": "KSbXkW_voiceBadge",
229
+ "taskUpdate": "KSbXkW_taskUpdate",
230
+ "taskChevron": "KSbXkW_taskChevron"
231
231
  };
232
232
  //#endregion
233
233
  //#region ../ui-voice/src/client/VoiceNodeViews.tsx
@@ -356,11 +356,11 @@ window.__ModuleLoader__.load({
356
356
  }
357
357
  var VoiceOverlay_module_css_default = {
358
358
  "wave": "AAuDqa_wave",
359
+ "root": "AAuDqa_root",
359
360
  "link": "AAuDqa_link",
360
361
  "stop": "AAuDqa_stop",
361
- "root": "AAuDqa_root",
362
- "voice-wave": "AAuDqa_voice-wave",
363
- "status": "AAuDqa_status"
362
+ "status": "AAuDqa_status",
363
+ "voice-wave": "AAuDqa_voice-wave"
364
364
  };
365
365
  //#endregion
366
366
  //#region ../ui-voice/src/client/VoiceOverlay.tsx
@@ -431,6 +431,11 @@ window.__ModuleLoader__.load({
431
431
  generation = 0;
432
432
  frame;
433
433
  pendingText = [];
434
+ onConversationStarted;
435
+ conversationStarted = false;
436
+ constructor(options = {}) {
437
+ this.onConversationStarted = options.onConversationStarted;
438
+ }
434
439
  /** Return the identity-stable snapshot until a voice fact changes. */
435
440
  getSnapshot = () => this.snapshot;
436
441
  /** Subscribe to state and frame-coalesced transcript changes. */
@@ -447,6 +452,7 @@ window.__ModuleLoader__.load({
447
452
  async start(sessionId) {
448
453
  const generation = ++this.generation;
449
454
  this.pendingText.splice(0);
455
+ this.conversationStarted = false;
450
456
  const contexts = createAudioContexts();
451
457
  await this.closeTransport();
452
458
  if (generation !== this.generation) {
@@ -584,7 +590,9 @@ window.__ModuleLoader__.load({
584
590
  return;
585
591
  }
586
592
  if (type === "transcription.completed") {
587
- this.setText(stringOf(event.utteranceId), stringOf(event.text), "immediate");
593
+ const text = stringOf(event.text);
594
+ this.setText(stringOf(event.utteranceId), text, "immediate");
595
+ if (text.trim() !== "") this.markConversationStarted();
588
596
  return;
589
597
  }
590
598
  if (type === "output_text.started") {
@@ -598,6 +606,7 @@ window.__ModuleLoader__.load({
598
606
  if (type === "output_text.done") {
599
607
  const text = stringOf(event.text);
600
608
  if (text !== "") this.setText(stringOf(event.utteranceId), text, "immediate");
609
+ if (text.trim() !== "") this.markConversationStarted();
601
610
  return;
602
611
  }
603
612
  if (type === "output_audio.started") {
@@ -610,6 +619,13 @@ window.__ModuleLoader__.load({
610
619
  }
611
620
  if (type === "error") this.handleUnexpectedClose();
612
621
  }
622
+ markConversationStarted() {
623
+ if (this.conversationStarted) return;
624
+ const sessionId = this.snapshot.sessionId;
625
+ if (sessionId === void 0) return;
626
+ this.conversationStarted = true;
627
+ this.onConversationStarted?.(sessionId);
628
+ }
613
629
  publish(snapshot) {
614
630
  if (this.frame !== void 0) {
615
631
  cancelAnimationFrame(this.frame);
@@ -743,7 +759,7 @@ window.__ModuleLoader__.load({
743
759
  return;
744
760
  }
745
761
  const record = recordOf(event);
746
- if (record?.type === "transcription.started" || record?.type === "response.interrupted") stopPlayback();
762
+ if (record?.type === "response.interrupted") stopPlayback();
747
763
  if (record?.type === "output_audio.done") {
748
764
  outputDone = true;
749
765
  notifyPlaybackEnded();
@@ -1357,9 +1373,19 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
1357
1373
  "sessions",
1358
1374
  "locale"
1359
1375
  ];
1376
+ /** Refresh the Host-backed Session list when the runtime exposes its baseline pull. */
1377
+ function refreshSessions(ctx) {
1378
+ const service = ctx.sessions;
1379
+ if (typeof service.refresh !== "function") return;
1380
+ service.refresh.call(ctx.sessions).catch((cause) => {
1381
+ console.warn("voice session list refresh failed:", cause);
1382
+ });
1383
+ }
1360
1384
  /** Mount root transport controls and durable Voice conversation renderers. @param ctx - browser context. */
1361
1385
  function apply(ctx) {
1362
- const controller = new VoiceController();
1386
+ const controller = new VoiceController({ onConversationStarted: () => {
1387
+ refreshSessions(ctx);
1388
+ } });
1363
1389
  const history = new VoiceHistoryStore();
1364
1390
  const textSubmit = new VoiceTextSubmitBridge(controller, (sessionId) => {
1365
1391
  const scope = ctx.sessions.scope(sessionId);
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["css","useState","css","css","useState","css","createSnapshotStore"],"sources":["../../../packages/ui-voice/src/client/VoiceControl.tsx","../../../packages/ui-voice/src/client/VoiceHistoryAction.tsx","../../../packages/ui-voice/src/client/VoiceNodeViews.tsx","../../../packages/ui-voice/src/client/VoiceOverlay.tsx","../../../packages/ui-voice/src/client/voice-controller.ts","../../../packages/ui-voice/src/client/voice-history.ts","../../../packages/ui-voice/src/client/voice-text-submit.ts","../../../packages/ui-voice/src/client/voice-definitions.ts","../../../packages/ui-voice/src/client/locales.ts","../../../packages/ui-voice/src/client/index.ts"],"sourcesContent":["import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport css from './VoiceControl.module.css'\r\n\r\n/** Business callbacks and the root-owned transport snapshot. */\r\nexport interface VoiceControlInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n /** Start a fresh Voice conversation located from the current Session. */\r\n readonly startVoice: (sourceSessionId: SessionId) => Promise<void>\r\n readonly retryVoice: () => Promise<void>\r\n readonly stopVoice: () => Promise<void>\r\n}\r\n\r\nexport type VoiceControlProps =\r\n PropsRuntime<'conversation.input.right'> & InjectFace<VoiceControlInjected> & PropsLocale<'voice'>\r\n\r\n/** Start a fresh Voice Session from this Session's location or control the active connection. */\r\nexport function VoiceControl({\r\n sessionId, useVoice, startVoice, retryVoice, stopVoice, t,\r\n}: VoiceControlProps) {\r\n const state = useVoice(snapshot => snapshot.state)\r\n const activeSessionId = useVoice(snapshot => snapshot.sessionId)\r\n\r\n const toggle = async (): Promise<void> => {\r\n if (state === 'error' && activeSessionId !== undefined) {\r\n try { await retryVoice() } catch { /* controller owns the visible error state */ }\r\n return\r\n }\r\n if (state !== 'off' && state !== 'error') {\r\n await stopVoice()\r\n return\r\n }\r\n try {\r\n await startVoice(sessionId)\r\n } catch { /* controller owns the visible error state */ }\r\n }\r\n\r\n const label = state === 'off'\r\n ? t('control.start')\r\n : state === 'error'\r\n ? t('control.retry')\r\n : t('control.stop')\r\n return (\r\n <button\r\n type=\"button\"\r\n className={`${css.button} ${state !== 'off' ? css.active : ''}`}\r\n aria-label={label}\r\n title={label}\r\n data-state={state}\r\n onClick={() => { void toggle() }}\r\n >\r\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\r\n <rect x=\"5\" y=\"2\" width=\"6\" height=\"8\" rx=\"3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\r\n <path d=\"M3.5 7.5a4.5 4.5 0 0 0 9 0M8 12v2M5.5 14h5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\r\n </svg>\r\n </button>\r\n )\r\n}\r\n","import { useEffect, useState } from 'react'\r\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport type { VoiceHistorySnapshot } from './voice-history.ts'\r\nimport css from './VoiceHistoryAction.module.css'\r\n\r\n/** History state and navigation owned by the Voice UI plugin. */\r\nexport interface VoiceHistoryActionInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n voiceHistory: { getSnapshot(): VoiceHistorySnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openSession: (id: SessionId) => void\r\n}\r\n\r\nexport type VoiceHistoryActionProps =\r\n PropsRuntime<'sidebar.footer.action'> & InjectFace<VoiceHistoryActionInjected> & PropsLocale<'voice'>\r\n\r\n/** Render the plugin-owned Voice history entry and its Session navigation panel. */\r\nexport function VoiceHistoryAction({\r\n wide, useSessions, useVoice, useVoiceHistory, openSession, t,\r\n}: VoiceHistoryActionProps) {\r\n const [open, setOpen] = useState(false)\r\n const sessions = useSessions(state => state)\r\n const entries = useVoiceHistory(state => state.entries)\r\n const activeSessionId = useVoice(state => state.sessionId)\r\n const available = entries.flatMap((entry) => {\r\n const session = sessions.byId[entry.sessionId]\r\n return session === undefined ? [] : [{ entry, session }]\r\n })\r\n\r\n useEffect(() => {\r\n if (!open) return\r\n const close = (event: KeyboardEvent): void => { if (event.key === 'Escape') setOpen(false) }\r\n document.addEventListener('keydown', close)\r\n return () => { document.removeEventListener('keydown', close) }\r\n }, [open])\r\n\r\n return (\r\n <>\r\n <button\r\n type=\"button\"\r\n className={css.trigger}\r\n aria-label={t('history.open')}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n onClick={() => { setOpen(value => !value) }}\r\n >\r\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\r\n <path d=\"M3 8a5 5 0 1 0 1.5-3.55M3 2.7v2.8h2.8M8 5.2V8l2 1.2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.35\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\r\n </svg>\r\n {wide && <span>{t('history.label')}</span>}\r\n </button>\r\n {open && (\r\n <section className={css.panel} role=\"dialog\" aria-label={t('history.title')}>\r\n <header className={css.header}>\r\n <strong>{t('history.title')}</strong>\r\n <button type=\"button\" className={css.close} aria-label={t('history.close')} onClick={() => { setOpen(false) }}>×</button>\r\n </header>\r\n <div className={css.list}>\r\n {available.length === 0 && <p className={css.empty}>{t('history.empty')}</p>}\r\n {available.map(({ entry, session }) => (\r\n <button\r\n key={entry.sessionId}\r\n type=\"button\"\r\n className={css.row}\r\n data-active={entry.sessionId === activeSessionId || undefined}\r\n onClick={() => {\r\n openSession(entry.sessionId)\r\n setOpen(false)\r\n }}\r\n >\r\n <span className={css.wave} aria-hidden><i /><i /><i /></span>\r\n <span className={css.rowText}>\r\n <span className={css.title}>{session.displayTitle}</span>\r\n <span className={css.meta}>{entry.sessionId === activeSessionId ? t('history.active') : t('history.saved')}</span>\r\n </span>\r\n </button>\r\n ))}\r\n </div>\r\n </section>\r\n )}\r\n </>\r\n )\r\n}\r\n","import { useState } from 'react'\r\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport type { VoiceDelegationChatData } from './voice-definitions.ts'\r\nimport type { VoiceKey } from './locales.ts'\r\nimport css from './VoiceNodeViews.module.css'\r\n\r\n/** Live transcript source injected into the Voice utterance renderer. */\r\nexport interface VoiceUtteranceInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n}\r\n\r\n/** Navigation action injected into the task-card renderer. */\r\nexport interface VoiceDelegationInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openSession: (id: SessionId) => void\r\n readonly cancelTask: (id: string) => void\r\n}\r\n\r\nexport type VoiceUtteranceViewProps =\r\n PropsRuntime<'conversation.chat.node', 'voice-utterance'>\r\n & InjectFace<VoiceUtteranceInjected>\r\n & PropsLocale<'voice'>\r\nexport type VoiceDelegationViewProps =\r\n PropsRuntime<'conversation.chat.node', 'voice-delegation'>\r\n & InjectFace<VoiceDelegationInjected>\r\n & PropsLocale<'voice'>\r\n\r\n/** Render a durable Voice utterance while replacing its unsettled text with live WebSocket deltas. */\r\nexport function VoiceUtteranceView({ node, sessionId, useVoice, t }: VoiceUtteranceViewProps) {\r\n const liveText = useVoice(snapshot => snapshot.sessionId === sessionId\r\n ? snapshot.textById[String(node.data.utteranceId)]?.text\r\n : undefined)\r\n const text = node.data.text ?? liveText ?? (node.data.role === 'user'\r\n ? t('utterance.user.pending')\r\n : t('utterance.assistant.pending'))\r\n return (\r\n <article\r\n className={css.utterance}\r\n data-voice-utterance\r\n data-role={node.data.role}\r\n data-state={node.data.state}\r\n >\r\n <div className={css.bubble}>{text}</div>\r\n <div className={css.meta}>\r\n <span className={css.voiceBadge}>\r\n <span className={css.miniWave} aria-hidden><i /><i /><i /></span>\r\n {t('badge.voice')}\r\n </span>\r\n {node.data.state === 'interrupted' && <span>{t('utterance.interrupted')}</span>}\r\n </div>\r\n </article>\r\n )\r\n}\r\n\r\nconst STATUS_KEYS: Record<VoiceDelegationChatData['status'], VoiceKey> = {\r\n accepted: 'task.status.accepted',\r\n queued: 'task.status.queued',\r\n running: 'task.status.running',\r\n 'waiting-user': 'task.status.waiting-user',\r\n completed: 'task.status.completed',\r\n failed: 'task.status.failed',\r\n cancelled: 'task.status.cancelled',\r\n interrupted: 'task.status.interrupted',\r\n}\r\n\r\n/** Render the task link from a Voice Session to its bound background Agent Session. */\r\nexport function VoiceDelegationView({ node, sessionId, useSessions, useVoice, openSession, cancelTask, t }: VoiceDelegationViewProps) {\r\n const [expanded, setExpanded] = useState(false)\r\n const navigable = useSessions(sessions => sessions.ids.includes(node.data.taskSessionId))\r\n const connected = useVoice(voice => voice.sessionId === sessionId && voice.state !== 'off' && voice.state !== 'error')\r\n const cancellable = connected && !isTerminalTaskStatus(node.data.status)\r\n return (\r\n <article\r\n className={css.taskCard}\r\n data-voice-delegation\r\n data-status={node.data.status}\r\n data-expanded={expanded}\r\n >\r\n <button\r\n type=\"button\"\r\n className={css.taskSummary}\r\n aria-expanded={expanded}\r\n aria-label={expanded ? t('task.collapse') : t('task.expand')}\r\n onClick={() => { setExpanded(value => !value) }}\r\n >\r\n <span className={css.taskDot} aria-hidden />\r\n <span className={css.taskTitle}>{t('task.title')}</span>\r\n <span className={css.taskStatus}>{t(STATUS_KEYS[node.data.status])}</span>\r\n <span className={css.taskChevron} aria-hidden />\r\n </button>\r\n {expanded && (\r\n <div className={css.taskDetails}>\r\n <p className={css.taskInput}>{node.data.input}</p>\r\n {node.data.update !== undefined && <p className={css.taskUpdate}>{node.data.update}</p>}\r\n <div className={css.taskActions}>\r\n {cancellable && (\r\n <button type=\"button\" className={`${css.taskLink} ${css.taskCancel}`} onClick={() => { cancelTask(String(node.data.taskId)) }}>\r\n {t('task.cancel')}\r\n </button>\r\n )}\r\n <button\r\n type=\"button\"\r\n className={css.taskLink}\r\n disabled={!navigable}\r\n onClick={() => { openSession(node.data.taskSessionId) }}\r\n >\r\n {t('task.open')}\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </article>\r\n )\r\n}\r\n\r\nfunction isTerminalTaskStatus(status: VoiceDelegationChatData['status']): boolean {\r\n return status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'interrupted'\r\n}\r\n","import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot, VoiceClientState } from './voice-controller.ts'\r\nimport type { VoiceKey } from './locales.ts'\r\nimport css from './VoiceOverlay.module.css'\r\n\r\n/** Root-level voice connection actions. */\r\nexport interface VoiceOverlayInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openVoiceSession: (id: SessionId) => void\r\n readonly stopVoice: () => Promise<void>\r\n}\r\n\r\nexport type VoiceOverlayProps =\r\n PropsRuntime<'shell.overlay'> & InjectFace<VoiceOverlayInjected> & PropsLocale<'voice'>\r\n\r\nconst STATE_KEYS: Record<Exclude<VoiceClientState, 'off'>, VoiceKey> = {\r\n connecting: 'overlay.connecting',\r\n listening: 'overlay.listening',\r\n speaking: 'overlay.speaking',\r\n error: 'overlay.error',\r\n}\r\n\r\n/** Keep the active Voice Session controllable while another Session is selected. */\r\nexport function VoiceOverlay({ useVoice, useSessions, openVoiceSession, stopVoice, t }: VoiceOverlayProps) {\r\n const voice = useVoice(snapshot => snapshot)\r\n const navigable = useSessions(sessions => voice.sessionId !== undefined && sessions.ids.includes(voice.sessionId))\r\n if (voice.state === 'off') return null\r\n return (\r\n <aside className={css.root} data-voice-overlay data-state={voice.state} aria-live=\"polite\">\r\n <span className={css.wave} aria-hidden>\r\n <i /><i /><i /><i />\r\n </span>\r\n <span className={css.status}>{t(STATE_KEYS[voice.state])}</span>\r\n {voice.sessionId !== undefined && (\r\n <button\r\n type=\"button\"\r\n className={css.link}\r\n disabled={!navigable}\r\n onClick={() => { openVoiceSession(voice.sessionId as SessionId) }}\r\n >\r\n {t('overlay.return')}\r\n </button>\r\n )}\r\n <button type=\"button\" className={css.stop} onClick={() => { void stopVoice() }}>\r\n {t('overlay.stop')}\r\n </button>\r\n </aside>\r\n )\r\n}\r\n","import type { ObservableSnapshot, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\n\r\n/** Browser-visible lifecycle of the one active voice transport. */\r\nexport type VoiceClientState = 'off' | 'connecting' | 'listening' | 'speaking' | 'error'\r\n\r\n/** Live text retained until the matching durable utterance settles. */\r\nexport interface VoiceLiveText {\r\n readonly text: string\r\n}\r\n\r\n/** Root-owned voice transport projection shared by every UI seat. */\r\nexport interface VoiceClientSnapshot {\r\n readonly state: VoiceClientState\r\n readonly sessionId?: SessionId\r\n readonly textById: Readonly<Record<string, VoiceLiveText>>\r\n}\r\n\r\ninterface VoiceTransport {\r\n close(): Promise<void>\r\n cancelTask(taskId: string): void\r\n submitText(text: string): void\r\n}\r\n\r\ninterface VoiceTransportHandlers {\r\n readonly onBinary: (audio: ArrayBuffer) => void\r\n readonly onEvent: (event: unknown) => void\r\n readonly onPlaybackEnded: () => void\r\n readonly onUnexpectedClose: () => void\r\n}\r\n\r\ninterface VoiceReadyEvent {\r\n readonly type: 'ready'\r\n readonly voiceSessionId: string\r\n readonly audio: {\r\n readonly inputSampleRate: number\r\n readonly outputSampleRate: number\r\n readonly format: 'pcm_s16le' | 'audio_mpeg'\r\n }\r\n readonly interactionMode: 'speech-shell' | 'frontend-agent'\r\n}\r\n\r\nconst INITIAL_SNAPSHOT: VoiceClientSnapshot = { state: 'off', textById: {} }\r\n\r\n/** Own the single browser microphone, provider WebSocket, playback queue, and live transcript feed. */\r\nexport class VoiceController implements ObservableSnapshot<VoiceClientSnapshot> {\r\n private snapshot = INITIAL_SNAPSHOT\r\n private readonly listeners = new Set<() => void>()\r\n private transport: VoiceTransport | undefined\r\n private openingTransport: VoiceTransport | undefined\r\n private openingAbort: AbortController | undefined\r\n private generation = 0\r\n private frame: number | undefined\r\n private readonly pendingText: string[] = []\r\n\r\n /** Return the identity-stable snapshot until a voice fact changes. */\r\n getSnapshot = (): VoiceClientSnapshot => this.snapshot\r\n\r\n /** Subscribe to state and frame-coalesced transcript changes. */\r\n subscribe = (listener: () => void): (() => void) => {\r\n this.listeners.add(listener)\r\n return () => { this.listeners.delete(listener) }\r\n }\r\n\r\n /**\r\n * Open one voice transport and make it the root-owned active session.\r\n * @param sessionId - durable voice session to connect.\r\n */\r\n async start(sessionId: SessionId): Promise<void> {\r\n const generation = ++this.generation\r\n this.pendingText.splice(0)\r\n // Construct and unlock both contexts before the first await so browser\r\n // autoplay policy still treats the microphone button as the user gesture.\r\n const contexts = createAudioContexts()\r\n await this.closeTransport()\r\n if (generation !== this.generation) {\r\n await closeAudioContexts(contexts)\r\n return\r\n }\r\n const openingAbort = new AbortController()\r\n this.openingAbort = openingAbort\r\n this.publish({ state: 'connecting', sessionId, textById: {} })\r\n try {\r\n const transport = await openVoiceTransport(String(sessionId), {\r\n onBinary: () => { this.setState('speaking') },\r\n onEvent: (event) => { this.handleEvent(event) },\r\n onPlaybackEnded: () => { this.setState('listening') },\r\n onUnexpectedClose: () => { this.handleUnexpectedClose() },\r\n }, openingAbort.signal, (opening) => {\r\n if (generation === this.generation) this.openingTransport = opening\r\n else void opening.close()\r\n }, contexts)\r\n if (generation !== this.generation) {\r\n await transport.close()\r\n return\r\n }\r\n this.openingTransport = undefined\r\n this.transport = transport\r\n for (const text of this.pendingText.splice(0)) transport.submitText(text)\r\n this.setState('listening')\r\n } catch (cause) {\r\n if (generation !== this.generation) return\r\n this.setState('error')\r\n throw cause\r\n } finally {\r\n if (this.openingAbort === openingAbort) this.openingAbort = undefined\r\n if (generation === this.generation) this.openingTransport = undefined\r\n }\r\n }\r\n\r\n /** Retry the failed active Voice Session, when one was created successfully. */\r\n async retry(): Promise<void> {\r\n const sessionId = this.snapshot.sessionId\r\n if (sessionId === undefined) throw new Error('voice retry requires an active session')\r\n await this.start(sessionId)\r\n }\r\n\r\n /** Cancel the exact active background task through the attached Voice transport. */\r\n cancelTask(taskId: string): void {\r\n if (taskId === '') throw new Error('voice task id is required')\r\n if (this.transport === undefined) throw new Error('voice task cancellation requires an active connection')\r\n this.transport.cancelTask(taskId)\r\n }\r\n\r\n /** Submit composer text as a voice turn, queueing it while the transport connects. */\r\n submitText(text: string): void {\r\n const value = text.trim()\r\n if (value === '') throw new Error('voice text submission requires non-empty text')\r\n if (this.snapshot.state === 'off' || this.snapshot.state === 'error' || this.snapshot.sessionId === undefined) {\r\n throw new Error('voice text submission requires an active connection')\r\n }\r\n if (this.transport === undefined) this.pendingText.push(value)\r\n else this.transport.submitText(value)\r\n }\r\n\r\n /** Stop capture/playback and await owned browser-resource teardown. */\r\n async stop(): Promise<void> {\r\n ++this.generation\r\n this.openingAbort?.abort()\r\n this.openingAbort = undefined\r\n this.publish(INITIAL_SNAPSHOT)\r\n this.pendingText.splice(0)\r\n await this.closeTransport()\r\n }\r\n\r\n private async closeTransport(): Promise<void> {\r\n const transports = new Set<VoiceTransport>()\r\n if (this.transport !== undefined) transports.add(this.transport)\r\n if (this.openingTransport !== undefined) transports.add(this.openingTransport)\r\n this.transport = undefined\r\n this.openingTransport = undefined\r\n if (this.frame !== undefined) {\r\n cancelAnimationFrame(this.frame)\r\n this.frame = undefined\r\n }\r\n const settled = await Promise.allSettled([...transports].map(transport => transport.close()))\r\n const failures: unknown[] = []\r\n for (const result of settled) {\r\n if (result.status === 'rejected') failures.push(result.reason as unknown)\r\n }\r\n if (failures.length > 0) throw new AggregateError(failures, 'voice transport cleanup failed')\r\n }\r\n\r\n private handleUnexpectedClose(): void {\r\n this.openingAbort?.abort()\r\n this.setState('error')\r\n void this.closeTransport().catch((cause: unknown) => {\r\n console.error('voice transport cleanup failed:', cause)\r\n })\r\n }\r\n\r\n private setState(state: VoiceClientState): void {\r\n if (this.snapshot.state === state) return\r\n this.publish({ ...this.snapshot, state })\r\n }\r\n\r\n private setText(id: string, text: string, cadence: 'immediate' | 'animation-frame'): void {\r\n if (id === '') return\r\n const current = this.snapshot.textById[id]\r\n if (current?.text === text) return\r\n this.snapshot = {\r\n ...this.snapshot,\r\n textById: { ...this.snapshot.textById, [id]: { text } },\r\n }\r\n if (cadence === 'immediate') this.notify()\r\n else this.notifyOnFrame()\r\n }\r\n\r\n private appendText(id: string, delta: string): void {\r\n if (id === '' || delta === '') return\r\n this.setText(id, `${this.snapshot.textById[id]?.text ?? ''}${delta}`, 'animation-frame')\r\n }\r\n\r\n private handleEvent(input: unknown): void {\r\n const event = recordOf(input)\r\n if (event === undefined) return\r\n const type = stringOf(event.type)\r\n if (type === 'transcription.started') {\r\n this.setText(stringOf(event.utteranceId), '', 'immediate')\r\n this.setState('listening')\r\n return\r\n }\r\n if (type === 'transcription.updated') {\r\n this.setText(stringOf(event.utteranceId), stringOf(event.text), 'animation-frame')\r\n return\r\n }\r\n if (type === 'transcription.completed') {\r\n this.setText(stringOf(event.utteranceId), stringOf(event.text), 'immediate')\r\n return\r\n }\r\n if (type === 'output_text.started') {\r\n this.setText(stringOf(event.utteranceId), '', 'immediate')\r\n return\r\n }\r\n if (type === 'output_text.delta') {\r\n this.appendText(stringOf(event.utteranceId), stringOf(event.text))\r\n return\r\n }\r\n if (type === 'output_text.done') {\r\n const text = stringOf(event.text)\r\n if (text !== '') this.setText(stringOf(event.utteranceId), text, 'immediate')\r\n return\r\n }\r\n if (type === 'output_audio.started') {\r\n this.setState('speaking')\r\n return\r\n }\r\n if (type === 'response.interrupted') {\r\n this.setState('listening')\r\n return\r\n }\r\n if (type === 'error') this.handleUnexpectedClose()\r\n }\r\n\r\n private publish(snapshot: VoiceClientSnapshot): void {\r\n if (this.frame !== undefined) {\r\n cancelAnimationFrame(this.frame)\r\n this.frame = undefined\r\n }\r\n this.snapshot = snapshot\r\n this.notify()\r\n }\r\n\r\n private notifyOnFrame(): void {\r\n this.frame ??= requestAnimationFrame(() => {\r\n this.frame = undefined\r\n this.notify()\r\n })\r\n }\r\n\r\n private notify(): void {\r\n for (const listener of this.listeners) {\r\n try { listener() } catch (cause) { console.error('voice snapshot listener failed:', cause) }\r\n }\r\n }\r\n}\r\n\r\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\r\n return value !== null && typeof value === 'object' ? value as Record<string, unknown> : undefined\r\n}\r\n\r\nfunction stringOf(value: unknown): string {\r\n return typeof value === 'string' ? value : ''\r\n}\r\n\r\nasync function openVoiceTransport(\r\n sessionId: string,\r\n handlers: VoiceTransportHandlers,\r\n signal: AbortSignal,\r\n onOpening: (transport: VoiceTransport) => void,\r\n contexts: AudioContexts,\r\n): Promise<VoiceTransport> {\r\n const { input: inputContext, output: outputContext } = contexts\r\n const socket = new WebSocket(voiceUrl(sessionId))\r\n const workletUrl = URL.createObjectURL(new Blob([CAPTURE_WORKLET], { type: 'text/javascript' }))\r\n let stream: MediaStream | undefined\r\n let source: MediaStreamAudioSourceNode | undefined\r\n let processor: AudioWorkletNode | undefined\r\n let silent: GainNode | undefined\r\n let stopped = false\r\n let socketClosed = false\r\n let ready = false\r\n let outputDone = false\r\n let encodedPending = 0\r\n let playbackGeneration = 0\r\n let encodedQueue: Promise<void> = Promise.resolve()\r\n let inputSampleRate = 16_000\r\n let outputSampleRate = 24_000\r\n let audioFormat: VoiceReadyEvent['audio']['format'] = 'pcm_s16le'\r\n let playAt = outputContext.currentTime\r\n const chunks: number[] = []\r\n const playing = new Set<AudioBufferSourceNode>()\r\n\r\n const transport: VoiceTransport = {\r\n cancelTask: (taskId) => {\r\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\r\n socket.send(JSON.stringify({ type: 'task.cancel', taskId }))\r\n },\r\n submitText: (text) => {\r\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\r\n socket.send(JSON.stringify({ type: 'text.submit', text }))\r\n },\r\n close: async () => {\r\n /* v8 ignore next -- controller ownership closes a transport once; the guard protects browser close races. */\r\n if (stopped) return\r\n stopped = true\r\n if (!socketClosed && socket.readyState === WebSocket.OPEN) {\r\n try { socket.send(JSON.stringify({ type: 'session.close' })) } catch {\r\n /* A socket closing concurrently cannot carry the final control; the server expires its reconnect grace. */\r\n }\r\n }\r\n socket.removeEventListener('message', onMessage)\r\n socket.removeEventListener('close', onClose)\r\n stopPlayback()\r\n source?.disconnect()\r\n processor?.disconnect()\r\n silent?.disconnect()\r\n processor?.port.close()\r\n for (const track of stream?.getTracks() ?? []) track.stop()\r\n socket.close()\r\n await Promise.allSettled([inputContext.close(), outputContext.close()])\r\n },\r\n }\r\n\r\n const stopPlayback = (): void => {\r\n playbackGeneration += 1\r\n encodedPending = 0\r\n encodedQueue = Promise.resolve()\r\n for (const node of playing) {\r\n try { node.stop() } catch { /* a completed source is already stopped */ }\r\n }\r\n playing.clear()\r\n playAt = outputContext.currentTime\r\n outputDone = false\r\n }\r\n const notifyPlaybackEnded = (): void => {\r\n if (outputDone && encodedPending === 0 && playing.size === 0 && socket.readyState === WebSocket.OPEN) {\r\n socket.send(JSON.stringify({ type: 'playback.ended' }))\r\n outputDone = false\r\n handlers.onPlaybackEnded()\r\n }\r\n }\r\n const onMessage = (message: MessageEvent): void => {\r\n if (!ready) return\r\n if (message.data instanceof ArrayBuffer) {\r\n if (audioFormat === 'audio_mpeg') {\r\n const generation = playbackGeneration\r\n encodedPending += 1\r\n encodedQueue = encodedQueue.catch(() => {}).then(() => scheduleEncoded(\r\n outputContext,\r\n message.data,\r\n playing,\r\n notifyPlaybackEnded,\r\n (value) => { playAt = value },\r\n () => playAt,\r\n () => generation === playbackGeneration && !stopped,\r\n )).catch(error => {\r\n if (generation === playbackGeneration && !stopped) {\r\n handlers.onEvent({ type: 'error', message: `audio decode failed: ${String(error)}` })\r\n }\r\n }).finally(() => {\r\n if (generation === playbackGeneration) {\r\n encodedPending -= 1\r\n notifyPlaybackEnded()\r\n }\r\n })\r\n } else {\r\n schedulePcm(outputContext, message.data, outputSampleRate, playing, notifyPlaybackEnded, (value) => { playAt = value }, playAt)\r\n }\r\n handlers.onBinary(message.data)\r\n return\r\n }\r\n let event: unknown\r\n try { event = JSON.parse(String(message.data)) } catch { return }\r\n const record = recordOf(event)\r\n if (record?.type === 'transcription.started' || record?.type === 'response.interrupted') stopPlayback()\r\n if (record?.type === 'output_audio.done') {\r\n outputDone = true\r\n notifyPlaybackEnded()\r\n }\r\n handlers.onEvent(event)\r\n }\r\n const onClose = (): void => {\r\n socketClosed = true\r\n handlers.onUnexpectedClose()\r\n }\r\n\r\n socket.binaryType = 'arraybuffer'\r\n onOpening(transport)\r\n try {\r\n await socketOpened(socket, signal)\r\n socket.addEventListener('message', onMessage)\r\n socket.addEventListener('close', onClose)\r\n const readyEvent = await socketReady(socket, signal)\r\n inputSampleRate = readyEvent.audio.inputSampleRate\r\n outputSampleRate = readyEvent.audio.outputSampleRate\r\n audioFormat = readyEvent.audio.format\r\n ready = true\r\n const captured = await microphone(signal)\r\n if (openingStopped(signal, stopped)) {\r\n for (const track of captured.getTracks()) track.stop()\r\n throw new Error('voice connection opening aborted')\r\n }\r\n stream = captured\r\n await abortable(inputContext.audioWorklet.addModule(workletUrl), signal)\r\n if (openingStopped(signal, stopped)) throw new Error('voice connection opening aborted')\r\n source = inputContext.createMediaStreamSource(stream)\r\n processor = new AudioWorkletNode(inputContext, 'dsh-pcm-capture')\r\n silent = inputContext.createGain()\r\n silent.gain.value = 0\r\n processor.port.onmessage = (event: MessageEvent<Float32Array>) => {\r\n if (socket.readyState !== WebSocket.OPEN) return\r\n chunks.push(...resample(event.data, inputContext.sampleRate, inputSampleRate))\r\n const frameSize = Math.max(1, Math.round(inputSampleRate / 5))\r\n while (chunks.length >= frameSize) {\r\n const part = chunks.splice(0, frameSize)\r\n const pcm = new Int16Array(part.length)\r\n for (const [index, sample] of part.entries()) {\r\n pcm[index] = Math.round(Math.max(-1, Math.min(1, sample)) * 32767)\r\n }\r\n socket.send(pcm.buffer)\r\n }\r\n }\r\n source.connect(processor)\r\n processor.connect(silent)\r\n silent.connect(inputContext.destination)\r\n } catch (cause) {\r\n await transport.close()\r\n throw cause\r\n } finally {\r\n URL.revokeObjectURL(workletUrl)\r\n }\r\n\r\n return transport\r\n}\r\n\r\ninterface AudioContexts {\r\n readonly input: AudioContext\r\n readonly output: AudioContext\r\n}\r\n\r\nfunction createAudioContexts(): AudioContexts {\r\n const contexts = { input: new AudioContext(), output: new AudioContext() }\r\n // Calling resume synchronously is required on browsers that start contexts\r\n // suspended when they are created outside a trusted user gesture.\r\n void contexts.input.resume().catch(() => {})\r\n void contexts.output.resume().catch(() => {})\r\n return contexts\r\n}\r\n\r\nasync function closeAudioContexts(contexts: AudioContexts): Promise<void> {\r\n await Promise.allSettled([contexts.input.close(), contexts.output.close()])\r\n}\r\n\r\nfunction microphone(signal: AbortSignal): Promise<MediaStream> {\r\n if (signal.aborted) return Promise.reject(new Error('voice connection opening aborted'))\r\n return new Promise((resolve, reject) => {\r\n let cancelled = false\r\n const cleanup = (): void => { signal.removeEventListener('abort', aborted) }\r\n const aborted = (): void => {\r\n cancelled = true\r\n cleanup()\r\n reject(new Error('voice connection opening aborted'))\r\n }\r\n signal.addEventListener('abort', aborted, { once: true })\r\n let request: Promise<MediaStream>\r\n try {\r\n request = navigator.mediaDevices.getUserMedia({\r\n audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },\r\n })\r\n } catch (cause) {\r\n cleanup()\r\n reject(errorOf(cause))\r\n return\r\n }\r\n void request.then((value) => {\r\n cleanup()\r\n if (cancelled || signal.aborted) {\r\n for (const track of value.getTracks()) track.stop()\r\n return\r\n }\r\n resolve(value)\r\n }, (cause: unknown) => {\r\n cleanup()\r\n if (!cancelled) reject(errorOf(cause))\r\n })\r\n })\r\n}\r\n\r\nfunction abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {\r\n if (signal.aborted) return Promise.reject(new Error('voice connection opening aborted'))\r\n return new Promise((resolve, reject) => {\r\n let cancelled = false\r\n const cleanup = (): void => { signal.removeEventListener('abort', aborted) }\r\n const aborted = (): void => {\r\n cleanup()\r\n cancelled = true\r\n reject(new Error('voice connection opening aborted'))\r\n }\r\n signal.addEventListener('abort', aborted, { once: true })\r\n void operation.then((value) => {\r\n cleanup()\r\n if (!cancelled) resolve(value)\r\n }, (cause: unknown) => {\r\n cleanup()\r\n if (!cancelled) reject(errorOf(cause))\r\n })\r\n })\r\n}\r\n\r\nfunction socketOpened(socket: WebSocket, signal: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const cleanup = (): void => {\r\n socket.removeEventListener('open', opened)\r\n socket.removeEventListener('error', failed)\r\n signal.removeEventListener('abort', aborted)\r\n }\r\n const opened = (): void => { cleanup(); resolve() }\r\n const failed = (): void => { cleanup(); reject(new Error('voice websocket failed')) }\r\n const aborted = (): void => { cleanup(); reject(new Error('voice connection opening aborted')) }\r\n if (signal.aborted) { aborted(); return }\r\n socket.addEventListener('open', opened, { once: true })\r\n socket.addEventListener('error', failed, { once: true })\r\n signal.addEventListener('abort', aborted, { once: true })\r\n })\r\n}\r\n\r\nfunction socketReady(socket: WebSocket, signal: AbortSignal): Promise<VoiceReadyEvent> {\r\n return new Promise((resolve, reject) => {\r\n const cleanup = (): void => {\r\n socket.removeEventListener('message', received)\r\n socket.removeEventListener('error', failed)\r\n socket.removeEventListener('close', closed)\r\n signal.removeEventListener('abort', aborted)\r\n }\r\n const settle = (operation: () => void): void => { cleanup(); operation() }\r\n const received = (message: MessageEvent): void => {\r\n try {\r\n const value: unknown = message.data instanceof ArrayBuffer\r\n ? undefined\r\n : JSON.parse(String(message.data)) as unknown\r\n settle(() => { resolve(parseReady(value)) })\r\n } catch (cause) {\r\n settle(() => { reject(errorOf(cause)) })\r\n }\r\n }\r\n const failed = (): void => { settle(() => { reject(new Error('voice websocket failed before ready')) }) }\r\n const closed = (): void => { settle(() => { reject(new Error('voice websocket closed before ready')) }) }\r\n const aborted = (): void => { settle(() => { reject(new Error('voice connection opening aborted')) }) }\r\n if (signal.aborted) { aborted(); return }\r\n if (socket.readyState !== WebSocket.OPEN) { closed(); return }\r\n socket.addEventListener('message', received)\r\n socket.addEventListener('error', failed, { once: true })\r\n socket.addEventListener('close', closed, { once: true })\r\n signal.addEventListener('abort', aborted, { once: true })\r\n })\r\n}\r\n\r\nfunction openingStopped(signal: AbortSignal, stopped: boolean): boolean {\r\n return signal.aborted || stopped\r\n}\r\n\r\nfunction errorOf(cause: unknown): Error {\r\n return cause instanceof Error ? cause : new Error(String(cause))\r\n}\r\n\r\nfunction parseReady(value: unknown): VoiceReadyEvent {\r\n const event = recordOf(value)\r\n if (event?.type === 'error') {\r\n const message = stringOf(event.message)\r\n throw new Error(message === '' ? 'voice setup failed before ready' : message)\r\n }\r\n if (event?.type !== 'ready') throw new Error('voice websocket expected a ready event')\r\n const voiceSessionId = stringOf(event.voiceSessionId)\r\n if (voiceSessionId === '') throw new Error('voice websocket ready event has no voiceSessionId')\r\n const audio = recordOf(event.audio)\r\n if (audio?.inputSampleRate !== 16_000\r\n || (audio.outputSampleRate !== 24_000 && audio.outputSampleRate !== 48_000)\r\n || (audio.format !== 'pcm_s16le' && audio.format !== 'audio_mpeg')) {\r\n throw new Error('voice websocket ready event has unsupported audio settings')\r\n }\r\n if (event.interactionMode !== 'speech-shell' && event.interactionMode !== 'frontend-agent') {\r\n throw new Error('voice websocket ready event has an invalid interactionMode')\r\n }\r\n return { type: 'ready', voiceSessionId, audio: {\r\n inputSampleRate: audio.inputSampleRate,\r\n outputSampleRate: audio.outputSampleRate,\r\n format: audio.format,\r\n }, interactionMode: event.interactionMode }\r\n}\r\n\r\n/**\r\n * Resolve the dedicated WebSocket URL for one durable Voice Session.\r\n * @param sessionId - durable voice session id.\r\n * @param location - browser location used for host and protocol defaults.\r\n * @returns the /voice WebSocket URL carrying the session id.\r\n */\r\nexport function voiceUrl(sessionId: string, location: Pick<Location, 'href' | 'protocol'> = window.location): URL {\r\n const url = new URL('/voice', location.href)\r\n url.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'\r\n url.searchParams.set('sessionId', sessionId)\r\n return url\r\n}\r\n\r\nconst CAPTURE_WORKLET = `\r\nclass DshPcmCaptureProcessor extends AudioWorkletProcessor {\r\n process(inputs) {\r\n const channel = inputs[0] && inputs[0][0]\r\n if (channel) this.port.postMessage(channel.slice())\r\n return true\r\n }\r\n}\r\nregisterProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)\r\n`\r\n\r\n/**\r\n * Resample browser float PCM into the provider input rate.\r\n * @param input - float samples at `fromRate`.\r\n * @param fromRate - source sample rate in Hz.\r\n * @param toRate - target sample rate in Hz.\r\n * @returns linearly interpolated samples at `toRate`.\r\n */\r\nexport function resample(input: Float32Array, fromRate: number, toRate: number): number[] {\r\n if (fromRate === toRate) return [...input]\r\n const ratio = fromRate / toRate\r\n const result = new Array<number>(Math.floor(input.length / ratio))\r\n for (let index = 0; index < result.length; index += 1) {\r\n const position = index * ratio\r\n const left = Math.floor(position)\r\n const fraction = position - left\r\n const leftSample = input[left] as number\r\n const rightSample = input[Math.min(left + 1, input.length - 1)] as number\r\n result[index] = leftSample * (1 - fraction) + rightSample * fraction\r\n }\r\n return result\r\n}\r\n\r\nfunction schedulePcm(\r\n context: AudioContext,\r\n bytes: ArrayBuffer,\r\n sampleRate: number,\r\n playing: Set<AudioBufferSourceNode>,\r\n ended: () => void,\r\n setPlayAt: (value: number) => void,\r\n currentPlayAt: number,\r\n): void {\r\n const pcm = new Int16Array(bytes)\r\n const buffer = context.createBuffer(1, pcm.length, sampleRate)\r\n const channel = buffer.getChannelData(0)\r\n for (const [index, sample] of pcm.entries()) channel[index] = sample / 32768\r\n const node = context.createBufferSource()\r\n node.buffer = buffer\r\n node.connect(context.destination)\r\n const start = Math.max(currentPlayAt, context.currentTime)\r\n setPlayAt(start + buffer.duration)\r\n playing.add(node)\r\n node.onended = () => { playing.delete(node); ended() }\r\n node.start(start)\r\n}\r\n\r\nasync function scheduleEncoded(\r\n context: AudioContext,\r\n bytes: ArrayBuffer,\r\n playing: Set<AudioBufferSourceNode>,\r\n ended: () => void,\r\n setPlayAt: (value: number) => void,\r\n getPlayAt: () => number,\r\n shouldSchedule: () => boolean,\r\n): Promise<void> {\r\n const buffer = await context.decodeAudioData(bytes.slice(0))\r\n if (!shouldSchedule()) return\r\n const node = context.createBufferSource()\r\n node.buffer = buffer\r\n node.connect(context.destination)\r\n const start = Math.max(getPlayAt(), context.currentTime)\r\n setPlayAt(start + buffer.duration)\r\n playing.add(node)\r\n node.onended = () => { playing.delete(node); ended() }\r\n node.start(start)\r\n}\r\n","import {\r\n createSnapshotStore, type SessionId, type SnapshotStore,\r\n} from '@deepseek-ai/dsh-client-runtime/client'\r\n\r\nconst STORAGE_KEY = 'dsh.voice.history.v1'\r\n\r\n/** One locally indexed durable Voice Session. */\r\nexport interface VoiceHistoryEntry {\r\n readonly sessionId: SessionId\r\n readonly lastActiveAt: number\r\n}\r\n\r\n/** Browser-persistent Voice Session index. */\r\nexport interface VoiceHistorySnapshot {\r\n readonly entries: readonly VoiceHistoryEntry[]\r\n}\r\n\r\ninterface StoredVoiceHistory {\r\n readonly version: 1\r\n readonly entries: readonly VoiceHistoryEntry[]\r\n}\r\n\r\nfunction parseEntry(input: unknown): VoiceHistoryEntry | undefined {\r\n if (typeof input !== 'object' || input === null) return undefined\r\n const value = input as Record<string, unknown>\r\n if (typeof value.sessionId !== 'string' || value.sessionId === '') return undefined\r\n if (typeof value.lastActiveAt !== 'number' || !Number.isFinite(value.lastActiveAt) || value.lastActiveAt < 0) {\r\n return undefined\r\n }\r\n return { sessionId: value.sessionId as SessionId, lastActiveAt: value.lastActiveAt }\r\n}\r\n\r\nfunction readHistory(storage: Storage | undefined): VoiceHistorySnapshot {\r\n if (storage === undefined) return { entries: [] }\r\n try {\r\n const raw = storage.getItem(STORAGE_KEY)\r\n if (raw === null) return { entries: [] }\r\n const parsed = JSON.parse(raw) as unknown\r\n if (typeof parsed !== 'object' || parsed === null) return { entries: [] }\r\n const value = parsed as Record<string, unknown>\r\n if (value.version !== 1 || !Array.isArray(value.entries)) return { entries: [] }\r\n const entries: VoiceHistoryEntry[] = []\r\n const seen = new Set<SessionId>()\r\n for (const candidate of value.entries) {\r\n const entry = parseEntry(candidate)\r\n if (entry === undefined || seen.has(entry.sessionId)) continue\r\n seen.add(entry.sessionId)\r\n entries.push(entry)\r\n }\r\n entries.sort((left, right) => right.lastActiveAt - left.lastActiveAt)\r\n return { entries }\r\n } catch (error: unknown) {\r\n console.error('voice history rehydration failed:', error)\r\n return { entries: [] }\r\n }\r\n}\r\n\r\n/** Own the local history index without adding Voice metadata to Host session projections. */\r\nexport class VoiceHistoryStore {\r\n readonly snapshot: SnapshotStore<VoiceHistorySnapshot>\r\n private readonly storage: Storage | undefined\r\n private readonly unsubscribe: () => void\r\n\r\n constructor(storage: Storage | undefined = typeof localStorage === 'undefined' ? undefined : localStorage) {\r\n this.storage = storage\r\n this.snapshot = createSnapshotStore(readHistory(storage))\r\n this.unsubscribe = this.snapshot.subscribe(() => { this.persist() })\r\n }\r\n\r\n /** Move one successfully opened Voice Session to the front of the index. */\r\n record(sessionId: SessionId, lastActiveAt = Date.now()): void {\r\n this.snapshot.set({\r\n entries: [\r\n { sessionId, lastActiveAt },\r\n ...this.snapshot.getSnapshot().entries.filter(entry => entry.sessionId !== sessionId),\r\n ],\r\n })\r\n }\r\n\r\n /** Stop persistence notifications. */\r\n dispose(): void {\r\n this.unsubscribe()\r\n }\r\n\r\n private persist(): void {\r\n if (this.storage === undefined) return\r\n const value: StoredVoiceHistory = { version: 1, entries: this.snapshot.getSnapshot().entries }\r\n try {\r\n this.storage.setItem(STORAGE_KEY, JSON.stringify(value))\r\n } catch (error: unknown) {\r\n console.error('voice history persistence failed:', error)\r\n }\r\n }\r\n}\r\n","import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\n\r\ninterface VoiceTextInputState {\r\n readonly draft: string\r\n readonly imageIds: readonly unknown[]\r\n}\r\n\r\nexport interface VoiceTextInput {\r\n readonly state: { getSnapshot(): VoiceTextInputState }\r\n setDraft(text: string): void\r\n submit(mode?: unknown): void\r\n notify(level: 'info' | 'error', text: string): void\r\n}\r\n\r\ninterface VoiceTextController {\r\n getSnapshot(): VoiceClientSnapshot\r\n submitText(text: string): void\r\n}\r\n\r\ninterface PatchedInput {\r\n readonly input: VoiceTextInput\r\n readonly originalOwnDescriptor: PropertyDescriptor | undefined\r\n readonly patchedSubmit: (mode?: unknown) => void\r\n}\r\n\r\n/** Routes the native composer through Voice while preserving its ordinary offline submission. */\r\nexport class VoiceTextSubmitBridge {\r\n private readonly patched = new Map<SessionId, PatchedInput>()\r\n\r\n constructor(\r\n private readonly voice: VoiceTextController,\r\n private readonly resolveInput: (sessionId: SessionId) => VoiceTextInput,\r\n ) {}\r\n\r\n install(sessionId: SessionId): void {\r\n if (this.patched.has(sessionId)) return\r\n const input = this.resolveInput(sessionId)\r\n const originalOwnDescriptor = Object.getOwnPropertyDescriptor(input, 'submit')\r\n const originalSubmit = input.submit.bind(input)\r\n const patchedSubmit = (mode?: unknown): void => {\r\n const voice = this.voice.getSnapshot()\r\n const state = input.state.getSnapshot()\r\n const active = voice.sessionId === sessionId\r\n && voice.state !== 'off'\r\n && voice.state !== 'error'\r\n if (!active || state.draft.trim() === '' || state.imageIds.length > 0) {\r\n originalSubmit(mode)\r\n return\r\n }\r\n try {\r\n this.voice.submitText(state.draft)\r\n input.setDraft('')\r\n } catch (cause: unknown) {\r\n input.notify('error', cause instanceof Error ? cause.message : String(cause))\r\n }\r\n }\r\n input.submit = patchedSubmit\r\n this.patched.set(sessionId, { input, originalOwnDescriptor, patchedSubmit })\r\n }\r\n\r\n dispose(): void {\r\n for (const { input, originalOwnDescriptor, patchedSubmit } of this.patched.values()) {\r\n if (input.submit !== patchedSubmit) continue\r\n if (originalOwnDescriptor === undefined) delete (input as Partial<VoiceTextInput>).submit\r\n else Object.defineProperty(input, 'submit', originalOwnDescriptor)\r\n }\r\n this.patched.clear()\r\n }\r\n}\r\n","import type {\r\n ChatConversationViewNode, ConversationNodeDefinition, SessionId,\r\n} from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type {\r\n TaskObservation, VoiceTaskId, VoiceUtteranceId,\r\n} from '@flowingspring/dsh-voice'\n\r\n/** Renderer-ready durable Voice utterance. */\r\nexport interface VoiceUtteranceChatData {\r\n readonly utteranceId: VoiceUtteranceId\r\n readonly role: 'user' | 'assistant'\r\n readonly text?: string\r\n readonly state: 'streaming' | 'completed' | 'interrupted'\r\n}\r\n\r\n/** Renderer-ready delegated task link and its latest observation. */\r\nexport interface VoiceDelegationChatData {\r\n readonly taskId: VoiceTaskId\r\n readonly taskSessionId: SessionId\r\n readonly input: string\r\n readonly status: TaskObservation['status']\r\n readonly update?: string\r\n}\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-conversation/client' {\r\n interface ChatNodeDataMap {\r\n /** One user ASR or assistant TTS transcript produced in Voice Mode. */\r\n 'voice-utterance': VoiceUtteranceChatData\r\n /** One independent DSH task delegated by the Voice frontend Agent. */\r\n 'voice-delegation': VoiceDelegationChatData\r\n }\r\n}\r\n\r\ntype VoiceUtteranceState = VoiceUtteranceChatData\r\ntype VoiceDelegationState = VoiceDelegationChatData\r\n\r\n/** Fold one streamed utterance into a stable Chat node. */\r\nexport const voiceUtteranceDefinition: ConversationNodeDefinition<VoiceUtteranceState> = {\r\n kind: 'voice-utterance',\r\n target: 'chat',\r\n match: (event) => {\r\n if (event.type === 'voice/utterance-start') {\r\n return { id: String(event.data.utteranceId), role: 'start' }\r\n }\r\n if (event.type === 'voice/utterance-end') {\r\n return { id: String(event.data.utteranceId), role: 'update' }\r\n }\r\n return null\r\n },\r\n start: (_context, match) => {\r\n if (match.event.type !== 'voice/utterance-start') {\r\n throw new Error('voice utterance start requires voice/utterance-start')\r\n }\r\n return {\r\n utteranceId: match.event.data.utteranceId,\r\n role: match.event.data.role,\r\n state: 'streaming',\r\n }\r\n },\r\n update: (context, match) => {\r\n if (match.event.type !== 'voice/utterance-end') return context.state\r\n return {\r\n ...context.state,\r\n text: match.event.data.text,\r\n state: match.event.data.state,\r\n }\r\n },\r\n buildViewNode: (context): ChatConversationViewNode | null => {\r\n if (context.start === undefined || context.state === undefined) return null\r\n const emptyInterrupted = context.state.state === 'interrupted'\r\n && (context.state.text?.trim() ?? '') === ''\r\n return {\r\n key: context.key,\r\n kind: 'voice-utterance',\r\n id: context.id,\r\n target: 'chat',\r\n anchorSeq: context.start.event.seq,\r\n location: context.start.location,\r\n // A live start may already be materialized before an empty noise segment\r\n // settles. Conversation nodes cannot be withdrawn after publication, so\r\n // keep its stable identity and hide it instead.\r\n visibility: emptyInterrupted ? 'hidden' : 'visible',\r\n data: context.state as VoiceUtteranceState,\r\n }\r\n },\r\n}\r\n\r\n/** Fold a delegated task and all observations into one navigation card. */\r\nexport const voiceDelegationDefinition: ConversationNodeDefinition<VoiceDelegationState> = {\r\n kind: 'voice-delegation',\r\n target: 'chat',\r\n match: (event) => {\r\n if (event.type === 'voice/task-delegated') {\r\n return { id: String(event.data.taskId), role: 'start' }\r\n }\r\n if (event.type === 'voice/task-observation') {\r\n return { id: String(event.data.taskId), role: 'update' }\r\n }\r\n return null\r\n },\r\n start: (_context, match) => {\r\n if (match.event.type !== 'voice/task-delegated') {\r\n throw new Error('voice delegation start requires voice/task-delegated')\r\n }\r\n return {\r\n taskId: match.event.data.taskId,\r\n taskSessionId: match.event.data.taskSessionId,\r\n input: match.event.data.input,\r\n status: 'accepted',\r\n }\r\n },\r\n update: (context, match) => {\r\n if (match.event.type !== 'voice/task-observation') return context.state\r\n const observation = match.event.data\r\n const update = observation.voiceMessage?.text\r\n ?? observation.announcement\r\n ?? observation.reason\r\n ?? context.state.update\r\n return {\r\n ...context.state,\r\n status: observation.status,\r\n ...(update === undefined ? {} : { update }),\r\n }\r\n },\r\n buildViewNode: (context): ChatConversationViewNode | null => {\r\n if (context.start === undefined) return null\r\n return {\r\n key: context.key,\r\n kind: 'voice-delegation',\r\n id: context.id,\r\n target: 'chat',\r\n anchorSeq: context.start.event.seq,\r\n location: context.start.location,\r\n visibility: 'visible',\r\n data: context.state as VoiceDelegationState,\r\n }\r\n },\r\n}\r\n","/** `voice` namespace dictionaries. */\r\n\r\n/** Dictionary namespace owned by this plugin. */\r\nexport const NS = 'voice'\r\n\r\n/** Simplified Chinese dictionary (the key-set source of truth). */\r\nexport const zh = {\r\n 'badge.voice': 'VOICE',\r\n 'control.start': '开始语音对话',\r\n 'control.retry': '语音连接失败,点击重试',\r\n 'control.stop': '结束语音对话',\r\n 'overlay.connecting': '正在连接语音',\r\n 'overlay.listening': '正在聆听',\r\n 'overlay.speaking': '正在播放回复',\r\n 'overlay.error': '语音连接失败',\r\n 'overlay.return': '返回语音对话',\r\n 'overlay.stop': '结束',\r\n 'history.open': '打开语音历史',\r\n 'history.label': '语音历史',\r\n 'history.title': '语音对话',\r\n 'history.close': '关闭语音历史',\r\n 'history.empty': '还没有语音对话',\r\n 'history.active': '通话中',\r\n 'history.saved': '已保存',\r\n 'utterance.user.pending': '正在聆听…',\r\n 'utterance.assistant.pending': '正在生成语音回复…',\r\n 'utterance.interrupted': '已打断',\r\n 'task.title': '后台处理',\r\n 'task.status.accepted': '已接受',\r\n 'task.status.queued': '排队中',\r\n 'task.status.running': '执行中',\r\n 'task.status.waiting-user': '等待确认',\r\n 'task.status.completed': '已完成',\r\n 'task.status.failed': '失败',\r\n 'task.status.cancelled': '已取消',\r\n 'task.status.interrupted': '服务中断',\r\n 'task.expand': '展开后台处理详情',\r\n 'task.collapse': '收起后台处理详情',\r\n 'task.cancel': '取消任务',\r\n 'task.open': '查看后台详情',\r\n} satisfies Record<string, string>\r\n\r\n/** Union of this namespace's dictionary keys. */\r\nexport type VoiceKey = keyof typeof zh\r\n\r\n/** English dictionary, checked complete against the zh key set. */\r\nexport const en = {\r\n 'badge.voice': 'VOICE',\r\n 'control.start': 'Start voice conversation',\r\n 'control.retry': 'Voice connection failed, press to retry',\r\n 'control.stop': 'End voice conversation',\r\n 'overlay.connecting': 'Connecting voice',\r\n 'overlay.listening': 'Listening',\r\n 'overlay.speaking': 'Playing response',\r\n 'overlay.error': 'Voice connection failed',\r\n 'overlay.return': 'Return to voice conversation',\r\n 'overlay.stop': 'End',\r\n 'history.open': 'Open voice history',\r\n 'history.label': 'Voice history',\r\n 'history.title': 'Voice conversations',\r\n 'history.close': 'Close voice history',\r\n 'history.empty': 'No voice conversations yet',\r\n 'history.active': 'Active',\r\n 'history.saved': 'Saved',\r\n 'utterance.user.pending': 'Listening…',\r\n 'utterance.assistant.pending': 'Generating voice response…',\r\n 'utterance.interrupted': 'Interrupted',\r\n 'task.title': 'Background task',\r\n 'task.status.accepted': 'Accepted',\r\n 'task.status.queued': 'Queued',\r\n 'task.status.running': 'Running',\r\n 'task.status.waiting-user': 'Waiting for you',\r\n 'task.status.completed': 'Completed',\r\n 'task.status.failed': 'Failed',\r\n 'task.status.cancelled': 'Cancelled',\r\n 'task.status.interrupted': 'Interrupted',\r\n 'task.expand': 'Show background task details',\r\n 'task.collapse': 'Hide background task details',\r\n 'task.cancel': 'Cancel task',\r\n 'task.open': 'View background details',\r\n} satisfies Record<VoiceKey, string>\r\n","/** Browser voice UI assembly. @module @flowingspring/dsh-client-ui-voice/client */\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\r\nimport { VoiceControl, type VoiceControlInjected } from './VoiceControl.tsx'\r\nimport { VoiceHistoryAction, type VoiceHistoryActionInjected } from './VoiceHistoryAction.tsx'\r\nimport {\r\n VoiceDelegationView, type VoiceDelegationInjected,\r\n VoiceUtteranceView, type VoiceUtteranceInjected,\r\n} from './VoiceNodeViews.tsx'\r\nimport { VoiceOverlay, type VoiceOverlayInjected } from './VoiceOverlay.tsx'\r\nimport { VoiceController } from './voice-controller.ts'\r\nimport { VoiceHistoryStore } from './voice-history.ts'\r\nimport { VoiceTextSubmitBridge, type VoiceTextInput } from './voice-text-submit.ts'\r\nimport { voiceDelegationDefinition, voiceUtteranceDefinition } from './voice-definitions.ts'\r\nimport { en, NS, type VoiceKey, zh } from './locales.ts'\r\n\r\nexport type { VoiceKey } from './locales.ts'\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\r\n interface LocaleNamespaceMap {\r\n /** Browser Voice conversation copy. */\r\n voice: VoiceKey\r\n }\r\n}\r\n\r\nexport const inject = ['conversation', 'conversationEvents', 'slots', 'sessions', 'locale']\r\n\r\n/** Mount root transport controls and durable Voice conversation renderers. @param ctx - browser context. */\r\nexport function apply(ctx: ClientContext): void {\r\n const controller = new VoiceController()\r\n const history = new VoiceHistoryStore()\r\n const textSubmit = new VoiceTextSubmitBridge(controller, (sessionId) => {\r\n const scope = ctx.sessions.scope(sessionId)\r\n if (scope === undefined) throw new Error(`voice input session \"${sessionId}\" is unavailable`)\r\n return ctx.conversation.input.for(scope) as VoiceTextInput\r\n })\r\n const stopVoice = (): Promise<void> => controller.stop()\r\n const startVoice = async (sourceSessionId: SessionId): Promise<void> => {\r\n ctx.sessions.open(sourceSessionId)\r\n textSubmit.install(sourceSessionId)\r\n await controller.start(sourceSessionId)\r\n history.record(sourceSessionId)\r\n }\r\n const voiceControlInjected = (): VoiceControlInjected => ({\r\n hooks: { voice: controller },\r\n startVoice,\r\n retryVoice: async () => {\r\n await controller.retry()\r\n const sessionId = controller.getSnapshot().sessionId\r\n if (sessionId !== undefined) history.record(sessionId)\r\n },\r\n stopVoice,\r\n })\r\n const voiceOverlayInjected = (): VoiceOverlayInjected => ({\r\n hooks: { voice: controller },\r\n openVoiceSession: (id) => { ctx.sessions.open(id) },\r\n stopVoice,\r\n })\r\n const utteranceInjected = (): VoiceUtteranceInjected => ({ hooks: { voice: controller } })\r\n const delegationInjected = (): VoiceDelegationInjected => ({\r\n hooks: { voice: controller },\r\n openSession: (id) => { ctx.sessions.open(id) },\r\n cancelTask: (id) => { controller.cancelTask(id) },\r\n })\r\n const historyInjected = (): VoiceHistoryActionInjected => ({\r\n hooks: { voice: controller, voiceHistory: history.snapshot },\r\n openSession: (id) => {\r\n history.record(id)\r\n ctx.sessions.open(id)\r\n },\r\n })\r\n\r\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-voice: dictionaries')\r\n ctx.effect(() => async () => { await controller.stop() }, 'ui-voice: transport teardown')\r\n ctx.effect(() => () => { textSubmit.dispose() }, 'ui-voice: text submit bridge teardown')\r\n ctx.effect(() => () => { history.dispose() }, 'ui-voice: history persistence teardown')\r\n ctx.conversationEvents.register(voiceUtteranceDefinition)\r\n ctx.conversationEvents.register(voiceDelegationDefinition)\r\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\r\n name: 'conversation.input.right',\r\n id: 'voice',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceControlInjected,\r\n }, VoiceControl))\r\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\r\n name: 'shell.overlay',\r\n id: 'voice-active',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceOverlayInjected,\r\n }, VoiceOverlay))\r\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({\r\n name: 'sidebar.footer.action',\r\n id: 'voice-history',\r\n order: 10,\r\n locale: NS,\r\n inject: historyInjected,\r\n }, VoiceHistoryAction))\r\n ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({\r\n name: 'conversation.chat.node',\r\n key: 'voice-utterance',\r\n locale: NS,\r\n inject: utteranceInjected,\r\n }, VoiceUtteranceView))\r\n ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({\r\n name: 'conversation.chat.node',\r\n key: 'voice-delegation',\r\n locale: NS,\r\n inject: delegationInjected,\r\n }, VoiceDelegationView))\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["css","useState","css","css","useState","css","createSnapshotStore"],"sources":["../../../packages/ui-voice/src/client/VoiceControl.tsx","../../../packages/ui-voice/src/client/VoiceHistoryAction.tsx","../../../packages/ui-voice/src/client/VoiceNodeViews.tsx","../../../packages/ui-voice/src/client/VoiceOverlay.tsx","../../../packages/ui-voice/src/client/voice-controller.ts","../../../packages/ui-voice/src/client/voice-history.ts","../../../packages/ui-voice/src/client/voice-text-submit.ts","../../../packages/ui-voice/src/client/voice-definitions.ts","../../../packages/ui-voice/src/client/locales.ts","../../../packages/ui-voice/src/client/index.ts"],"sourcesContent":["import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport css from './VoiceControl.module.css'\r\n\r\n/** Business callbacks and the root-owned transport snapshot. */\r\nexport interface VoiceControlInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n /** Start a fresh Voice conversation located from the current Session. */\r\n readonly startVoice: (sourceSessionId: SessionId) => Promise<void>\r\n readonly retryVoice: () => Promise<void>\r\n readonly stopVoice: () => Promise<void>\r\n}\r\n\r\nexport type VoiceControlProps =\r\n PropsRuntime<'conversation.input.right'> & InjectFace<VoiceControlInjected> & PropsLocale<'voice'>\r\n\r\n/** Start a fresh Voice Session from this Session's location or control the active connection. */\r\nexport function VoiceControl({\r\n sessionId, useVoice, startVoice, retryVoice, stopVoice, t,\r\n}: VoiceControlProps) {\r\n const state = useVoice(snapshot => snapshot.state)\r\n const activeSessionId = useVoice(snapshot => snapshot.sessionId)\r\n\r\n const toggle = async (): Promise<void> => {\r\n if (state === 'error' && activeSessionId !== undefined) {\r\n try { await retryVoice() } catch { /* controller owns the visible error state */ }\r\n return\r\n }\r\n if (state !== 'off' && state !== 'error') {\r\n await stopVoice()\r\n return\r\n }\r\n try {\r\n await startVoice(sessionId)\r\n } catch { /* controller owns the visible error state */ }\r\n }\r\n\r\n const label = state === 'off'\r\n ? t('control.start')\r\n : state === 'error'\r\n ? t('control.retry')\r\n : t('control.stop')\r\n return (\r\n <button\r\n type=\"button\"\r\n className={`${css.button} ${state !== 'off' ? css.active : ''}`}\r\n aria-label={label}\r\n title={label}\r\n data-state={state}\r\n onClick={() => { void toggle() }}\r\n >\r\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\r\n <rect x=\"5\" y=\"2\" width=\"6\" height=\"8\" rx=\"3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\r\n <path d=\"M3.5 7.5a4.5 4.5 0 0 0 9 0M8 12v2M5.5 14h5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\r\n </svg>\r\n </button>\r\n )\r\n}\r\n","import { useEffect, useState } from 'react'\r\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport type { VoiceHistorySnapshot } from './voice-history.ts'\r\nimport css from './VoiceHistoryAction.module.css'\r\n\r\n/** History state and navigation owned by the Voice UI plugin. */\r\nexport interface VoiceHistoryActionInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n voiceHistory: { getSnapshot(): VoiceHistorySnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openSession: (id: SessionId) => void\r\n}\r\n\r\nexport type VoiceHistoryActionProps =\r\n PropsRuntime<'sidebar.footer.action'> & InjectFace<VoiceHistoryActionInjected> & PropsLocale<'voice'>\r\n\r\n/** Render the plugin-owned Voice history entry and its Session navigation panel. */\r\nexport function VoiceHistoryAction({\r\n wide, useSessions, useVoice, useVoiceHistory, openSession, t,\r\n}: VoiceHistoryActionProps) {\r\n const [open, setOpen] = useState(false)\r\n const sessions = useSessions(state => state)\r\n const entries = useVoiceHistory(state => state.entries)\r\n const activeSessionId = useVoice(state => state.sessionId)\r\n const available = entries.flatMap((entry) => {\r\n const session = sessions.byId[entry.sessionId]\r\n return session === undefined ? [] : [{ entry, session }]\r\n })\r\n\r\n useEffect(() => {\r\n if (!open) return\r\n const close = (event: KeyboardEvent): void => { if (event.key === 'Escape') setOpen(false) }\r\n document.addEventListener('keydown', close)\r\n return () => { document.removeEventListener('keydown', close) }\r\n }, [open])\r\n\r\n return (\r\n <>\r\n <button\r\n type=\"button\"\r\n className={css.trigger}\r\n aria-label={t('history.open')}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n onClick={() => { setOpen(value => !value) }}\r\n >\r\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\r\n <path d=\"M3 8a5 5 0 1 0 1.5-3.55M3 2.7v2.8h2.8M8 5.2V8l2 1.2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.35\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\r\n </svg>\r\n {wide && <span>{t('history.label')}</span>}\r\n </button>\r\n {open && (\r\n <section className={css.panel} role=\"dialog\" aria-label={t('history.title')}>\r\n <header className={css.header}>\r\n <strong>{t('history.title')}</strong>\r\n <button type=\"button\" className={css.close} aria-label={t('history.close')} onClick={() => { setOpen(false) }}>×</button>\r\n </header>\r\n <div className={css.list}>\r\n {available.length === 0 && <p className={css.empty}>{t('history.empty')}</p>}\r\n {available.map(({ entry, session }) => (\r\n <button\r\n key={entry.sessionId}\r\n type=\"button\"\r\n className={css.row}\r\n data-active={entry.sessionId === activeSessionId || undefined}\r\n onClick={() => {\r\n openSession(entry.sessionId)\r\n setOpen(false)\r\n }}\r\n >\r\n <span className={css.wave} aria-hidden><i /><i /><i /></span>\r\n <span className={css.rowText}>\r\n <span className={css.title}>{session.displayTitle}</span>\r\n <span className={css.meta}>{entry.sessionId === activeSessionId ? t('history.active') : t('history.saved')}</span>\r\n </span>\r\n </button>\r\n ))}\r\n </div>\r\n </section>\r\n )}\r\n </>\r\n )\r\n}\r\n","import { useState } from 'react'\r\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport type { VoiceDelegationChatData } from './voice-definitions.ts'\r\nimport type { VoiceKey } from './locales.ts'\r\nimport css from './VoiceNodeViews.module.css'\r\n\r\n/** Live transcript source injected into the Voice utterance renderer. */\r\nexport interface VoiceUtteranceInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n}\r\n\r\n/** Navigation action injected into the task-card renderer. */\r\nexport interface VoiceDelegationInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openSession: (id: SessionId) => void\r\n readonly cancelTask: (id: string) => void\r\n}\r\n\r\nexport type VoiceUtteranceViewProps =\r\n PropsRuntime<'conversation.chat.node', 'voice-utterance'>\r\n & InjectFace<VoiceUtteranceInjected>\r\n & PropsLocale<'voice'>\r\nexport type VoiceDelegationViewProps =\r\n PropsRuntime<'conversation.chat.node', 'voice-delegation'>\r\n & InjectFace<VoiceDelegationInjected>\r\n & PropsLocale<'voice'>\r\n\r\n/** Render a durable Voice utterance while replacing its unsettled text with live WebSocket deltas. */\r\nexport function VoiceUtteranceView({ node, sessionId, useVoice, t }: VoiceUtteranceViewProps) {\r\n const liveText = useVoice(snapshot => snapshot.sessionId === sessionId\r\n ? snapshot.textById[String(node.data.utteranceId)]?.text\r\n : undefined)\r\n const text = node.data.text ?? liveText ?? (node.data.role === 'user'\r\n ? t('utterance.user.pending')\r\n : t('utterance.assistant.pending'))\r\n return (\r\n <article\r\n className={css.utterance}\r\n data-voice-utterance\r\n data-role={node.data.role}\r\n data-state={node.data.state}\r\n >\r\n <div className={css.bubble}>{text}</div>\r\n <div className={css.meta}>\r\n <span className={css.voiceBadge}>\r\n <span className={css.miniWave} aria-hidden><i /><i /><i /></span>\r\n {t('badge.voice')}\r\n </span>\r\n {node.data.state === 'interrupted' && <span>{t('utterance.interrupted')}</span>}\r\n </div>\r\n </article>\r\n )\r\n}\r\n\r\nconst STATUS_KEYS: Record<VoiceDelegationChatData['status'], VoiceKey> = {\r\n accepted: 'task.status.accepted',\r\n queued: 'task.status.queued',\r\n running: 'task.status.running',\r\n 'waiting-user': 'task.status.waiting-user',\r\n completed: 'task.status.completed',\r\n failed: 'task.status.failed',\r\n cancelled: 'task.status.cancelled',\r\n interrupted: 'task.status.interrupted',\r\n}\r\n\r\n/** Render the task link from a Voice Session to its bound background Agent Session. */\r\nexport function VoiceDelegationView({ node, sessionId, useSessions, useVoice, openSession, cancelTask, t }: VoiceDelegationViewProps) {\r\n const [expanded, setExpanded] = useState(false)\r\n const navigable = useSessions(sessions => sessions.ids.includes(node.data.taskSessionId))\r\n const connected = useVoice(voice => voice.sessionId === sessionId && voice.state !== 'off' && voice.state !== 'error')\r\n const cancellable = connected && !isTerminalTaskStatus(node.data.status)\r\n return (\r\n <article\r\n className={css.taskCard}\r\n data-voice-delegation\r\n data-status={node.data.status}\r\n data-expanded={expanded}\r\n >\r\n <button\r\n type=\"button\"\r\n className={css.taskSummary}\r\n aria-expanded={expanded}\r\n aria-label={expanded ? t('task.collapse') : t('task.expand')}\r\n onClick={() => { setExpanded(value => !value) }}\r\n >\r\n <span className={css.taskDot} aria-hidden />\r\n <span className={css.taskTitle}>{t('task.title')}</span>\r\n <span className={css.taskStatus}>{t(STATUS_KEYS[node.data.status])}</span>\r\n <span className={css.taskChevron} aria-hidden />\r\n </button>\r\n {expanded && (\r\n <div className={css.taskDetails}>\r\n <p className={css.taskInput}>{node.data.input}</p>\r\n {node.data.update !== undefined && <p className={css.taskUpdate}>{node.data.update}</p>}\r\n <div className={css.taskActions}>\r\n {cancellable && (\r\n <button type=\"button\" className={`${css.taskLink} ${css.taskCancel}`} onClick={() => { cancelTask(String(node.data.taskId)) }}>\r\n {t('task.cancel')}\r\n </button>\r\n )}\r\n <button\r\n type=\"button\"\r\n className={css.taskLink}\r\n disabled={!navigable}\r\n onClick={() => { openSession(node.data.taskSessionId) }}\r\n >\r\n {t('task.open')}\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </article>\r\n )\r\n}\r\n\r\nfunction isTerminalTaskStatus(status: VoiceDelegationChatData['status']): boolean {\r\n return status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'interrupted'\r\n}\r\n","import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot, VoiceClientState } from './voice-controller.ts'\r\nimport type { VoiceKey } from './locales.ts'\r\nimport css from './VoiceOverlay.module.css'\r\n\r\n/** Root-level voice connection actions. */\r\nexport interface VoiceOverlayInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openVoiceSession: (id: SessionId) => void\r\n readonly stopVoice: () => Promise<void>\r\n}\r\n\r\nexport type VoiceOverlayProps =\r\n PropsRuntime<'shell.overlay'> & InjectFace<VoiceOverlayInjected> & PropsLocale<'voice'>\r\n\r\nconst STATE_KEYS: Record<Exclude<VoiceClientState, 'off'>, VoiceKey> = {\r\n connecting: 'overlay.connecting',\r\n listening: 'overlay.listening',\r\n speaking: 'overlay.speaking',\r\n error: 'overlay.error',\r\n}\r\n\r\n/** Keep the active Voice Session controllable while another Session is selected. */\r\nexport function VoiceOverlay({ useVoice, useSessions, openVoiceSession, stopVoice, t }: VoiceOverlayProps) {\r\n const voice = useVoice(snapshot => snapshot)\r\n const navigable = useSessions(sessions => voice.sessionId !== undefined && sessions.ids.includes(voice.sessionId))\r\n if (voice.state === 'off') return null\r\n return (\r\n <aside className={css.root} data-voice-overlay data-state={voice.state} aria-live=\"polite\">\r\n <span className={css.wave} aria-hidden>\r\n <i /><i /><i /><i />\r\n </span>\r\n <span className={css.status}>{t(STATE_KEYS[voice.state])}</span>\r\n {voice.sessionId !== undefined && (\r\n <button\r\n type=\"button\"\r\n className={css.link}\r\n disabled={!navigable}\r\n onClick={() => { openVoiceSession(voice.sessionId as SessionId) }}\r\n >\r\n {t('overlay.return')}\r\n </button>\r\n )}\r\n <button type=\"button\" className={css.stop} onClick={() => { void stopVoice() }}>\r\n {t('overlay.stop')}\r\n </button>\r\n </aside>\r\n )\r\n}\r\n","import type { ObservableSnapshot, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\n\r\n/** Browser-visible lifecycle of the one active voice transport. */\r\nexport type VoiceClientState = 'off' | 'connecting' | 'listening' | 'speaking' | 'error'\r\n\r\n/** Live text retained until the matching durable utterance settles. */\r\nexport interface VoiceLiveText {\r\n readonly text: string\r\n}\r\n\r\n/** Root-owned voice transport projection shared by every UI seat. */\r\nexport interface VoiceClientSnapshot {\r\n readonly state: VoiceClientState\r\n readonly sessionId?: SessionId\r\n readonly textById: Readonly<Record<string, VoiceLiveText>>\r\n}\r\n\r\ninterface VoiceTransport {\r\n close(): Promise<void>\r\n cancelTask(taskId: string): void\r\n submitText(text: string): void\r\n}\r\n\r\ninterface VoiceTransportHandlers {\n readonly onBinary: (audio: ArrayBuffer) => void\r\n readonly onEvent: (event: unknown) => void\r\n readonly onPlaybackEnded: () => void\r\n readonly onUnexpectedClose: () => void\r\n}\n\nexport interface VoiceControllerOptions {\n /** Called once after the first non-empty durable voice turn is completed. */\n readonly onConversationStarted?: (sessionId: SessionId) => void\n}\n\r\ninterface VoiceReadyEvent {\r\n readonly type: 'ready'\r\n readonly voiceSessionId: string\r\n readonly audio: {\r\n readonly inputSampleRate: number\r\n readonly outputSampleRate: number\r\n readonly format: 'pcm_s16le' | 'audio_mpeg'\r\n }\r\n readonly interactionMode: 'speech-shell' | 'frontend-agent'\r\n}\r\n\r\nconst INITIAL_SNAPSHOT: VoiceClientSnapshot = { state: 'off', textById: {} }\r\n\r\n/** Own the single browser microphone, provider WebSocket, playback queue, and live transcript feed. */\r\nexport class VoiceController implements ObservableSnapshot<VoiceClientSnapshot> {\n private snapshot = INITIAL_SNAPSHOT\r\n private readonly listeners = new Set<() => void>()\r\n private transport: VoiceTransport | undefined\r\n private openingTransport: VoiceTransport | undefined\r\n private openingAbort: AbortController | undefined\r\n private generation = 0\r\n private frame: number | undefined\r\n private readonly pendingText: string[] = []\n private readonly onConversationStarted: ((sessionId: SessionId) => void) | undefined\n private conversationStarted = false\n\n constructor(options: VoiceControllerOptions = {}) {\n this.onConversationStarted = options.onConversationStarted\n }\n\r\n /** Return the identity-stable snapshot until a voice fact changes. */\r\n getSnapshot = (): VoiceClientSnapshot => this.snapshot\r\n\r\n /** Subscribe to state and frame-coalesced transcript changes. */\r\n subscribe = (listener: () => void): (() => void) => {\r\n this.listeners.add(listener)\r\n return () => { this.listeners.delete(listener) }\r\n }\r\n\r\n /**\r\n * Open one voice transport and make it the root-owned active session.\r\n * @param sessionId - durable voice session to connect.\r\n */\r\n async start(sessionId: SessionId): Promise<void> {\n const generation = ++this.generation\n this.pendingText.splice(0)\n this.conversationStarted = false\n // Construct and unlock both contexts before the first await so browser\r\n // autoplay policy still treats the microphone button as the user gesture.\r\n const contexts = createAudioContexts()\r\n await this.closeTransport()\r\n if (generation !== this.generation) {\r\n await closeAudioContexts(contexts)\r\n return\r\n }\r\n const openingAbort = new AbortController()\r\n this.openingAbort = openingAbort\r\n this.publish({ state: 'connecting', sessionId, textById: {} })\r\n try {\r\n const transport = await openVoiceTransport(String(sessionId), {\r\n onBinary: () => { this.setState('speaking') },\r\n onEvent: (event) => { this.handleEvent(event) },\r\n onPlaybackEnded: () => { this.setState('listening') },\r\n onUnexpectedClose: () => { this.handleUnexpectedClose() },\r\n }, openingAbort.signal, (opening) => {\r\n if (generation === this.generation) this.openingTransport = opening\r\n else void opening.close()\r\n }, contexts)\r\n if (generation !== this.generation) {\r\n await transport.close()\r\n return\r\n }\r\n this.openingTransport = undefined\r\n this.transport = transport\r\n for (const text of this.pendingText.splice(0)) transport.submitText(text)\r\n this.setState('listening')\r\n } catch (cause) {\r\n if (generation !== this.generation) return\r\n this.setState('error')\r\n throw cause\r\n } finally {\r\n if (this.openingAbort === openingAbort) this.openingAbort = undefined\r\n if (generation === this.generation) this.openingTransport = undefined\r\n }\r\n }\r\n\r\n /** Retry the failed active Voice Session, when one was created successfully. */\r\n async retry(): Promise<void> {\r\n const sessionId = this.snapshot.sessionId\r\n if (sessionId === undefined) throw new Error('voice retry requires an active session')\r\n await this.start(sessionId)\r\n }\r\n\r\n /** Cancel the exact active background task through the attached Voice transport. */\r\n cancelTask(taskId: string): void {\r\n if (taskId === '') throw new Error('voice task id is required')\r\n if (this.transport === undefined) throw new Error('voice task cancellation requires an active connection')\r\n this.transport.cancelTask(taskId)\r\n }\r\n\r\n /** Submit composer text as a voice turn, queueing it while the transport connects. */\r\n submitText(text: string): void {\r\n const value = text.trim()\r\n if (value === '') throw new Error('voice text submission requires non-empty text')\r\n if (this.snapshot.state === 'off' || this.snapshot.state === 'error' || this.snapshot.sessionId === undefined) {\r\n throw new Error('voice text submission requires an active connection')\r\n }\r\n if (this.transport === undefined) this.pendingText.push(value)\r\n else this.transport.submitText(value)\r\n }\r\n\r\n /** Stop capture/playback and await owned browser-resource teardown. */\r\n async stop(): Promise<void> {\r\n ++this.generation\r\n this.openingAbort?.abort()\r\n this.openingAbort = undefined\r\n this.publish(INITIAL_SNAPSHOT)\r\n this.pendingText.splice(0)\r\n await this.closeTransport()\r\n }\r\n\r\n private async closeTransport(): Promise<void> {\r\n const transports = new Set<VoiceTransport>()\r\n if (this.transport !== undefined) transports.add(this.transport)\r\n if (this.openingTransport !== undefined) transports.add(this.openingTransport)\r\n this.transport = undefined\r\n this.openingTransport = undefined\r\n if (this.frame !== undefined) {\r\n cancelAnimationFrame(this.frame)\r\n this.frame = undefined\r\n }\r\n const settled = await Promise.allSettled([...transports].map(transport => transport.close()))\r\n const failures: unknown[] = []\r\n for (const result of settled) {\r\n if (result.status === 'rejected') failures.push(result.reason as unknown)\r\n }\r\n if (failures.length > 0) throw new AggregateError(failures, 'voice transport cleanup failed')\r\n }\r\n\r\n private handleUnexpectedClose(): void {\r\n this.openingAbort?.abort()\r\n this.setState('error')\r\n void this.closeTransport().catch((cause: unknown) => {\r\n console.error('voice transport cleanup failed:', cause)\r\n })\r\n }\r\n\r\n private setState(state: VoiceClientState): void {\r\n if (this.snapshot.state === state) return\r\n this.publish({ ...this.snapshot, state })\r\n }\r\n\r\n private setText(id: string, text: string, cadence: 'immediate' | 'animation-frame'): void {\r\n if (id === '') return\r\n const current = this.snapshot.textById[id]\r\n if (current?.text === text) return\r\n this.snapshot = {\r\n ...this.snapshot,\r\n textById: { ...this.snapshot.textById, [id]: { text } },\r\n }\r\n if (cadence === 'immediate') this.notify()\r\n else this.notifyOnFrame()\r\n }\r\n\r\n private appendText(id: string, delta: string): void {\r\n if (id === '' || delta === '') return\r\n this.setText(id, `${this.snapshot.textById[id]?.text ?? ''}${delta}`, 'animation-frame')\r\n }\r\n\r\n private handleEvent(input: unknown): void {\r\n const event = recordOf(input)\r\n if (event === undefined) return\r\n const type = stringOf(event.type)\r\n if (type === 'transcription.started') {\r\n this.setText(stringOf(event.utteranceId), '', 'immediate')\r\n this.setState('listening')\r\n return\r\n }\r\n if (type === 'transcription.updated') {\r\n this.setText(stringOf(event.utteranceId), stringOf(event.text), 'animation-frame')\r\n return\r\n }\r\n if (type === 'transcription.completed') {\n const text = stringOf(event.text)\n this.setText(stringOf(event.utteranceId), text, 'immediate')\n if (text.trim() !== '') this.markConversationStarted()\n return\n }\r\n if (type === 'output_text.started') {\r\n this.setText(stringOf(event.utteranceId), '', 'immediate')\r\n return\r\n }\r\n if (type === 'output_text.delta') {\r\n this.appendText(stringOf(event.utteranceId), stringOf(event.text))\r\n return\r\n }\r\n if (type === 'output_text.done') {\n const text = stringOf(event.text)\n if (text !== '') this.setText(stringOf(event.utteranceId), text, 'immediate')\n if (text.trim() !== '') this.markConversationStarted()\n return\n }\r\n if (type === 'output_audio.started') {\r\n this.setState('speaking')\r\n return\r\n }\r\n if (type === 'response.interrupted') {\r\n this.setState('listening')\r\n return\r\n }\r\n if (type === 'error') this.handleUnexpectedClose()\r\n }\n\n private markConversationStarted(): void {\n if (this.conversationStarted) return\n const sessionId = this.snapshot.sessionId\n if (sessionId === undefined) return\n this.conversationStarted = true\n this.onConversationStarted?.(sessionId)\n }\n\r\n private publish(snapshot: VoiceClientSnapshot): void {\r\n if (this.frame !== undefined) {\r\n cancelAnimationFrame(this.frame)\r\n this.frame = undefined\r\n }\r\n this.snapshot = snapshot\r\n this.notify()\r\n }\r\n\r\n private notifyOnFrame(): void {\r\n this.frame ??= requestAnimationFrame(() => {\r\n this.frame = undefined\r\n this.notify()\r\n })\r\n }\r\n\r\n private notify(): void {\r\n for (const listener of this.listeners) {\r\n try { listener() } catch (cause) { console.error('voice snapshot listener failed:', cause) }\r\n }\r\n }\r\n}\r\n\r\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\r\n return value !== null && typeof value === 'object' ? value as Record<string, unknown> : undefined\r\n}\r\n\r\nfunction stringOf(value: unknown): string {\r\n return typeof value === 'string' ? value : ''\r\n}\r\n\r\nasync function openVoiceTransport(\r\n sessionId: string,\r\n handlers: VoiceTransportHandlers,\r\n signal: AbortSignal,\r\n onOpening: (transport: VoiceTransport) => void,\r\n contexts: AudioContexts,\r\n): Promise<VoiceTransport> {\r\n const { input: inputContext, output: outputContext } = contexts\r\n const socket = new WebSocket(voiceUrl(sessionId))\r\n const workletUrl = URL.createObjectURL(new Blob([CAPTURE_WORKLET], { type: 'text/javascript' }))\r\n let stream: MediaStream | undefined\r\n let source: MediaStreamAudioSourceNode | undefined\r\n let processor: AudioWorkletNode | undefined\r\n let silent: GainNode | undefined\r\n let stopped = false\r\n let socketClosed = false\r\n let ready = false\r\n let outputDone = false\r\n let encodedPending = 0\r\n let playbackGeneration = 0\r\n let encodedQueue: Promise<void> = Promise.resolve()\r\n let inputSampleRate = 16_000\r\n let outputSampleRate = 24_000\r\n let audioFormat: VoiceReadyEvent['audio']['format'] = 'pcm_s16le'\r\n let playAt = outputContext.currentTime\r\n const chunks: number[] = []\r\n const playing = new Set<AudioBufferSourceNode>()\r\n\r\n const transport: VoiceTransport = {\r\n cancelTask: (taskId) => {\r\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\r\n socket.send(JSON.stringify({ type: 'task.cancel', taskId }))\r\n },\r\n submitText: (text) => {\r\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\r\n socket.send(JSON.stringify({ type: 'text.submit', text }))\r\n },\r\n close: async () => {\r\n /* v8 ignore next -- controller ownership closes a transport once; the guard protects browser close races. */\r\n if (stopped) return\r\n stopped = true\r\n if (!socketClosed && socket.readyState === WebSocket.OPEN) {\r\n try { socket.send(JSON.stringify({ type: 'session.close' })) } catch {\r\n /* A socket closing concurrently cannot carry the final control; the server expires its reconnect grace. */\r\n }\r\n }\r\n socket.removeEventListener('message', onMessage)\r\n socket.removeEventListener('close', onClose)\r\n stopPlayback()\r\n source?.disconnect()\r\n processor?.disconnect()\r\n silent?.disconnect()\r\n processor?.port.close()\r\n for (const track of stream?.getTracks() ?? []) track.stop()\r\n socket.close()\r\n await Promise.allSettled([inputContext.close(), outputContext.close()])\r\n },\r\n }\r\n\r\n const stopPlayback = (): void => {\r\n playbackGeneration += 1\r\n encodedPending = 0\r\n encodedQueue = Promise.resolve()\r\n for (const node of playing) {\r\n try { node.stop() } catch { /* a completed source is already stopped */ }\r\n }\r\n playing.clear()\r\n playAt = outputContext.currentTime\r\n outputDone = false\r\n }\r\n const notifyPlaybackEnded = (): void => {\r\n if (outputDone && encodedPending === 0 && playing.size === 0 && socket.readyState === WebSocket.OPEN) {\r\n socket.send(JSON.stringify({ type: 'playback.ended' }))\r\n outputDone = false\r\n handlers.onPlaybackEnded()\r\n }\r\n }\r\n const onMessage = (message: MessageEvent): void => {\r\n if (!ready) return\r\n if (message.data instanceof ArrayBuffer) {\r\n if (audioFormat === 'audio_mpeg') {\r\n const generation = playbackGeneration\r\n encodedPending += 1\r\n encodedQueue = encodedQueue.catch(() => {}).then(() => scheduleEncoded(\r\n outputContext,\r\n message.data,\r\n playing,\r\n notifyPlaybackEnded,\r\n (value) => { playAt = value },\r\n () => playAt,\r\n () => generation === playbackGeneration && !stopped,\r\n )).catch(error => {\r\n if (generation === playbackGeneration && !stopped) {\r\n handlers.onEvent({ type: 'error', message: `audio decode failed: ${String(error)}` })\r\n }\r\n }).finally(() => {\r\n if (generation === playbackGeneration) {\r\n encodedPending -= 1\r\n notifyPlaybackEnded()\r\n }\r\n })\r\n } else {\r\n schedulePcm(outputContext, message.data, outputSampleRate, playing, notifyPlaybackEnded, (value) => { playAt = value }, playAt)\r\n }\r\n handlers.onBinary(message.data)\r\n return\r\n }\r\n let event: unknown\r\n try { event = JSON.parse(String(message.data)) } catch { return }\r\n const record = recordOf(event)\r\n if (record?.type === 'response.interrupted') stopPlayback()\n if (record?.type === 'output_audio.done') {\r\n outputDone = true\r\n notifyPlaybackEnded()\r\n }\r\n handlers.onEvent(event)\r\n }\r\n const onClose = (): void => {\r\n socketClosed = true\r\n handlers.onUnexpectedClose()\r\n }\r\n\r\n socket.binaryType = 'arraybuffer'\r\n onOpening(transport)\r\n try {\r\n await socketOpened(socket, signal)\r\n socket.addEventListener('message', onMessage)\r\n socket.addEventListener('close', onClose)\r\n const readyEvent = await socketReady(socket, signal)\r\n inputSampleRate = readyEvent.audio.inputSampleRate\r\n outputSampleRate = readyEvent.audio.outputSampleRate\r\n audioFormat = readyEvent.audio.format\r\n ready = true\r\n const captured = await microphone(signal)\r\n if (openingStopped(signal, stopped)) {\r\n for (const track of captured.getTracks()) track.stop()\r\n throw new Error('voice connection opening aborted')\r\n }\r\n stream = captured\r\n await abortable(inputContext.audioWorklet.addModule(workletUrl), signal)\r\n if (openingStopped(signal, stopped)) throw new Error('voice connection opening aborted')\r\n source = inputContext.createMediaStreamSource(stream)\r\n processor = new AudioWorkletNode(inputContext, 'dsh-pcm-capture')\r\n silent = inputContext.createGain()\r\n silent.gain.value = 0\r\n processor.port.onmessage = (event: MessageEvent<Float32Array>) => {\r\n if (socket.readyState !== WebSocket.OPEN) return\r\n chunks.push(...resample(event.data, inputContext.sampleRate, inputSampleRate))\r\n const frameSize = Math.max(1, Math.round(inputSampleRate / 5))\r\n while (chunks.length >= frameSize) {\r\n const part = chunks.splice(0, frameSize)\r\n const pcm = new Int16Array(part.length)\r\n for (const [index, sample] of part.entries()) {\r\n pcm[index] = Math.round(Math.max(-1, Math.min(1, sample)) * 32767)\r\n }\r\n socket.send(pcm.buffer)\r\n }\r\n }\r\n source.connect(processor)\r\n processor.connect(silent)\r\n silent.connect(inputContext.destination)\r\n } catch (cause) {\r\n await transport.close()\r\n throw cause\r\n } finally {\r\n URL.revokeObjectURL(workletUrl)\r\n }\r\n\r\n return transport\r\n}\r\n\r\ninterface AudioContexts {\r\n readonly input: AudioContext\r\n readonly output: AudioContext\r\n}\r\n\r\nfunction createAudioContexts(): AudioContexts {\r\n const contexts = { input: new AudioContext(), output: new AudioContext() }\r\n // Calling resume synchronously is required on browsers that start contexts\r\n // suspended when they are created outside a trusted user gesture.\r\n void contexts.input.resume().catch(() => {})\r\n void contexts.output.resume().catch(() => {})\r\n return contexts\r\n}\r\n\r\nasync function closeAudioContexts(contexts: AudioContexts): Promise<void> {\r\n await Promise.allSettled([contexts.input.close(), contexts.output.close()])\r\n}\r\n\r\nfunction microphone(signal: AbortSignal): Promise<MediaStream> {\r\n if (signal.aborted) return Promise.reject(new Error('voice connection opening aborted'))\r\n return new Promise((resolve, reject) => {\r\n let cancelled = false\r\n const cleanup = (): void => { signal.removeEventListener('abort', aborted) }\r\n const aborted = (): void => {\r\n cancelled = true\r\n cleanup()\r\n reject(new Error('voice connection opening aborted'))\r\n }\r\n signal.addEventListener('abort', aborted, { once: true })\r\n let request: Promise<MediaStream>\r\n try {\r\n request = navigator.mediaDevices.getUserMedia({\r\n audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },\r\n })\r\n } catch (cause) {\r\n cleanup()\r\n reject(errorOf(cause))\r\n return\r\n }\r\n void request.then((value) => {\r\n cleanup()\r\n if (cancelled || signal.aborted) {\r\n for (const track of value.getTracks()) track.stop()\r\n return\r\n }\r\n resolve(value)\r\n }, (cause: unknown) => {\r\n cleanup()\r\n if (!cancelled) reject(errorOf(cause))\r\n })\r\n })\r\n}\r\n\r\nfunction abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {\r\n if (signal.aborted) return Promise.reject(new Error('voice connection opening aborted'))\r\n return new Promise((resolve, reject) => {\r\n let cancelled = false\r\n const cleanup = (): void => { signal.removeEventListener('abort', aborted) }\r\n const aborted = (): void => {\r\n cleanup()\r\n cancelled = true\r\n reject(new Error('voice connection opening aborted'))\r\n }\r\n signal.addEventListener('abort', aborted, { once: true })\r\n void operation.then((value) => {\r\n cleanup()\r\n if (!cancelled) resolve(value)\r\n }, (cause: unknown) => {\r\n cleanup()\r\n if (!cancelled) reject(errorOf(cause))\r\n })\r\n })\r\n}\r\n\r\nfunction socketOpened(socket: WebSocket, signal: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const cleanup = (): void => {\r\n socket.removeEventListener('open', opened)\r\n socket.removeEventListener('error', failed)\r\n signal.removeEventListener('abort', aborted)\r\n }\r\n const opened = (): void => { cleanup(); resolve() }\r\n const failed = (): void => { cleanup(); reject(new Error('voice websocket failed')) }\r\n const aborted = (): void => { cleanup(); reject(new Error('voice connection opening aborted')) }\r\n if (signal.aborted) { aborted(); return }\r\n socket.addEventListener('open', opened, { once: true })\r\n socket.addEventListener('error', failed, { once: true })\r\n signal.addEventListener('abort', aborted, { once: true })\r\n })\r\n}\r\n\r\nfunction socketReady(socket: WebSocket, signal: AbortSignal): Promise<VoiceReadyEvent> {\r\n return new Promise((resolve, reject) => {\r\n const cleanup = (): void => {\r\n socket.removeEventListener('message', received)\r\n socket.removeEventListener('error', failed)\r\n socket.removeEventListener('close', closed)\r\n signal.removeEventListener('abort', aborted)\r\n }\r\n const settle = (operation: () => void): void => { cleanup(); operation() }\r\n const received = (message: MessageEvent): void => {\r\n try {\r\n const value: unknown = message.data instanceof ArrayBuffer\r\n ? undefined\r\n : JSON.parse(String(message.data)) as unknown\r\n settle(() => { resolve(parseReady(value)) })\r\n } catch (cause) {\r\n settle(() => { reject(errorOf(cause)) })\r\n }\r\n }\r\n const failed = (): void => { settle(() => { reject(new Error('voice websocket failed before ready')) }) }\r\n const closed = (): void => { settle(() => { reject(new Error('voice websocket closed before ready')) }) }\r\n const aborted = (): void => { settle(() => { reject(new Error('voice connection opening aborted')) }) }\r\n if (signal.aborted) { aborted(); return }\r\n if (socket.readyState !== WebSocket.OPEN) { closed(); return }\r\n socket.addEventListener('message', received)\r\n socket.addEventListener('error', failed, { once: true })\r\n socket.addEventListener('close', closed, { once: true })\r\n signal.addEventListener('abort', aborted, { once: true })\r\n })\r\n}\r\n\r\nfunction openingStopped(signal: AbortSignal, stopped: boolean): boolean {\r\n return signal.aborted || stopped\r\n}\r\n\r\nfunction errorOf(cause: unknown): Error {\r\n return cause instanceof Error ? cause : new Error(String(cause))\r\n}\r\n\r\nfunction parseReady(value: unknown): VoiceReadyEvent {\r\n const event = recordOf(value)\r\n if (event?.type === 'error') {\r\n const message = stringOf(event.message)\r\n throw new Error(message === '' ? 'voice setup failed before ready' : message)\r\n }\r\n if (event?.type !== 'ready') throw new Error('voice websocket expected a ready event')\r\n const voiceSessionId = stringOf(event.voiceSessionId)\r\n if (voiceSessionId === '') throw new Error('voice websocket ready event has no voiceSessionId')\r\n const audio = recordOf(event.audio)\r\n if (audio?.inputSampleRate !== 16_000\r\n || (audio.outputSampleRate !== 24_000 && audio.outputSampleRate !== 48_000)\r\n || (audio.format !== 'pcm_s16le' && audio.format !== 'audio_mpeg')) {\r\n throw new Error('voice websocket ready event has unsupported audio settings')\r\n }\r\n if (event.interactionMode !== 'speech-shell' && event.interactionMode !== 'frontend-agent') {\r\n throw new Error('voice websocket ready event has an invalid interactionMode')\r\n }\r\n return { type: 'ready', voiceSessionId, audio: {\r\n inputSampleRate: audio.inputSampleRate,\r\n outputSampleRate: audio.outputSampleRate,\r\n format: audio.format,\r\n }, interactionMode: event.interactionMode }\r\n}\r\n\r\n/**\r\n * Resolve the dedicated WebSocket URL for one durable Voice Session.\r\n * @param sessionId - durable voice session id.\r\n * @param location - browser location used for host and protocol defaults.\r\n * @returns the /voice WebSocket URL carrying the session id.\r\n */\r\nexport function voiceUrl(sessionId: string, location: Pick<Location, 'href' | 'protocol'> = window.location): URL {\r\n const url = new URL('/voice', location.href)\r\n url.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'\r\n url.searchParams.set('sessionId', sessionId)\r\n return url\r\n}\r\n\r\nconst CAPTURE_WORKLET = `\r\nclass DshPcmCaptureProcessor extends AudioWorkletProcessor {\r\n process(inputs) {\r\n const channel = inputs[0] && inputs[0][0]\r\n if (channel) this.port.postMessage(channel.slice())\r\n return true\r\n }\r\n}\r\nregisterProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)\r\n`\r\n\r\n/**\r\n * Resample browser float PCM into the provider input rate.\r\n * @param input - float samples at `fromRate`.\r\n * @param fromRate - source sample rate in Hz.\r\n * @param toRate - target sample rate in Hz.\r\n * @returns linearly interpolated samples at `toRate`.\r\n */\r\nexport function resample(input: Float32Array, fromRate: number, toRate: number): number[] {\r\n if (fromRate === toRate) return [...input]\r\n const ratio = fromRate / toRate\r\n const result = new Array<number>(Math.floor(input.length / ratio))\r\n for (let index = 0; index < result.length; index += 1) {\r\n const position = index * ratio\r\n const left = Math.floor(position)\r\n const fraction = position - left\r\n const leftSample = input[left] as number\r\n const rightSample = input[Math.min(left + 1, input.length - 1)] as number\r\n result[index] = leftSample * (1 - fraction) + rightSample * fraction\r\n }\r\n return result\r\n}\r\n\r\nfunction schedulePcm(\r\n context: AudioContext,\r\n bytes: ArrayBuffer,\r\n sampleRate: number,\r\n playing: Set<AudioBufferSourceNode>,\r\n ended: () => void,\r\n setPlayAt: (value: number) => void,\r\n currentPlayAt: number,\r\n): void {\r\n const pcm = new Int16Array(bytes)\r\n const buffer = context.createBuffer(1, pcm.length, sampleRate)\r\n const channel = buffer.getChannelData(0)\r\n for (const [index, sample] of pcm.entries()) channel[index] = sample / 32768\r\n const node = context.createBufferSource()\r\n node.buffer = buffer\r\n node.connect(context.destination)\r\n const start = Math.max(currentPlayAt, context.currentTime)\r\n setPlayAt(start + buffer.duration)\r\n playing.add(node)\r\n node.onended = () => { playing.delete(node); ended() }\r\n node.start(start)\r\n}\r\n\r\nasync function scheduleEncoded(\r\n context: AudioContext,\r\n bytes: ArrayBuffer,\r\n playing: Set<AudioBufferSourceNode>,\r\n ended: () => void,\r\n setPlayAt: (value: number) => void,\r\n getPlayAt: () => number,\r\n shouldSchedule: () => boolean,\r\n): Promise<void> {\r\n const buffer = await context.decodeAudioData(bytes.slice(0))\r\n if (!shouldSchedule()) return\r\n const node = context.createBufferSource()\r\n node.buffer = buffer\r\n node.connect(context.destination)\r\n const start = Math.max(getPlayAt(), context.currentTime)\r\n setPlayAt(start + buffer.duration)\r\n playing.add(node)\r\n node.onended = () => { playing.delete(node); ended() }\r\n node.start(start)\r\n}\r\n","import {\r\n createSnapshotStore, type SessionId, type SnapshotStore,\r\n} from '@deepseek-ai/dsh-client-runtime/client'\r\n\r\nconst STORAGE_KEY = 'dsh.voice.history.v1'\r\n\r\n/** One locally indexed durable Voice Session. */\r\nexport interface VoiceHistoryEntry {\r\n readonly sessionId: SessionId\r\n readonly lastActiveAt: number\r\n}\r\n\r\n/** Browser-persistent Voice Session index. */\r\nexport interface VoiceHistorySnapshot {\r\n readonly entries: readonly VoiceHistoryEntry[]\r\n}\r\n\r\ninterface StoredVoiceHistory {\r\n readonly version: 1\r\n readonly entries: readonly VoiceHistoryEntry[]\r\n}\r\n\r\nfunction parseEntry(input: unknown): VoiceHistoryEntry | undefined {\r\n if (typeof input !== 'object' || input === null) return undefined\r\n const value = input as Record<string, unknown>\r\n if (typeof value.sessionId !== 'string' || value.sessionId === '') return undefined\r\n if (typeof value.lastActiveAt !== 'number' || !Number.isFinite(value.lastActiveAt) || value.lastActiveAt < 0) {\r\n return undefined\r\n }\r\n return { sessionId: value.sessionId as SessionId, lastActiveAt: value.lastActiveAt }\r\n}\r\n\r\nfunction readHistory(storage: Storage | undefined): VoiceHistorySnapshot {\r\n if (storage === undefined) return { entries: [] }\r\n try {\r\n const raw = storage.getItem(STORAGE_KEY)\r\n if (raw === null) return { entries: [] }\r\n const parsed = JSON.parse(raw) as unknown\r\n if (typeof parsed !== 'object' || parsed === null) return { entries: [] }\r\n const value = parsed as Record<string, unknown>\r\n if (value.version !== 1 || !Array.isArray(value.entries)) return { entries: [] }\r\n const entries: VoiceHistoryEntry[] = []\r\n const seen = new Set<SessionId>()\r\n for (const candidate of value.entries) {\r\n const entry = parseEntry(candidate)\r\n if (entry === undefined || seen.has(entry.sessionId)) continue\r\n seen.add(entry.sessionId)\r\n entries.push(entry)\r\n }\r\n entries.sort((left, right) => right.lastActiveAt - left.lastActiveAt)\r\n return { entries }\r\n } catch (error: unknown) {\r\n console.error('voice history rehydration failed:', error)\r\n return { entries: [] }\r\n }\r\n}\r\n\r\n/** Own the local history index without adding Voice metadata to Host session projections. */\r\nexport class VoiceHistoryStore {\r\n readonly snapshot: SnapshotStore<VoiceHistorySnapshot>\r\n private readonly storage: Storage | undefined\r\n private readonly unsubscribe: () => void\r\n\r\n constructor(storage: Storage | undefined = typeof localStorage === 'undefined' ? undefined : localStorage) {\r\n this.storage = storage\r\n this.snapshot = createSnapshotStore(readHistory(storage))\r\n this.unsubscribe = this.snapshot.subscribe(() => { this.persist() })\r\n }\r\n\r\n /** Move one successfully opened Voice Session to the front of the index. */\r\n record(sessionId: SessionId, lastActiveAt = Date.now()): void {\r\n this.snapshot.set({\r\n entries: [\r\n { sessionId, lastActiveAt },\r\n ...this.snapshot.getSnapshot().entries.filter(entry => entry.sessionId !== sessionId),\r\n ],\r\n })\r\n }\r\n\r\n /** Stop persistence notifications. */\r\n dispose(): void {\r\n this.unsubscribe()\r\n }\r\n\r\n private persist(): void {\r\n if (this.storage === undefined) return\r\n const value: StoredVoiceHistory = { version: 1, entries: this.snapshot.getSnapshot().entries }\r\n try {\r\n this.storage.setItem(STORAGE_KEY, JSON.stringify(value))\r\n } catch (error: unknown) {\r\n console.error('voice history persistence failed:', error)\r\n }\r\n }\r\n}\r\n","import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\n\r\ninterface VoiceTextInputState {\r\n readonly draft: string\r\n readonly imageIds: readonly unknown[]\r\n}\r\n\r\nexport interface VoiceTextInput {\r\n readonly state: { getSnapshot(): VoiceTextInputState }\r\n setDraft(text: string): void\r\n submit(mode?: unknown): void\r\n notify(level: 'info' | 'error', text: string): void\r\n}\r\n\r\ninterface VoiceTextController {\r\n getSnapshot(): VoiceClientSnapshot\r\n submitText(text: string): void\r\n}\r\n\r\ninterface PatchedInput {\r\n readonly input: VoiceTextInput\r\n readonly originalOwnDescriptor: PropertyDescriptor | undefined\r\n readonly patchedSubmit: (mode?: unknown) => void\r\n}\r\n\r\n/** Routes the native composer through Voice while preserving its ordinary offline submission. */\r\nexport class VoiceTextSubmitBridge {\r\n private readonly patched = new Map<SessionId, PatchedInput>()\r\n\r\n constructor(\r\n private readonly voice: VoiceTextController,\r\n private readonly resolveInput: (sessionId: SessionId) => VoiceTextInput,\r\n ) {}\r\n\r\n install(sessionId: SessionId): void {\r\n if (this.patched.has(sessionId)) return\r\n const input = this.resolveInput(sessionId)\r\n const originalOwnDescriptor = Object.getOwnPropertyDescriptor(input, 'submit')\r\n const originalSubmit = input.submit.bind(input)\r\n const patchedSubmit = (mode?: unknown): void => {\r\n const voice = this.voice.getSnapshot()\r\n const state = input.state.getSnapshot()\r\n const active = voice.sessionId === sessionId\r\n && voice.state !== 'off'\r\n && voice.state !== 'error'\r\n if (!active || state.draft.trim() === '' || state.imageIds.length > 0) {\r\n originalSubmit(mode)\r\n return\r\n }\r\n try {\r\n this.voice.submitText(state.draft)\r\n input.setDraft('')\r\n } catch (cause: unknown) {\r\n input.notify('error', cause instanceof Error ? cause.message : String(cause))\r\n }\r\n }\r\n input.submit = patchedSubmit\r\n this.patched.set(sessionId, { input, originalOwnDescriptor, patchedSubmit })\r\n }\r\n\r\n dispose(): void {\r\n for (const { input, originalOwnDescriptor, patchedSubmit } of this.patched.values()) {\r\n if (input.submit !== patchedSubmit) continue\r\n if (originalOwnDescriptor === undefined) delete (input as Partial<VoiceTextInput>).submit\r\n else Object.defineProperty(input, 'submit', originalOwnDescriptor)\r\n }\r\n this.patched.clear()\r\n }\r\n}\r\n","import type {\r\n ChatConversationViewNode, ConversationNodeDefinition, SessionId,\r\n} from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type {\r\n TaskObservation, VoiceTaskId, VoiceUtteranceId,\r\n} from '@flowingspring/dsh-voice'\n\r\n/** Renderer-ready durable Voice utterance. */\r\nexport interface VoiceUtteranceChatData {\r\n readonly utteranceId: VoiceUtteranceId\r\n readonly role: 'user' | 'assistant'\r\n readonly text?: string\r\n readonly state: 'streaming' | 'completed' | 'interrupted'\r\n}\r\n\r\n/** Renderer-ready delegated task link and its latest observation. */\r\nexport interface VoiceDelegationChatData {\r\n readonly taskId: VoiceTaskId\r\n readonly taskSessionId: SessionId\r\n readonly input: string\r\n readonly status: TaskObservation['status']\r\n readonly update?: string\r\n}\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-conversation/client' {\r\n interface ChatNodeDataMap {\r\n /** One user ASR or assistant TTS transcript produced in Voice Mode. */\r\n 'voice-utterance': VoiceUtteranceChatData\r\n /** One independent DSH task delegated by the Voice frontend Agent. */\r\n 'voice-delegation': VoiceDelegationChatData\r\n }\r\n}\r\n\r\ntype VoiceUtteranceState = VoiceUtteranceChatData\r\ntype VoiceDelegationState = VoiceDelegationChatData\r\n\r\n/** Fold one streamed utterance into a stable Chat node. */\r\nexport const voiceUtteranceDefinition: ConversationNodeDefinition<VoiceUtteranceState> = {\r\n kind: 'voice-utterance',\r\n target: 'chat',\r\n match: (event) => {\r\n if (event.type === 'voice/utterance-start') {\r\n return { id: String(event.data.utteranceId), role: 'start' }\r\n }\r\n if (event.type === 'voice/utterance-end') {\r\n return { id: String(event.data.utteranceId), role: 'update' }\r\n }\r\n return null\r\n },\r\n start: (_context, match) => {\r\n if (match.event.type !== 'voice/utterance-start') {\r\n throw new Error('voice utterance start requires voice/utterance-start')\r\n }\r\n return {\r\n utteranceId: match.event.data.utteranceId,\r\n role: match.event.data.role,\r\n state: 'streaming',\r\n }\r\n },\r\n update: (context, match) => {\r\n if (match.event.type !== 'voice/utterance-end') return context.state\r\n return {\r\n ...context.state,\r\n text: match.event.data.text,\r\n state: match.event.data.state,\r\n }\r\n },\r\n buildViewNode: (context): ChatConversationViewNode | null => {\r\n if (context.start === undefined || context.state === undefined) return null\r\n const emptyInterrupted = context.state.state === 'interrupted'\r\n && (context.state.text?.trim() ?? '') === ''\r\n return {\r\n key: context.key,\r\n kind: 'voice-utterance',\r\n id: context.id,\r\n target: 'chat',\r\n anchorSeq: context.start.event.seq,\r\n location: context.start.location,\r\n // A live start may already be materialized before an empty noise segment\r\n // settles. Conversation nodes cannot be withdrawn after publication, so\r\n // keep its stable identity and hide it instead.\r\n visibility: emptyInterrupted ? 'hidden' : 'visible',\r\n data: context.state as VoiceUtteranceState,\r\n }\r\n },\r\n}\r\n\r\n/** Fold a delegated task and all observations into one navigation card. */\r\nexport const voiceDelegationDefinition: ConversationNodeDefinition<VoiceDelegationState> = {\r\n kind: 'voice-delegation',\r\n target: 'chat',\r\n match: (event) => {\r\n if (event.type === 'voice/task-delegated') {\r\n return { id: String(event.data.taskId), role: 'start' }\r\n }\r\n if (event.type === 'voice/task-observation') {\r\n return { id: String(event.data.taskId), role: 'update' }\r\n }\r\n return null\r\n },\r\n start: (_context, match) => {\r\n if (match.event.type !== 'voice/task-delegated') {\r\n throw new Error('voice delegation start requires voice/task-delegated')\r\n }\r\n return {\r\n taskId: match.event.data.taskId,\r\n taskSessionId: match.event.data.taskSessionId,\r\n input: match.event.data.input,\r\n status: 'accepted',\r\n }\r\n },\r\n update: (context, match) => {\r\n if (match.event.type !== 'voice/task-observation') return context.state\r\n const observation = match.event.data\r\n const update = observation.voiceMessage?.text\r\n ?? observation.announcement\r\n ?? observation.reason\r\n ?? context.state.update\r\n return {\r\n ...context.state,\r\n status: observation.status,\r\n ...(update === undefined ? {} : { update }),\r\n }\r\n },\r\n buildViewNode: (context): ChatConversationViewNode | null => {\r\n if (context.start === undefined) return null\r\n return {\r\n key: context.key,\r\n kind: 'voice-delegation',\r\n id: context.id,\r\n target: 'chat',\r\n anchorSeq: context.start.event.seq,\r\n location: context.start.location,\r\n visibility: 'visible',\r\n data: context.state as VoiceDelegationState,\r\n }\r\n },\r\n}\r\n","/** `voice` namespace dictionaries. */\r\n\r\n/** Dictionary namespace owned by this plugin. */\r\nexport const NS = 'voice'\r\n\r\n/** Simplified Chinese dictionary (the key-set source of truth). */\r\nexport const zh = {\r\n 'badge.voice': 'VOICE',\r\n 'control.start': '开始语音对话',\r\n 'control.retry': '语音连接失败,点击重试',\r\n 'control.stop': '结束语音对话',\r\n 'overlay.connecting': '正在连接语音',\r\n 'overlay.listening': '正在聆听',\r\n 'overlay.speaking': '正在播放回复',\r\n 'overlay.error': '语音连接失败',\r\n 'overlay.return': '返回语音对话',\r\n 'overlay.stop': '结束',\r\n 'history.open': '打开语音历史',\r\n 'history.label': '语音历史',\r\n 'history.title': '语音对话',\r\n 'history.close': '关闭语音历史',\r\n 'history.empty': '还没有语音对话',\r\n 'history.active': '通话中',\r\n 'history.saved': '已保存',\r\n 'utterance.user.pending': '正在聆听…',\r\n 'utterance.assistant.pending': '正在生成语音回复…',\r\n 'utterance.interrupted': '已打断',\r\n 'task.title': '后台处理',\r\n 'task.status.accepted': '已接受',\r\n 'task.status.queued': '排队中',\r\n 'task.status.running': '执行中',\r\n 'task.status.waiting-user': '等待确认',\r\n 'task.status.completed': '已完成',\r\n 'task.status.failed': '失败',\r\n 'task.status.cancelled': '已取消',\r\n 'task.status.interrupted': '服务中断',\r\n 'task.expand': '展开后台处理详情',\r\n 'task.collapse': '收起后台处理详情',\r\n 'task.cancel': '取消任务',\r\n 'task.open': '查看后台详情',\r\n} satisfies Record<string, string>\r\n\r\n/** Union of this namespace's dictionary keys. */\r\nexport type VoiceKey = keyof typeof zh\r\n\r\n/** English dictionary, checked complete against the zh key set. */\r\nexport const en = {\r\n 'badge.voice': 'VOICE',\r\n 'control.start': 'Start voice conversation',\r\n 'control.retry': 'Voice connection failed, press to retry',\r\n 'control.stop': 'End voice conversation',\r\n 'overlay.connecting': 'Connecting voice',\r\n 'overlay.listening': 'Listening',\r\n 'overlay.speaking': 'Playing response',\r\n 'overlay.error': 'Voice connection failed',\r\n 'overlay.return': 'Return to voice conversation',\r\n 'overlay.stop': 'End',\r\n 'history.open': 'Open voice history',\r\n 'history.label': 'Voice history',\r\n 'history.title': 'Voice conversations',\r\n 'history.close': 'Close voice history',\r\n 'history.empty': 'No voice conversations yet',\r\n 'history.active': 'Active',\r\n 'history.saved': 'Saved',\r\n 'utterance.user.pending': 'Listening…',\r\n 'utterance.assistant.pending': 'Generating voice response…',\r\n 'utterance.interrupted': 'Interrupted',\r\n 'task.title': 'Background task',\r\n 'task.status.accepted': 'Accepted',\r\n 'task.status.queued': 'Queued',\r\n 'task.status.running': 'Running',\r\n 'task.status.waiting-user': 'Waiting for you',\r\n 'task.status.completed': 'Completed',\r\n 'task.status.failed': 'Failed',\r\n 'task.status.cancelled': 'Cancelled',\r\n 'task.status.interrupted': 'Interrupted',\r\n 'task.expand': 'Show background task details',\r\n 'task.collapse': 'Hide background task details',\r\n 'task.cancel': 'Cancel task',\r\n 'task.open': 'View background details',\r\n} satisfies Record<VoiceKey, string>\r\n","/** Browser voice UI assembly. @module @flowingspring/dsh-client-ui-voice/client */\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\r\nimport { VoiceControl, type VoiceControlInjected } from './VoiceControl.tsx'\r\nimport { VoiceHistoryAction, type VoiceHistoryActionInjected } from './VoiceHistoryAction.tsx'\r\nimport {\r\n VoiceDelegationView, type VoiceDelegationInjected,\r\n VoiceUtteranceView, type VoiceUtteranceInjected,\r\n} from './VoiceNodeViews.tsx'\r\nimport { VoiceOverlay, type VoiceOverlayInjected } from './VoiceOverlay.tsx'\r\nimport { VoiceController } from './voice-controller.ts'\r\nimport { VoiceHistoryStore } from './voice-history.ts'\r\nimport { VoiceTextSubmitBridge, type VoiceTextInput } from './voice-text-submit.ts'\r\nimport { voiceDelegationDefinition, voiceUtteranceDefinition } from './voice-definitions.ts'\r\nimport { en, NS, type VoiceKey, zh } from './locales.ts'\r\n\r\nexport type { VoiceKey } from './locales.ts'\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\r\n interface LocaleNamespaceMap {\r\n /** Browser Voice conversation copy. */\r\n voice: VoiceKey\r\n }\r\n}\r\n\r\nexport const inject = ['conversation', 'conversationEvents', 'slots', 'sessions', 'locale']\n\n/** Refresh the Host-backed Session list when the runtime exposes its baseline pull. */\nfunction refreshSessions(ctx: ClientContext): void {\n // `refresh()` exists on the concrete SessionRuntime used by dsh Web, but is\n // intentionally omitted from the narrow feature-facing ISessions contract.\n // Keep this compatibility bridge optional for older runtimes and fixtures.\n const service = ctx.sessions as unknown as { refresh?: () => Promise<void> }\n if (typeof service.refresh !== 'function') return\n void service.refresh.call(ctx.sessions).catch((cause: unknown) => {\n console.warn('voice session list refresh failed:', cause)\n })\n}\n\n/** Mount root transport controls and durable Voice conversation renderers. @param ctx - browser context. */\nexport function apply(ctx: ClientContext): void {\n const controller = new VoiceController({\n onConversationStarted: () => {\n // Voice events are appended by the Host, while the browser's session\n // list keeps its own blank-bit cache. Refresh once after the first\n // completed turn so Workspace new-session creation does not reuse the\n // now-engaged Voice Session.\n refreshSessions(ctx)\n },\n })\n const history = new VoiceHistoryStore()\r\n const textSubmit = new VoiceTextSubmitBridge(controller, (sessionId) => {\r\n const scope = ctx.sessions.scope(sessionId)\r\n if (scope === undefined) throw new Error(`voice input session \"${sessionId}\" is unavailable`)\r\n return ctx.conversation.input.for(scope) as VoiceTextInput\r\n })\r\n const stopVoice = (): Promise<void> => controller.stop()\r\n const startVoice = async (sourceSessionId: SessionId): Promise<void> => {\r\n ctx.sessions.open(sourceSessionId)\r\n textSubmit.install(sourceSessionId)\r\n await controller.start(sourceSessionId)\r\n history.record(sourceSessionId)\r\n }\r\n const voiceControlInjected = (): VoiceControlInjected => ({\r\n hooks: { voice: controller },\r\n startVoice,\r\n retryVoice: async () => {\r\n await controller.retry()\r\n const sessionId = controller.getSnapshot().sessionId\r\n if (sessionId !== undefined) history.record(sessionId)\r\n },\r\n stopVoice,\r\n })\r\n const voiceOverlayInjected = (): VoiceOverlayInjected => ({\r\n hooks: { voice: controller },\r\n openVoiceSession: (id) => { ctx.sessions.open(id) },\r\n stopVoice,\r\n })\r\n const utteranceInjected = (): VoiceUtteranceInjected => ({ hooks: { voice: controller } })\r\n const delegationInjected = (): VoiceDelegationInjected => ({\r\n hooks: { voice: controller },\r\n openSession: (id) => { ctx.sessions.open(id) },\r\n cancelTask: (id) => { controller.cancelTask(id) },\r\n })\r\n const historyInjected = (): VoiceHistoryActionInjected => ({\r\n hooks: { voice: controller, voiceHistory: history.snapshot },\r\n openSession: (id) => {\r\n history.record(id)\r\n ctx.sessions.open(id)\r\n },\r\n })\r\n\r\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-voice: dictionaries')\r\n ctx.effect(() => async () => { await controller.stop() }, 'ui-voice: transport teardown')\r\n ctx.effect(() => () => { textSubmit.dispose() }, 'ui-voice: text submit bridge teardown')\r\n ctx.effect(() => () => { history.dispose() }, 'ui-voice: history persistence teardown')\r\n ctx.conversationEvents.register(voiceUtteranceDefinition)\r\n ctx.conversationEvents.register(voiceDelegationDefinition)\r\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\r\n name: 'conversation.input.right',\r\n id: 'voice',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceControlInjected,\r\n }, VoiceControl))\r\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\r\n name: 'shell.overlay',\r\n id: 'voice-active',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceOverlayInjected,\r\n }, VoiceOverlay))\r\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({\r\n name: 'sidebar.footer.action',\r\n id: 'voice-history',\r\n order: 10,\r\n locale: NS,\r\n inject: historyInjected,\r\n }, VoiceHistoryAction))\r\n ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({\r\n name: 'conversation.chat.node',\r\n key: 'voice-utterance',\r\n locale: NS,\r\n inject: utteranceInjected,\r\n }, VoiceUtteranceView))\r\n ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({\r\n name: 'conversation.chat.node',\r\n key: 'voice-delegation',\r\n locale: NS,\r\n inject: delegationInjected,\r\n }, VoiceDelegationView))\r\n}\r\n"],"mappings":"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"}
@@ -13,25 +13,21 @@ const VOICE_MESSAGE_SECTION_ORDER = 118;
13
13
  * Install `send_voice_message` and its guidance into one delegated task Agent scope.
14
14
  * @param agentCtx - task-Agent context receiving the tool and guidance.
15
15
  * @param send - binding-owned message receiver.
16
+ * @param resolveDelegationId - resolves the active delegation without exposing its id to the model or transcript.
16
17
  * @returns disposer that revokes both registrations.
17
18
  */
18
- function installVoiceMessageTool(agentCtx, send) {
19
+ function installVoiceMessageTool(agentCtx, send, resolveDelegationId) {
19
20
  const disposeSection = agentCtx.systemPrompt.section({
20
21
  name: "tool:send-voice-message",
21
22
  order: VOICE_MESSAGE_SECTION_ORDER,
22
- text: "When a user message contains a <realtime_delegation> request or <realtime_delegation_update>, use send_voice_message with its delegation_id to keep the realtime voice assistant informed. Report structured events with type and detail. Use progress for meaningful progress, warning or error for important conditions, question when user input is required, and result exactly once before a successful final turn ends. Put the complete factual content in detail; the Voice layer independently rewrites every reported event against the user request. Keep detail complete enough to support an accurate rewrite. The full report remains in the task UI. The voice assistant does not automatically see your transcript, tool output, or reasoning. A result is held until the turn actually succeeds. A question leaves the task waiting for a user reply. Reporting never ends your turn. Do not use this tool for ordinary requests without a realtime_delegation envelope."
23
+ text: "This Agent handles tasks delegated by the realtime voice assistant. While a realtime voice delegation is active, use send_voice_message to keep the voice assistant informed. The tool is already bound to the exact active delegation; never ask for, invent, or expose an internal delegation id. Report structured events with type and detail. Use progress for meaningful progress, warning or error for important conditions, question when user input is required, and result exactly once before a successful final turn ends. Put the complete factual content in detail; the Voice layer independently rewrites every reported event against the user request. Keep detail complete enough to support an accurate rewrite. The full report remains in the task UI. The voice assistant does not automatically see your transcript, tool output, or reasoning. A result is held until the turn actually succeeds. A question leaves the task waiting for a user reply. Reporting never ends your turn. Do not use this tool when no realtime voice delegation is active."
23
24
  });
24
25
  let disposeTool;
25
26
  try {
26
27
  disposeTool = agentCtx.tools.register(defineTool({
27
28
  name: "send_voice_message",
28
- description: "Send a user-facing status or final result to the realtime voice assistant for the exact active delegation using type and complete factual detail. Progress, warnings, errors, and questions may repeat when meaningful. result may be called once. The Voice layer independently rewrites detail into conversational speech, so this tool accepts no Agent-authored speech field. COMPLETE is held until the Agent turn succeeds; it does not finish the turn. The voice assistant does not otherwise see this Agent transcript or tool output.",
29
+ description: "Send a user-facing status or final result to the realtime voice assistant for the exact active delegation using type and complete factual detail. The delegation is bound automatically; no delegation id is accepted. Progress, warnings, errors, and questions may repeat when meaningful. result may be called once. The Voice layer independently rewrites detail into conversational speech, so this tool accepts no Agent-authored speech field. COMPLETE is held until the Agent turn succeeds; it does not finish the turn. The voice assistant does not otherwise see this Agent transcript or tool output.",
29
30
  parameters: {
30
- delegation_id: {
31
- type: "string",
32
- required: true,
33
- description: "Exact delegation_id from the realtime_delegation request envelope."
34
- },
35
31
  type: {
36
32
  type: "string",
37
33
  enum: [
@@ -85,8 +81,10 @@ function installVoiceMessageTool(agentCtx, send) {
85
81
  const type = args.type ?? (args.channel === "COMPLETE" ? "result" : "progress");
86
82
  const detail = args.detail.trim();
87
83
  if (detail === "") throw new Error("send_voice_message detail must be non-empty");
84
+ const delegationId = resolveDelegationId();
85
+ if (delegationId === void 0) throw new Error("send_voice_message has no active voice delegation");
88
86
  return Promise.resolve(send({
89
- delegationId: VoiceTaskId(args.delegation_id),
87
+ delegationId,
90
88
  channel: type === "result" ? "COMPLETE" : "STATUS",
91
89
  type,
92
90
  detail
@@ -154,6 +152,9 @@ try {
154
152
  if (hostSession.KNOWN_SESSION_EVENT_TYPES !== void 0) registerVoiceSessionEventTypes(hostSession.KNOWN_SESSION_EVENT_TYPES);
155
153
  }
156
154
  } catch {}
155
+ function sameModelSelection(left, right) {
156
+ return left.provider === right.provider && left.model === right.model && left.reasoningEffort === right.reasoningEffort;
157
+ }
157
158
  const REWRITE_SYSTEM_PROMPT = `你是语音模式下的自然回复编辑器。你的任务是把后台处理结果改写成准确、自然、适合直接朗读的中文回复。
158
159
 
159
160
  必须遵守:
@@ -249,7 +250,13 @@ function apply(ctx, config = {}) {
249
250
  const voiceId = binding.voiceSessionId;
250
251
  if (voiceId === void 0 || !binding.voiceAttached || text.trim() === "") return;
251
252
  if (ctx.voice.appendSpeechText(voiceId, text)) {
252
- if (flush) ctx.voice.requestResponse(voiceId, { kind: "automatic" });
253
+ if (flush) {
254
+ debugVoiceLatency("tts-request", {
255
+ taskId,
256
+ textLength: text.length
257
+ });
258
+ ctx.voice.requestResponse(voiceId, { kind: "automatic" });
259
+ }
253
260
  return;
254
261
  }
255
262
  append(binding, {
@@ -279,6 +286,14 @@ function apply(ctx, config = {}) {
279
286
  const abort = new AbortController();
280
287
  binding.rewriteAbort = abort;
281
288
  const selection = ctx.agentDefaultModel.currentSelection();
289
+ debugVoiceLatency("rewrite-start", {
290
+ taskId,
291
+ eventType,
292
+ provider: selection.provider,
293
+ model: selection.model,
294
+ selectedReasoningEffort: selection.reasoningEffort,
295
+ originalLength: original.length
296
+ });
282
297
  const prompt = [
283
298
  "根据用户原话和处理结果,生成一条可以直接显示并朗读的最终回复。",
284
299
  REWRITE_EVENT_INSTRUCTIONS[eventType],
@@ -309,13 +324,13 @@ function apply(ctx, config = {}) {
309
324
  for await (const chunk of llm.stream({
310
325
  provider: selection.provider,
311
326
  model: selection.model,
312
- ...selection.reasoningEffort === void 0 ? {} : { reasoningEffort: selection.reasoningEffort },
313
327
  messages: [message],
314
328
  system: REWRITE_SYSTEM_PROMPT,
315
329
  signal: abort.signal
316
330
  })) {
317
331
  if (generation !== binding.rewriteGeneration || abort.signal.aborted) return;
318
332
  if (chunk.type !== "text-delta") continue;
333
+ if (rewritten === "") debugVoiceLatency("rewrite-first-text", { taskId });
319
334
  rewritten += chunk.text;
320
335
  pending += chunk.text;
321
336
  const split = speechFragments(pending, false);
@@ -329,6 +344,10 @@ function apply(ctx, config = {}) {
329
344
  if (rewritten.trim() === "" && pending.trim() === "") speakFragment(binding, taskId, fallbackEventSpeech(eventType, original));
330
345
  } catch (error) {
331
346
  if (!abort.signal.aborted && generation === binding.rewriteGeneration) {
347
+ debugVoiceLatency("rewrite-error", {
348
+ taskId,
349
+ error: String(error)
350
+ });
332
351
  ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
333
352
  speakFragment(binding, taskId, fallbackEventSpeech(eventType, original));
334
353
  }
@@ -429,12 +448,40 @@ function apply(ctx, config = {}) {
429
448
  binding.agent = handle.agent;
430
449
  return handle.agent;
431
450
  };
432
- const createTaskAgent = async (binding, taskSessionId) => {
451
+ const loggedSourceSelection = (binding) => {
452
+ const event = ctx.sessions.get(binding.sessionId)?.events.findLast((candidate) => candidate.type === "request/header");
453
+ if (event?.type !== "request/header") return void 0;
454
+ const config = event.data.header.config;
455
+ return {
456
+ selection: {
457
+ provider: config.provider,
458
+ model: config.model,
459
+ ...config.reasoningEffort === void 0 ? {} : { reasoningEffort: config.reasoningEffort }
460
+ },
461
+ seq: event.seq
462
+ };
463
+ };
464
+ const taskModelSelection = (binding) => {
465
+ const defaults = ctx.agentDefaultModel.currentSelection();
466
+ const logged = loggedSourceSelection(binding);
467
+ const current = binding.continuousTaskAgent?.selection;
468
+ if (current === void 0) return {
469
+ selection: logged?.selection ?? defaults,
470
+ sourceHeaderSeq: logged?.seq
471
+ };
472
+ if (logged !== void 0 && logged.seq !== binding.continuousTaskAgent?.sourceHeaderSeq) return {
473
+ selection: logged.selection,
474
+ sourceHeaderSeq: logged.seq
475
+ };
476
+ return {
477
+ selection: sameModelSelection(defaults, current) ? current : defaults,
478
+ sourceHeaderSeq: logged?.seq
479
+ };
480
+ };
481
+ const createTaskAgent = async (binding, taskSessionId, selection) => {
433
482
  const sourceAgent = await ensureAgent(binding);
434
483
  const sourceSession = ctx.sessions.get(binding.sessionId);
435
484
  if (sourceSession === void 0) throw new Error(`voice-assistant: source session "${binding.sessionId}" is not live`);
436
- const defaults = ctx.agentDefaultModel.currentSelection();
437
- const header = foldRequestHeader(sourceSession.events);
438
485
  const presetId = resolveSessionPreset(sourceSession);
439
486
  const presets = ctx.get("agentPresets");
440
487
  let disposeVoiceMessage;
@@ -445,12 +492,12 @@ function apply(ctx, config = {}) {
445
492
  ...presetId === void 0 ? {} : { agentPreset: presetId }
446
493
  },
447
494
  agentOptions: {
448
- provider: sourceAgent.options.provider ?? header?.config.provider ?? defaults.provider,
449
- model: sourceAgent.options.model ?? header?.config.model ?? defaults.model
495
+ provider: selection.provider,
496
+ model: selection.model
450
497
  },
451
498
  setup: (agentCtx) => {
452
499
  presets?.composeFrom(agentCtx, sourceAgent.ctx);
453
- disposeVoiceMessage = installVoiceMessageTool(agentCtx, (input) => sendVoiceMessage(binding, input));
500
+ disposeVoiceMessage = installVoiceMessageTool(agentCtx, (input) => sendVoiceMessage(binding, input), () => binding.active?.id);
454
501
  }
455
502
  });
456
503
  handles.set(taskSessionId, handle);
@@ -471,25 +518,24 @@ function apply(ctx, config = {}) {
471
518
  disposeVoiceMessage
472
519
  };
473
520
  };
474
- const resumeTaskAgent = async (binding, taskSessionId) => {
521
+ const resumeTaskAgent = async (binding, taskSessionId, selection) => {
475
522
  const live = ctx.agents.get(taskSessionId);
476
523
  if (live !== void 0) return {
477
524
  agent: live,
478
- disposeVoiceMessage: installVoiceMessageTool(live.ctx, (input) => sendVoiceMessage(binding, input))
525
+ disposeVoiceMessage: installVoiceMessageTool(live.ctx, (input) => sendVoiceMessage(binding, input), () => binding.active?.id)
479
526
  };
480
527
  const sourceAgent = await ensureAgent(binding);
481
- const defaults = ctx.agentDefaultModel.currentSelection();
482
528
  const presets = ctx.get("agentPresets");
483
529
  let disposeVoiceMessage;
484
530
  const handle = await ctx.agents.resume({
485
531
  resumeSessionId: taskSessionId,
486
532
  agentOptions: {
487
- provider: sourceAgent.options.provider ?? defaults.provider,
488
- model: sourceAgent.options.model ?? defaults.model
533
+ provider: selection.provider,
534
+ model: selection.model
489
535
  },
490
536
  setup: (agentCtx) => {
491
537
  presets?.composeFrom(agentCtx, sourceAgent.ctx);
492
- disposeVoiceMessage = installVoiceMessageTool(agentCtx, (input) => sendVoiceMessage(binding, input));
538
+ disposeVoiceMessage = installVoiceMessageTool(agentCtx, (input) => sendVoiceMessage(binding, input), () => binding.active?.id);
493
539
  }
494
540
  });
495
541
  handles.set(taskSessionId, handle);
@@ -504,7 +550,40 @@ function apply(ctx, config = {}) {
504
550
  };
505
551
  };
506
552
  const ensureContinuousTaskAgent = async (binding) => {
507
- if (binding.continuousTaskAgent !== void 0) return binding.continuousTaskAgent;
553
+ const { selection, sourceHeaderSeq } = taskModelSelection(binding);
554
+ const current = binding.continuousTaskAgent;
555
+ if (current !== void 0 && sameModelSelection(current.selection, selection)) {
556
+ if (current.sourceHeaderSeq === sourceHeaderSeq) return current;
557
+ const refreshed = {
558
+ ...current,
559
+ sourceHeaderSeq
560
+ };
561
+ binding.continuousTaskAgent = refreshed;
562
+ return refreshed;
563
+ }
564
+ if (current !== void 0) {
565
+ binding.continuousTaskAgent = void 0;
566
+ taskBindings.delete(current.taskSessionId);
567
+ try {
568
+ await current.agent.whenIdle();
569
+ } catch (error) {
570
+ ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
571
+ }
572
+ try {
573
+ current.disposeVoiceMessage();
574
+ } catch (error) {
575
+ ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
576
+ }
577
+ const handle = handles.get(current.taskSessionId);
578
+ if (handle !== void 0) {
579
+ handles.delete(current.taskSessionId);
580
+ try {
581
+ await handle.dispose();
582
+ } catch (error) {
583
+ ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
584
+ }
585
+ }
586
+ }
508
587
  const sourceSession = requireSourceSession(binding);
509
588
  const previousState = sourceSession.events.findLast((event) => event.type === "voice/agent-binding-state");
510
589
  const previousBound = sourceSession.events.findLast((event) => event.type === "voice/task-session-bound");
@@ -513,7 +592,9 @@ function apply(ctx, config = {}) {
513
592
  if (previousTaskSessionId !== void 0) try {
514
593
  const resource = {
515
594
  taskSessionId: previousTaskSessionId,
516
- ...await resumeTaskAgent(binding, previousTaskSessionId)
595
+ selection,
596
+ sourceHeaderSeq,
597
+ ...await resumeTaskAgent(binding, previousTaskSessionId, selection)
517
598
  };
518
599
  binding.continuousTaskAgent = resource;
519
600
  taskBindings.set(resource.taskSessionId, binding);
@@ -524,7 +605,9 @@ function apply(ctx, config = {}) {
524
605
  const taskSessionId = SessionId(`session-${randomUUID()}`);
525
606
  const resource = {
526
607
  taskSessionId,
527
- ...await createTaskAgent(binding, taskSessionId)
608
+ selection,
609
+ sourceHeaderSeq,
610
+ ...await createTaskAgent(binding, taskSessionId, selection)
528
611
  };
529
612
  binding.continuousTaskAgent = resource;
530
613
  taskBindings.set(taskSessionId, binding);
@@ -708,9 +791,10 @@ function apply(ctx, config = {}) {
708
791
  if (continuous) created = await ensureContinuousTaskAgent(binding);
709
792
  else {
710
793
  const taskSessionId = SessionId(`session-${randomUUID()}`);
794
+ const { selection } = taskModelSelection(binding);
711
795
  created = {
712
796
  taskSessionId,
713
- ...await createTaskAgent(binding, taskSessionId)
797
+ ...await createTaskAgent(binding, taskSessionId, selection)
714
798
  };
715
799
  }
716
800
  } catch (error) {
@@ -720,10 +804,21 @@ function apply(ctx, config = {}) {
720
804
  const message = createUserMessage({
721
805
  content: [{
722
806
  type: "text",
723
- text: renderRealtimeDelegation(taskId, call.command.input, call.command.transcriptDelta)
807
+ text: call.command.input
724
808
  }],
725
809
  source: { kind: "user" }
726
810
  });
811
+ const transcriptContext = call.command.transcriptDelta?.trim();
812
+ const contextMessage = transcriptContext === void 0 || transcriptContext === "" ? void 0 : createUserMessage({
813
+ content: [{
814
+ type: "text",
815
+ text: `语音转写补充上下文:${transcriptContext}`
816
+ }],
817
+ source: {
818
+ kind: "plugin",
819
+ plugin: "voice-assistant"
820
+ }
821
+ });
727
822
  const task = {
728
823
  id: taskId,
729
824
  interactionMode: "frontend-agent",
@@ -743,8 +838,12 @@ function apply(ctx, config = {}) {
743
838
  taskSessionId: created.taskSessionId,
744
839
  input: call.command.input
745
840
  });
841
+ if (contextMessage !== void 0) created.agent.inject(contextMessage);
746
842
  created.agent.followup(message);
747
843
  } catch (error) {
844
+ if (contextMessage !== void 0) try {
845
+ created.agent.inbox.remove(contextMessage.id);
846
+ } catch {}
748
847
  if (!continuous) taskBindings.delete(created.taskSessionId);
749
848
  binding.active = void 0;
750
849
  try {
@@ -785,7 +884,7 @@ function apply(ctx, config = {}) {
785
884
  const message = createUserMessage({
786
885
  content: [{
787
886
  type: "text",
788
- text: renderRealtimeDelegationUpdate(task.id, call.command.message)
887
+ text: call.command.message
789
888
  }],
790
889
  source: {
791
890
  kind: "plugin",
@@ -958,6 +1057,8 @@ function apply(ctx, config = {}) {
958
1057
  if (session.interactionMode === "frontend-agent") enqueue(binding, () => binding.voiceSessionId === session.id ? onTaskCommand(binding, session.id, event.call) : void 0);
959
1058
  return;
960
1059
  case "output_audio.started":
1060
+ debugVoiceLatency("audio-started");
1061
+ return;
961
1062
  case "output_audio.delta":
962
1063
  case "output_audio.done":
963
1064
  case "task.observation":
@@ -1071,6 +1172,11 @@ function speechFragments(text, flush) {
1071
1172
  }
1072
1173
  const DEFAULT_DELEGATION_ACKNOWLEDGEMENT = "好的,我先查看一下。";
1073
1174
  const MAX_DELEGATION_ACKNOWLEDGEMENT_LENGTH = 40;
1175
+ const VOICE_LATENCY_DEBUG_PREFIX = "[DEBUG-VOICE-LATENCY]";
1176
+ function debugVoiceLatency(...values) {
1177
+ if (globalThis.process?.env?.DSH_VOICE_LATENCY_DEBUG !== "1") return;
1178
+ console.info(VOICE_LATENCY_DEBUG_PREFIX, (/* @__PURE__ */ new Date()).toISOString(), ...values);
1179
+ }
1074
1180
  function delegationAcknowledgement(value) {
1075
1181
  if (typeof value !== "string") return DEFAULT_DELEGATION_ACKNOWLEDGEMENT;
1076
1182
  const normalized = value.replace(/\s+/gu, " ").trim();
@@ -1176,6 +1282,13 @@ function observeSessionEvent(binding, event, append, config, onDisposeError, rew
1176
1282
  }
1177
1283
  if (event.type !== "turn/end" || event.data.turn !== task.taskTurn) return false;
1178
1284
  const status = terminalStatus(event.data.reason.kind);
1285
+ const failureReason = status === "failed" ? event.data.reason.kind === "error" ? event.data.reason.error.message : event.data.reason.kind : void 0;
1286
+ debugVoiceLatency("turn-end", {
1287
+ taskId: task.id,
1288
+ turn: task.taskTurn,
1289
+ status,
1290
+ ...failureReason === void 0 ? {} : { failureReason }
1291
+ });
1179
1292
  if (status === "completed" && task.waitingUser && task.completionDetail === void 0) {
1180
1293
  delete task.taskTurn;
1181
1294
  delete task.lastAssistantMessage;
@@ -1201,7 +1314,7 @@ function observeSessionEvent(binding, event, append, config, onDisposeError, rew
1201
1314
  },
1202
1315
  ...message === void 0 ? {} : { voiceMessage: message },
1203
1316
  ...announcement === void 0 ? {} : { announcement },
1204
- ...status === "failed" ? { reason: event.data.reason.kind } : {}
1317
+ ...failureReason === void 0 ? {} : { reason: failureReason }
1205
1318
  }, message !== void 0 || announcement !== void 0, true);
1206
1319
  if (rewriteFinalResult) rewrite(task, resultText);
1207
1320
  const disposeVoiceMessage = task.disposeVoiceMessage;
@@ -1237,26 +1350,6 @@ function taskRejection(binding, taskId) {
1237
1350
  message: `task "${taskId}" is being cancelled`
1238
1351
  };
1239
1352
  }
1240
- function renderRealtimeDelegation(taskId, input, transcriptDelta) {
1241
- return [
1242
- "<realtime_delegation>",
1243
- ` <delegation_id>${escapeXmlText(taskId)}</delegation_id>`,
1244
- ` <input>${escapeXmlText(input)}</input>`,
1245
- ...transcriptDelta === void 0 ? [] : [` <transcript_delta>${escapeXmlText(transcriptDelta)}</transcript_delta>`],
1246
- "</realtime_delegation>"
1247
- ].join("\n");
1248
- }
1249
- function renderRealtimeDelegationUpdate(taskId, message) {
1250
- return [
1251
- "<realtime_delegation_update>",
1252
- ` <delegation_id>${escapeXmlText(taskId)}</delegation_id>`,
1253
- ` <message>${escapeXmlText(message)}</message>`,
1254
- "</realtime_delegation_update>"
1255
- ].join("\n");
1256
- }
1257
- function escapeXmlText(value) {
1258
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1259
- }
1260
1353
  function terminalStatus(reason) {
1261
1354
  if (reason === "completed") return "completed";
1262
1355
  if (reason === "aborted") return "cancelled";
@@ -165,17 +165,17 @@ var LocalSession = class {
165
165
  utteranceId,
166
166
  text: event.text
167
167
  });
168
- if (this.interactionMode === "frontend-agent" && event.text.trim() !== "") {
169
- const text = event.text.trim();
170
- this.emitTaskCommand(this.activeTaskId === void 0 ? {
171
- type: "route_transcription",
172
- input: text
173
- } : {
174
- type: "send_task_message",
175
- taskId: this.activeTaskId,
176
- message: text
177
- });
178
- }
168
+ const text = event.text.trim();
169
+ if (text === "") return;
170
+ if (this.interactionMode === "frontend-agent") this.emitTaskCommand(this.activeTaskId === void 0 ? {
171
+ type: "route_transcription",
172
+ input: text
173
+ } : {
174
+ type: "send_task_message",
175
+ taskId: this.activeTaskId,
176
+ message: text
177
+ });
178
+ this.interruptResponse();
179
179
  return;
180
180
  }
181
181
  case "transcription.failed":
@@ -460,7 +460,6 @@ var NodeSpeechBackend = class {
460
460
  const utterance = this.active;
461
461
  if (utterance === void 0 || utterance.confirmed) return;
462
462
  utterance.confirmed = true;
463
- this.interrupt();
464
463
  this.emit?.({
465
464
  type: "transcription.started",
466
465
  utteranceId: utterance.id
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flowingspring/dsh-voco",
3
3
  "description": "Persistent voice conversations for DSH with cloud speech recognition, Edge TTS, and background Agent delegation",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",