@happyvertical/speech 0.97.0 → 0.99.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.
Files changed (77) hide show
  1. package/AGENT.md +31 -3
  2. package/README.md +310 -2
  3. package/dist/__tests__/fake-websocket.d.ts +38 -0
  4. package/dist/__tests__/fake-websocket.d.ts.map +1 -0
  5. package/dist/adapters/local/abort.d.ts +6 -0
  6. package/dist/adapters/local/abort.d.ts.map +1 -0
  7. package/dist/adapters/local/audio.d.ts +45 -0
  8. package/dist/adapters/local/audio.d.ts.map +1 -0
  9. package/dist/adapters/local/env.d.ts +15 -0
  10. package/dist/adapters/local/env.d.ts.map +1 -0
  11. package/dist/adapters/local/runtime.d.ts +58 -0
  12. package/dist/adapters/local/runtime.d.ts.map +1 -0
  13. package/dist/adapters/local/transcriber.d.ts +40 -0
  14. package/dist/adapters/local/transcriber.d.ts.map +1 -0
  15. package/dist/adapters/local/transformers-shim.d.ts +8 -0
  16. package/dist/adapters/local/transformers-shim.d.ts.map +1 -0
  17. package/dist/adapters/local/types.d.ts +92 -0
  18. package/dist/adapters/local/types.d.ts.map +1 -0
  19. package/dist/adapters/local/worker.d.ts +118 -0
  20. package/dist/adapters/local/worker.d.ts.map +1 -0
  21. package/dist/adapters/openai-compatible.d.ts +2 -1
  22. package/dist/adapters/openai-compatible.d.ts.map +1 -1
  23. package/dist/adapters/openai-realtime.d.ts +48 -0
  24. package/dist/adapters/openai-realtime.d.ts.map +1 -0
  25. package/dist/adapters/voxtral-realtime.d.ts +44 -0
  26. package/dist/adapters/voxtral-realtime.d.ts.map +1 -0
  27. package/dist/chunks/errors-CQBX3OPS.js +41 -0
  28. package/dist/chunks/errors-CQBX3OPS.js.map +1 -0
  29. package/dist/chunks/registry-C_5BakmM.js +306 -0
  30. package/dist/chunks/registry-C_5BakmM.js.map +1 -0
  31. package/dist/chunks/websocket-CR9uXAuS.js +44 -0
  32. package/dist/chunks/websocket-CR9uXAuS.js.map +1 -0
  33. package/dist/conversation/openai-webrtc.d.ts +4 -0
  34. package/dist/conversation/openai-webrtc.d.ts.map +1 -0
  35. package/dist/conversation/types.d.ts +60 -0
  36. package/dist/conversation/types.d.ts.map +1 -0
  37. package/dist/conversation-server.d.ts +40 -0
  38. package/dist/conversation-server.d.ts.map +1 -0
  39. package/dist/conversation-server.js +174 -0
  40. package/dist/conversation-server.js.map +1 -0
  41. package/dist/conversation.d.ts +4 -0
  42. package/dist/conversation.d.ts.map +1 -0
  43. package/dist/conversation.js +346 -0
  44. package/dist/conversation.js.map +1 -0
  45. package/dist/index.d.ts +9 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1824 -326
  48. package/dist/index.js.map +1 -1
  49. package/dist/local.d.ts +21 -0
  50. package/dist/local.d.ts.map +1 -0
  51. package/dist/local.js +964 -0
  52. package/dist/local.js.map +1 -0
  53. package/dist/shared/env.d.ts +5 -0
  54. package/dist/shared/env.d.ts.map +1 -1
  55. package/dist/shared/factory.d.ts.map +1 -1
  56. package/dist/shared/pcm.d.ts +21 -0
  57. package/dist/shared/pcm.d.ts.map +1 -0
  58. package/dist/shared/realtime-auth.d.ts +34 -0
  59. package/dist/shared/realtime-auth.d.ts.map +1 -0
  60. package/dist/shared/realtime-session.d.ts +240 -0
  61. package/dist/shared/realtime-session.d.ts.map +1 -0
  62. package/dist/shared/registry.d.ts +9 -0
  63. package/dist/shared/registry.d.ts.map +1 -0
  64. package/dist/shared/streaming-client-secret.d.ts +90 -0
  65. package/dist/shared/streaming-client-secret.d.ts.map +1 -0
  66. package/dist/shared/streaming-factory.d.ts +65 -0
  67. package/dist/shared/streaming-factory.d.ts.map +1 -0
  68. package/dist/shared/streaming-types.d.ts +303 -0
  69. package/dist/shared/streaming-types.d.ts.map +1 -0
  70. package/dist/shared/turn-limit.d.ts +84 -0
  71. package/dist/shared/turn-limit.d.ts.map +1 -0
  72. package/dist/shared/types.d.ts +24 -6
  73. package/dist/shared/types.d.ts.map +1 -1
  74. package/dist/shared/websocket.d.ts +56 -0
  75. package/dist/shared/websocket.d.ts.map +1 -0
  76. package/metadata.json +9 -2
  77. package/package.json +27 -3
