@happyvertical/speech 0.105.11 → 0.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -537,4 +537,6 @@ Terminal `response.done` callbacks add optional allowlisted `status` (`completed
537
537
 
538
538
  An already-finished cancellation (`response_cancel_not_active`) is ignored only when nested `error.event_id` matches an outstanding cancellation issued by this session. Correlation is consumed once, bounded to the latest 32 requests, and cleared on close. Missing, unmatched, replayed, evicted or other errors remain visible.
539
539
 
540
+ Set server-owned `automaticResponse: false` on `createOpenAIVoiceCall` when the host must accept finalized input before requesting a reply. Default `true` preserves existing behavior. Semantic and server VAD retain boundaries and interruption; transcription remains enabled, and manual turns stay manual. The concrete `OpenAIWebRTCVoiceSession` capability exposes `sendText(text, false)`; generic `VoiceSession` adapters retain the baseline one-argument method and do not promise suppression. On the concrete session, `voice.sendText(text, false)` appends user input and emits its transcript without requesting or queueing generation. After trusted intake, call `voice.respond()` explicitly. Hosts still call `voice.interrupt()` for typed barge-in. Empty or late ASR does not itself request a response.
541
+
540
542
  Protocol reference: [OpenAI WebRTC](https://developers.openai.com/api/docs/guides/voice-webrtc), [server controls](https://developers.openai.com/api/docs/guides/voice-server-controls). Real media behavior still requires browser QA; deterministic transport tests do not prove microphone permissions or echo cancellation.
@@ -63,6 +63,8 @@ export interface VoiceSession {
63
63
  }
64
64
  /** OpenAI WebRTC capabilities; legacy VoiceSession implementations remain compatible. */
65
65
  export interface OpenAIWebRTCVoiceSession extends VoiceSession {
66
+ /** Append a user text item and transcript; request a reply unless respond is false. */
67
+ sendText(text: string, respond?: boolean): string;
66
68
  /** Append nonempty assistant text (up to 16000 characters) to provider history, without emitting a transcript or requesting a response. */
67
69
  appendAssistantContext(text: string): string;
68
70
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversation/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversation/types.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,8 @@ export interface OpenAIVoiceCallOptions {
10
10
  instructions?: string;
11
11
  transcriptionModel?: string;
12
12
  turnDetection?: StreamingTurnDetection;
13
+ /** Automatically reply to VAD turns. Default true; false keeps VAD and interruption enabled for host-owned response scheduling. */
14
+ automaticResponse?: boolean;
13
15
  maxOutputTokens?: number;
14
16
  tools?: Array<{
15
17
  type: 'function';
@@ -27,7 +29,7 @@ export interface OpenAIVoiceCall {
27
29
  callId: string;
28
30
  }
29
31
  /** Build the GA speech-to-speech session config. Transcription is an optional side channel. */
30
- export declare function openAIConversationConfig(options: Pick<OpenAIVoiceCallOptions, 'model' | 'voice' | 'instructions' | 'transcriptionModel' | 'turnDetection' | 'maxOutputTokens' | 'tools'>): Record<string, unknown>;
32
+ export declare function openAIConversationConfig(options: Pick<OpenAIVoiceCallOptions, 'model' | 'voice' | 'instructions' | 'transcriptionModel' | 'turnDetection' | 'automaticResponse' | 'maxOutputTokens' | 'tools'>): Record<string, unknown>;
31
33
  /** No retries: retrying call creation can create duplicate billable sessions. */
32
34
  export declare function createOpenAIVoiceCall(offer: string, options: OpenAIVoiceCallOptions): Promise<OpenAIVoiceCall>;
33
35
  export type OpenAIVoiceCallControlOptions = Pick<OpenAIVoiceCallOptions, 'apiKey' | 'baseUrl' | 'fetch' | 'signal' | 'timeoutMs'>;
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-server.d.ts","sourceRoot":"","sources":["../src/conversation-server.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,OAAO,EAEL,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,+FAA+F;AAC/F,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,IAAI,CACX,sBAAsB,EACpB,OAAO,GACP,OAAO,GACP,cAAc,GACd,oBAAoB,GACpB,eAAe,GACf,iBAAiB,GACjB,OAAO,CACV,GACA,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0DzB;AAED,iFAAiF;AACjF,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CA0C1B;AAED,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAC9C,sBAAsB,EACtB,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CACxD,CAAC;AAEF,kFAAkF;AAClF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CAKf;AAuFD,qGAAqG;AACrG,qBAAa,mBAAoB,SAAQ,mBAAmB;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAMpC"}
1
+ {"version":3,"file":"conversation-server.d.ts","sourceRoot":"","sources":["../src/conversation-server.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,OAAO,EAEL,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,mIAAmI;IACnI,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,+FAA+F;AAC/F,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,IAAI,CACX,sBAAsB,EACpB,OAAO,GACP,OAAO,GACP,cAAc,GACd,oBAAoB,GACpB,eAAe,GACf,mBAAmB,GACnB,iBAAiB,GACjB,OAAO,CACV,GACA,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0DzB;AAED,iFAAiF;AACjF,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CA0C1B;AAED,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAC9C,sBAAsB,EACtB,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CACxD,CAAC;AAEF,kFAAkF;AAClF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CAKf;AAuFD,qGAAqG;AACrG,qBAAa,mBAAoB,SAAQ,mBAAmB;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAMpC"}
@@ -18,7 +18,7 @@ function openAIConversationConfig(options) {
18
18
  turnDetection = {
19
19
  type: "semantic_vad",
20
20
  eagerness: detection.eagerness ?? "auto",
21
- create_response: true,
21
+ create_response: options.automaticResponse ?? true,
22
22
  interrupt_response: true
23
23
  };
24
24
  break;
@@ -28,7 +28,7 @@ function openAIConversationConfig(options) {
28
28
  ...detection.threshold === void 0 ? {} : { threshold: detection.threshold },
29
29
  ...detection.prefixPaddingMs === void 0 ? {} : { prefix_padding_ms: detection.prefixPaddingMs },
30
30
  ...detection.silenceDurationMs === void 0 ? {} : { silence_duration_ms: detection.silenceDurationMs },
31
- create_response: true,
31
+ create_response: options.automaticResponse ?? true,
32
32
  interrupt_response: true
33
33
  };
34
34
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-server.js","names":[],"sources":["../src/conversation-server.ts"],"sourcesContent":["/** Server-only OpenAI conversational call lifecycle. Hosts own authorization and admission policy. */\nimport {\n SpeechConfigurationError,\n SpeechProviderError,\n} from './shared/errors.js';\nimport type { StreamingTurnDetection } from './shared/streaming-types.js';\nimport type { SpeechFetch } from './shared/types.js';\nimport { isBrowserRuntime } from './shared/websocket.js';\n\nexport interface OpenAIVoiceCallOptions {\n apiKey: string;\n /** Fixed by the host, never copied from untrusted browser JSON. */\n model: string;\n voice?: string;\n instructions?: string;\n transcriptionModel?: string;\n turnDetection?: StreamingTurnDetection;\n maxOutputTokens?: number;\n tools?: Array<{\n type: 'function';\n name: string;\n description?: string;\n parameters: Record<string, unknown>;\n }>;\n baseUrl?: string;\n fetch?: SpeechFetch;\n signal?: AbortSignal;\n timeoutMs?: number;\n}\n\nexport interface OpenAIVoiceCall {\n answer: string;\n callId: string;\n}\n\n/** Build the GA speech-to-speech session config. Transcription is an optional side channel. */\nexport function openAIConversationConfig(\n options: Pick<\n OpenAIVoiceCallOptions,\n | 'model'\n | 'voice'\n | 'instructions'\n | 'transcriptionModel'\n | 'turnDetection'\n | 'maxOutputTokens'\n | 'tools'\n >,\n): Record<string, unknown> {\n if (!options.model?.trim())\n throw new SpeechConfigurationError('Conversation model is required');\n const detection = options.turnDetection ?? {\n type: 'semantic_vad',\n eagerness: 'auto',\n };\n let turnDetection: Record<string, unknown> | null;\n switch (detection.type) {\n case 'manual':\n turnDetection = null;\n break;\n case 'semantic_vad':\n turnDetection = {\n type: 'semantic_vad',\n eagerness: detection.eagerness ?? 'auto',\n create_response: true,\n interrupt_response: true,\n };\n break;\n case 'server_vad':\n turnDetection = {\n type: 'server_vad',\n ...(detection.threshold === undefined\n ? {}\n : { threshold: detection.threshold }),\n ...(detection.prefixPaddingMs === undefined\n ? {}\n : { prefix_padding_ms: detection.prefixPaddingMs }),\n ...(detection.silenceDurationMs === undefined\n ? {}\n : { silence_duration_ms: detection.silenceDurationMs }),\n create_response: true,\n interrupt_response: true,\n };\n break;\n default:\n throw new SpeechConfigurationError('Unknown conversation turn detection');\n }\n return {\n type: 'realtime',\n model: options.model.trim(),\n output_modalities: ['audio'],\n ...(options.instructions ? { instructions: options.instructions } : {}),\n audio: {\n input: {\n turn_detection: turnDetection,\n ...(options.transcriptionModel\n ? { transcription: { model: options.transcriptionModel } }\n : {}),\n },\n output: { voice: options.voice ?? 'marin' },\n },\n ...(options.maxOutputTokens === undefined\n ? {}\n : { max_output_tokens: options.maxOutputTokens }),\n ...(options.tools ? { tools: options.tools } : {}),\n };\n}\n\n/** No retries: retrying call creation can create duplicate billable sessions. */\nexport async function createOpenAIVoiceCall(\n offer: string,\n options: OpenAIVoiceCallOptions,\n): Promise<OpenAIVoiceCall> {\n assertServer(options);\n if (!validSdp(offer))\n throw new SpeechConfigurationError('Expected a bounded SDP offer');\n const body = new FormData();\n body.set('sdp', offer);\n body.set('session', JSON.stringify(openAIConversationConfig(options)));\n let callId: string | undefined;\n try {\n const response = await request(\n callBase(options.baseUrl),\n { body },\n options,\n (headers) => {\n const value = headers.get('location')?.split('/').pop();\n if (!value || !/^rtc_[A-Za-z0-9_-]+$/.test(value))\n throw new SpeechProviderError(\n 'openai-conversation',\n 'Call response did not contain a WebRTC call id',\n );\n // Capture identity before consuming a response body that can fail or time out.\n callId = value;\n },\n );\n const answer = await response.text();\n if (!validSdp(answer))\n throw new SpeechProviderError(\n 'openai-conversation',\n 'Call response did not contain a valid SDP answer',\n );\n options.signal?.throwIfAborted();\n return { answer, callId: callId! };\n } catch (error) {\n if (callId) {\n try {\n await hangupOpenAIVoiceCall(callId, { ...options, signal: undefined });\n } catch {\n throw new VoiceCallSetupError(callId);\n }\n }\n throw error;\n }\n}\n\nexport type OpenAIVoiceCallControlOptions = Pick<\n OpenAIVoiceCallOptions,\n 'apiKey' | 'baseUrl' | 'fetch' | 'signal' | 'timeoutMs'\n>;\n\n/** Hosts should call this on close, idle/hard timeout and failed client setup. */\nexport async function hangupOpenAIVoiceCall(\n callId: string,\n options: OpenAIVoiceCallControlOptions,\n): Promise<void> {\n assertServer(options);\n if (!/^rtc_[A-Za-z0-9_-]+$/.test(callId))\n throw new SpeechConfigurationError('Invalid WebRTC call id');\n await request(`${callBase(options.baseUrl)}/${callId}/hangup`, {}, options);\n}\n\nfunction assertServer(options: OpenAIVoiceCallControlOptions): void {\n if (isBrowserRuntime())\n throw new SpeechConfigurationError(\n 'Voice call management is server-only; never expose a provider key',\n );\n if (!options.apiKey?.trim())\n throw new SpeechConfigurationError(\n 'Voice call management requires a server API key',\n );\n const timeout = options.timeoutMs ?? 15_000;\n if (!Number.isFinite(timeout) || timeout <= 0)\n throw new SpeechConfigurationError('timeoutMs must be positive');\n}\n\nfunction callBase(baseUrl = 'https://api.openai.com/v1'): string {\n const url = new URL(baseUrl);\n if (\n !['http:', 'https:'].includes(url.protocol) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n )\n throw new SpeechConfigurationError('Invalid voice call API base URL');\n url.pathname = `${url.pathname.replace(/\\/$/, '')}${/\\/v\\d+$/.test(url.pathname.replace(/\\/$/, '')) ? '' : '/v1'}/realtime/calls`;\n return url.toString();\n}\n\nfunction validSdp(value: string): boolean {\n return (\n typeof value === 'string' &&\n value.length <= 65_536 &&\n value.startsWith('v=0') &&\n value.includes('m=audio')\n );\n}\n\nasync function request(\n url: string,\n init: RequestInit,\n options: OpenAIVoiceCallControlOptions,\n onHeaders?: (headers: Headers) => void,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n options.timeoutMs ?? 15_000,\n );\n const signal = AbortSignal.any([\n controller.signal,\n ...(options.signal ? [options.signal] : []),\n ]);\n try {\n const response = await (options.fetch ?? fetch)(url, {\n ...init,\n method: 'POST',\n headers: { authorization: `Bearer ${options.apiKey}` },\n signal,\n });\n if (!response.ok)\n throw new SpeechProviderError(\n 'openai-conversation',\n 'OpenAI voice call request failed',\n { status: response.status },\n );\n onHeaders?.(response.headers);\n const body = await readBody(response, signal);\n return new Response(response.status === 204 ? null : body, {\n status: response.status,\n headers: response.headers,\n });\n } catch (error) {\n if (error instanceof SpeechProviderError) throw error;\n // Deliberately omit provider payloads and transport messages, which can contain credentials.\n throw new SpeechProviderError(\n 'openai-conversation',\n signal.aborted\n ? 'OpenAI voice call request cancelled or timed out'\n : 'OpenAI voice call transport failed',\n );\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** Failed setup whose cleanup also failed. The host must reserve admission and retry termination. */\nexport class VoiceCallSetupError extends SpeechProviderError {\n constructor(readonly callId: string) {\n super(\n 'openai-conversation',\n 'Voice setup failed and termination needs retry',\n );\n }\n}\n\nasync function readBody(\n response: Response,\n signal: AbortSignal,\n): Promise<string> {\n const reader = response.body?.getReader();\n if (!reader) return '';\n const chunks: Uint8Array[] = [];\n let size = 0;\n const cancel = () => {\n void reader.cancel().catch(() => undefined);\n };\n signal.addEventListener('abort', cancel, { once: true });\n try {\n while (true) {\n signal.throwIfAborted();\n const { done, value } = await reader.read();\n signal.throwIfAborted();\n if (done) break;\n size += value.byteLength;\n if (size > 65_536)\n throw new SpeechProviderError(\n 'openai-conversation',\n 'Voice call response exceeded its size limit',\n );\n chunks.push(value);\n }\n const data = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n data.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return new TextDecoder().decode(data);\n } finally {\n signal.removeEventListener('abort', cancel);\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"conversation-server.js","names":[],"sources":["../src/conversation-server.ts"],"sourcesContent":["/** Server-only OpenAI conversational call lifecycle. Hosts own authorization and admission policy. */\nimport {\n SpeechConfigurationError,\n SpeechProviderError,\n} from './shared/errors.js';\nimport type { StreamingTurnDetection } from './shared/streaming-types.js';\nimport type { SpeechFetch } from './shared/types.js';\nimport { isBrowserRuntime } from './shared/websocket.js';\n\nexport interface OpenAIVoiceCallOptions {\n apiKey: string;\n /** Fixed by the host, never copied from untrusted browser JSON. */\n model: string;\n voice?: string;\n instructions?: string;\n transcriptionModel?: string;\n turnDetection?: StreamingTurnDetection;\n /** Automatically reply to VAD turns. Default true; false keeps VAD and interruption enabled for host-owned response scheduling. */\n automaticResponse?: boolean;\n maxOutputTokens?: number;\n tools?: Array<{\n type: 'function';\n name: string;\n description?: string;\n parameters: Record<string, unknown>;\n }>;\n baseUrl?: string;\n fetch?: SpeechFetch;\n signal?: AbortSignal;\n timeoutMs?: number;\n}\n\nexport interface OpenAIVoiceCall {\n answer: string;\n callId: string;\n}\n\n/** Build the GA speech-to-speech session config. Transcription is an optional side channel. */\nexport function openAIConversationConfig(\n options: Pick<\n OpenAIVoiceCallOptions,\n | 'model'\n | 'voice'\n | 'instructions'\n | 'transcriptionModel'\n | 'turnDetection'\n | 'automaticResponse'\n | 'maxOutputTokens'\n | 'tools'\n >,\n): Record<string, unknown> {\n if (!options.model?.trim())\n throw new SpeechConfigurationError('Conversation model is required');\n const detection = options.turnDetection ?? {\n type: 'semantic_vad',\n eagerness: 'auto',\n };\n let turnDetection: Record<string, unknown> | null;\n switch (detection.type) {\n case 'manual':\n turnDetection = null;\n break;\n case 'semantic_vad':\n turnDetection = {\n type: 'semantic_vad',\n eagerness: detection.eagerness ?? 'auto',\n create_response: options.automaticResponse ?? true,\n interrupt_response: true,\n };\n break;\n case 'server_vad':\n turnDetection = {\n type: 'server_vad',\n ...(detection.threshold === undefined\n ? {}\n : { threshold: detection.threshold }),\n ...(detection.prefixPaddingMs === undefined\n ? {}\n : { prefix_padding_ms: detection.prefixPaddingMs }),\n ...(detection.silenceDurationMs === undefined\n ? {}\n : { silence_duration_ms: detection.silenceDurationMs }),\n create_response: options.automaticResponse ?? true,\n interrupt_response: true,\n };\n break;\n default:\n throw new SpeechConfigurationError('Unknown conversation turn detection');\n }\n return {\n type: 'realtime',\n model: options.model.trim(),\n output_modalities: ['audio'],\n ...(options.instructions ? { instructions: options.instructions } : {}),\n audio: {\n input: {\n turn_detection: turnDetection,\n ...(options.transcriptionModel\n ? { transcription: { model: options.transcriptionModel } }\n : {}),\n },\n output: { voice: options.voice ?? 'marin' },\n },\n ...(options.maxOutputTokens === undefined\n ? {}\n : { max_output_tokens: options.maxOutputTokens }),\n ...(options.tools ? { tools: options.tools } : {}),\n };\n}\n\n/** No retries: retrying call creation can create duplicate billable sessions. */\nexport async function createOpenAIVoiceCall(\n offer: string,\n options: OpenAIVoiceCallOptions,\n): Promise<OpenAIVoiceCall> {\n assertServer(options);\n if (!validSdp(offer))\n throw new SpeechConfigurationError('Expected a bounded SDP offer');\n const body = new FormData();\n body.set('sdp', offer);\n body.set('session', JSON.stringify(openAIConversationConfig(options)));\n let callId: string | undefined;\n try {\n const response = await request(\n callBase(options.baseUrl),\n { body },\n options,\n (headers) => {\n const value = headers.get('location')?.split('/').pop();\n if (!value || !/^rtc_[A-Za-z0-9_-]+$/.test(value))\n throw new SpeechProviderError(\n 'openai-conversation',\n 'Call response did not contain a WebRTC call id',\n );\n // Capture identity before consuming a response body that can fail or time out.\n callId = value;\n },\n );\n const answer = await response.text();\n if (!validSdp(answer))\n throw new SpeechProviderError(\n 'openai-conversation',\n 'Call response did not contain a valid SDP answer',\n );\n options.signal?.throwIfAborted();\n return { answer, callId: callId! };\n } catch (error) {\n if (callId) {\n try {\n await hangupOpenAIVoiceCall(callId, { ...options, signal: undefined });\n } catch {\n throw new VoiceCallSetupError(callId);\n }\n }\n throw error;\n }\n}\n\nexport type OpenAIVoiceCallControlOptions = Pick<\n OpenAIVoiceCallOptions,\n 'apiKey' | 'baseUrl' | 'fetch' | 'signal' | 'timeoutMs'\n>;\n\n/** Hosts should call this on close, idle/hard timeout and failed client setup. */\nexport async function hangupOpenAIVoiceCall(\n callId: string,\n options: OpenAIVoiceCallControlOptions,\n): Promise<void> {\n assertServer(options);\n if (!/^rtc_[A-Za-z0-9_-]+$/.test(callId))\n throw new SpeechConfigurationError('Invalid WebRTC call id');\n await request(`${callBase(options.baseUrl)}/${callId}/hangup`, {}, options);\n}\n\nfunction assertServer(options: OpenAIVoiceCallControlOptions): void {\n if (isBrowserRuntime())\n throw new SpeechConfigurationError(\n 'Voice call management is server-only; never expose a provider key',\n );\n if (!options.apiKey?.trim())\n throw new SpeechConfigurationError(\n 'Voice call management requires a server API key',\n );\n const timeout = options.timeoutMs ?? 15_000;\n if (!Number.isFinite(timeout) || timeout <= 0)\n throw new SpeechConfigurationError('timeoutMs must be positive');\n}\n\nfunction callBase(baseUrl = 'https://api.openai.com/v1'): string {\n const url = new URL(baseUrl);\n if (\n !['http:', 'https:'].includes(url.protocol) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n )\n throw new SpeechConfigurationError('Invalid voice call API base URL');\n url.pathname = `${url.pathname.replace(/\\/$/, '')}${/\\/v\\d+$/.test(url.pathname.replace(/\\/$/, '')) ? '' : '/v1'}/realtime/calls`;\n return url.toString();\n}\n\nfunction validSdp(value: string): boolean {\n return (\n typeof value === 'string' &&\n value.length <= 65_536 &&\n value.startsWith('v=0') &&\n value.includes('m=audio')\n );\n}\n\nasync function request(\n url: string,\n init: RequestInit,\n options: OpenAIVoiceCallControlOptions,\n onHeaders?: (headers: Headers) => void,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n options.timeoutMs ?? 15_000,\n );\n const signal = AbortSignal.any([\n controller.signal,\n ...(options.signal ? [options.signal] : []),\n ]);\n try {\n const response = await (options.fetch ?? fetch)(url, {\n ...init,\n method: 'POST',\n headers: { authorization: `Bearer ${options.apiKey}` },\n signal,\n });\n if (!response.ok)\n throw new SpeechProviderError(\n 'openai-conversation',\n 'OpenAI voice call request failed',\n { status: response.status },\n );\n onHeaders?.(response.headers);\n const body = await readBody(response, signal);\n return new Response(response.status === 204 ? null : body, {\n status: response.status,\n headers: response.headers,\n });\n } catch (error) {\n if (error instanceof SpeechProviderError) throw error;\n // Deliberately omit provider payloads and transport messages, which can contain credentials.\n throw new SpeechProviderError(\n 'openai-conversation',\n signal.aborted\n ? 'OpenAI voice call request cancelled or timed out'\n : 'OpenAI voice call transport failed',\n );\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** Failed setup whose cleanup also failed. The host must reserve admission and retry termination. */\nexport class VoiceCallSetupError extends SpeechProviderError {\n constructor(readonly callId: string) {\n super(\n 'openai-conversation',\n 'Voice setup failed and termination needs retry',\n );\n }\n}\n\nasync function readBody(\n response: Response,\n signal: AbortSignal,\n): Promise<string> {\n const reader = response.body?.getReader();\n if (!reader) return '';\n const chunks: Uint8Array[] = [];\n let size = 0;\n const cancel = () => {\n void reader.cancel().catch(() => undefined);\n };\n signal.addEventListener('abort', cancel, { once: true });\n try {\n while (true) {\n signal.throwIfAborted();\n const { done, value } = await reader.read();\n signal.throwIfAborted();\n if (done) break;\n size += value.byteLength;\n if (size > 65_536)\n throw new SpeechProviderError(\n 'openai-conversation',\n 'Voice call response exceeded its size limit',\n );\n chunks.push(value);\n }\n const data = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n data.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return new TextDecoder().decode(data);\n } finally {\n signal.removeEventListener('abort', cancel);\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n}\n"],"mappings":"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"}
@@ -332,7 +332,7 @@ function createOpenAIWebRTCVoiceSession(options) {
332
332
  });
333
333
  return pending;
334
334
  },
335
- sendText(text) {
335
+ sendText(text, respond = true) {
336
336
  const trimmed = text.trim();
337
337
  if (!trimmed || trimmed.length > 16e3) throw new SpeechConfigurationError("Text must contain 1–16000 characters", ADAPTER);
338
338
  const id = `hv_${crypto.randomUUID().replaceAll("-", "").slice(0, 24)}`;
@@ -349,7 +349,7 @@ function createOpenAIWebRTCVoiceSession(options) {
349
349
  }
350
350
  });
351
351
  transcript(id, "user", trimmed, true);
352
- session.respond();
352
+ if (respond) session.respond();
353
353
  return id;
354
354
  },
355
355
  appendAssistantContext(text) {
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.js","names":[],"sources":["../src/conversation/openai-webrtc.ts"],"sourcesContent":["import {\n SpeechConfigurationError,\n SpeechProviderError,\n} from '../shared/errors.js';\nimport type {\n OpenAIWebRTCVoiceOptions,\n OpenAIWebRTCVoiceSession,\n VoiceSessionEventMetadata,\n VoiceSessionEvents,\n VoiceSessionState,\n VoiceSpeakingMetadata,\n VoiceTranscript,\n} from './types.js';\n\nconst ADAPTER = 'openai-conversation';\nconst MAX_PENDING_CANCELLATIONS = 32;\n\n/** Browser WebRTC transport. No credentials, Node dependencies or globals are read at import. */\nexport function createOpenAIWebRTCVoiceSession(\n options: OpenAIWebRTCVoiceOptions,\n): OpenAIWebRTCVoiceSession {\n let state: VoiceSessionState = 'idle';\n let pending: Promise<void> | undefined;\n let peer: RTCPeerConnection | undefined;\n let channel: RTCDataChannel | undefined;\n let microphone: MediaStream | undefined;\n let audio: HTMLAudioElement | undefined;\n let micMuted = options.micMuted ?? false;\n let outputMuted = options.outputMuted ?? false;\n let responseId: string | undefined;\n let responseActive = false;\n let pendingResponseCreateEventId: string | undefined;\n const pendingCancellations = new Set<string>();\n let responseQueued = false;\n let queuedResponseInstructions: string | undefined;\n let speaking = false;\n let playbackResponseId: string | undefined;\n const abort = new AbortController();\n const listeners = new Map<\n keyof VoiceSessionEvents,\n Set<(value: never, metadata?: VoiceSessionEventMetadata) => void>\n >();\n const turns = new Map<string, VoiceTranscript>();\n const disposers: (() => void)[] = [];\n\n function emit<E extends keyof VoiceSessionEvents>(\n event: E,\n value: VoiceSessionEvents[E],\n metadata?: VoiceSessionEventMetadata,\n ): void {\n for (const listener of listeners.get(event) ?? []) {\n if (metadata) listener(value as never, metadata);\n else listener(value as never);\n }\n }\n function change(next: VoiceSessionState): void {\n state = next;\n emit('state', next);\n }\n function playback(\n value: boolean,\n metadata?: VoiceSpeakingMetadata,\n identityChanged = false,\n ): void {\n if (speaking === value && !identityChanged) return;\n speaking = value;\n emit('speaking', value, metadata);\n }\n function playbackEvent(\n active: boolean,\n reason: 'started' | 'stopped' | 'cleared',\n providerId: unknown,\n ): void {\n const id = identity(providerId);\n const metadata = { ...(id ? { responseId: id } : {}), reason };\n if (!active && id && playbackResponseId && id !== playbackResponseId) {\n // A late drain for A does not stop the current B playback.\n emit('speaking', speaking, metadata);\n return;\n }\n const changed = active && id !== playbackResponseId;\n playbackResponseId = active ? id : undefined;\n playback(active && !outputMuted, metadata, changed && !outputMuted);\n }\n function cleanup(reason?: 'closed'): void {\n abort.abort();\n for (const dispose of disposers.splice(0)) dispose();\n microphone?.getTracks().forEach((track) => {\n track.stop();\n });\n microphone = undefined;\n if (channel) {\n channel.onopen =\n channel.onmessage =\n channel.onclose =\n channel.onerror =\n null;\n channel.close();\n }\n if (peer) {\n peer.ontrack = peer.onconnectionstatechange = null;\n peer.close();\n }\n if (audio) {\n audio.pause();\n audio.srcObject = null;\n }\n playback(false, {\n ...(playbackResponseId ? { responseId: playbackResponseId } : {}),\n ...(reason ? { reason } : {}),\n });\n playbackResponseId = undefined;\n responseActive = false;\n pendingResponseCreateEventId = undefined;\n responseQueued = false;\n responseId = undefined;\n queuedResponseInstructions = undefined;\n turns.clear();\n pendingCancellations.clear();\n }\n function fail(message: string): void {\n if (state === 'closed' || state === 'failed') return;\n state = 'failed';\n cleanup();\n emit('state', state);\n emit('error', new SpeechProviderError(ADAPTER, message));\n }\n function send(event: Record<string, unknown>): void {\n if (state !== 'connected' || channel?.readyState !== 'open')\n throw new SpeechConfigurationError(\n 'Voice session is not connected',\n ADAPTER,\n );\n channel.send(JSON.stringify(event));\n }\n function createResponse(instructions?: string): void {\n // A response is active as soon as the request is sent. Waiting for\n // response.created leaves a gap in which a typed turn can create a\n // conflicting default-conversation response. The event ID lets an error\n // prove that this speculative request was rejected before it became active.\n const eventId = `hv_${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}`;\n responseId = undefined;\n send({\n event_id: eventId,\n type: 'response.create',\n ...(instructions ? { response: { instructions } } : {}),\n });\n pendingResponseCreateEventId = eventId;\n responseActive = true;\n }\n function requestResponse(instructions?: string): void {\n if (responseActive) {\n responseQueued = true;\n if (instructions) queuedResponseInstructions = instructions;\n return;\n }\n responseQueued = false;\n const nextInstructions = instructions ?? queuedResponseInstructions;\n queuedResponseInstructions = undefined;\n createResponse(nextInstructions);\n }\n function releaseQueuedResponse(): void {\n if (!responseQueued || responseActive || state !== 'connected') return;\n responseQueued = false;\n const instructions = queuedResponseInstructions;\n queuedResponseInstructions = undefined;\n createResponse(instructions);\n }\n function transcript(\n itemId: unknown,\n role: VoiceTranscript['role'],\n text: unknown,\n final: boolean,\n delta = false,\n providerResponseId?: unknown,\n ): void {\n if (typeof itemId !== 'string' || typeof text !== 'string') return;\n const prior = turns.get(itemId);\n // Late duplicates/deltas must not overwrite a final turn or its role.\n if (prior?.final || (prior && prior.role !== role)) return;\n const turn = {\n itemId,\n role,\n text: delta ? (prior?.text ?? '') + text : text,\n final,\n ...((identity(providerResponseId) ?? prior?.responseId)\n ? { responseId: identity(providerResponseId) ?? prior?.responseId }\n : {}),\n };\n turns.set(itemId, turn);\n emit('transcript', turn);\n }\n function receive(data: unknown): void {\n let event: Record<string, unknown>;\n try {\n const parsed: unknown =\n typeof data === 'string' ? JSON.parse(data) : data;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return;\n event = parsed as Record<string, unknown>;\n } catch {\n return;\n }\n switch (event.type) {\n case 'conversation.item.input_audio_transcription.delta':\n transcript(event.item_id, 'user', event.delta, false, true);\n break;\n case 'conversation.item.input_audio_transcription.completed':\n transcript(event.item_id, 'user', event.transcript, true);\n break;\n case 'conversation.item.input_audio_transcription.failed':\n emit(\n 'error',\n new SpeechProviderError(ADAPTER, 'Input transcription failed'),\n );\n break;\n case 'response.output_audio_transcript.delta':\n transcript(\n event.item_id,\n 'assistant',\n event.delta,\n false,\n true,\n event.response_id,\n );\n break;\n case 'response.output_audio_transcript.done':\n transcript(\n event.item_id,\n 'assistant',\n event.transcript,\n true,\n false,\n event.response_id,\n );\n break;\n case 'response.output_text.delta':\n transcript(\n event.item_id,\n 'assistant',\n event.delta,\n false,\n true,\n event.response_id,\n );\n break;\n case 'response.output_text.done':\n transcript(\n event.item_id,\n 'assistant',\n event.text,\n true,\n false,\n event.response_id,\n );\n break;\n case 'input_audio_buffer.speech_started':\n emit('inputSpeech', true, {\n itemId:\n typeof event.item_id === 'string' && event.item_id\n ? event.item_id\n : undefined,\n });\n break;\n case 'input_audio_buffer.speech_stopped':\n emit('inputSpeech', false, {\n itemId:\n typeof event.item_id === 'string' && event.item_id\n ? event.item_id\n : undefined,\n });\n break;\n case 'output_audio_buffer.started':\n playbackEvent(true, 'started', event.response_id);\n break;\n case 'output_audio_buffer.stopped':\n playbackEvent(false, 'stopped', event.response_id);\n break;\n case 'output_audio_buffer.cleared':\n playbackEvent(false, 'cleared', event.response_id);\n break;\n case 'response.created': {\n const response = record(event.response);\n responseId = typeof response.id === 'string' ? response.id : undefined;\n pendingResponseCreateEventId = undefined;\n responseActive = true;\n emit('response', { active: true, responseId });\n break;\n }\n case 'response.done': {\n const response = record(event.response);\n const wasActive = responseActive;\n pendingResponseCreateEventId = undefined;\n responseActive = false;\n const status = response.status;\n const safeStatus =\n status === 'completed' ||\n status === 'incomplete' ||\n status === 'cancelled' ||\n status === 'failed'\n ? status\n : undefined;\n const reason = record(response.status_details).reason;\n const safeReason =\n (status === 'incomplete' &&\n (reason === 'max_output_tokens' || reason === 'content_filter')) ||\n (status === 'cancelled' &&\n (reason === 'turn_detected' || reason === 'client_cancelled'))\n ? reason\n : undefined;\n emit('response', {\n active: false,\n responseId,\n ...(safeStatus ? { status: safeStatus } : {}),\n ...(safeReason ? { statusReason: safeReason } : {}),\n });\n\n if (response.usage && typeof response.usage === 'object')\n emit('usage', record(response.usage));\n if (response.status === 'failed' || response.status === 'incomplete')\n emit(\n 'error',\n new SpeechProviderError(\n ADAPTER,\n response.status === 'incomplete'\n ? 'Voice response incomplete'\n : 'Voice response failed',\n ),\n );\n // A response listener may synchronously request a tool continuation.\n // That request consumes the coalesced typed turn, so only drain when it\n // remains pending after listeners have run.\n if (wasActive) releaseQueuedResponse();\n break;\n }\n case 'response.function_call_arguments.done':\n if (\n typeof event.call_id === 'string' &&\n typeof event.name === 'string' &&\n typeof event.arguments === 'string'\n )\n emit('toolCall', {\n callId: event.call_id,\n name: event.name,\n arguments: event.arguments,\n });\n break;\n case 'error': {\n const error = record(event.error);\n // Only nested error.event_id identifies the client request; the outer\n // event_id identifies the server event and grants no correlation.\n const ownCancellation =\n typeof error.event_id === 'string' &&\n pendingCancellations.delete(error.event_id);\n if (ownCancellation && error.code === 'response_cancel_not_active')\n break;\n\n const rejectedPendingResponse =\n pendingResponseCreateEventId !== undefined &&\n error.event_id === pendingResponseCreateEventId;\n if (rejectedPendingResponse) {\n pendingResponseCreateEventId = undefined;\n responseActive = false;\n responseId = undefined;\n }\n emit(\n 'error',\n new SpeechProviderError(\n ADAPTER,\n 'Realtime provider rejected a conversation event',\n ),\n );\n // Error handlers can synchronously request a retry. If none did, a\n // correlated rejection must release the coalesced typed response.\n if (rejectedPendingResponse) releaseQueuedResponse();\n break;\n }\n }\n }\n\n const session: OpenAIWebRTCVoiceSession = {\n get state() {\n return state;\n },\n connect() {\n if (pending) return pending;\n if (state === 'connected') return Promise.resolve();\n if (state !== 'idle')\n return Promise.reject(\n new SpeechConfigurationError(\n 'Create a new voice session after close or failure',\n ADAPTER,\n ),\n );\n const timeoutMs = options.connectTimeoutMs ?? 20_000;\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)\n return Promise.reject(\n new SpeechConfigurationError(\n 'connectTimeoutMs must be positive',\n ADAPTER,\n ),\n );\n change('connecting');\n const signal = AbortSignal.any([\n abort.signal,\n ...(options.signal ? [options.signal] : []),\n ]);\n pending = (async () => {\n const timeout = setTimeout(\n () => fail('Voice connection timed out'),\n timeoutMs,\n );\n const onAbort = () => {\n if (state !== 'closed' && state !== 'failed')\n fail('Voice connection cancelled');\n };\n signal.addEventListener('abort', onAbort, { once: true });\n disposers.push(() => signal.removeEventListener('abort', onAbort));\n try {\n signal.throwIfAborted();\n // Permission acquisition cannot be aborted; dispose even if it resolves after close.\n const acquisition = options.getMicrophone().then((stream) => {\n if (signal.aborted)\n stream.getTracks().forEach((track) => {\n track.stop();\n });\n else microphone = stream;\n return stream;\n });\n const stream = await abortable(acquisition, signal);\n signal.throwIfAborted();\n stream.getAudioTracks().forEach((track) => {\n track.enabled = !micMuted;\n const ended = () =>\n fail('Microphone ended; allow access and start a new session');\n track.addEventListener('ended', ended);\n disposers.push(() => track.removeEventListener('ended', ended));\n if (track.readyState === 'ended')\n throw new SpeechConfigurationError(\n 'Microphone stream has ended',\n ADAPTER,\n );\n });\n if (!stream.getAudioTracks().length)\n throw new SpeechConfigurationError(\n 'Microphone stream has no audio tracks',\n ADAPTER,\n );\n peer = options.createPeerConnection?.() ?? new RTCPeerConnection();\n audio = options.audio ?? new Audio();\n audio.autoplay = true;\n audio.muted = outputMuted;\n peer.ontrack = (event) => {\n if (!audio || signal.aborted) return;\n audio.srcObject =\n event.streams[0] ?? new MediaStream([event.track]);\n void audio\n .play()\n .catch(() =>\n emit(\n 'error',\n new SpeechProviderError(\n ADAPTER,\n 'Audio playback needs a user gesture',\n ),\n ),\n );\n };\n peer.onconnectionstatechange = () => {\n if (\n peer?.connectionState === 'failed' ||\n peer?.connectionState === 'disconnected'\n )\n fail('Voice connection lost; start a new session');\n };\n for (const track of stream.getAudioTracks())\n peer.addTrack(track, stream);\n channel = peer.createDataChannel('oai-events');\n channel.onmessage = (event) => receive(event.data);\n // Attach before SDP negotiation; some transports open during setRemoteDescription.\n const opened = new Promise<void>((resolve, reject) => {\n const cancel = () =>\n reject(\n new SpeechProviderError(ADAPTER, 'Voice connection cancelled'),\n );\n signal.addEventListener('abort', cancel, { once: true });\n disposers.push(() => signal.removeEventListener('abort', cancel));\n if (channel) {\n channel.onopen = () => {\n signal.removeEventListener('abort', cancel);\n resolve();\n };\n channel.onclose = () => {\n fail('Voice channel closed');\n reject(\n new SpeechProviderError(ADAPTER, 'Voice channel closed'),\n );\n };\n channel.onerror = () => {\n fail('Voice channel failed');\n reject(\n new SpeechProviderError(ADAPTER, 'Voice channel failed'),\n );\n };\n }\n });\n // Keep an early rejection handled while negotiation is pending.\n void opened.catch(() => undefined);\n const offer = await abortable(peer.createOffer(), signal);\n await abortable(peer.setLocalDescription(offer), signal);\n signal.throwIfAborted();\n if (!offer.sdp)\n throw new SpeechConfigurationError(\n 'WebRTC did not produce an SDP offer',\n ADAPTER,\n );\n const answer = await abortable(\n options.negotiate(offer.sdp, signal),\n signal,\n );\n signal.throwIfAborted();\n await abortable(\n peer.setRemoteDescription({ type: 'answer', sdp: answer }),\n signal,\n );\n await opened;\n signal.throwIfAborted();\n change('connected');\n } catch (error) {\n fail('Unable to connect voice session');\n throw error;\n } finally {\n clearTimeout(timeout);\n }\n })().finally(() => {\n pending = undefined;\n });\n return pending;\n },\n sendText(text) {\n const trimmed = text.trim();\n if (!trimmed || trimmed.length > 16_000)\n throw new SpeechConfigurationError(\n 'Text must contain 1–16000 characters',\n ADAPTER,\n );\n const id = `hv_${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}`;\n send({\n type: 'conversation.item.create',\n item: {\n id,\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: trimmed }],\n },\n });\n transcript(id, 'user', trimmed, true);\n session.respond();\n return id;\n },\n appendAssistantContext(text) {\n const trimmed = text.trim();\n if (!trimmed || trimmed.length > 16_000)\n throw new SpeechConfigurationError(\n 'Text must contain 1–16000 characters',\n ADAPTER,\n );\n const id = `hv_${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}`;\n send({\n type: 'conversation.item.create',\n item: {\n id,\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: trimmed }],\n },\n });\n return id;\n },\n commitInput(respond = true) {\n send({ type: 'input_audio_buffer.commit' });\n if (respond) session.respond();\n },\n respond(instructions) {\n requestResponse(instructions);\n },\n interrupt() {\n if (state !== 'connected') return;\n if (responseActive) {\n const eventId = `hv_${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}`;\n send({\n event_id: eventId,\n type: 'response.cancel',\n ...(responseId ? { response_id: responseId } : {}),\n });\n pendingCancellations.add(eventId);\n if (pendingCancellations.size > MAX_PENDING_CANCELLATIONS) {\n const oldest = pendingCancellations.values().next().value;\n if (oldest) pendingCancellations.delete(oldest);\n }\n }\n send({ type: 'output_audio_buffer.clear' });\n playback(false, {\n ...(playbackResponseId ? { responseId: playbackResponseId } : {}),\n reason: 'interrupted',\n });\n playbackResponseId = undefined;\n },\n setMicMuted(muted) {\n micMuted = muted;\n microphone?.getAudioTracks().forEach((track) => {\n track.enabled = !muted;\n });\n if (muted && state === 'connected')\n send({ type: 'input_audio_buffer.clear' });\n },\n setOutputMuted(muted) {\n outputMuted = muted;\n if (audio) audio.muted = muted;\n if (muted)\n playback(false, {\n ...(playbackResponseId ? { responseId: playbackResponseId } : {}),\n reason: 'muted',\n });\n },\n submitToolResult(callId, result) {\n send({\n type: 'conversation.item.create',\n item: {\n type: 'function_call_output',\n call_id: callId,\n output: JSON.stringify(result),\n },\n });\n },\n close() {\n if (state === 'closed') return;\n state = 'closed';\n cleanup('closed');\n emit('state', state);\n listeners.clear();\n },\n on(event, listener) {\n const set = listeners.get(event) ?? new Set();\n set.add(\n listener as (\n value: never,\n metadata?: VoiceSessionEventMetadata,\n ) => void,\n );\n listeners.set(event, set);\n return () => {\n set.delete(\n listener as (\n value: never,\n metadata?: VoiceSessionEventMetadata,\n ) => void,\n );\n };\n },\n };\n return session;\n}\n\nfunction identity(value: unknown): string | undefined {\n return typeof value === 'string' && value ? value : undefined;\n}\n\nfunction record(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(signal.reason);\n return new Promise((resolve, reject) => {\n const cancel = () => reject(signal.reason);\n signal.addEventListener('abort', cancel, { once: true });\n promise\n .then(resolve, reject)\n .finally(() => signal.removeEventListener('abort', cancel));\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"conversation.js","names":[],"sources":["../src/conversation/openai-webrtc.ts"],"sourcesContent":["import {\n SpeechConfigurationError,\n SpeechProviderError,\n} from '../shared/errors.js';\nimport type {\n OpenAIWebRTCVoiceOptions,\n OpenAIWebRTCVoiceSession,\n VoiceSessionEventMetadata,\n VoiceSessionEvents,\n VoiceSessionState,\n VoiceSpeakingMetadata,\n VoiceTranscript,\n} from './types.js';\n\nconst ADAPTER = 'openai-conversation';\nconst MAX_PENDING_CANCELLATIONS = 32;\n\n/** Browser WebRTC transport. No credentials, Node dependencies or globals are read at import. */\nexport function createOpenAIWebRTCVoiceSession(\n options: OpenAIWebRTCVoiceOptions,\n): OpenAIWebRTCVoiceSession {\n let state: VoiceSessionState = 'idle';\n let pending: Promise<void> | undefined;\n let peer: RTCPeerConnection | undefined;\n let channel: RTCDataChannel | undefined;\n let microphone: MediaStream | undefined;\n let audio: HTMLAudioElement | undefined;\n let micMuted = options.micMuted ?? false;\n let outputMuted = options.outputMuted ?? false;\n let responseId: string | undefined;\n let responseActive = false;\n let pendingResponseCreateEventId: string | undefined;\n const pendingCancellations = new Set<string>();\n let responseQueued = false;\n let queuedResponseInstructions: string | undefined;\n let speaking = false;\n let playbackResponseId: string | undefined;\n const abort = new AbortController();\n const listeners = new Map<\n keyof VoiceSessionEvents,\n Set<(value: never, metadata?: VoiceSessionEventMetadata) => void>\n >();\n const turns = new Map<string, VoiceTranscript>();\n const disposers: (() => void)[] = [];\n\n function emit<E extends keyof VoiceSessionEvents>(\n event: E,\n value: VoiceSessionEvents[E],\n metadata?: VoiceSessionEventMetadata,\n ): void {\n for (const listener of listeners.get(event) ?? []) {\n if (metadata) listener(value as never, metadata);\n else listener(value as never);\n }\n }\n function change(next: VoiceSessionState): void {\n state = next;\n emit('state', next);\n }\n function playback(\n value: boolean,\n metadata?: VoiceSpeakingMetadata,\n identityChanged = false,\n ): void {\n if (speaking === value && !identityChanged) return;\n speaking = value;\n emit('speaking', value, metadata);\n }\n function playbackEvent(\n active: boolean,\n reason: 'started' | 'stopped' | 'cleared',\n providerId: unknown,\n ): void {\n const id = identity(providerId);\n const metadata = { ...(id ? { responseId: id } : {}), reason };\n if (!active && id && playbackResponseId && id !== playbackResponseId) {\n // A late drain for A does not stop the current B playback.\n emit('speaking', speaking, metadata);\n return;\n }\n const changed = active && id !== playbackResponseId;\n playbackResponseId = active ? id : undefined;\n playback(active && !outputMuted, metadata, changed && !outputMuted);\n }\n function cleanup(reason?: 'closed'): void {\n abort.abort();\n for (const dispose of disposers.splice(0)) dispose();\n microphone?.getTracks().forEach((track) => {\n track.stop();\n });\n microphone = undefined;\n if (channel) {\n channel.onopen =\n channel.onmessage =\n channel.onclose =\n channel.onerror =\n null;\n channel.close();\n }\n if (peer) {\n peer.ontrack = peer.onconnectionstatechange = null;\n peer.close();\n }\n if (audio) {\n audio.pause();\n audio.srcObject = null;\n }\n playback(false, {\n ...(playbackResponseId ? { responseId: playbackResponseId } : {}),\n ...(reason ? { reason } : {}),\n });\n playbackResponseId = undefined;\n responseActive = false;\n pendingResponseCreateEventId = undefined;\n responseQueued = false;\n responseId = undefined;\n queuedResponseInstructions = undefined;\n turns.clear();\n pendingCancellations.clear();\n }\n function fail(message: string): void {\n if (state === 'closed' || state === 'failed') return;\n state = 'failed';\n cleanup();\n emit('state', state);\n emit('error', new SpeechProviderError(ADAPTER, message));\n }\n function send(event: Record<string, unknown>): void {\n if (state !== 'connected' || channel?.readyState !== 'open')\n throw new SpeechConfigurationError(\n 'Voice session is not connected',\n ADAPTER,\n );\n channel.send(JSON.stringify(event));\n }\n function createResponse(instructions?: string): void {\n // A response is active as soon as the request is sent. Waiting for\n // response.created leaves a gap in which a typed turn can create a\n // conflicting default-conversation response. The event ID lets an error\n // prove that this speculative request was rejected before it became active.\n const eventId = `hv_${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}`;\n responseId = undefined;\n send({\n event_id: eventId,\n type: 'response.create',\n ...(instructions ? { response: { instructions } } : {}),\n });\n pendingResponseCreateEventId = eventId;\n responseActive = true;\n }\n function requestResponse(instructions?: string): void {\n if (responseActive) {\n responseQueued = true;\n if (instructions) queuedResponseInstructions = instructions;\n return;\n }\n responseQueued = false;\n const nextInstructions = instructions ?? queuedResponseInstructions;\n queuedResponseInstructions = undefined;\n createResponse(nextInstructions);\n }\n function releaseQueuedResponse(): void {\n if (!responseQueued || responseActive || state !== 'connected') return;\n responseQueued = false;\n const instructions = queuedResponseInstructions;\n queuedResponseInstructions = undefined;\n createResponse(instructions);\n }\n function transcript(\n itemId: unknown,\n role: VoiceTranscript['role'],\n text: unknown,\n final: boolean,\n delta = false,\n providerResponseId?: unknown,\n ): void {\n if (typeof itemId !== 'string' || typeof text !== 'string') return;\n const prior = turns.get(itemId);\n // Late duplicates/deltas must not overwrite a final turn or its role.\n if (prior?.final || (prior && prior.role !== role)) return;\n const turn = {\n itemId,\n role,\n text: delta ? (prior?.text ?? '') + text : text,\n final,\n ...((identity(providerResponseId) ?? prior?.responseId)\n ? { responseId: identity(providerResponseId) ?? prior?.responseId }\n : {}),\n };\n turns.set(itemId, turn);\n emit('transcript', turn);\n }\n function receive(data: unknown): void {\n let event: Record<string, unknown>;\n try {\n const parsed: unknown =\n typeof data === 'string' ? JSON.parse(data) : data;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return;\n event = parsed as Record<string, unknown>;\n } catch {\n return;\n }\n switch (event.type) {\n case 'conversation.item.input_audio_transcription.delta':\n transcript(event.item_id, 'user', event.delta, false, true);\n break;\n case 'conversation.item.input_audio_transcription.completed':\n transcript(event.item_id, 'user', event.transcript, true);\n break;\n case 'conversation.item.input_audio_transcription.failed':\n emit(\n 'error',\n new SpeechProviderError(ADAPTER, 'Input transcription failed'),\n );\n break;\n case 'response.output_audio_transcript.delta':\n transcript(\n event.item_id,\n 'assistant',\n event.delta,\n false,\n true,\n event.response_id,\n );\n break;\n case 'response.output_audio_transcript.done':\n transcript(\n event.item_id,\n 'assistant',\n event.transcript,\n true,\n false,\n event.response_id,\n );\n break;\n case 'response.output_text.delta':\n transcript(\n event.item_id,\n 'assistant',\n event.delta,\n false,\n true,\n event.response_id,\n );\n break;\n case 'response.output_text.done':\n transcript(\n event.item_id,\n 'assistant',\n event.text,\n true,\n false,\n event.response_id,\n );\n break;\n case 'input_audio_buffer.speech_started':\n emit('inputSpeech', true, {\n itemId:\n typeof event.item_id === 'string' && event.item_id\n ? event.item_id\n : undefined,\n });\n break;\n case 'input_audio_buffer.speech_stopped':\n emit('inputSpeech', false, {\n itemId:\n typeof event.item_id === 'string' && event.item_id\n ? event.item_id\n : undefined,\n });\n break;\n case 'output_audio_buffer.started':\n playbackEvent(true, 'started', event.response_id);\n break;\n case 'output_audio_buffer.stopped':\n playbackEvent(false, 'stopped', event.response_id);\n break;\n case 'output_audio_buffer.cleared':\n playbackEvent(false, 'cleared', event.response_id);\n break;\n case 'response.created': {\n const response = record(event.response);\n responseId = typeof response.id === 'string' ? response.id : undefined;\n pendingResponseCreateEventId = undefined;\n responseActive = true;\n emit('response', { active: true, responseId });\n break;\n }\n case 'response.done': {\n const response = record(event.response);\n const wasActive = responseActive;\n pendingResponseCreateEventId = undefined;\n responseActive = false;\n const status = response.status;\n const safeStatus =\n status === 'completed' ||\n status === 'incomplete' ||\n status === 'cancelled' ||\n status === 'failed'\n ? status\n : undefined;\n const reason = record(response.status_details).reason;\n const safeReason =\n (status === 'incomplete' &&\n (reason === 'max_output_tokens' || reason === 'content_filter')) ||\n (status === 'cancelled' &&\n (reason === 'turn_detected' || reason === 'client_cancelled'))\n ? reason\n : undefined;\n emit('response', {\n active: false,\n responseId,\n ...(safeStatus ? { status: safeStatus } : {}),\n ...(safeReason ? { statusReason: safeReason } : {}),\n });\n\n if (response.usage && typeof response.usage === 'object')\n emit('usage', record(response.usage));\n if (response.status === 'failed' || response.status === 'incomplete')\n emit(\n 'error',\n new SpeechProviderError(\n ADAPTER,\n response.status === 'incomplete'\n ? 'Voice response incomplete'\n : 'Voice response failed',\n ),\n );\n // A response listener may synchronously request a tool continuation.\n // That request consumes the coalesced typed turn, so only drain when it\n // remains pending after listeners have run.\n if (wasActive) releaseQueuedResponse();\n break;\n }\n case 'response.function_call_arguments.done':\n if (\n typeof event.call_id === 'string' &&\n typeof event.name === 'string' &&\n typeof event.arguments === 'string'\n )\n emit('toolCall', {\n callId: event.call_id,\n name: event.name,\n arguments: event.arguments,\n });\n break;\n case 'error': {\n const error = record(event.error);\n // Only nested error.event_id identifies the client request; the outer\n // event_id identifies the server event and grants no correlation.\n const ownCancellation =\n typeof error.event_id === 'string' &&\n pendingCancellations.delete(error.event_id);\n if (ownCancellation && error.code === 'response_cancel_not_active')\n break;\n\n const rejectedPendingResponse =\n pendingResponseCreateEventId !== undefined &&\n error.event_id === pendingResponseCreateEventId;\n if (rejectedPendingResponse) {\n pendingResponseCreateEventId = undefined;\n responseActive = false;\n responseId = undefined;\n }\n emit(\n 'error',\n new SpeechProviderError(\n ADAPTER,\n 'Realtime provider rejected a conversation event',\n ),\n );\n // Error handlers can synchronously request a retry. If none did, a\n // correlated rejection must release the coalesced typed response.\n if (rejectedPendingResponse) releaseQueuedResponse();\n break;\n }\n }\n }\n\n const session: OpenAIWebRTCVoiceSession = {\n get state() {\n return state;\n },\n connect() {\n if (pending) return pending;\n if (state === 'connected') return Promise.resolve();\n if (state !== 'idle')\n return Promise.reject(\n new SpeechConfigurationError(\n 'Create a new voice session after close or failure',\n ADAPTER,\n ),\n );\n const timeoutMs = options.connectTimeoutMs ?? 20_000;\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)\n return Promise.reject(\n new SpeechConfigurationError(\n 'connectTimeoutMs must be positive',\n ADAPTER,\n ),\n );\n change('connecting');\n const signal = AbortSignal.any([\n abort.signal,\n ...(options.signal ? [options.signal] : []),\n ]);\n pending = (async () => {\n const timeout = setTimeout(\n () => fail('Voice connection timed out'),\n timeoutMs,\n );\n const onAbort = () => {\n if (state !== 'closed' && state !== 'failed')\n fail('Voice connection cancelled');\n };\n signal.addEventListener('abort', onAbort, { once: true });\n disposers.push(() => signal.removeEventListener('abort', onAbort));\n try {\n signal.throwIfAborted();\n // Permission acquisition cannot be aborted; dispose even if it resolves after close.\n const acquisition = options.getMicrophone().then((stream) => {\n if (signal.aborted)\n stream.getTracks().forEach((track) => {\n track.stop();\n });\n else microphone = stream;\n return stream;\n });\n const stream = await abortable(acquisition, signal);\n signal.throwIfAborted();\n stream.getAudioTracks().forEach((track) => {\n track.enabled = !micMuted;\n const ended = () =>\n fail('Microphone ended; allow access and start a new session');\n track.addEventListener('ended', ended);\n disposers.push(() => track.removeEventListener('ended', ended));\n if (track.readyState === 'ended')\n throw new SpeechConfigurationError(\n 'Microphone stream has ended',\n ADAPTER,\n );\n });\n if (!stream.getAudioTracks().length)\n throw new SpeechConfigurationError(\n 'Microphone stream has no audio tracks',\n ADAPTER,\n );\n peer = options.createPeerConnection?.() ?? new RTCPeerConnection();\n audio = options.audio ?? new Audio();\n audio.autoplay = true;\n audio.muted = outputMuted;\n peer.ontrack = (event) => {\n if (!audio || signal.aborted) return;\n audio.srcObject =\n event.streams[0] ?? new MediaStream([event.track]);\n void audio\n .play()\n .catch(() =>\n emit(\n 'error',\n new SpeechProviderError(\n ADAPTER,\n 'Audio playback needs a user gesture',\n ),\n ),\n );\n };\n peer.onconnectionstatechange = () => {\n if (\n peer?.connectionState === 'failed' ||\n peer?.connectionState === 'disconnected'\n )\n fail('Voice connection lost; start a new session');\n };\n for (const track of stream.getAudioTracks())\n peer.addTrack(track, stream);\n channel = peer.createDataChannel('oai-events');\n channel.onmessage = (event) => receive(event.data);\n // Attach before SDP negotiation; some transports open during setRemoteDescription.\n const opened = new Promise<void>((resolve, reject) => {\n const cancel = () =>\n reject(\n new SpeechProviderError(ADAPTER, 'Voice connection cancelled'),\n );\n signal.addEventListener('abort', cancel, { once: true });\n disposers.push(() => signal.removeEventListener('abort', cancel));\n if (channel) {\n channel.onopen = () => {\n signal.removeEventListener('abort', cancel);\n resolve();\n };\n channel.onclose = () => {\n fail('Voice channel closed');\n reject(\n new SpeechProviderError(ADAPTER, 'Voice channel closed'),\n );\n };\n channel.onerror = () => {\n fail('Voice channel failed');\n reject(\n new SpeechProviderError(ADAPTER, 'Voice channel failed'),\n );\n };\n }\n });\n // Keep an early rejection handled while negotiation is pending.\n void opened.catch(() => undefined);\n const offer = await abortable(peer.createOffer(), signal);\n await abortable(peer.setLocalDescription(offer), signal);\n signal.throwIfAborted();\n if (!offer.sdp)\n throw new SpeechConfigurationError(\n 'WebRTC did not produce an SDP offer',\n ADAPTER,\n );\n const answer = await abortable(\n options.negotiate(offer.sdp, signal),\n signal,\n );\n signal.throwIfAborted();\n await abortable(\n peer.setRemoteDescription({ type: 'answer', sdp: answer }),\n signal,\n );\n await opened;\n signal.throwIfAborted();\n change('connected');\n } catch (error) {\n fail('Unable to connect voice session');\n throw error;\n } finally {\n clearTimeout(timeout);\n }\n })().finally(() => {\n pending = undefined;\n });\n return pending;\n },\n sendText(text, respond = true) {\n const trimmed = text.trim();\n if (!trimmed || trimmed.length > 16_000)\n throw new SpeechConfigurationError(\n 'Text must contain 1–16000 characters',\n ADAPTER,\n );\n const id = `hv_${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}`;\n send({\n type: 'conversation.item.create',\n item: {\n id,\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: trimmed }],\n },\n });\n transcript(id, 'user', trimmed, true);\n if (respond) session.respond();\n return id;\n },\n appendAssistantContext(text) {\n const trimmed = text.trim();\n if (!trimmed || trimmed.length > 16_000)\n throw new SpeechConfigurationError(\n 'Text must contain 1–16000 characters',\n ADAPTER,\n );\n const id = `hv_${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}`;\n send({\n type: 'conversation.item.create',\n item: {\n id,\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: trimmed }],\n },\n });\n return id;\n },\n commitInput(respond = true) {\n send({ type: 'input_audio_buffer.commit' });\n if (respond) session.respond();\n },\n respond(instructions) {\n requestResponse(instructions);\n },\n interrupt() {\n if (state !== 'connected') return;\n if (responseActive) {\n const eventId = `hv_${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}`;\n send({\n event_id: eventId,\n type: 'response.cancel',\n ...(responseId ? { response_id: responseId } : {}),\n });\n pendingCancellations.add(eventId);\n if (pendingCancellations.size > MAX_PENDING_CANCELLATIONS) {\n const oldest = pendingCancellations.values().next().value;\n if (oldest) pendingCancellations.delete(oldest);\n }\n }\n send({ type: 'output_audio_buffer.clear' });\n playback(false, {\n ...(playbackResponseId ? { responseId: playbackResponseId } : {}),\n reason: 'interrupted',\n });\n playbackResponseId = undefined;\n },\n setMicMuted(muted) {\n micMuted = muted;\n microphone?.getAudioTracks().forEach((track) => {\n track.enabled = !muted;\n });\n if (muted && state === 'connected')\n send({ type: 'input_audio_buffer.clear' });\n },\n setOutputMuted(muted) {\n outputMuted = muted;\n if (audio) audio.muted = muted;\n if (muted)\n playback(false, {\n ...(playbackResponseId ? { responseId: playbackResponseId } : {}),\n reason: 'muted',\n });\n },\n submitToolResult(callId, result) {\n send({\n type: 'conversation.item.create',\n item: {\n type: 'function_call_output',\n call_id: callId,\n output: JSON.stringify(result),\n },\n });\n },\n close() {\n if (state === 'closed') return;\n state = 'closed';\n cleanup('closed');\n emit('state', state);\n listeners.clear();\n },\n on(event, listener) {\n const set = listeners.get(event) ?? new Set();\n set.add(\n listener as (\n value: never,\n metadata?: VoiceSessionEventMetadata,\n ) => void,\n );\n listeners.set(event, set);\n return () => {\n set.delete(\n listener as (\n value: never,\n metadata?: VoiceSessionEventMetadata,\n ) => void,\n );\n };\n },\n };\n return session;\n}\n\nfunction identity(value: unknown): string | undefined {\n return typeof value === 'string' && value ? value : undefined;\n}\n\nfunction record(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(signal.reason);\n return new Promise((resolve, reject) => {\n const cancel = () => reject(signal.reason);\n signal.addEventListener('abort', cancel, { once: true });\n promise\n .then(resolve, reject)\n .finally(() => signal.removeEventListener('abort', cancel));\n });\n}\n"],"mappings":";;AAcA,IAAM,UAAU;AAChB,IAAM,4BAA4B;;AAGlC,SAAgB,+BACd,SAC0B;CAC1B,IAAI,QAA2B;CAC/B,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI,WAAW,QAAQ,YAAY;CACnC,IAAI,cAAc,QAAQ,eAAe;CACzC,IAAI;CACJ,IAAI,iBAAiB;CACrB,IAAI;CACJ,MAAM,uCAAuB,IAAI,IAAY;CAC7C,IAAI,iBAAiB;CACrB,IAAI;CACJ,IAAI,WAAW;CACf,IAAI;CACJ,MAAM,QAAQ,IAAI,gBAAgB;CAClC,MAAM,4BAAY,IAAI,IAGpB;CACF,MAAM,wBAAQ,IAAI,IAA6B;CAC/C,MAAM,YAA4B,CAAC;CAEnC,SAAS,KACP,OACA,OACA,UACM;EACN,KAAK,MAAM,YAAY,UAAU,IAAI,KAAK,KAAK,CAAC,GAC9C,IAAI,UAAU,SAAS,OAAgB,QAAQ;OAC1C,SAAS,KAAc;CAEhC;CACA,SAAS,OAAO,MAA+B;EAC7C,QAAQ;EACR,KAAK,SAAS,IAAI;CACpB;CACA,SAAS,SACP,OACA,UACA,kBAAkB,OACZ;EACN,IAAI,aAAa,SAAS,CAAC,iBAAiB;EAC5C,WAAW;EACX,KAAK,YAAY,OAAO,QAAQ;CAClC;CACA,SAAS,cACP,QACA,QACA,YACM;EACN,MAAM,KAAK,SAAS,UAAU;EAC9B,MAAM,WAAW;GAAE,GAAI,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;GAAI;EAAO;EAC7D,IAAI,CAAC,UAAU,MAAM,sBAAsB,OAAO,oBAAoB;GAEpE,KAAK,YAAY,UAAU,QAAQ;GACnC;EACF;EACA,MAAM,UAAU,UAAU,OAAO;EACjC,qBAAqB,SAAS,KAAK,KAAA;EACnC,SAAS,UAAU,CAAC,aAAa,UAAU,WAAW,CAAC,WAAW;CACpE;CACA,SAAS,QAAQ,QAAyB;EACxC,MAAM,MAAM;EACZ,KAAK,MAAM,WAAW,UAAU,OAAO,CAAC,GAAG,QAAQ;EACnD,YAAY,UAAU,CAAC,CAAC,SAAS,UAAU;GACzC,MAAM,KAAK;EACb,CAAC;EACD,aAAa,KAAA;EACb,IAAI,SAAS;GACX,QAAQ,SACN,QAAQ,YACR,QAAQ,UACR,QAAQ,UACN;GACJ,QAAQ,MAAM;EAChB;EACA,IAAI,MAAM;GACR,KAAK,UAAU,KAAK,0BAA0B;GAC9C,KAAK,MAAM;EACb;EACA,IAAI,OAAO;GACT,MAAM,MAAM;GACZ,MAAM,YAAY;EACpB;EACA,SAAS,OAAO;GACd,GAAI,qBAAqB,EAAE,YAAY,mBAAmB,IAAI,CAAC;GAC/D,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC7B,CAAC;EACD,qBAAqB,KAAA;EACrB,iBAAiB;EACjB,+BAA+B,KAAA;EAC/B,iBAAiB;EACjB,aAAa,KAAA;EACb,6BAA6B,KAAA;EAC7B,MAAM,MAAM;EACZ,qBAAqB,MAAM;CAC7B;CACA,SAAS,KAAK,SAAuB;EACnC,IAAI,UAAU,YAAY,UAAU,UAAU;EAC9C,QAAQ;EACR,QAAQ;EACR,KAAK,SAAS,KAAK;EACnB,KAAK,SAAS,IAAI,oBAAoB,SAAS,OAAO,CAAC;CACzD;CACA,SAAS,KAAK,OAAsC;EAClD,IAAI,UAAU,eAAe,SAAS,eAAe,QACnD,MAAM,IAAI,yBACR,kCACA,OACF;EACF,QAAQ,KAAK,KAAK,UAAU,KAAK,CAAC;CACpC;CACA,SAAS,eAAe,cAA6B;EAKnD,MAAM,UAAU,MAAM,OAAO,WAAW,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE;EACzE,aAAa,KAAA;EACb,KAAK;GACH,UAAU;GACV,MAAM;GACN,GAAI,eAAe,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,CAAC;EACvD,CAAC;EACD,+BAA+B;EAC/B,iBAAiB;CACnB;CACA,SAAS,gBAAgB,cAA6B;EACpD,IAAI,gBAAgB;GAClB,iBAAiB;GACjB,IAAI,cAAc,6BAA6B;GAC/C;EACF;EACA,iBAAiB;EACjB,MAAM,mBAAmB,gBAAgB;EACzC,6BAA6B,KAAA;EAC7B,eAAe,gBAAgB;CACjC;CACA,SAAS,wBAA8B;EACrC,IAAI,CAAC,kBAAkB,kBAAkB,UAAU,aAAa;EAChE,iBAAiB;EACjB,MAAM,eAAe;EACrB,6BAA6B,KAAA;EAC7B,eAAe,YAAY;CAC7B;CACA,SAAS,WACP,QACA,MACA,MACA,OACA,QAAQ,OACR,oBACM;EACN,IAAI,OAAO,WAAW,YAAY,OAAO,SAAS,UAAU;EAC5D,MAAM,QAAQ,MAAM,IAAI,MAAM;EAE9B,IAAI,OAAO,SAAU,SAAS,MAAM,SAAS,MAAO;EACpD,MAAM,OAAO;GACX;GACA;GACA,MAAM,SAAS,OAAO,QAAQ,MAAM,OAAO;GAC3C;GACA,GAAK,SAAS,kBAAkB,KAAK,OAAO,aACxC,EAAE,YAAY,SAAS,kBAAkB,KAAK,OAAO,WAAW,IAChE,CAAC;EACP;EACA,MAAM,IAAI,QAAQ,IAAI;EACtB,KAAK,cAAc,IAAI;CACzB;CACA,SAAS,QAAQ,MAAqB;EACpC,IAAI;EACJ,IAAI;GACF,MAAM,SACJ,OAAO,SAAS,WAAW,KAAK,MAAM,IAAI,IAAI;GAChD,IAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAC/D;GACF,QAAQ;EACV,QAAQ;GACN;EACF;EACA,QAAQ,MAAM,MAAd;GACE,KAAK;IACH,WAAW,MAAM,SAAS,QAAQ,MAAM,OAAO,OAAO,IAAI;IAC1D;GACF,KAAK;IACH,WAAW,MAAM,SAAS,QAAQ,MAAM,YAAY,IAAI;IACxD;GACF,KAAK;IACH,KACE,SACA,IAAI,oBAAoB,SAAS,4BAA4B,CAC/D;IACA;GACF,KAAK;IACH,WACE,MAAM,SACN,aACA,MAAM,OACN,OACA,MACA,MAAM,WACR;IACA;GACF,KAAK;IACH,WACE,MAAM,SACN,aACA,MAAM,YACN,MACA,OACA,MAAM,WACR;IACA;GACF,KAAK;IACH,WACE,MAAM,SACN,aACA,MAAM,OACN,OACA,MACA,MAAM,WACR;IACA;GACF,KAAK;IACH,WACE,MAAM,SACN,aACA,MAAM,MACN,MACA,OACA,MAAM,WACR;IACA;GACF,KAAK;IACH,KAAK,eAAe,MAAM,EACxB,QACE,OAAO,MAAM,YAAY,YAAY,MAAM,UACvC,MAAM,UACN,KAAA,EACR,CAAC;IACD;GACF,KAAK;IACH,KAAK,eAAe,OAAO,EACzB,QACE,OAAO,MAAM,YAAY,YAAY,MAAM,UACvC,MAAM,UACN,KAAA,EACR,CAAC;IACD;GACF,KAAK;IACH,cAAc,MAAM,WAAW,MAAM,WAAW;IAChD;GACF,KAAK;IACH,cAAc,OAAO,WAAW,MAAM,WAAW;IACjD;GACF,KAAK;IACH,cAAc,OAAO,WAAW,MAAM,WAAW;IACjD;GACF,KAAK,oBAAoB;IACvB,MAAM,WAAW,OAAO,MAAM,QAAQ;IACtC,aAAa,OAAO,SAAS,OAAO,WAAW,SAAS,KAAK,KAAA;IAC7D,+BAA+B,KAAA;IAC/B,iBAAiB;IACjB,KAAK,YAAY;KAAE,QAAQ;KAAM;IAAW,CAAC;IAC7C;GACF;GACA,KAAK,iBAAiB;IACpB,MAAM,WAAW,OAAO,MAAM,QAAQ;IACtC,MAAM,YAAY;IAClB,+BAA+B,KAAA;IAC/B,iBAAiB;IACjB,MAAM,SAAS,SAAS;IACxB,MAAM,aACJ,WAAW,eACX,WAAW,gBACX,WAAW,eACX,WAAW,WACP,SACA,KAAA;IACN,MAAM,SAAS,OAAO,SAAS,cAAc,CAAC,CAAC;IAC/C,MAAM,aACH,WAAW,iBACT,WAAW,uBAAuB,WAAW,qBAC/C,WAAW,gBACT,WAAW,mBAAmB,WAAW,sBACxC,SACA,KAAA;IACN,KAAK,YAAY;KACf,QAAQ;KACR;KACA,GAAI,aAAa,EAAE,QAAQ,WAAW,IAAI,CAAC;KAC3C,GAAI,aAAa,EAAE,cAAc,WAAW,IAAI,CAAC;IACnD,CAAC;IAED,IAAI,SAAS,SAAS,OAAO,SAAS,UAAU,UAC9C,KAAK,SAAS,OAAO,SAAS,KAAK,CAAC;IACtC,IAAI,SAAS,WAAW,YAAY,SAAS,WAAW,cACtD,KACE,SACA,IAAI,oBACF,SACA,SAAS,WAAW,eAChB,8BACA,uBACN,CACF;IAIF,IAAI,WAAW,sBAAsB;IACrC;GACF;GACA,KAAK;IACH,IACE,OAAO,MAAM,YAAY,YACzB,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,cAAc,UAE3B,KAAK,YAAY;KACf,QAAQ,MAAM;KACd,MAAM,MAAM;KACZ,WAAW,MAAM;IACnB,CAAC;IACH;GACF,KAAK,SAAS;IACZ,MAAM,QAAQ,OAAO,MAAM,KAAK;IAMhC,IAFE,OAAO,MAAM,aAAa,YAC1B,qBAAqB,OAAO,MAAM,QAAQ,KACrB,MAAM,SAAS,8BACpC;IAEF,MAAM,0BACJ,iCAAiC,KAAA,KACjC,MAAM,aAAa;IACrB,IAAI,yBAAyB;KAC3B,+BAA+B,KAAA;KAC/B,iBAAiB;KACjB,aAAa,KAAA;IACf;IACA,KACE,SACA,IAAI,oBACF,SACA,iDACF,CACF;IAGA,IAAI,yBAAyB,sBAAsB;IACnD;GACF;EACF;CACF;CAEA,MAAM,UAAoC;EACxC,IAAI,QAAQ;GACV,OAAO;EACT;EACA,UAAU;GACR,IAAI,SAAS,OAAO;GACpB,IAAI,UAAU,aAAa,OAAO,QAAQ,QAAQ;GAClD,IAAI,UAAU,QACZ,OAAO,QAAQ,OACb,IAAI,yBACF,qDACA,OACF,CACF;GACF,MAAM,YAAY,QAAQ,oBAAoB;GAC9C,IAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,GAC9C,OAAO,QAAQ,OACb,IAAI,yBACF,qCACA,OACF,CACF;GACF,OAAO,YAAY;GACnB,MAAM,SAAS,YAAY,IAAI,CAC7B,MAAM,QACN,GAAI,QAAQ,SAAS,CAAC,QAAQ,MAAM,IAAI,CAAC,CAC3C,CAAC;GACD,WAAW,YAAY;IACrB,MAAM,UAAU,iBACR,KAAK,4BAA4B,GACvC,SACF;IACA,MAAM,gBAAgB;KACpB,IAAI,UAAU,YAAY,UAAU,UAClC,KAAK,4BAA4B;IACrC;IACA,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;IACxD,UAAU,WAAW,OAAO,oBAAoB,SAAS,OAAO,CAAC;IACjE,IAAI;KACF,OAAO,eAAe;KAUtB,MAAM,SAAS,MAAM,UARD,QAAQ,cAAc,CAAC,CAAC,MAAM,WAAW;MAC3D,IAAI,OAAO,SACT,OAAO,UAAU,CAAC,CAAC,SAAS,UAAU;OACpC,MAAM,KAAK;MACb,CAAC;WACE,aAAa;MAClB,OAAO;KACT,CAC+B,GAAa,MAAM;KAClD,OAAO,eAAe;KACtB,OAAO,eAAe,CAAC,CAAC,SAAS,UAAU;MACzC,MAAM,UAAU,CAAC;MACjB,MAAM,cACJ,KAAK,wDAAwD;MAC/D,MAAM,iBAAiB,SAAS,KAAK;MACrC,UAAU,WAAW,MAAM,oBAAoB,SAAS,KAAK,CAAC;MAC9D,IAAI,MAAM,eAAe,SACvB,MAAM,IAAI,yBACR,+BACA,OACF;KACJ,CAAC;KACD,IAAI,CAAC,OAAO,eAAe,CAAC,CAAC,QAC3B,MAAM,IAAI,yBACR,yCACA,OACF;KACF,OAAO,QAAQ,uBAAuB,KAAK,IAAI,kBAAkB;KACjE,QAAQ,QAAQ,SAAS,IAAI,MAAM;KACnC,MAAM,WAAW;KACjB,MAAM,QAAQ;KACd,KAAK,WAAW,UAAU;MACxB,IAAI,CAAC,SAAS,OAAO,SAAS;MAC9B,MAAM,YACJ,MAAM,QAAQ,MAAM,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;MACnD,MACG,KAAK,CAAC,CACN,YACC,KACE,SACA,IAAI,oBACF,SACA,qCACF,CACF,CACF;KACJ;KACA,KAAK,gCAAgC;MACnC,IACE,MAAM,oBAAoB,YAC1B,MAAM,oBAAoB,gBAE1B,KAAK,4CAA4C;KACrD;KACA,KAAK,MAAM,SAAS,OAAO,eAAe,GACxC,KAAK,SAAS,OAAO,MAAM;KAC7B,UAAU,KAAK,kBAAkB,YAAY;KAC7C,QAAQ,aAAa,UAAU,QAAQ,MAAM,IAAI;KAEjD,MAAM,SAAS,IAAI,SAAe,SAAS,WAAW;MACpD,MAAM,eACJ,OACE,IAAI,oBAAoB,SAAS,4BAA4B,CAC/D;MACF,OAAO,iBAAiB,SAAS,QAAQ,EAAE,MAAM,KAAK,CAAC;MACvD,UAAU,WAAW,OAAO,oBAAoB,SAAS,MAAM,CAAC;MAChE,IAAI,SAAS;OACX,QAAQ,eAAe;QACrB,OAAO,oBAAoB,SAAS,MAAM;QAC1C,QAAQ;OACV;OACA,QAAQ,gBAAgB;QACtB,KAAK,sBAAsB;QAC3B,OACE,IAAI,oBAAoB,SAAS,sBAAsB,CACzD;OACF;OACA,QAAQ,gBAAgB;QACtB,KAAK,sBAAsB;QAC3B,OACE,IAAI,oBAAoB,SAAS,sBAAsB,CACzD;OACF;MACF;KACF,CAAC;KAED,OAAY,YAAY,KAAA,CAAS;KACjC,MAAM,QAAQ,MAAM,UAAU,KAAK,YAAY,GAAG,MAAM;KACxD,MAAM,UAAU,KAAK,oBAAoB,KAAK,GAAG,MAAM;KACvD,OAAO,eAAe;KACtB,IAAI,CAAC,MAAM,KACT,MAAM,IAAI,yBACR,uCACA,OACF;KACF,MAAM,SAAS,MAAM,UACnB,QAAQ,UAAU,MAAM,KAAK,MAAM,GACnC,MACF;KACA,OAAO,eAAe;KACtB,MAAM,UACJ,KAAK,qBAAqB;MAAE,MAAM;MAAU,KAAK;KAAO,CAAC,GACzD,MACF;KACA,MAAM;KACN,OAAO,eAAe;KACtB,OAAO,WAAW;IACpB,SAAS,OAAO;KACd,KAAK,iCAAiC;KACtC,MAAM;IACR,UAAU;KACR,aAAa,OAAO;IACtB;GACF,EAAA,CAAG,CAAC,CAAC,cAAc;IACjB,UAAU,KAAA;GACZ,CAAC;GACD,OAAO;EACT;EACA,SAAS,MAAM,UAAU,MAAM;GAC7B,MAAM,UAAU,KAAK,KAAK;GAC1B,IAAI,CAAC,WAAW,QAAQ,SAAS,MAC/B,MAAM,IAAI,yBACR,wCACA,OACF;GACF,MAAM,KAAK,MAAM,OAAO,WAAW,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE;GACpE,KAAK;IACH,MAAM;IACN,MAAM;KACJ;KACA,MAAM;KACN,MAAM;KACN,SAAS,CAAC;MAAE,MAAM;MAAc,MAAM;KAAQ,CAAC;IACjD;GACF,CAAC;GACD,WAAW,IAAI,QAAQ,SAAS,IAAI;GACpC,IAAI,SAAS,QAAQ,QAAQ;GAC7B,OAAO;EACT;EACA,uBAAuB,MAAM;GAC3B,MAAM,UAAU,KAAK,KAAK;GAC1B,IAAI,CAAC,WAAW,QAAQ,SAAS,MAC/B,MAAM,IAAI,yBACR,wCACA,OACF;GACF,MAAM,KAAK,MAAM,OAAO,WAAW,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE;GACpE,KAAK;IACH,MAAM;IACN,MAAM;KACJ;KACA,MAAM;KACN,MAAM;KACN,SAAS,CAAC;MAAE,MAAM;MAAe,MAAM;KAAQ,CAAC;IAClD;GACF,CAAC;GACD,OAAO;EACT;EACA,YAAY,UAAU,MAAM;GAC1B,KAAK,EAAE,MAAM,4BAA4B,CAAC;GAC1C,IAAI,SAAS,QAAQ,QAAQ;EAC/B;EACA,QAAQ,cAAc;GACpB,gBAAgB,YAAY;EAC9B;EACA,YAAY;GACV,IAAI,UAAU,aAAa;GAC3B,IAAI,gBAAgB;IAClB,MAAM,UAAU,MAAM,OAAO,WAAW,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE;IACzE,KAAK;KACH,UAAU;KACV,MAAM;KACN,GAAI,aAAa,EAAE,aAAa,WAAW,IAAI,CAAC;IAClD,CAAC;IACD,qBAAqB,IAAI,OAAO;IAChC,IAAI,qBAAqB,OAAO,2BAA2B;KACzD,MAAM,SAAS,qBAAqB,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;KACpD,IAAI,QAAQ,qBAAqB,OAAO,MAAM;IAChD;GACF;GACA,KAAK,EAAE,MAAM,4BAA4B,CAAC;GAC1C,SAAS,OAAO;IACd,GAAI,qBAAqB,EAAE,YAAY,mBAAmB,IAAI,CAAC;IAC/D,QAAQ;GACV,CAAC;GACD,qBAAqB,KAAA;EACvB;EACA,YAAY,OAAO;GACjB,WAAW;GACX,YAAY,eAAe,CAAC,CAAC,SAAS,UAAU;IAC9C,MAAM,UAAU,CAAC;GACnB,CAAC;GACD,IAAI,SAAS,UAAU,aACrB,KAAK,EAAE,MAAM,2BAA2B,CAAC;EAC7C;EACA,eAAe,OAAO;GACpB,cAAc;GACd,IAAI,OAAO,MAAM,QAAQ;GACzB,IAAI,OACF,SAAS,OAAO;IACd,GAAI,qBAAqB,EAAE,YAAY,mBAAmB,IAAI,CAAC;IAC/D,QAAQ;GACV,CAAC;EACL;EACA,iBAAiB,QAAQ,QAAQ;GAC/B,KAAK;IACH,MAAM;IACN,MAAM;KACJ,MAAM;KACN,SAAS;KACT,QAAQ,KAAK,UAAU,MAAM;IAC/B;GACF,CAAC;EACH;EACA,QAAQ;GACN,IAAI,UAAU,UAAU;GACxB,QAAQ;GACR,QAAQ,QAAQ;GAChB,KAAK,SAAS,KAAK;GACnB,UAAU,MAAM;EAClB;EACA,GAAG,OAAO,UAAU;GAClB,MAAM,MAAM,UAAU,IAAI,KAAK,qBAAK,IAAI,IAAI;GAC5C,IAAI,IACF,QAIF;GACA,UAAU,IAAI,OAAO,GAAG;GACxB,aAAa;IACX,IAAI,OACF,QAIF;GACF;EACF;CACF;CACA,OAAO;AACT;AAEA,SAAS,SAAS,OAAoC;CACpD,OAAO,OAAO,UAAU,YAAY,QAAQ,QAAQ,KAAA;AACtD;AAEA,SAAS,OAAO,OAAyC;CACvD,OAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC5D,QACD,CAAC;AACP;AAEA,SAAS,UAAa,SAAqB,QAAiC;CAC1E,IAAI,OAAO,SAAS,OAAO,QAAQ,OAAO,OAAO,MAAM;CACvD,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,eAAe,OAAO,OAAO,MAAM;EACzC,OAAO,iBAAiB,SAAS,QAAQ,EAAE,MAAM,KAAK,CAAC;EACvD,QACG,KAAK,SAAS,MAAM,CAAC,CACrB,cAAc,OAAO,oBAAoB,SAAS,MAAM,CAAC;CAC9D,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/speech",
3
- "version": "0.105.11",
3
+ "version": "0.106.0",
4
4
  "description": "Speech provider abstraction for STT and TTS backends",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",