@happyvertical/speech 0.105.8 → 0.105.10
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 +4 -0
- package/dist/conversation/openai-webrtc.d.ts.map +1 -1
- package/dist/conversation/types.d.ts +11 -1
- package/dist/conversation/types.d.ts.map +1 -1
- package/dist/conversation.d.ts +1 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +55 -17
- package/dist/conversation.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -531,4 +531,8 @@ The concrete `createOpenAIWebRTCVoiceSession()` factory returns `OpenAIWebRTCVoi
|
|
|
531
531
|
|
|
532
532
|
Connection timeout includes microphone permission acquisition. Closing or timing out settles that wait; any stream granted during cancellation or afterward is released. The session owns the stream before acquisition continuation can race cleanup. Retry uses a fresh session.
|
|
533
533
|
|
|
534
|
+
Assistant transcripts expose optional provider `responseId`. `speaking` retains its boolean first callback argument and adds optional `{ responseId, reason }` metadata. Provider reasons are `started`, `stopped`, and `cleared`; local reasons are `interrupted`, `muted`, and `closed`. Playback identity comes from output-buffer events independently of generation. Identity changes can emit `true` again; a late stop/clear for an older response emits its metadata with the current boolean without stopping newer playback. Missing identity stays absent; local reasons use only known playback identity. Muted identity changes do not emit redundant false notifications. Failure cleanup preserves known playback identity without an intentional `closed` reason; only caller-initiated close reports that reason.
|
|
535
|
+
|
|
536
|
+
Terminal `response.done` callbacks add optional allowlisted `status` (`completed`, `incomplete`, `cancelled`, `failed`) and `statusReason` (`max_output_tokens` or `content_filter` for incomplete; `turn_detected` or `client_cancelled` for cancelled). Unknown, malformed or mismatched values are omitted. Raw provider errors are never copied; transcript finalization remains distinct from playback completion.
|
|
537
|
+
|
|
534
538
|
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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-webrtc.d.ts","sourceRoot":"","sources":["../../src/conversation/openai-webrtc.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,wBAAwB,EACxB,wBAAwB,
|
|
1
|
+
{"version":3,"file":"openai-webrtc.d.ts","sourceRoot":"","sources":["../../src/conversation/openai-webrtc.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,wBAAwB,EACxB,wBAAwB,EAMzB,MAAM,YAAY,CAAC;AAIpB,iGAAiG;AACjG,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,wBAAwB,GAChC,wBAAwB,CAgnB1B"}
|
|
@@ -5,12 +5,19 @@ export interface VoiceTranscript {
|
|
|
5
5
|
role: 'user' | 'assistant';
|
|
6
6
|
text: string;
|
|
7
7
|
final: boolean;
|
|
8
|
+
/** Response identity supplied by the provider for assistant output. */
|
|
9
|
+
responseId?: string;
|
|
8
10
|
}
|
|
9
11
|
/** Provider identity for an input utterance; absent when not supplied. */
|
|
10
12
|
export interface VoiceInputSpeechMetadata {
|
|
11
13
|
itemId?: string;
|
|
12
14
|
}
|
|
13
|
-
|
|
15
|
+
/** Playback event identity; it never comes from the latest generation response. */
|
|
16
|
+
export interface VoiceSpeakingMetadata {
|
|
17
|
+
responseId?: string;
|
|
18
|
+
reason?: 'started' | 'stopped' | 'cleared' | 'interrupted' | 'muted' | 'closed';
|
|
19
|
+
}
|
|
20
|
+
export interface VoiceSessionEventMetadata extends VoiceInputSpeechMetadata, VoiceSpeakingMetadata {
|
|
14
21
|
}
|
|
15
22
|
export interface VoiceSessionEvents {
|
|
16
23
|
state: VoiceSessionState;
|
|
@@ -21,6 +28,9 @@ export interface VoiceSessionEvents {
|
|
|
21
28
|
response: {
|
|
22
29
|
active: boolean;
|
|
23
30
|
responseId?: string;
|
|
31
|
+
/** Safe terminal fields from response.done; unknown provider values are omitted. */
|
|
32
|
+
status?: 'completed' | 'incomplete' | 'cancelled' | 'failed';
|
|
33
|
+
statusReason?: 'max_output_tokens' | 'content_filter' | 'turn_detected' | 'client_cancelled';
|
|
24
34
|
};
|
|
25
35
|
usage: Record<string, unknown>;
|
|
26
36
|
toolCall: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversation/types.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,YAAY,GACZ,WAAW,GACX,QAAQ,GACR,QAAQ,CAAC;AAEb,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversation/types.ts"],"names":[],"mappings":"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"}
|
package/dist/conversation.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Browser-safe, optional bidirectional voice conversation entry. */
|
|
2
2
|
export { createOpenAIWebRTCVoiceSession } from './conversation/openai-webrtc.js';
|
|
3
|
-
export type { OpenAIWebRTCVoiceOptions, OpenAIWebRTCVoiceSession, VoiceInputSpeechMetadata, VoiceSession, VoiceSessionEventMetadata, VoiceSessionEvents, VoiceSessionState, VoiceTranscript, } from './conversation/types.js';
|
|
3
|
+
export type { OpenAIWebRTCVoiceOptions, OpenAIWebRTCVoiceSession, VoiceInputSpeechMetadata, VoiceSession, VoiceSessionEventMetadata, VoiceSessionEvents, VoiceSessionState, VoiceSpeakingMetadata, VoiceTranscript, } from './conversation/types.js';
|
|
4
4
|
//# sourceMappingURL=conversation.d.ts.map
|
|
@@ -1 +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,wBAAwB,EACxB,wBAAwB,EACxB,YAAY,EACZ,yBAAyB,EACzB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,yBAAyB,CAAC"}
|
|
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,wBAAwB,EACxB,wBAAwB,EACxB,YAAY,EACZ,yBAAyB,EACzB,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,GAChB,MAAM,yBAAyB,CAAC"}
|
package/dist/conversation.js
CHANGED
|
@@ -17,6 +17,7 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
17
17
|
let responseQueued = false;
|
|
18
18
|
let queuedResponseInstructions;
|
|
19
19
|
let speaking = false;
|
|
20
|
+
let playbackResponseId;
|
|
20
21
|
const abort = new AbortController();
|
|
21
22
|
const listeners = /* @__PURE__ */ new Map();
|
|
22
23
|
const turns = /* @__PURE__ */ new Map();
|
|
@@ -29,12 +30,26 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
29
30
|
state = next;
|
|
30
31
|
emit("state", next);
|
|
31
32
|
}
|
|
32
|
-
function playback(value) {
|
|
33
|
-
if (speaking === value) return;
|
|
33
|
+
function playback(value, metadata, identityChanged = false) {
|
|
34
|
+
if (speaking === value && !identityChanged) return;
|
|
34
35
|
speaking = value;
|
|
35
|
-
emit("speaking", value);
|
|
36
|
+
emit("speaking", value, metadata);
|
|
36
37
|
}
|
|
37
|
-
function
|
|
38
|
+
function playbackEvent(active, reason, providerId) {
|
|
39
|
+
const id = identity(providerId);
|
|
40
|
+
const metadata = {
|
|
41
|
+
...id ? { responseId: id } : {},
|
|
42
|
+
reason
|
|
43
|
+
};
|
|
44
|
+
if (!active && id && playbackResponseId && id !== playbackResponseId) {
|
|
45
|
+
emit("speaking", speaking, metadata);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const changed = active && id !== playbackResponseId;
|
|
49
|
+
playbackResponseId = active ? id : void 0;
|
|
50
|
+
playback(active && !outputMuted, metadata, changed && !outputMuted);
|
|
51
|
+
}
|
|
52
|
+
function cleanup(reason) {
|
|
38
53
|
abort.abort();
|
|
39
54
|
for (const dispose of disposers.splice(0)) dispose();
|
|
40
55
|
microphone?.getTracks().forEach((track) => {
|
|
@@ -53,7 +68,11 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
53
68
|
audio.pause();
|
|
54
69
|
audio.srcObject = null;
|
|
55
70
|
}
|
|
56
|
-
playback(false
|
|
71
|
+
playback(false, {
|
|
72
|
+
...playbackResponseId ? { responseId: playbackResponseId } : {},
|
|
73
|
+
...reason ? { reason } : {}
|
|
74
|
+
});
|
|
75
|
+
playbackResponseId = void 0;
|
|
57
76
|
responseActive = false;
|
|
58
77
|
pendingResponseCreateEventId = void 0;
|
|
59
78
|
responseQueued = false;
|
|
@@ -101,7 +120,7 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
101
120
|
queuedResponseInstructions = void 0;
|
|
102
121
|
createResponse(instructions);
|
|
103
122
|
}
|
|
104
|
-
function transcript(itemId, role, text, final, delta = false) {
|
|
123
|
+
function transcript(itemId, role, text, final, delta = false, providerResponseId) {
|
|
105
124
|
if (typeof itemId !== "string" || typeof text !== "string") return;
|
|
106
125
|
const prior = turns.get(itemId);
|
|
107
126
|
if (prior?.final || prior && prior.role !== role) return;
|
|
@@ -109,7 +128,8 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
109
128
|
itemId,
|
|
110
129
|
role,
|
|
111
130
|
text: delta ? (prior?.text ?? "") + text : text,
|
|
112
|
-
final
|
|
131
|
+
final,
|
|
132
|
+
...identity(providerResponseId) ?? prior?.responseId ? { responseId: identity(providerResponseId) ?? prior?.responseId } : {}
|
|
113
133
|
};
|
|
114
134
|
turns.set(itemId, turn);
|
|
115
135
|
emit("transcript", turn);
|
|
@@ -134,16 +154,16 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
134
154
|
emit("error", new SpeechProviderError(ADAPTER, "Input transcription failed"));
|
|
135
155
|
break;
|
|
136
156
|
case "response.output_audio_transcript.delta":
|
|
137
|
-
transcript(event.item_id, "assistant", event.delta, false, true);
|
|
157
|
+
transcript(event.item_id, "assistant", event.delta, false, true, event.response_id);
|
|
138
158
|
break;
|
|
139
159
|
case "response.output_audio_transcript.done":
|
|
140
|
-
transcript(event.item_id, "assistant", event.transcript, true);
|
|
160
|
+
transcript(event.item_id, "assistant", event.transcript, true, false, event.response_id);
|
|
141
161
|
break;
|
|
142
162
|
case "response.output_text.delta":
|
|
143
|
-
transcript(event.item_id, "assistant", event.delta, false, true);
|
|
163
|
+
transcript(event.item_id, "assistant", event.delta, false, true, event.response_id);
|
|
144
164
|
break;
|
|
145
165
|
case "response.output_text.done":
|
|
146
|
-
transcript(event.item_id, "assistant", event.text, true);
|
|
166
|
+
transcript(event.item_id, "assistant", event.text, true, false, event.response_id);
|
|
147
167
|
break;
|
|
148
168
|
case "input_audio_buffer.speech_started":
|
|
149
169
|
emit("inputSpeech", true, { itemId: typeof event.item_id === "string" && event.item_id ? event.item_id : void 0 });
|
|
@@ -152,11 +172,13 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
152
172
|
emit("inputSpeech", false, { itemId: typeof event.item_id === "string" && event.item_id ? event.item_id : void 0 });
|
|
153
173
|
break;
|
|
154
174
|
case "output_audio_buffer.started":
|
|
155
|
-
|
|
175
|
+
playbackEvent(true, "started", event.response_id);
|
|
156
176
|
break;
|
|
157
177
|
case "output_audio_buffer.stopped":
|
|
178
|
+
playbackEvent(false, "stopped", event.response_id);
|
|
179
|
+
break;
|
|
158
180
|
case "output_audio_buffer.cleared":
|
|
159
|
-
|
|
181
|
+
playbackEvent(false, "cleared", event.response_id);
|
|
160
182
|
break;
|
|
161
183
|
case "response.created": {
|
|
162
184
|
const response = record(event.response);
|
|
@@ -174,9 +196,15 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
174
196
|
const wasActive = responseActive;
|
|
175
197
|
pendingResponseCreateEventId = void 0;
|
|
176
198
|
responseActive = false;
|
|
199
|
+
const status = response.status;
|
|
200
|
+
const safeStatus = status === "completed" || status === "incomplete" || status === "cancelled" || status === "failed" ? status : void 0;
|
|
201
|
+
const reason = record(response.status_details).reason;
|
|
202
|
+
const safeReason = status === "incomplete" && (reason === "max_output_tokens" || reason === "content_filter") || status === "cancelled" && (reason === "turn_detected" || reason === "client_cancelled") ? reason : void 0;
|
|
177
203
|
emit("response", {
|
|
178
204
|
active: false,
|
|
179
|
-
responseId
|
|
205
|
+
responseId,
|
|
206
|
+
...safeStatus ? { status: safeStatus } : {},
|
|
207
|
+
...safeReason ? { statusReason: safeReason } : {}
|
|
180
208
|
});
|
|
181
209
|
if (response.usage && typeof response.usage === "object") emit("usage", record(response.usage));
|
|
182
210
|
if (response.status === "failed" || response.status === "incomplete") emit("error", new SpeechProviderError(ADAPTER, response.status === "incomplete" ? "Voice response incomplete" : "Voice response failed"));
|
|
@@ -352,7 +380,11 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
352
380
|
...responseId ? { response_id: responseId } : {}
|
|
353
381
|
});
|
|
354
382
|
send({ type: "output_audio_buffer.clear" });
|
|
355
|
-
playback(false
|
|
383
|
+
playback(false, {
|
|
384
|
+
...playbackResponseId ? { responseId: playbackResponseId } : {},
|
|
385
|
+
reason: "interrupted"
|
|
386
|
+
});
|
|
387
|
+
playbackResponseId = void 0;
|
|
356
388
|
},
|
|
357
389
|
setMicMuted(muted) {
|
|
358
390
|
micMuted = muted;
|
|
@@ -364,7 +396,10 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
364
396
|
setOutputMuted(muted) {
|
|
365
397
|
outputMuted = muted;
|
|
366
398
|
if (audio) audio.muted = muted;
|
|
367
|
-
if (muted) playback(false
|
|
399
|
+
if (muted) playback(false, {
|
|
400
|
+
...playbackResponseId ? { responseId: playbackResponseId } : {},
|
|
401
|
+
reason: "muted"
|
|
402
|
+
});
|
|
368
403
|
},
|
|
369
404
|
submitToolResult(callId, result) {
|
|
370
405
|
send({
|
|
@@ -379,7 +414,7 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
379
414
|
close() {
|
|
380
415
|
if (state === "closed") return;
|
|
381
416
|
state = "closed";
|
|
382
|
-
cleanup();
|
|
417
|
+
cleanup("closed");
|
|
383
418
|
emit("state", state);
|
|
384
419
|
listeners.clear();
|
|
385
420
|
},
|
|
@@ -394,6 +429,9 @@ function createOpenAIWebRTCVoiceSession(options) {
|
|
|
394
429
|
};
|
|
395
430
|
return session;
|
|
396
431
|
}
|
|
432
|
+
function identity(value) {
|
|
433
|
+
return typeof value === "string" && value ? value : void 0;
|
|
434
|
+
}
|
|
397
435
|
function record(value) {
|
|
398
436
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
399
437
|
}
|
package/dist/conversation.js.map
CHANGED
|
@@ -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 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): 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 let responseQueued = false;\n let queuedResponseInstructions: string | undefined;\n let speaking = false;\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(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 pendingResponseCreateEventId = undefined;\n responseQueued = false;\n responseId = undefined;\n queuedResponseInstructions = undefined;\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 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 ): 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 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 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 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 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 // 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 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 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(\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 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';\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 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 }\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 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 send({\n type: 'response.cancel',\n ...(responseId ? { response_id: responseId } : {}),\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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"}
|