@@ -0,0 +1,174 @@
1
+ import { i as SpeechProviderError, n as SpeechConfigurationError } from "./chunks/errors-CQBX3OPS.js";
2
+ import { t as isBrowserRuntime } from "./chunks/websocket-CR9uXAuS.js";
3
+ //#region src/conversation-server.ts
4
+ /** Server-only OpenAI conversational call lifecycle. Hosts own authorization and admission policy. */
5
+ /** Build the GA speech-to-speech session config. Transcription is an optional side channel. */
6
+ function openAIConversationConfig(options) {
7
+ if (!options.model?.trim()) throw new SpeechConfigurationError("Conversation model is required");
8
+ const detection = options.turnDetection ?? {
9
+ type: "semantic_vad",
10
+ eagerness: "auto"
11
+ };
12
+ let turnDetection;
13
+ switch (detection.type) {
14
+ case "manual":
15
+ turnDetection = null;
16
+ break;
17
+ case "semantic_vad":
18
+ turnDetection = {
19
+ type: "semantic_vad",
20
+ eagerness: detection.eagerness ?? "auto",
21
+ create_response: true,
22
+ interrupt_response: true
23
+ };
24
+ break;
25
+ case "server_vad":
26
+ turnDetection = {
27
+ type: "server_vad",
28
+ ...detection.threshold === void 0 ? {} : { threshold: detection.threshold },
29
+ ...detection.prefixPaddingMs === void 0 ? {} : { prefix_padding_ms: detection.prefixPaddingMs },
30
+ ...detection.silenceDurationMs === void 0 ? {} : { silence_duration_ms: detection.silenceDurationMs },
31
+ create_response: true,
32
+ interrupt_response: true
33
+ };
34
+ break;
35
+ default: throw new SpeechConfigurationError("Unknown conversation turn detection");
36
+ }
37
+ return {
38
+ type: "realtime",
39
+ model: options.model.trim(),
40
+ output_modalities: ["audio"],
41
+ ...options.instructions ? { instructions: options.instructions } : {},
42
+ audio: {
43
+ input: {
44
+ turn_detection: turnDetection,
45
+ ...options.transcriptionModel ? { transcription: { model: options.transcriptionModel } } : {}
46
+ },
47
+ output: { voice: options.voice ?? "marin" }
48
+ },
49
+ ...options.maxOutputTokens === void 0 ? {} : { max_output_tokens: options.maxOutputTokens },
50
+ ...options.tools ? { tools: options.tools } : {}
51
+ };
52
+ }
53
+ /** No retries: retrying call creation can create duplicate billable sessions. */
54
+ async function createOpenAIVoiceCall(offer, options) {
55
+ assertServer(options);
56
+ if (!validSdp(offer)) throw new SpeechConfigurationError("Expected a bounded SDP offer");
57
+ const body = new FormData();
58
+ body.set("sdp", offer);
59
+ body.set("session", JSON.stringify(openAIConversationConfig(options)));
60
+ let callId;
61
+ try {
62
+ const answer = await (await request(callBase(options.baseUrl), { body }, options, (headers) => {
63
+ const value = headers.get("location")?.split("/").pop();
64
+ if (!value || !/^rtc_[A-Za-z0-9_-]+$/.test(value)) throw new SpeechProviderError("openai-conversation", "Call response did not contain a WebRTC call id");
65
+ callId = value;
66
+ })).text();
67
+ if (!validSdp(answer)) throw new SpeechProviderError("openai-conversation", "Call response did not contain a valid SDP answer");
68
+ options.signal?.throwIfAborted();
69
+ return {
70
+ answer,
71
+ callId
72
+ };
73
+ } catch (error) {
74
+ if (callId) try {
75
+ await hangupOpenAIVoiceCall(callId, {
76
+ ...options,
77
+ signal: void 0
78
+ });
79
+ } catch {
80
+ throw new VoiceCallSetupError(callId);
81
+ }
82
+ throw error;
83
+ }
84
+ }
85
+ /** Hosts should call this on close, idle/hard timeout and failed client setup. */
86
+ async function hangupOpenAIVoiceCall(callId, options) {
87
+ assertServer(options);
88
+ if (!/^rtc_[A-Za-z0-9_-]+$/.test(callId)) throw new SpeechConfigurationError("Invalid WebRTC call id");
89
+ await request(`${callBase(options.baseUrl)}/${callId}/hangup`, {}, options);
90
+ }
91
+ function assertServer(options) {
92
+ if (isBrowserRuntime()) throw new SpeechConfigurationError("Voice call management is server-only; never expose a provider key");
93
+ if (!options.apiKey?.trim()) throw new SpeechConfigurationError("Voice call management requires a server API key");
94
+ const timeout = options.timeoutMs ?? 15e3;
95
+ if (!Number.isFinite(timeout) || timeout <= 0) throw new SpeechConfigurationError("timeoutMs must be positive");
96
+ }
97
+ function callBase(baseUrl = "https://api.openai.com/v1") {
98
+ const url = new URL(baseUrl);
99
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new SpeechConfigurationError("Invalid voice call API base URL");
100
+ url.pathname = `${url.pathname.replace(/\/$/, "")}${/\/v\d+$/.test(url.pathname.replace(/\/$/, "")) ? "" : "/v1"}/realtime/calls`;
101
+ return url.toString();
102
+ }
103
+ function validSdp(value) {
104
+ return typeof value === "string" && value.length <= 65536 && value.startsWith("v=0") && value.includes("m=audio");
105
+ }
106
+ async function request(url, init, options, onHeaders) {
107
+ const controller = new AbortController();
108
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 15e3);
109
+ const signal = AbortSignal.any([controller.signal, ...options.signal ? [options.signal] : []]);
110
+ try {
111
+ const response = await (options.fetch ?? fetch)(url, {
112
+ ...init,
113
+ method: "POST",
114
+ headers: { authorization: `Bearer ${options.apiKey}` },
115
+ signal
116
+ });
117
+ if (!response.ok) throw new SpeechProviderError("openai-conversation", "OpenAI voice call request failed", { status: response.status });
118
+ onHeaders?.(response.headers);
119
+ const body = await readBody(response, signal);
120
+ return new Response(response.status === 204 ? null : body, {
121
+ status: response.status,
122
+ headers: response.headers
123
+ });
124
+ } catch (error) {
125
+ if (error instanceof SpeechProviderError) throw error;
126
+ throw new SpeechProviderError("openai-conversation", signal.aborted ? "OpenAI voice call request cancelled or timed out" : "OpenAI voice call transport failed");
127
+ } finally {
128
+ clearTimeout(timer);
129
+ }
130
+ }
131
+ /** Failed setup whose cleanup also failed. The host must reserve admission and retry termination. */
132
+ var VoiceCallSetupError = class extends SpeechProviderError {
133
+ callId;
134
+ constructor(callId) {
135
+ super("openai-conversation", "Voice setup failed and termination needs retry");
136
+ this.callId = callId;
137
+ }
138
+ };
139
+ async function readBody(response, signal) {
140
+ const reader = response.body?.getReader();
141
+ if (!reader) return "";
142
+ const chunks = [];
143
+ let size = 0;
144
+ const cancel = () => {
145
+ reader.cancel().catch(() => void 0);
146
+ };
147
+ signal.addEventListener("abort", cancel, { once: true });
148
+ try {
149
+ while (true) {
150
+ signal.throwIfAborted();
151
+ const { done, value } = await reader.read();
152
+ signal.throwIfAborted();
153
+ if (done) break;
154
+ size += value.byteLength;
155
+ if (size > 65536) throw new SpeechProviderError("openai-conversation", "Voice call response exceeded its size limit");
156
+ chunks.push(value);
157
+ }
158
+ const data = new Uint8Array(size);
159
+ let offset = 0;
160
+ for (const chunk of chunks) {
161
+ data.set(chunk, offset);
162
+ offset += chunk.byteLength;
163
+ }
164
+ return new TextDecoder().decode(data);
165
+ } finally {
166
+ signal.removeEventListener("abort", cancel);
167
+ await reader.cancel().catch(() => void 0);
168
+ reader.releaseLock();
169
+ }
170
+ }
171
+ //#endregion
172
+ export { VoiceCallSetupError, createOpenAIVoiceCall, hangupOpenAIVoiceCall, openAIConversationConfig };
173
+
174
+ //# sourceMappingURL=conversation-server.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,4 @@
1
+ /** Browser-safe, optional bidirectional voice conversation entry. */
2
+ export { createOpenAIWebRTCVoiceSession } from './conversation/openai-webrtc.js';
3
+ export type { OpenAIWebRTCVoiceOptions, VoiceSession, VoiceSessionEvents, VoiceSessionState, VoiceTranscript, } from './conversation/types.js';
4
+ //# sourceMappingURL=conversation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../src/conversation.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AACjF,YAAY,EACV,wBAAwB,EACxB,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,346 @@
1
+ import { i as SpeechProviderError, n as SpeechConfigurationError } from "./chunks/errors-CQBX3OPS.js";
2
+ //#region src/conversation/openai-webrtc.ts
3
+ var ADAPTER = "openai-conversation";
4
+ /** Browser WebRTC transport. No credentials, Node dependencies or globals are read at import. */
5
+ function createOpenAIWebRTCVoiceSession(options) {
6
+ let state = "idle";
7
+ let pending;
8
+ let peer;
9
+ let channel;
10
+ let microphone;
11
+ let audio;
12
+ let micMuted = options.micMuted ?? false;
13
+ let outputMuted = options.outputMuted ?? false;
14
+ let responseId;
15
+ let responseActive = false;
16
+ let speaking = false;
17
+ const abort = new AbortController();
18
+ const listeners = /* @__PURE__ */ new Map();
19
+ const turns = /* @__PURE__ */ new Map();
20
+ const disposers = [];
21
+ function emit(event, value) {
22
+ for (const listener of listeners.get(event) ?? []) listener(value);
23
+ }
24
+ function change(next) {
25
+ state = next;
26
+ emit("state", next);
27
+ }
28
+ function playback(value) {
29
+ if (speaking === value) return;
30
+ speaking = value;
31
+ emit("speaking", value);
32
+ }
33
+ function cleanup() {
34
+ abort.abort();
35
+ for (const dispose of disposers.splice(0)) dispose();
36
+ microphone?.getTracks().forEach((track) => {
37
+ track.stop();
38
+ });
39
+ microphone = void 0;
40
+ if (channel) {
41
+ channel.onopen = channel.onmessage = channel.onclose = channel.onerror = null;
42
+ channel.close();
43
+ }
44
+ if (peer) {
45
+ peer.ontrack = peer.onconnectionstatechange = null;
46
+ peer.close();
47
+ }
48
+ if (audio) {
49
+ audio.pause();
50
+ audio.srcObject = null;
51
+ }
52
+ playback(false);
53
+ responseActive = false;
54
+ turns.clear();
55
+ }
56
+ function fail(message) {
57
+ if (state === "closed" || state === "failed") return;
58
+ state = "failed";
59
+ cleanup();
60
+ emit("state", state);
61
+ emit("error", new SpeechProviderError(ADAPTER, message));
62
+ }
63
+ function send(event) {
64
+ if (state !== "connected" || channel?.readyState !== "open") throw new SpeechConfigurationError("Voice session is not connected", ADAPTER);
65
+ channel.send(JSON.stringify(event));
66
+ }
67
+ function transcript(itemId, role, text, final, delta = false) {
68
+ if (typeof itemId !== "string" || typeof text !== "string") return;
69
+ const prior = turns.get(itemId);
70
+ if (prior?.final || prior && prior.role !== role) return;
71
+ const turn = {
72
+ itemId,
73
+ role,
74
+ text: delta ? (prior?.text ?? "") + text : text,
75
+ final
76
+ };
77
+ turns.set(itemId, turn);
78
+ emit("transcript", turn);
79
+ }
80
+ function receive(data) {
81
+ let event;
82
+ try {
83
+ const parsed = typeof data === "string" ? JSON.parse(data) : data;
84
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
85
+ event = parsed;
86
+ } catch {
87
+ return;
88
+ }
89
+ switch (event.type) {
90
+ case "conversation.item.input_audio_transcription.delta":
91
+ transcript(event.item_id, "user", event.delta, false, true);
92
+ break;
93
+ case "conversation.item.input_audio_transcription.completed":
94
+ transcript(event.item_id, "user", event.transcript, true);
95
+ break;
96
+ case "conversation.item.input_audio_transcription.failed":
97
+ emit("error", new SpeechProviderError(ADAPTER, "Input transcription failed"));
98
+ break;
99
+ case "response.output_audio_transcript.delta":
100
+ transcript(event.item_id, "assistant", event.delta, false, true);
101
+ break;
102
+ case "response.output_audio_transcript.done":
103
+ transcript(event.item_id, "assistant", event.transcript, true);
104
+ break;
105
+ case "response.output_text.delta":
106
+ transcript(event.item_id, "assistant", event.delta, false, true);
107
+ break;
108
+ case "response.output_text.done":
109
+ transcript(event.item_id, "assistant", event.text, true);
110
+ break;
111
+ case "input_audio_buffer.speech_started":
112
+ emit("inputSpeech", true);
113
+ break;
114
+ case "input_audio_buffer.speech_stopped":
115
+ emit("inputSpeech", false);
116
+ break;
117
+ case "output_audio_buffer.started":
118
+ playback(!outputMuted);
119
+ break;
120
+ case "output_audio_buffer.stopped":
121
+ case "output_audio_buffer.cleared":
122
+ playback(false);
123
+ break;
124
+ case "response.created": {
125
+ const response = record(event.response);
126
+ responseId = typeof response.id === "string" ? response.id : void 0;
127
+ responseActive = true;
128
+ emit("response", {
129
+ active: true,
130
+ responseId
131
+ });
132
+ break;
133
+ }
134
+ case "response.done": {
135
+ const response = record(event.response);
136
+ responseActive = false;
137
+ emit("response", {
138
+ active: false,
139
+ responseId
140
+ });
141
+ if (response.usage && typeof response.usage === "object") emit("usage", record(response.usage));
142
+ if (response.status === "failed" || response.status === "incomplete") emit("error", new SpeechProviderError(ADAPTER, response.status === "incomplete" ? "Voice response incomplete" : "Voice response failed"));
143
+ break;
144
+ }
145
+ case "response.function_call_arguments.done":
146
+ if (typeof event.call_id === "string" && typeof event.name === "string" && typeof event.arguments === "string") emit("toolCall", {
147
+ callId: event.call_id,
148
+ name: event.name,
149
+ arguments: event.arguments
150
+ });
151
+ break;
152
+ case "error":
153
+ emit("error", new SpeechProviderError(ADAPTER, "Realtime provider rejected a conversation event"));
154
+ break;
155
+ }
156
+ }
157
+ const session = {
158
+ get state() {
159
+ return state;
160
+ },
161
+ connect() {
162
+ if (pending) return pending;
163
+ if (state === "connected") return Promise.resolve();
164
+ if (state !== "idle") return Promise.reject(new SpeechConfigurationError("Create a new voice session after close or failure", ADAPTER));
165
+ const timeoutMs = options.connectTimeoutMs ?? 2e4;
166
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return Promise.reject(new SpeechConfigurationError("connectTimeoutMs must be positive", ADAPTER));
167
+ change("connecting");
168
+ const signal = AbortSignal.any([abort.signal, ...options.signal ? [options.signal] : []]);
169
+ pending = (async () => {
170
+ const timeout = setTimeout(() => fail("Voice connection timed out"), timeoutMs);
171
+ const onAbort = () => {
172
+ if (state !== "closed" && state !== "failed") fail("Voice connection cancelled");
173
+ };
174
+ signal.addEventListener("abort", onAbort, { once: true });
175
+ disposers.push(() => signal.removeEventListener("abort", onAbort));
176
+ try {
177
+ signal.throwIfAborted();
178
+ const stream = await abortable(options.getMicrophone().then((stream) => {
179
+ if (signal.aborted) stream.getTracks().forEach((track) => {
180
+ track.stop();
181
+ });
182
+ return stream;
183
+ }), signal);
184
+ signal.throwIfAborted();
185
+ microphone = stream;
186
+ stream.getAudioTracks().forEach((track) => {
187
+ track.enabled = !micMuted;
188
+ const ended = () => fail("Microphone ended; allow access and start a new session");
189
+ track.addEventListener("ended", ended);
190
+ disposers.push(() => track.removeEventListener("ended", ended));
191
+ if (track.readyState === "ended") throw new SpeechConfigurationError("Microphone stream has ended", ADAPTER);
192
+ });
193
+ if (!stream.getAudioTracks().length) throw new SpeechConfigurationError("Microphone stream has no audio tracks", ADAPTER);
194
+ peer = options.createPeerConnection?.() ?? new RTCPeerConnection();
195
+ audio = options.audio ?? new Audio();
196
+ audio.autoplay = true;
197
+ audio.muted = outputMuted;
198
+ peer.ontrack = (event) => {
199
+ if (!audio || signal.aborted) return;
200
+ audio.srcObject = event.streams[0] ?? new MediaStream([event.track]);
201
+ audio.play().catch(() => emit("error", new SpeechProviderError(ADAPTER, "Audio playback needs a user gesture")));
202
+ };
203
+ peer.onconnectionstatechange = () => {
204
+ if (peer?.connectionState === "failed" || peer?.connectionState === "disconnected") fail("Voice connection lost; start a new session");
205
+ };
206
+ for (const track of stream.getAudioTracks()) peer.addTrack(track, stream);
207
+ channel = peer.createDataChannel("oai-events");
208
+ channel.onmessage = (event) => receive(event.data);
209
+ const opened = new Promise((resolve, reject) => {
210
+ const cancel = () => reject(new SpeechProviderError(ADAPTER, "Voice connection cancelled"));
211
+ signal.addEventListener("abort", cancel, { once: true });
212
+ disposers.push(() => signal.removeEventListener("abort", cancel));
213
+ if (channel) {
214
+ channel.onopen = () => {
215
+ signal.removeEventListener("abort", cancel);
216
+ resolve();
217
+ };
218
+ channel.onclose = () => {
219
+ fail("Voice channel closed");
220
+ reject(new SpeechProviderError(ADAPTER, "Voice channel closed"));
221
+ };
222
+ channel.onerror = () => {
223
+ fail("Voice channel failed");
224
+ reject(new SpeechProviderError(ADAPTER, "Voice channel failed"));
225
+ };
226
+ }
227
+ });
228
+ opened.catch(() => void 0);
229
+ const offer = await abortable(peer.createOffer(), signal);
230
+ await abortable(peer.setLocalDescription(offer), signal);
231
+ signal.throwIfAborted();
232
+ if (!offer.sdp) throw new SpeechConfigurationError("WebRTC did not produce an SDP offer", ADAPTER);
233
+ const answer = await abortable(options.negotiate(offer.sdp, signal), signal);
234
+ signal.throwIfAborted();
235
+ await abortable(peer.setRemoteDescription({
236
+ type: "answer",
237
+ sdp: answer
238
+ }), signal);
239
+ await opened;
240
+ signal.throwIfAborted();
241
+ change("connected");
242
+ } catch (error) {
243
+ fail("Unable to connect voice session");
244
+ throw error;
245
+ } finally {
246
+ clearTimeout(timeout);
247
+ }
248
+ })().finally(() => {
249
+ pending = void 0;
250
+ });
251
+ return pending;
252
+ },
253
+ sendText(text) {
254
+ const trimmed = text.trim();
255
+ if (!trimmed || trimmed.length > 16e3) throw new SpeechConfigurationError("Text must contain 1–16000 characters", ADAPTER);
256
+ const id = `hv_${crypto.randomUUID().replaceAll("-", "").slice(0, 24)}`;
257
+ send({
258
+ type: "conversation.item.create",
259
+ item: {
260
+ id,
261
+ type: "message",
262
+ role: "user",
263
+ content: [{
264
+ type: "input_text",
265
+ text: trimmed
266
+ }]
267
+ }
268
+ });
269
+ transcript(id, "user", trimmed, true);
270
+ session.respond();
271
+ return id;
272
+ },
273
+ commitInput(respond = true) {
274
+ send({ type: "input_audio_buffer.commit" });
275
+ if (respond) session.respond();
276
+ },
277
+ respond(instructions) {
278
+ send({
279
+ type: "response.create",
280
+ ...instructions ? { response: { instructions } } : {}
281
+ });
282
+ },
283
+ interrupt() {
284
+ if (state !== "connected") return;
285
+ if (responseActive) send({
286
+ type: "response.cancel",
287
+ ...responseId ? { response_id: responseId } : {}
288
+ });
289
+ send({ type: "output_audio_buffer.clear" });
290
+ playback(false);
291
+ },
292
+ setMicMuted(muted) {
293
+ micMuted = muted;
294
+ microphone?.getAudioTracks().forEach((track) => {
295
+ track.enabled = !muted;
296
+ });
297
+ if (muted && state === "connected") send({ type: "input_audio_buffer.clear" });
298
+ },
299
+ setOutputMuted(muted) {
300
+ outputMuted = muted;
301
+ if (audio) audio.muted = muted;
302
+ if (muted) playback(false);
303
+ },
304
+ submitToolResult(callId, result) {
305
+ send({
306
+ type: "conversation.item.create",
307
+ item: {
308
+ type: "function_call_output",
309
+ call_id: callId,
310
+ output: JSON.stringify(result)
311
+ }
312
+ });
313
+ },
314
+ close() {
315
+ if (state === "closed") return;
316
+ state = "closed";
317
+ cleanup();
318
+ emit("state", state);
319
+ listeners.clear();
320
+ },
321
+ on(event, listener) {
322
+ const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
323
+ set.add(listener);
324
+ listeners.set(event, set);
325
+ return () => {
326
+ set.delete(listener);
327
+ };
328
+ }
329
+ };
330
+ return session;
331
+ }
332
+ function record(value) {
333
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
334
+ }
335
+ function abortable(promise, signal) {
336
+ if (signal.aborted) return Promise.reject(signal.reason);
337
+ return new Promise((resolve, reject) => {
338
+ const cancel = () => reject(signal.reason);
339
+ signal.addEventListener("abort", cancel, { once: true });
340
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", cancel));
341
+ });
342
+ }
343
+ //#endregion
344
+ export { createOpenAIWebRTCVoiceSession };
345
+
346
+ //# sourceMappingURL=conversation.js.map
@@ -0,0 +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 VoiceSession,\n VoiceSessionEvents,\n VoiceSessionState,\n VoiceTranscript,\n} from './types.js';\n\nconst ADAPTER = 'openai-conversation';\n\n/** Browser WebRTC transport. No credentials, Node dependencies or globals are read at import. */\nexport function createOpenAIWebRTCVoiceSession(\n options: OpenAIWebRTCVoiceOptions,\n): VoiceSession {\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 speaking = false;\n const abort = new AbortController();\n const listeners = new Map<\n keyof VoiceSessionEvents,\n Set<(value: never) => 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 ): void {\n for (const listener of listeners.get(event) ?? []) listener(value as never);\n }\n function change(next: VoiceSessionState): void {\n state = next;\n emit('state', next);\n }\n function playback(value: boolean): void {\n if (speaking === value) return;\n speaking = value;\n emit('speaking', value);\n }\n function cleanup(): 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 responseActive = false;\n turns.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 transcript(\n itemId: unknown,\n role: VoiceTranscript['role'],\n text: unknown,\n final: boolean,\n delta = false,\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 };\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(event.item_id, 'assistant', event.delta, false, true);\n break;\n case 'response.output_audio_transcript.done':\n transcript(event.item_id, 'assistant', event.transcript, true);\n break;\n case 'response.output_text.delta':\n transcript(event.item_id, 'assistant', event.delta, false, true);\n break;\n case 'response.output_text.done':\n transcript(event.item_id, 'assistant', event.text, true);\n break;\n case 'input_audio_buffer.speech_started':\n emit('inputSpeech', true);\n break;\n case 'input_audio_buffer.speech_stopped':\n emit('inputSpeech', false);\n break;\n case 'output_audio_buffer.started':\n playback(!outputMuted);\n break;\n case 'output_audio_buffer.stopped':\n case 'output_audio_buffer.cleared':\n playback(false);\n break;\n case 'response.created': {\n const response = record(event.response);\n responseId = typeof response.id === 'string' ? response.id : undefined;\n responseActive = true;\n emit('response', { active: true, responseId });\n break;\n }\n case 'response.done': {\n const response = record(event.response);\n responseActive = false;\n emit('response', { active: false, responseId });\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 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 emit(\n 'error',\n new SpeechProviderError(\n ADAPTER,\n 'Realtime provider rejected a conversation event',\n ),\n );\n break;\n }\n }\n\n const session: VoiceSession = {\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 return stream;\n });\n const stream = await abortable(acquisition, signal);\n signal.throwIfAborted();\n microphone = stream;\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 commitInput(respond = true) {\n send({ type: 'input_audio_buffer.commit' });\n if (respond) session.respond();\n },\n respond(instructions) {\n send({\n type: 'response.create',\n ...(instructions ? { response: { instructions } } : {}),\n });\n },\n interrupt() {\n if (state !== 'connected') return;\n if (responseActive)\n send({\n type: 'response.cancel',\n ...(responseId ? { response_id: responseId } : {}),\n });\n send({ type: 'output_audio_buffer.clear' });\n playback(false);\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) playback(false);\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();\n emit('state', state);\n listeners.clear();\n },\n on(event, listener) {\n const set = listeners.get(event) ?? new Set();\n set.add(listener as (value: never) => void);\n listeners.set(event, set);\n return () => {\n set.delete(listener as (value: never) => void);\n };\n },\n };\n return session;\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":";;AAYA,IAAM,UAAU;;AAGhB,SAAgB,+BACd,SACc;CACd,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,WAAW;CACf,MAAM,QAAQ,IAAI,gBAAgB;CAClC,MAAM,4BAAY,IAAI,IAGpB;CACF,MAAM,wBAAQ,IAAI,IAA6B;CAC/C,MAAM,YAA4B,CAAC;CAEnC,SAAS,KACP,OACA,OACM;EACN,KAAK,MAAM,YAAY,UAAU,IAAI,KAAK,KAAK,CAAC,GAAG,SAAS,KAAc;CAC5E;CACA,SAAS,OAAO,MAA+B;EAC7C,QAAQ;EACR,KAAK,SAAS,IAAI;CACpB;CACA,SAAS,SAAS,OAAsB;EACtC,IAAI,aAAa,OAAO;EACxB,WAAW;EACX,KAAK,YAAY,KAAK;CACxB;CACA,SAAS,UAAgB;EACvB,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,KAAK;EACd,iBAAiB;EACjB,MAAM,MAAM;CACd;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,WACP,QACA,MACA,MACA,OACA,QAAQ,OACF;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;EACF;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,WAAW,MAAM,SAAS,aAAa,MAAM,OAAO,OAAO,IAAI;IAC/D;GACF,KAAK;IACH,WAAW,MAAM,SAAS,aAAa,MAAM,YAAY,IAAI;IAC7D;GACF,KAAK;IACH,WAAW,MAAM,SAAS,aAAa,MAAM,OAAO,OAAO,IAAI;IAC/D;GACF,KAAK;IACH,WAAW,MAAM,SAAS,aAAa,MAAM,MAAM,IAAI;IACvD;GACF,KAAK;IACH,KAAK,eAAe,IAAI;IACxB;GACF,KAAK;IACH,KAAK,eAAe,KAAK;IACzB;GACF,KAAK;IACH,SAAS,CAAC,WAAW;IACrB;GACF,KAAK;GACL,KAAK;IACH,SAAS,KAAK;IACd;GACF,KAAK,oBAAoB;IACvB,MAAM,WAAW,OAAO,MAAM,QAAQ;IACtC,aAAa,OAAO,SAAS,OAAO,WAAW,SAAS,KAAK,KAAA;IAC7D,iBAAiB;IACjB,KAAK,YAAY;KAAE,QAAQ;KAAM;IAAW,CAAC;IAC7C;GACF;GACA,KAAK,iBAAiB;IACpB,MAAM,WAAW,OAAO,MAAM,QAAQ;IACtC,iBAAiB;IACjB,KAAK,YAAY;KAAE,QAAQ;KAAO;IAAW,CAAC;IAC9C,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;IACF;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;IACH,KACE,SACA,IAAI,oBACF,SACA,iDACF,CACF;IACA;EACJ;CACF;CAEA,MAAM,UAAwB;EAC5B,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;KAStB,MAAM,SAAS,MAAM,UAPD,QAAQ,cAAc,CAAC,CAAC,MAAM,WAAW;MAC3D,IAAI,OAAO,SACT,OAAO,UAAU,CAAC,CAAC,SAAS,UAAU;OACpC,MAAM,KAAK;MACb,CAAC;MACH,OAAO;KACT,CAC+B,GAAa,MAAM;KAClD,OAAO,eAAe;KACtB,aAAa;KACb,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;GACb,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,QAAQ,QAAQ;GAChB,OAAO;EACT;EACA,YAAY,UAAU,MAAM;GAC1B,KAAK,EAAE,MAAM,4BAA4B,CAAC;GAC1C,IAAI,SAAS,QAAQ,QAAQ;EAC/B;EACA,QAAQ,cAAc;GACpB,KAAK;IACH,MAAM;IACN,GAAI,eAAe,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,CAAC;GACvD,CAAC;EACH;EACA,YAAY;GACV,IAAI,UAAU,aAAa;GAC3B,IAAI,gBACF,KAAK;IACH,MAAM;IACN,GAAI,aAAa,EAAE,aAAa,WAAW,IAAI,CAAC;GAClD,CAAC;GACH,KAAK,EAAE,MAAM,4BAA4B,CAAC;GAC1C,SAAS,KAAK;EAChB;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,OAAO,SAAS,KAAK;EAC3B;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;GACR,KAAK,SAAS,KAAK;GACnB,UAAU,MAAM;EAClB;EACA,GAAG,OAAO,UAAU;GAClB,MAAM,MAAM,UAAU,IAAI,KAAK,qBAAK,IAAI,IAAI;GAC5C,IAAI,IAAI,QAAkC;GAC1C,UAAU,IAAI,OAAO,GAAG;GACxB,aAAa;IACX,IAAI,OAAO,QAAkC;GAC/C;EACF;CACF;CACA,OAAO;AACT;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/dist/index.d.ts CHANGED
@@ -21,9 +21,18 @@
21
21
  *
22
22
  * @packageDocumentation
23
23
  */
24
+ export { OPENAI_REALTIME_DEFAULT_MODEL, OPENAI_REALTIME_DEFAULT_URL, openAIRealtimeProtocol, openAIRealtimeTranscriptionSession, parseOpenAIRealtimeEvent, resolveOpenAIRealtimeFormat, resolveRealtimeUrl, } from './adapters/openai-realtime.js';
25
+ export { parseVoxtralRealtimeEvent, resolveVoxtralRealtimeUrl, VOXTRAL_REALTIME_AUDIO_FORMAT, VOXTRAL_REALTIME_DEFAULT_MODEL, voxtralRealtimeProtocol, } from './adapters/voxtral-realtime.js';
24
26
  export { DEFAULT_MAX_AUDIO_BYTES, mimeTypeToAudioExtension, } from './shared/audio.js';
25
27
  export { type SpeechEnv, TRANSCRIBER_ENV_KEYS, } from './shared/env.js';
26
28
  export * from './shared/errors.js';
27
29
  export { getAvailableSpeechAdapters, getSpeech, getSpeechSynthesizer, getTranscriber, type SpeechFactoryContext, } from './shared/factory.js';
30
+ export { audioSecondsForBytes, bytesPerSecond, unwrapRawAudio, } from './shared/pcm.js';
31
+ export { type RealtimeConnection, type RealtimeProtocol, type RealtimeProtocolEvent, type RealtimeSessionConfig, type RealtimeSessionInit, RealtimeTranscriptionSession, } from './shared/realtime-session.js';
32
+ export { CLIENT_SECRET_TTL_RANGE, type CreateStreamingClientSecretOptions, createStreamingClientSecret, DEFAULT_CLIENT_SECRET_TTL_SECONDS, resolveClientSecretUrl, type StreamingClientSecretContext, type StreamingClientSecretResult, } from './shared/streaming-client-secret.js';
33
+ export { getStreamingTranscriber, isStreamingTranscriberType, parseMaxTurnSeconds, STREAMING_TRANSCRIBER_ENV_KEYS, STREAMING_TRANSCRIBER_TYPES, type StreamingFactoryContext, type StreamingTranscriberWrapperOptions, wrapStreamingTranscriber, } from './shared/streaming-factory.js';
34
+ export type { GetStreamingTranscriberOptions, OpenAIRealtimeTranscriberOptions, StreamingAudioChunk, StreamingAudioEncoding, StreamingAudioFormat, StreamingClientSecret, StreamingCloseEvent, StreamingFinalEvent, StreamingPartialEvent, StreamingRolloverOptions, StreamingSession, StreamingSessionEventName, StreamingSessionEvents, StreamingSessionListener, StreamingSessionOptions, StreamingSessionSettings, StreamingSessionState, StreamingSpeechEvent, StreamingTranscriber, StreamingTranscriberOptions, StreamingTranscriberType, StreamingTurnDetection, StreamingTurnLimit, VoxtralRealtimeTranscriberOptions, } from './shared/streaming-types.js';
35
+ export { DEFAULT_MAX_TURN_SECONDS, DEFAULT_ROLLOVER_MIN_SILENCE_MS, DEFAULT_ROLLOVER_SILENCE_THRESHOLD, DEFAULT_ROLLOVER_WINDOW_SECONDS, } from './shared/turn-limit.js';
28
36
  export type { AudioBytes, AudioInput, AudioSource, GetSpeechOptions, GetSpeechSynthesizerOptions, GetTranscriberOptions, OpenAICompatibleSpeechSynthesizerOptions, OpenAICompatibleTranscriberOptions, Qwen3SpeechSynthesizerOptions, Speech, SpeechAdapterAvailability, SpeechAdapterType, SpeechFetch, SpeechOperation, SpeechRetryOptions, SpeechSynthesizer, SpeechSynthesizerType, SpeechUsage, SpeechUsageCallback, SpeechVoice, SpeechVoiceInput, StudioServerSpeechSynthesizerOptions, StudioServerTranscriberOptions, SynthesisRequest, SynthesizedSpeech, TimestampGranularity, Transcriber, TranscriberType, TranscriptionRequest, TranscriptionResponseFormat, TranscriptResult, TranscriptSegment, WordTiming, } from './shared/types.js';
37
+ export { isBrowserRuntime, type SpeechWebSocket, type SpeechWebSocketConstructor, type SpeechWebSocketFactory, type SpeechWebSocketInit, } from './shared/websocket.js';
29
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,SAAS,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,0BAA0B,EAC1B,SAAS,EACT,oBAAoB,EACpB,cAAc,EACd,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,2BAA2B,EAC3B,qBAAqB,EACrB,wCAAwC,EACxC,kCAAkC,EAClC,6BAA6B,EAC7B,MAAM,EACN,yBAAyB,EACzB,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,WAAW,EACX,mBAAmB,EACnB,WAAW,EACX,gBAAgB,EAChB,oCAAoC,EACpC,8BAA8B,EAC9B,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,2BAA2B,EAC3B,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,6BAA6B,EAC7B,2BAA2B,EAC3B,sBAAsB,EACtB,kCAAkC,EAClC,wBAAwB,EACxB,2BAA2B,EAC3B,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,EAC7B,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,SAAS,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,0BAA0B,EAC1B,SAAS,EACT,oBAAoB,EACpB,cAAc,EACd,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,4BAA4B,GAC7B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,uBAAuB,EACvB,KAAK,kCAAkC,EACvC,2BAA2B,EAC3B,iCAAiC,EACjC,sBAAsB,EACtB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,GACjC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,mBAAmB,EACnB,8BAA8B,EAC9B,2BAA2B,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,kCAAkC,EACvC,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,8BAA8B,EAC9B,gCAAgC,EAChC,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,EAClB,iCAAiC,GAClC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,wBAAwB,EACxB,+BAA+B,EAC/B,kCAAkC,EAClC,+BAA+B,GAChC,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,2BAA2B,EAC3B,qBAAqB,EACrB,wCAAwC,EACxC,kCAAkC,EAClC,6BAA6B,EAC7B,MAAM,EACN,yBAAyB,EACzB,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,WAAW,EACX,mBAAmB,EACnB,WAAW,EACX,gBAAgB,EAChB,oCAAoC,EACpC,8BAA8B,EAC9B,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,2BAA2B,EAC3B,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,GACzB,MAAM,uBAAuB,CAAC"}