@cognidesk/voice-websocket 0.0.3-dev.3 → 0.0.3-dev.5
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/dist/index.d.ts +125 -17
- package/dist/index.js +351 -222
- package/package.json +5 -4
- package/dist/index.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,117 @@
|
|
|
1
|
+
import { StartVoiceResult, RuntimeEvent, VoiceSocketMetadata, VoiceProfile, HandleVoiceUserMessageResult, HandleVoiceUserMessageInput } from '@cognidesk/core';
|
|
1
2
|
import * as ws from 'ws';
|
|
2
3
|
import { WebSocket } from 'ws';
|
|
3
4
|
import { Server, IncomingMessage } from 'node:http';
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
declare const cognideskVoiceWebSocketProviderManifest: {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
packageName: string;
|
|
10
|
+
provider: string;
|
|
11
|
+
category: string;
|
|
12
|
+
trustLevel: "community" | "official" | "verified" | "experimental";
|
|
13
|
+
directions: ("receive-only" | "send-only" | "inbound-only" | "outbound-only" | "bidirectional")[];
|
|
14
|
+
capabilities: {
|
|
15
|
+
capability: string;
|
|
16
|
+
label?: string | undefined;
|
|
17
|
+
description?: string | undefined;
|
|
18
|
+
audiences?: ("customer-facing" | "internal-support" | "mixed")[] | undefined;
|
|
19
|
+
providerObjects?: {
|
|
20
|
+
kind: string;
|
|
21
|
+
label?: string | undefined;
|
|
22
|
+
description?: string | undefined;
|
|
23
|
+
schemaName?: string | undefined;
|
|
24
|
+
metadata?: Record<string, unknown> | undefined;
|
|
25
|
+
}[] | undefined;
|
|
26
|
+
requiresCredential?: boolean | undefined;
|
|
27
|
+
sideEffect?: boolean | undefined;
|
|
28
|
+
exposesSensitiveData?: boolean | undefined;
|
|
29
|
+
changesWorkflow?: boolean | undefined;
|
|
30
|
+
extension?: boolean | undefined;
|
|
31
|
+
metadata?: Record<string, unknown> | undefined;
|
|
32
|
+
}[];
|
|
33
|
+
operations: {
|
|
34
|
+
alias: string;
|
|
35
|
+
capability: string;
|
|
36
|
+
extension: boolean;
|
|
37
|
+
label?: string | undefined;
|
|
38
|
+
description?: string | undefined;
|
|
39
|
+
providerObject?: string | undefined;
|
|
40
|
+
providerObjects?: {
|
|
41
|
+
kind: string;
|
|
42
|
+
label?: string | undefined;
|
|
43
|
+
description?: string | undefined;
|
|
44
|
+
schemaName?: string | undefined;
|
|
45
|
+
metadata?: Record<string, unknown> | undefined;
|
|
46
|
+
}[] | undefined;
|
|
47
|
+
audience?: "customer-facing" | "internal-support" | "mixed" | undefined;
|
|
48
|
+
audiences?: ("customer-facing" | "internal-support" | "mixed")[] | undefined;
|
|
49
|
+
requiresCredential?: boolean | undefined;
|
|
50
|
+
requiresApproval?: boolean | undefined;
|
|
51
|
+
sideEffect?: boolean | undefined;
|
|
52
|
+
externallyVisible?: boolean | undefined;
|
|
53
|
+
exposesSensitiveData?: boolean | undefined;
|
|
54
|
+
changesWorkflow?: boolean | undefined;
|
|
55
|
+
requiredPolicyIds?: string[] | undefined;
|
|
56
|
+
inputSchemaName?: string | undefined;
|
|
57
|
+
outputSchemaName?: string | undefined;
|
|
58
|
+
inputSchemaRef?: string | undefined;
|
|
59
|
+
outputSchemaRef?: string | undefined;
|
|
60
|
+
inputSchema?: unknown;
|
|
61
|
+
outputSchema?: unknown;
|
|
62
|
+
metadata?: Record<string, unknown> | undefined;
|
|
63
|
+
providerOperation?: string | undefined;
|
|
64
|
+
}[];
|
|
65
|
+
channelAudiences: ("customer-facing" | "internal-support" | "mixed")[];
|
|
66
|
+
credentialRequirements: {
|
|
67
|
+
id: string;
|
|
68
|
+
scopes: string[];
|
|
69
|
+
required: boolean;
|
|
70
|
+
label?: string | undefined;
|
|
71
|
+
description?: string | undefined;
|
|
72
|
+
metadata?: Record<string, unknown> | undefined;
|
|
73
|
+
}[];
|
|
74
|
+
coverage: {
|
|
75
|
+
scope: "support-workflow-subset" | "provider-api-subset" | "connector-required" | "local-protocol" | "full-provider-api";
|
|
76
|
+
notes: string[];
|
|
77
|
+
evidence: {
|
|
78
|
+
label: string;
|
|
79
|
+
url?: string | undefined;
|
|
80
|
+
}[];
|
|
81
|
+
};
|
|
82
|
+
privacyNotes: string[];
|
|
83
|
+
limitations: string[];
|
|
84
|
+
maintainers: {
|
|
85
|
+
name: string;
|
|
86
|
+
type: "community" | "official" | "unknown" | "partner";
|
|
87
|
+
url?: string | undefined;
|
|
88
|
+
}[];
|
|
89
|
+
metadata?: Record<string, unknown> | undefined;
|
|
90
|
+
};
|
|
5
91
|
|
|
6
92
|
declare const COGNIDESK_VOICE_PROTOCOL: "cognidesk.voice.v1";
|
|
7
93
|
type VoiceProtocol = typeof COGNIDESK_VOICE_PROTOCOL;
|
|
94
|
+
type VoiceJsonPrimitive = string | number | boolean | null;
|
|
95
|
+
type VoiceJsonValue = VoiceJsonPrimitive | VoiceJsonObject | VoiceJsonValue[];
|
|
96
|
+
type VoiceWebsocketJsonPrimitive = string | number | boolean | null;
|
|
97
|
+
type VoiceWebsocketJsonValue = VoiceWebsocketJsonPrimitive | VoiceWebsocketJsonObject | readonly VoiceWebsocketJsonValue[];
|
|
98
|
+
type VoiceWebsocketProviderExtensionValue = VoiceWebsocketJsonValue | object | undefined;
|
|
99
|
+
interface VoiceWebsocketJsonObject {
|
|
100
|
+
[key: string]: VoiceWebsocketProviderExtensionValue;
|
|
101
|
+
}
|
|
102
|
+
type VoiceWebsocketProviderPayload = VoiceWebsocketJsonObject | object;
|
|
103
|
+
type VoiceWebsocketProviderQuery = Record<string, VoiceWebsocketProviderExtensionValue>;
|
|
104
|
+
interface VoiceWebsocketProviderResponse extends VoiceWebsocketJsonObject {
|
|
105
|
+
}
|
|
106
|
+
interface VoiceWebsocketProviderExtensionFields extends VoiceWebsocketJsonObject {
|
|
107
|
+
}
|
|
108
|
+
interface VoiceJsonObject {
|
|
109
|
+
[key: string]: VoiceJsonValue;
|
|
110
|
+
}
|
|
8
111
|
type VoiceBrowserClientEvent = {
|
|
9
112
|
type: "session.update";
|
|
10
113
|
event_id?: string;
|
|
11
|
-
session?:
|
|
114
|
+
session?: VoiceJsonObject;
|
|
12
115
|
} | {
|
|
13
116
|
type: "input_audio_buffer.append";
|
|
14
117
|
event_id?: string;
|
|
@@ -88,7 +191,7 @@ type VoiceBrowserServerEvent = {
|
|
|
88
191
|
startedAtMs?: number;
|
|
89
192
|
endedAtMs?: number;
|
|
90
193
|
transcriptionSource?: string;
|
|
91
|
-
metadata?:
|
|
194
|
+
metadata?: VoiceJsonObject;
|
|
92
195
|
} | {
|
|
93
196
|
type: "response.output_audio.delta";
|
|
94
197
|
event_id?: string;
|
|
@@ -123,7 +226,7 @@ type VoiceBrowserServerEvent = {
|
|
|
123
226
|
} | {
|
|
124
227
|
type: "response.done";
|
|
125
228
|
event_id?: string;
|
|
126
|
-
response?:
|
|
229
|
+
response?: VoiceJsonObject;
|
|
127
230
|
} | {
|
|
128
231
|
type: "error";
|
|
129
232
|
event_id?: string;
|
|
@@ -131,7 +234,7 @@ type VoiceBrowserServerEvent = {
|
|
|
131
234
|
code: string;
|
|
132
235
|
message: string;
|
|
133
236
|
retryable?: boolean;
|
|
134
|
-
details?:
|
|
237
|
+
details?: VoiceJsonValue;
|
|
135
238
|
};
|
|
136
239
|
};
|
|
137
240
|
type VoiceProviderEvent = {
|
|
@@ -141,7 +244,7 @@ type VoiceProviderEvent = {
|
|
|
141
244
|
startedAtMs?: number;
|
|
142
245
|
endedAtMs?: number;
|
|
143
246
|
transcriptionSource?: string;
|
|
144
|
-
metadata?:
|
|
247
|
+
metadata?: VoiceJsonObject;
|
|
145
248
|
} | {
|
|
146
249
|
kind: "server_event";
|
|
147
250
|
event: VoiceBrowserServerEvent;
|
|
@@ -153,7 +256,7 @@ type VoiceProviderEvent = {
|
|
|
153
256
|
code?: string;
|
|
154
257
|
message: string;
|
|
155
258
|
retryable?: boolean;
|
|
156
|
-
details?:
|
|
259
|
+
details?: VoiceJsonValue;
|
|
157
260
|
};
|
|
158
261
|
interface VoiceProviderConnectInput {
|
|
159
262
|
session: VoiceSocketSession;
|
|
@@ -177,23 +280,23 @@ interface VoiceProvider {
|
|
|
177
280
|
readonly id: string;
|
|
178
281
|
connect(input: VoiceProviderConnectInput): Promise<VoiceProviderSession>;
|
|
179
282
|
}
|
|
180
|
-
interface VoiceControlTool {
|
|
283
|
+
interface VoiceControlTool<TParameters extends VoiceJsonObject = VoiceJsonObject> {
|
|
181
284
|
name: string;
|
|
182
285
|
description?: string;
|
|
183
|
-
parameters?:
|
|
286
|
+
parameters?: TParameters;
|
|
184
287
|
}
|
|
185
|
-
interface VoiceControlToolCall {
|
|
288
|
+
interface VoiceControlToolCall<TArguments extends VoiceJsonValue = VoiceJsonValue> {
|
|
186
289
|
session: VoiceSocketSession;
|
|
187
290
|
name: string;
|
|
188
|
-
arguments:
|
|
291
|
+
arguments: TArguments;
|
|
189
292
|
callId: string;
|
|
190
293
|
itemId?: string;
|
|
191
294
|
responseId?: string;
|
|
192
295
|
signal: AbortSignal;
|
|
193
296
|
notify?(notification: VoiceControlNotification): Promise<void>;
|
|
194
297
|
}
|
|
195
|
-
interface VoiceControlToolResult {
|
|
196
|
-
output:
|
|
298
|
+
interface VoiceControlToolResult<TOutput extends VoiceJsonValue = VoiceJsonValue> {
|
|
299
|
+
output: TOutput;
|
|
197
300
|
events?: RuntimeEvent[];
|
|
198
301
|
}
|
|
199
302
|
interface VoiceControlNotification {
|
|
@@ -202,13 +305,13 @@ interface VoiceControlNotification {
|
|
|
202
305
|
responseInstructions?: string;
|
|
203
306
|
createResponse?: boolean;
|
|
204
307
|
}
|
|
205
|
-
interface VoiceControlSurface {
|
|
308
|
+
interface VoiceControlSurface<TArguments extends VoiceJsonValue = VoiceJsonValue, TOutput extends VoiceJsonValue = VoiceJsonValue> {
|
|
206
309
|
tools: VoiceControlTool[];
|
|
207
310
|
instructions?: string;
|
|
208
311
|
createSessionInstructions?(input: {
|
|
209
312
|
session: VoiceSocketSession;
|
|
210
313
|
}): Promise<string> | string;
|
|
211
|
-
handleToolCall(input: VoiceControlToolCall): Promise<VoiceControlToolResult
|
|
314
|
+
handleToolCall(input: VoiceControlToolCall<TArguments>): Promise<VoiceControlToolResult<TOutput>> | VoiceControlToolResult<TOutput>;
|
|
212
315
|
}
|
|
213
316
|
interface VoiceSocketLike {
|
|
214
317
|
send(data: string): void;
|
|
@@ -284,10 +387,12 @@ interface VoiceSessionStore {
|
|
|
284
387
|
markEnded(sessionId: string, now?: Date): Promise<VoiceSocketSession>;
|
|
285
388
|
getSession(sessionId: string): Promise<VoiceSocketSession | null>;
|
|
286
389
|
}
|
|
390
|
+
|
|
287
391
|
interface InMemoryVoiceSessionStoreOptions {
|
|
288
392
|
createToken?: () => string;
|
|
289
393
|
}
|
|
290
394
|
declare function createInMemoryVoiceSessionStore(options?: InMemoryVoiceSessionStoreOptions): VoiceSessionStore;
|
|
395
|
+
|
|
291
396
|
interface VoiceSocketHandshakeOptions {
|
|
292
397
|
store: VoiceSessionStore;
|
|
293
398
|
tokenTtlMs?: number;
|
|
@@ -301,6 +406,7 @@ declare function createVoiceSocketHandshake(options: VoiceSocketHandshakeOptions
|
|
|
301
406
|
basePath: string;
|
|
302
407
|
}): Promise<VoiceSocketMetadata>;
|
|
303
408
|
};
|
|
409
|
+
|
|
304
410
|
interface VoiceRuntime {
|
|
305
411
|
handleVoiceUserMessage<TTurn = unknown>(input: HandleVoiceUserMessageInput<TTurn>): Promise<HandleVoiceUserMessageResult>;
|
|
306
412
|
commitVoiceTranscript?(input: {
|
|
@@ -312,7 +418,7 @@ interface VoiceRuntime {
|
|
|
312
418
|
startedAtMs?: number;
|
|
313
419
|
endedAtMs?: number;
|
|
314
420
|
transcriptionSource?: string;
|
|
315
|
-
metadata?:
|
|
421
|
+
metadata?: VoiceWebsocketProviderExtensionFields;
|
|
316
422
|
}): Promise<{
|
|
317
423
|
events: RuntimeEvent[];
|
|
318
424
|
event: RuntimeEvent;
|
|
@@ -352,7 +458,9 @@ interface HandleVoiceSocketOptions {
|
|
|
352
458
|
turnPreambleMs?: number;
|
|
353
459
|
signal?: AbortSignal;
|
|
354
460
|
}
|
|
461
|
+
|
|
355
462
|
declare function handleVoiceSocket(options: HandleVoiceSocketOptions): Promise<void>;
|
|
463
|
+
|
|
356
464
|
interface AttachNodeVoiceWebSocketAdapterOptions {
|
|
357
465
|
server: Server;
|
|
358
466
|
store: VoiceSessionStore;
|
|
@@ -372,4 +480,4 @@ declare function attachNodeVoiceWebSocketAdapter(options: AttachNodeVoiceWebSock
|
|
|
372
480
|
webSocketServer: ws.Server<typeof WebSocket, typeof IncomingMessage>;
|
|
373
481
|
};
|
|
374
482
|
|
|
375
|
-
export { type AttachNodeVoiceWebSocketAdapterOptions, COGNIDESK_VOICE_PROTOCOL, type HandleVoiceSocketOptions, type InMemoryVoiceSessionStoreOptions, type VoiceBrowserClientEvent, type VoiceBrowserServerEvent, type VoiceControlNotification, type VoiceControlSurface, type VoiceControlTool, type VoiceControlToolCall, type VoiceControlToolResult, type VoiceProtocol, type VoiceProvider, type VoiceProviderConnectInput, type VoiceProviderEvent, type VoiceProviderSession, type VoiceRecorder, type VoiceRuntime, type VoiceSessionStore, type VoiceSocketHandshakeOptions, type VoiceSocketLike, type VoiceSocketSession, type VoiceSocketToken, attachNodeVoiceWebSocketAdapter, createInMemoryVoiceSessionStore, createVoiceSocketHandshake, handleVoiceSocket };
|
|
483
|
+
export { type AttachNodeVoiceWebSocketAdapterOptions, COGNIDESK_VOICE_PROTOCOL, type HandleVoiceSocketOptions, type InMemoryVoiceSessionStoreOptions, type VoiceBrowserClientEvent, type VoiceBrowserServerEvent, type VoiceControlNotification, type VoiceControlSurface, type VoiceControlTool, type VoiceControlToolCall, type VoiceControlToolResult, type VoiceJsonObject, type VoiceJsonPrimitive, type VoiceJsonValue, type VoiceProtocol, type VoiceProvider, type VoiceProviderConnectInput, type VoiceProviderEvent, type VoiceProviderSession, type VoiceRecorder, type VoiceRuntime, type VoiceSessionStore, type VoiceSocketHandshakeOptions, type VoiceSocketLike, type VoiceSocketSession, type VoiceSocketToken, type VoiceWebsocketJsonObject, type VoiceWebsocketJsonPrimitive, type VoiceWebsocketJsonValue, type VoiceWebsocketProviderExtensionFields, type VoiceWebsocketProviderExtensionValue, type VoiceWebsocketProviderPayload, type VoiceWebsocketProviderQuery, type VoiceWebsocketProviderResponse, attachNodeVoiceWebSocketAdapter, cognideskVoiceWebSocketProviderManifest, createInMemoryVoiceSessionStore, createVoiceSocketHandshake, handleVoiceSocket };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,100 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
1
|
+
// src/manifest.ts
|
|
2
|
+
import { defineProviderPackage } from "@cognidesk/core";
|
|
3
|
+
var cognideskVoiceWebSocketProviderManifest = defineProviderPackage({
|
|
4
|
+
id: "voice.websocket",
|
|
5
|
+
name: "Cognidesk Voice WebSocket",
|
|
6
|
+
packageName: "@cognidesk/voice-websocket",
|
|
7
|
+
provider: "websocket",
|
|
8
|
+
category: "voice",
|
|
9
|
+
trustLevel: "official",
|
|
10
|
+
directions: ["bidirectional"],
|
|
11
|
+
channelAudiences: ["customer-facing", "mixed"],
|
|
12
|
+
coverage: {
|
|
13
|
+
scope: "local-protocol",
|
|
14
|
+
notes: [
|
|
15
|
+
"Implements the Cognidesk browser voice WebSocket protocol, session token store, reconnect flow, runtime transcript bridge, and provider-session event bridge.",
|
|
16
|
+
"Does not implement an external voice provider API, telephony carrier API, OpenAI API client, Twilio/Vonage call control, SIP stack, recording storage, or browser credential issuance.",
|
|
17
|
+
"External provider media, telephony setup, consent, recording, retention, and routing policy are supplied by the selected VoiceProvider and SDK-user configuration."
|
|
18
|
+
],
|
|
19
|
+
evidence: [
|
|
20
|
+
{ label: "CONTEXT.md Voice Browser Protocol definition" },
|
|
21
|
+
{ label: "CONTEXT.md Voice WebSocket Adapter definition" },
|
|
22
|
+
{
|
|
23
|
+
label: "ADR-0063 Voice WebSocket Protocol",
|
|
24
|
+
url: "https://github.com/cognilabz/cognidesk/blob/main/docs/adr/0063-use-a-cognidesk-voice-websocket-protocol.md"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
label: "ADR-0067 Node Voice WebSocket Adapter",
|
|
28
|
+
url: "https://github.com/cognilabz/cognidesk/blob/main/docs/adr/0067-ship-a-node-voice-websocket-adapter.md"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
capabilities: [
|
|
33
|
+
{
|
|
34
|
+
capability: "receive",
|
|
35
|
+
label: "Receive browser voice input",
|
|
36
|
+
description: "Accepts live browser voice protocol events for a Cognidesk voice Channel Segment.",
|
|
37
|
+
audiences: ["customer-facing"],
|
|
38
|
+
providerObjects: [{ kind: "voiceConnection", label: "Voice Connection" }]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
capability: "send",
|
|
42
|
+
label: "Send browser voice output",
|
|
43
|
+
description: "Streams assistant voice protocol events back to the browser client.",
|
|
44
|
+
audiences: ["customer-facing"],
|
|
45
|
+
providerObjects: [{ kind: "voiceConnection", label: "Voice Connection" }],
|
|
46
|
+
sideEffect: true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
capability: "media",
|
|
50
|
+
label: "Voice media stream",
|
|
51
|
+
description: "Carries audio, transcript, interruption, and acknowledgement events through the Cognidesk voice protocol.",
|
|
52
|
+
audiences: ["customer-facing"],
|
|
53
|
+
providerObjects: [
|
|
54
|
+
{ kind: "voiceConnection", label: "Voice Connection" },
|
|
55
|
+
{ kind: "voiceTranscript", label: "Voice Transcript" }
|
|
56
|
+
],
|
|
57
|
+
exposesSensitiveData: true
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
capability: "thread",
|
|
61
|
+
label: "Attach to conversation",
|
|
62
|
+
description: "Maps a voice Channel Segment to a Cognidesk Conversation selected by SDK-user routing.",
|
|
63
|
+
audiences: ["customer-facing", "internal-support"],
|
|
64
|
+
providerObjects: [{ kind: "conversation", label: "Conversation" }]
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
privacyNotes: [
|
|
68
|
+
"Browser audio and transcript events pass through the application-hosted Cognidesk Voice Browser Protocol.",
|
|
69
|
+
"The package does not grant browser access to provider realtime credentials."
|
|
70
|
+
],
|
|
71
|
+
limitations: [
|
|
72
|
+
"Provider realtime transport is supplied by a VoiceProvider implementation.",
|
|
73
|
+
"Conversation routing, consent, recording, and retention behavior are configured by the SDK user."
|
|
74
|
+
],
|
|
75
|
+
metadata: {
|
|
76
|
+
channelCoverage: {
|
|
77
|
+
browserVoiceProtocol: "sdk-owned-local-protocol",
|
|
78
|
+
audioStream: "typed-protocol",
|
|
79
|
+
transcriptStream: "typed-protocol",
|
|
80
|
+
reconnectSessionToken: "typed-protocol",
|
|
81
|
+
providerRealtimeTransport: "not-covered",
|
|
82
|
+
telephonyCarrier: "not-covered"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
maintainers: [{ name: "Cognidesk", type: "official" }]
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// src/protocol.ts
|
|
3
89
|
var COGNIDESK_VOICE_PROTOCOL = "cognidesk.voice.v1";
|
|
90
|
+
|
|
91
|
+
// src/ids.ts
|
|
92
|
+
function createId(prefix) {
|
|
93
|
+
const random = globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);
|
|
94
|
+
return `${prefix}_${random}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/session-store.ts
|
|
4
98
|
function createInMemoryVoiceSessionStore(options = {}) {
|
|
5
99
|
const sessions = /* @__PURE__ */ new Map();
|
|
6
100
|
const tokens = /* @__PURE__ */ new Map();
|
|
@@ -58,8 +152,7 @@ function createInMemoryVoiceSessionStore(options = {}) {
|
|
|
58
152
|
};
|
|
59
153
|
},
|
|
60
154
|
async issueReconnectToken(input) {
|
|
61
|
-
const session = sessions
|
|
62
|
-
if (!session) throw new Error(`Voice session '${input.sessionId}' was not found.`);
|
|
155
|
+
const session = requireSession(sessions, input.sessionId);
|
|
63
156
|
const token = createTokenRecord({
|
|
64
157
|
createToken,
|
|
65
158
|
connectionId: session.connection.id,
|
|
@@ -108,6 +201,37 @@ function createInMemoryVoiceSessionStore(options = {}) {
|
|
|
108
201
|
}
|
|
109
202
|
};
|
|
110
203
|
}
|
|
204
|
+
function createTokenRecord(input) {
|
|
205
|
+
return {
|
|
206
|
+
token: input.createToken(),
|
|
207
|
+
connectionId: input.connectionId,
|
|
208
|
+
sessionId: input.sessionId,
|
|
209
|
+
purpose: input.purpose,
|
|
210
|
+
expiresAt: new Date(input.now.getTime() + input.ttlMs).toISOString()
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function requireSession(sessions, sessionId) {
|
|
214
|
+
const session = sessions.get(sessionId);
|
|
215
|
+
if (!session) throw new Error(`Voice session '${sessionId}' was not found.`);
|
|
216
|
+
return session;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/urls.ts
|
|
220
|
+
function buildSocketUrl(input) {
|
|
221
|
+
const requestUrl = new URL(input.requestUrl);
|
|
222
|
+
const base = input.baseUrl ? new URL(input.baseUrl) : requestUrl;
|
|
223
|
+
const protocol = base.protocol === "https:" ? "wss:" : "ws:";
|
|
224
|
+
const url = new URL(`${input.basePath}${input.pathPrefix}/${encodeURIComponent(input.connectionId)}/socket`, base);
|
|
225
|
+
url.protocol = protocol;
|
|
226
|
+
url.searchParams.set("token", input.token);
|
|
227
|
+
return url.toString();
|
|
228
|
+
}
|
|
229
|
+
function normalizePathPrefix(path) {
|
|
230
|
+
const withSlash = path.startsWith("/") ? path : `/${path}`;
|
|
231
|
+
return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/handshake.ts
|
|
111
235
|
function createVoiceSocketHandshake(options) {
|
|
112
236
|
const tokenTtlMs = options.tokenTtlMs ?? 6e4;
|
|
113
237
|
const pathPrefix = normalizePathPrefix(options.pathPrefix ?? "/voice/connections");
|
|
@@ -131,6 +255,170 @@ function createVoiceSocketHandshake(options) {
|
|
|
131
255
|
}
|
|
132
256
|
};
|
|
133
257
|
}
|
|
258
|
+
|
|
259
|
+
// src/speech.ts
|
|
260
|
+
function mergeInputTranscript(current, next) {
|
|
261
|
+
if (!current) return next;
|
|
262
|
+
const merged = {
|
|
263
|
+
kind: "input_transcript.completed",
|
|
264
|
+
text: `${current.text} ${next.text}`.trim()
|
|
265
|
+
};
|
|
266
|
+
const itemId = next.itemId ?? current.itemId;
|
|
267
|
+
if (itemId) merged.itemId = itemId;
|
|
268
|
+
const startedAtMs = current.startedAtMs ?? next.startedAtMs;
|
|
269
|
+
if (startedAtMs !== void 0) merged.startedAtMs = startedAtMs;
|
|
270
|
+
const endedAtMs = next.endedAtMs ?? current.endedAtMs;
|
|
271
|
+
if (endedAtMs !== void 0) merged.endedAtMs = endedAtMs;
|
|
272
|
+
const transcriptionSource = next.transcriptionSource ?? current.transcriptionSource;
|
|
273
|
+
if (transcriptionSource) merged.transcriptionSource = transcriptionSource;
|
|
274
|
+
const metadata = {
|
|
275
|
+
...current.metadata ?? {},
|
|
276
|
+
...next.metadata ?? {}
|
|
277
|
+
};
|
|
278
|
+
if (Object.keys(metadata).length > 0) merged.metadata = metadata;
|
|
279
|
+
return merged;
|
|
280
|
+
}
|
|
281
|
+
function isAgentResponseSignal(event) {
|
|
282
|
+
return event.type === "response.output_audio.delta" || event.type === "response.output_audio_transcript.delta" || event.type === "response.output_audio_transcript.done" || event.type === "response.done";
|
|
283
|
+
}
|
|
284
|
+
function takeSpeakablePrefix(text, force) {
|
|
285
|
+
if (!text.trim()) return null;
|
|
286
|
+
if (force) return { text: normalizeSpeechText(text), consumed: text.length };
|
|
287
|
+
const sentenceBoundary = findLastSentenceBoundary(text);
|
|
288
|
+
if (sentenceBoundary > 0) {
|
|
289
|
+
return {
|
|
290
|
+
text: normalizeSpeechText(text.slice(0, sentenceBoundary)),
|
|
291
|
+
consumed: sentenceBoundary
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (text.length < 180) return null;
|
|
295
|
+
const softBoundary = findSoftBoundary(text, 140);
|
|
296
|
+
if (softBoundary <= 0) return null;
|
|
297
|
+
return {
|
|
298
|
+
text: normalizeSpeechText(text.slice(0, softBoundary)),
|
|
299
|
+
consumed: softBoundary
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function normalizeSpeechText(text) {
|
|
303
|
+
return text.replace(/\s+/g, " ").trim();
|
|
304
|
+
}
|
|
305
|
+
function debounceMsForTranscript(text, baseMs) {
|
|
306
|
+
const wordCount = text.trim().split(" ").filter(Boolean).length;
|
|
307
|
+
return wordCount <= 2 ? Math.max(baseMs, 900) : baseMs;
|
|
308
|
+
}
|
|
309
|
+
function findLastSentenceBoundary(text) {
|
|
310
|
+
let boundary = -1;
|
|
311
|
+
const pattern = /[.!?。!?](?:["')\]]+)?\s+/g;
|
|
312
|
+
let match;
|
|
313
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
314
|
+
boundary = match.index + match[0].length;
|
|
315
|
+
}
|
|
316
|
+
return boundary;
|
|
317
|
+
}
|
|
318
|
+
function findSoftBoundary(text, minIndex) {
|
|
319
|
+
const candidates = [", ", "; ", ": ", "\n", " "];
|
|
320
|
+
for (const candidate of candidates) {
|
|
321
|
+
const boundary = text.lastIndexOf(candidate);
|
|
322
|
+
if (boundary >= minIndex) return boundary + candidate.length;
|
|
323
|
+
}
|
|
324
|
+
return -1;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/wire.ts
|
|
328
|
+
function parseClientEvent(raw) {
|
|
329
|
+
let parsed;
|
|
330
|
+
try {
|
|
331
|
+
parsed = JSON.parse(raw);
|
|
332
|
+
} catch {
|
|
333
|
+
throw new Error("Voice socket message must be valid JSON.");
|
|
334
|
+
}
|
|
335
|
+
if (!isRecord(parsed)) throw new Error("Voice socket message must be a JSON object.");
|
|
336
|
+
const type = parsed.type;
|
|
337
|
+
if (typeof type !== "string") throw new Error("Voice socket message type is required.");
|
|
338
|
+
switch (type) {
|
|
339
|
+
case "session.update":
|
|
340
|
+
return { type, ...optionalEventId(parsed), ...isRecord(parsed.session) ? { session: toVoiceJsonObject(parsed.session) } : {} };
|
|
341
|
+
case "input_audio_buffer.append": {
|
|
342
|
+
const audio = requiredString(parsed, "audio");
|
|
343
|
+
const sequence = optionalInteger(parsed, "sequence");
|
|
344
|
+
return { type, audio, ...optionalEventId(parsed), ...sequence !== void 0 ? { sequence } : {} };
|
|
345
|
+
}
|
|
346
|
+
case "input_audio_buffer.commit":
|
|
347
|
+
case "input_audio_buffer.clear":
|
|
348
|
+
return { type, ...optionalEventId(parsed) };
|
|
349
|
+
case "response.cancel":
|
|
350
|
+
return {
|
|
351
|
+
type,
|
|
352
|
+
...optionalEventId(parsed),
|
|
353
|
+
...optionalStringField("response_id", optionalString(parsed, "response_id")),
|
|
354
|
+
...optionalStringField("interruptedMessageId", optionalString(parsed, "interruptedMessageId")),
|
|
355
|
+
...optionalStringField("reason", optionalString(parsed, "reason")),
|
|
356
|
+
...optionalNumberField("playedUntilMs", optionalInteger(parsed, "playedUntilMs")),
|
|
357
|
+
...optionalNumberField("audioEndMs", optionalInteger(parsed, "audioEndMs"))
|
|
358
|
+
};
|
|
359
|
+
case "conversation.item.truncate":
|
|
360
|
+
return {
|
|
361
|
+
type,
|
|
362
|
+
...optionalEventId(parsed),
|
|
363
|
+
...optionalStringField("item_id", optionalString(parsed, "item_id")),
|
|
364
|
+
...optionalNumberField("content_index", optionalInteger(parsed, "content_index")),
|
|
365
|
+
...optionalNumberField("audio_end_ms", optionalInteger(parsed, "audio_end_ms"))
|
|
366
|
+
};
|
|
367
|
+
default:
|
|
368
|
+
throw new Error(`Unsupported voice socket event '${type}'.`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function assertBase64Audio(value) {
|
|
372
|
+
if (value.length === 0) throw new Error("audio must not be empty.");
|
|
373
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value)) throw new Error("audio must be base64 encoded.");
|
|
374
|
+
}
|
|
375
|
+
function optionalStringField(key, value) {
|
|
376
|
+
return value ? { [key]: value } : {};
|
|
377
|
+
}
|
|
378
|
+
function optionalNumberField(key, value) {
|
|
379
|
+
return value !== void 0 ? { [key]: value } : {};
|
|
380
|
+
}
|
|
381
|
+
function optionalEventId(value) {
|
|
382
|
+
return optionalStringField("event_id", optionalString(value, "event_id"));
|
|
383
|
+
}
|
|
384
|
+
function requiredString(value, key) {
|
|
385
|
+
const result = optionalString(value, key);
|
|
386
|
+
if (!result) throw new Error(`${key} must be a non-empty string.`);
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
function optionalString(value, key) {
|
|
390
|
+
const candidate = value[key];
|
|
391
|
+
if (candidate === void 0 || candidate === null) return void 0;
|
|
392
|
+
if (typeof candidate !== "string") throw new Error(`${key} must be a string.`);
|
|
393
|
+
const trimmed = candidate.trim();
|
|
394
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
395
|
+
}
|
|
396
|
+
function optionalInteger(value, key) {
|
|
397
|
+
const candidate = value[key];
|
|
398
|
+
if (candidate === void 0 || candidate === null) return void 0;
|
|
399
|
+
if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 0) {
|
|
400
|
+
throw new Error(`${key} must be a non-negative integer.`);
|
|
401
|
+
}
|
|
402
|
+
return candidate;
|
|
403
|
+
}
|
|
404
|
+
function toVoiceJsonValue(value) {
|
|
405
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
408
|
+
if (Array.isArray(value)) return value.map(toVoiceJsonValue);
|
|
409
|
+
if (isRecord(value)) return toVoiceJsonObject(value);
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
function toVoiceJsonObject(value) {
|
|
413
|
+
return Object.fromEntries(
|
|
414
|
+
Object.entries(value).map(([key, item]) => [key, toVoiceJsonValue(item)])
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
function isRecord(value) {
|
|
418
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/handler.ts
|
|
134
422
|
async function handleVoiceSocket(options) {
|
|
135
423
|
const claimed = await options.store.claimToken({
|
|
136
424
|
connectionId: options.connectionId,
|
|
@@ -163,6 +451,27 @@ async function handleVoiceSocket(options) {
|
|
|
163
451
|
let inputTranscriptQueue = Promise.resolve();
|
|
164
452
|
let speechQueue = Promise.resolve();
|
|
165
453
|
let speechGeneration = 0;
|
|
454
|
+
const cleanupConnection = () => {
|
|
455
|
+
if (closed) return false;
|
|
456
|
+
closed = true;
|
|
457
|
+
speechGeneration++;
|
|
458
|
+
if (pendingInputTranscriptTimer) clearTimeout(pendingInputTranscriptTimer);
|
|
459
|
+
clearTurnPreambleTimer();
|
|
460
|
+
pendingInputTranscript = null;
|
|
461
|
+
controller.abort();
|
|
462
|
+
options.signal?.removeEventListener("abort", abort);
|
|
463
|
+
void providerSession?.close();
|
|
464
|
+
return true;
|
|
465
|
+
};
|
|
466
|
+
const endSession = async (reason) => {
|
|
467
|
+
await options.store.markEnded(session.id);
|
|
468
|
+
await options.runtime.endVoiceSegment({
|
|
469
|
+
conversationId: session.conversation.id,
|
|
470
|
+
channelSegmentId: session.channelSegment.id,
|
|
471
|
+
connectionId: session.connection.id,
|
|
472
|
+
reason
|
|
473
|
+
});
|
|
474
|
+
};
|
|
166
475
|
const sendRuntimeEvents = (events) => {
|
|
167
476
|
for (const event of events) {
|
|
168
477
|
send(options.socket, {
|
|
@@ -189,6 +498,19 @@ async function handleVoiceSocket(options) {
|
|
|
189
498
|
clearTimeout(turnPreambleTimer);
|
|
190
499
|
turnPreambleTimer = null;
|
|
191
500
|
};
|
|
501
|
+
const handleSocketClose = (code) => {
|
|
502
|
+
if (!cleanupConnection()) return;
|
|
503
|
+
const normalClose = code === void 0 || code === 1e3 || code === 1001;
|
|
504
|
+
if (normalClose) {
|
|
505
|
+
void endSession("socket_closed");
|
|
506
|
+
} else {
|
|
507
|
+
void options.store.markReconnecting(
|
|
508
|
+
session.id,
|
|
509
|
+
/* @__PURE__ */ new Date(),
|
|
510
|
+
options.reconnectGraceMs ?? 3e4
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
192
514
|
const queueSpeechAction = (generation, action) => {
|
|
193
515
|
const queued = speechQueue.catch(() => void 0).then(async () => {
|
|
194
516
|
if (closed || generation !== speechGeneration) return;
|
|
@@ -360,6 +682,17 @@ async function handleVoiceSocket(options) {
|
|
|
360
682
|
return options.control.handleToolCall(call);
|
|
361
683
|
}
|
|
362
684
|
} : void 0;
|
|
685
|
+
options.socket.on("error", (error) => {
|
|
686
|
+
send(options.socket, {
|
|
687
|
+
type: "error",
|
|
688
|
+
event_id: createId("voice_event"),
|
|
689
|
+
error: {
|
|
690
|
+
code: "voice_socket_error",
|
|
691
|
+
message: error instanceof Error ? error.message : "Voice socket error."
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
options.socket.on("close", handleSocketClose);
|
|
363
696
|
const commitInputTranscript = async (event) => {
|
|
364
697
|
const generation = ++speechGeneration;
|
|
365
698
|
let assistantSpeechBuffer = "";
|
|
@@ -443,7 +776,14 @@ async function handleVoiceSocket(options) {
|
|
|
443
776
|
signal: controller.signal,
|
|
444
777
|
onEvent: handleProviderEvent
|
|
445
778
|
});
|
|
779
|
+
if (closed) {
|
|
780
|
+
void providerSession.close();
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
446
783
|
} catch (error) {
|
|
784
|
+
if (closed) return;
|
|
785
|
+
cleanupConnection();
|
|
786
|
+
await endSession("provider_connect_failed");
|
|
447
787
|
send(options.socket, {
|
|
448
788
|
type: "error",
|
|
449
789
|
event_id: createId("voice_event"),
|
|
@@ -480,44 +820,6 @@ async function handleVoiceSocket(options) {
|
|
|
480
820
|
});
|
|
481
821
|
});
|
|
482
822
|
});
|
|
483
|
-
options.socket.on("error", (error) => {
|
|
484
|
-
send(options.socket, {
|
|
485
|
-
type: "error",
|
|
486
|
-
event_id: createId("voice_event"),
|
|
487
|
-
error: {
|
|
488
|
-
code: "voice_socket_error",
|
|
489
|
-
message: error instanceof Error ? error.message : "Voice socket error."
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
options.socket.on("close", (code) => {
|
|
494
|
-
if (closed) return;
|
|
495
|
-
closed = true;
|
|
496
|
-
speechGeneration++;
|
|
497
|
-
if (pendingInputTranscriptTimer) clearTimeout(pendingInputTranscriptTimer);
|
|
498
|
-
clearTurnPreambleTimer();
|
|
499
|
-
pendingInputTranscript = null;
|
|
500
|
-
controller.abort();
|
|
501
|
-
options.signal?.removeEventListener("abort", abort);
|
|
502
|
-
void providerSession?.close();
|
|
503
|
-
const normalClose = code === void 0 || code === 1e3 || code === 1001;
|
|
504
|
-
if (normalClose) {
|
|
505
|
-
void options.store.markEnded(session.id).then(
|
|
506
|
-
() => options.runtime.endVoiceSegment({
|
|
507
|
-
conversationId: session.conversation.id,
|
|
508
|
-
channelSegmentId: session.channelSegment.id,
|
|
509
|
-
connectionId: session.connection.id,
|
|
510
|
-
reason: "socket_closed"
|
|
511
|
-
})
|
|
512
|
-
);
|
|
513
|
-
} else {
|
|
514
|
-
void options.store.markReconnecting(
|
|
515
|
-
session.id,
|
|
516
|
-
/* @__PURE__ */ new Date(),
|
|
517
|
-
options.reconnectGraceMs ?? 3e4
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
823
|
async function handleClientMessage(raw) {
|
|
522
824
|
const event = parseClientEvent(raw);
|
|
523
825
|
if (event.type === "input_audio_buffer.append") {
|
|
@@ -567,6 +869,12 @@ async function handleVoiceSocket(options) {
|
|
|
567
869
|
await providerSession?.send(event);
|
|
568
870
|
}
|
|
569
871
|
}
|
|
872
|
+
function send(socket, event) {
|
|
873
|
+
socket.send(JSON.stringify(event));
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// src/node-adapter.ts
|
|
877
|
+
import { WebSocketServer } from "ws";
|
|
570
878
|
function attachNodeVoiceWebSocketAdapter(options) {
|
|
571
879
|
const pathPrefix = normalizePathPrefix(options.pathPrefix ?? "/voice/connections");
|
|
572
880
|
const webSocketServer = new WebSocketServer({ noServer: true });
|
|
@@ -639,145 +947,6 @@ function adaptNodeWebSocket(socket) {
|
|
|
639
947
|
}
|
|
640
948
|
};
|
|
641
949
|
}
|
|
642
|
-
function parseClientEvent(raw) {
|
|
643
|
-
let parsed;
|
|
644
|
-
try {
|
|
645
|
-
parsed = JSON.parse(raw);
|
|
646
|
-
} catch {
|
|
647
|
-
throw new Error("Voice socket message must be valid JSON.");
|
|
648
|
-
}
|
|
649
|
-
if (!isRecord(parsed)) throw new Error("Voice socket message must be a JSON object.");
|
|
650
|
-
const type = parsed.type;
|
|
651
|
-
if (typeof type !== "string") throw new Error("Voice socket message type is required.");
|
|
652
|
-
switch (type) {
|
|
653
|
-
case "session.update":
|
|
654
|
-
return { type, ...optionalEventId(parsed), ...isRecord(parsed.session) ? { session: parsed.session } : {} };
|
|
655
|
-
case "input_audio_buffer.append": {
|
|
656
|
-
const audio = requiredString(parsed, "audio");
|
|
657
|
-
const sequence = optionalInteger(parsed, "sequence");
|
|
658
|
-
return { type, audio, ...optionalEventId(parsed), ...sequence !== void 0 ? { sequence } : {} };
|
|
659
|
-
}
|
|
660
|
-
case "input_audio_buffer.commit":
|
|
661
|
-
case "input_audio_buffer.clear":
|
|
662
|
-
return { type, ...optionalEventId(parsed) };
|
|
663
|
-
case "response.cancel":
|
|
664
|
-
return {
|
|
665
|
-
type,
|
|
666
|
-
...optionalEventId(parsed),
|
|
667
|
-
...optionalStringField("response_id", optionalString(parsed, "response_id")),
|
|
668
|
-
...optionalStringField("interruptedMessageId", optionalString(parsed, "interruptedMessageId")),
|
|
669
|
-
...optionalStringField("reason", optionalString(parsed, "reason")),
|
|
670
|
-
...optionalNumberField("playedUntilMs", optionalInteger(parsed, "playedUntilMs")),
|
|
671
|
-
...optionalNumberField("audioEndMs", optionalInteger(parsed, "audioEndMs"))
|
|
672
|
-
};
|
|
673
|
-
case "conversation.item.truncate":
|
|
674
|
-
return {
|
|
675
|
-
type,
|
|
676
|
-
...optionalEventId(parsed),
|
|
677
|
-
...optionalStringField("item_id", optionalString(parsed, "item_id")),
|
|
678
|
-
...optionalNumberField("content_index", optionalInteger(parsed, "content_index")),
|
|
679
|
-
...optionalNumberField("audio_end_ms", optionalInteger(parsed, "audio_end_ms"))
|
|
680
|
-
};
|
|
681
|
-
default:
|
|
682
|
-
throw new Error(`Unsupported voice socket event '${type}'.`);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
function buildSocketUrl(input) {
|
|
686
|
-
const requestUrl = new URL(input.requestUrl);
|
|
687
|
-
const base = input.baseUrl ? new URL(input.baseUrl) : requestUrl;
|
|
688
|
-
const protocol = base.protocol === "https:" ? "wss:" : "ws:";
|
|
689
|
-
const url = new URL(`${input.basePath}${input.pathPrefix}/${encodeURIComponent(input.connectionId)}/socket`, base);
|
|
690
|
-
url.protocol = protocol;
|
|
691
|
-
url.searchParams.set("token", input.token);
|
|
692
|
-
return url.toString();
|
|
693
|
-
}
|
|
694
|
-
function normalizePathPrefix(path) {
|
|
695
|
-
const withSlash = path.startsWith("/") ? path : `/${path}`;
|
|
696
|
-
return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
|
|
697
|
-
}
|
|
698
|
-
function createTokenRecord(input) {
|
|
699
|
-
return {
|
|
700
|
-
token: input.createToken(),
|
|
701
|
-
connectionId: input.connectionId,
|
|
702
|
-
sessionId: input.sessionId,
|
|
703
|
-
purpose: input.purpose,
|
|
704
|
-
expiresAt: new Date(input.now.getTime() + input.ttlMs).toISOString()
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
function requireSession(sessions, sessionId) {
|
|
708
|
-
const session = sessions.get(sessionId);
|
|
709
|
-
if (!session) throw new Error(`Voice session '${sessionId}' was not found.`);
|
|
710
|
-
return session;
|
|
711
|
-
}
|
|
712
|
-
function send(socket, event) {
|
|
713
|
-
socket.send(JSON.stringify(event));
|
|
714
|
-
}
|
|
715
|
-
function mergeInputTranscript(current, next) {
|
|
716
|
-
if (!current) return next;
|
|
717
|
-
const merged = {
|
|
718
|
-
kind: "input_transcript.completed",
|
|
719
|
-
text: `${current.text} ${next.text}`.trim()
|
|
720
|
-
};
|
|
721
|
-
const itemId = next.itemId ?? current.itemId;
|
|
722
|
-
if (itemId) merged.itemId = itemId;
|
|
723
|
-
const startedAtMs = current.startedAtMs ?? next.startedAtMs;
|
|
724
|
-
if (startedAtMs !== void 0) merged.startedAtMs = startedAtMs;
|
|
725
|
-
const endedAtMs = next.endedAtMs ?? current.endedAtMs;
|
|
726
|
-
if (endedAtMs !== void 0) merged.endedAtMs = endedAtMs;
|
|
727
|
-
const transcriptionSource = next.transcriptionSource ?? current.transcriptionSource;
|
|
728
|
-
if (transcriptionSource) merged.transcriptionSource = transcriptionSource;
|
|
729
|
-
const metadata = {
|
|
730
|
-
...current.metadata ?? {},
|
|
731
|
-
...next.metadata ?? {}
|
|
732
|
-
};
|
|
733
|
-
if (Object.keys(metadata).length > 0) merged.metadata = metadata;
|
|
734
|
-
return merged;
|
|
735
|
-
}
|
|
736
|
-
function isAgentResponseSignal(event) {
|
|
737
|
-
return event.type === "response.output_audio.delta" || event.type === "response.output_audio_transcript.delta" || event.type === "response.output_audio_transcript.done" || event.type === "response.done";
|
|
738
|
-
}
|
|
739
|
-
function takeSpeakablePrefix(text, force) {
|
|
740
|
-
if (!text.trim()) return null;
|
|
741
|
-
if (force) return { text: normalizeSpeechText(text), consumed: text.length };
|
|
742
|
-
const sentenceBoundary = findLastSentenceBoundary(text);
|
|
743
|
-
if (sentenceBoundary > 0) {
|
|
744
|
-
return {
|
|
745
|
-
text: normalizeSpeechText(text.slice(0, sentenceBoundary)),
|
|
746
|
-
consumed: sentenceBoundary
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
if (text.length < 180) return null;
|
|
750
|
-
const softBoundary = findSoftBoundary(text, 140);
|
|
751
|
-
if (softBoundary <= 0) return null;
|
|
752
|
-
return {
|
|
753
|
-
text: normalizeSpeechText(text.slice(0, softBoundary)),
|
|
754
|
-
consumed: softBoundary
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
function findLastSentenceBoundary(text) {
|
|
758
|
-
let boundary = -1;
|
|
759
|
-
const pattern = /[.!?。!?](?:["')\]]+)?\s+/g;
|
|
760
|
-
let match;
|
|
761
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
762
|
-
boundary = match.index + match[0].length;
|
|
763
|
-
}
|
|
764
|
-
return boundary;
|
|
765
|
-
}
|
|
766
|
-
function findSoftBoundary(text, minIndex) {
|
|
767
|
-
const candidates = [", ", "; ", ": ", "\n", " "];
|
|
768
|
-
for (const candidate of candidates) {
|
|
769
|
-
const boundary = text.lastIndexOf(candidate);
|
|
770
|
-
if (boundary >= minIndex) return boundary + candidate.length;
|
|
771
|
-
}
|
|
772
|
-
return -1;
|
|
773
|
-
}
|
|
774
|
-
function normalizeSpeechText(text) {
|
|
775
|
-
return text.replace(/\s+/g, " ").trim();
|
|
776
|
-
}
|
|
777
|
-
function debounceMsForTranscript(text, baseMs) {
|
|
778
|
-
const wordCount = text.trim().split(" ").filter(Boolean).length;
|
|
779
|
-
return wordCount <= 2 ? Math.max(baseMs, 900) : baseMs;
|
|
780
|
-
}
|
|
781
950
|
function rawDataToString(data) {
|
|
782
951
|
if (typeof data === "string") return data;
|
|
783
952
|
if (Buffer.isBuffer(data)) return data.toString("utf8");
|
|
@@ -791,51 +960,11 @@ function parseTokenFromProtocol(value) {
|
|
|
791
960
|
const bearer = protocols.find((candidate) => candidate.startsWith("cognidesk.voice.token."));
|
|
792
961
|
return bearer?.slice("cognidesk.voice.token.".length);
|
|
793
962
|
}
|
|
794
|
-
function assertBase64Audio(value) {
|
|
795
|
-
if (value.length === 0) throw new Error("audio must not be empty.");
|
|
796
|
-
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value)) throw new Error("audio must be base64 encoded.");
|
|
797
|
-
}
|
|
798
|
-
function optionalEventId(value) {
|
|
799
|
-
return optionalStringField("event_id", optionalString(value, "event_id"));
|
|
800
|
-
}
|
|
801
|
-
function requiredString(value, key) {
|
|
802
|
-
const result = optionalString(value, key);
|
|
803
|
-
if (!result) throw new Error(`${key} must be a non-empty string.`);
|
|
804
|
-
return result;
|
|
805
|
-
}
|
|
806
|
-
function optionalString(value, key) {
|
|
807
|
-
const candidate = value[key];
|
|
808
|
-
if (candidate === void 0 || candidate === null) return void 0;
|
|
809
|
-
if (typeof candidate !== "string") throw new Error(`${key} must be a string.`);
|
|
810
|
-
const trimmed = candidate.trim();
|
|
811
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
812
|
-
}
|
|
813
|
-
function optionalInteger(value, key) {
|
|
814
|
-
const candidate = value[key];
|
|
815
|
-
if (candidate === void 0 || candidate === null) return void 0;
|
|
816
|
-
if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 0) {
|
|
817
|
-
throw new Error(`${key} must be a non-negative integer.`);
|
|
818
|
-
}
|
|
819
|
-
return candidate;
|
|
820
|
-
}
|
|
821
|
-
function optionalStringField(key, value) {
|
|
822
|
-
return value ? { [key]: value } : {};
|
|
823
|
-
}
|
|
824
|
-
function optionalNumberField(key, value) {
|
|
825
|
-
return value !== void 0 ? { [key]: value } : {};
|
|
826
|
-
}
|
|
827
|
-
function isRecord(value) {
|
|
828
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
829
|
-
}
|
|
830
|
-
function createId(prefix) {
|
|
831
|
-
const random = globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);
|
|
832
|
-
return `${prefix}_${random}`;
|
|
833
|
-
}
|
|
834
963
|
export {
|
|
835
964
|
COGNIDESK_VOICE_PROTOCOL,
|
|
836
965
|
attachNodeVoiceWebSocketAdapter,
|
|
966
|
+
cognideskVoiceWebSocketProviderManifest,
|
|
837
967
|
createInMemoryVoiceSessionStore,
|
|
838
968
|
createVoiceSocketHandshake,
|
|
839
969
|
handleVoiceSocket
|
|
840
970
|
};
|
|
841
|
-
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cognidesk/voice-websocket",
|
|
3
|
-
"version": "0.0.3-dev.
|
|
3
|
+
"version": "0.0.3-dev.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build": "tsup src/index.ts --format esm --dts --
|
|
15
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
16
16
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
17
17
|
"test": "vitest run --passWithNoTests",
|
|
18
18
|
"lint": "tsc -p tsconfig.json --noEmit"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@cognidesk/core": "0.0.3-dev.
|
|
21
|
+
"@cognidesk/core": "0.0.3-dev.5",
|
|
22
22
|
"ws": "^8.21.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"registry": "https://registry.npmjs.org/"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"dist"
|
|
35
|
+
"dist",
|
|
36
|
+
"!dist/**/*.map"
|
|
36
37
|
],
|
|
37
38
|
"repository": {
|
|
38
39
|
"type": "git",
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { WebSocketServer } from \"ws\";\nimport type { IncomingMessage, Server as HttpServer } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport type { RawData, WebSocket } from \"ws\";\nimport type {\n HandleVoiceUserMessageInput,\n HandleVoiceUserMessageResult,\n RuntimeEvent,\n StartVoiceResult,\n VoiceProfile,\n VoiceSocketMetadata,\n} from \"@cognidesk/core\";\n\nexport const COGNIDESK_VOICE_PROTOCOL = \"cognidesk.voice.v1\" as const;\n\nexport type VoiceProtocol = typeof COGNIDESK_VOICE_PROTOCOL;\n\nexport type VoiceBrowserClientEvent =\n | {\n type: \"session.update\";\n event_id?: string;\n session?: Record<string, unknown>;\n }\n | {\n type: \"input_audio_buffer.append\";\n event_id?: string;\n audio: string;\n sequence?: number;\n }\n | {\n type: \"input_audio_buffer.commit\";\n event_id?: string;\n }\n | {\n type: \"input_audio_buffer.clear\";\n event_id?: string;\n }\n | {\n type: \"response.cancel\";\n event_id?: string;\n response_id?: string;\n interruptedMessageId?: string;\n playedUntilMs?: number;\n audioEndMs?: number;\n reason?: string;\n }\n | {\n type: \"conversation.item.truncate\";\n event_id?: string;\n item_id?: string;\n content_index?: number;\n audio_end_ms?: number;\n };\n\nexport type VoiceBrowserServerEvent =\n | {\n type: \"cognidesk.connection.ready\";\n event_id: string;\n protocol: VoiceProtocol;\n conversation: StartVoiceResult[\"conversation\"];\n channelSegment: StartVoiceResult[\"channelSegment\"];\n connection: StartVoiceResult[\"connection\"];\n lastAckSequence: number;\n }\n | {\n type: \"cognidesk.connection.reconnect_token\";\n event_id: string;\n token: string;\n expiresAt: string;\n }\n | {\n type: \"cognidesk.audio.ack\";\n event_id: string;\n sequence: number;\n }\n | {\n type: \"cognidesk.runtime_event\";\n event_id: string;\n event: RuntimeEvent;\n }\n | {\n type: \"cognidesk.turn.completed\";\n event_id: string;\n text: string;\n activeJourneyId?: string;\n }\n | {\n type: \"cognidesk.interruption.recorded\";\n event_id: string;\n event: RuntimeEvent;\n }\n | {\n type: \"cognidesk.voice.preamble\";\n event_id: string;\n text: string;\n elapsedMs: number;\n }\n | {\n type: \"input_audio_buffer.speech_started\";\n event_id?: string;\n audio_start_ms?: number;\n item_id?: string;\n }\n | {\n type: \"input_audio_buffer.speech_stopped\";\n event_id?: string;\n audio_end_ms?: number;\n item_id?: string;\n }\n | {\n type: \"input_audio_transcription.completed\";\n event_id: string;\n text: string;\n item_id?: string;\n startedAtMs?: number;\n endedAtMs?: number;\n transcriptionSource?: string;\n metadata?: Record<string, unknown>;\n }\n | {\n type: \"response.output_audio.delta\";\n event_id?: string;\n response_id?: string;\n item_id?: string;\n output_index?: number;\n content_index?: number;\n delta: string;\n }\n | {\n type: \"response.output_audio.done\";\n event_id?: string;\n response_id?: string;\n item_id?: string;\n output_index?: number;\n content_index?: number;\n }\n | {\n type: \"response.output_audio_transcript.delta\";\n event_id?: string;\n response_id?: string;\n item_id?: string;\n output_index?: number;\n content_index?: number;\n delta: string;\n }\n | {\n type: \"response.output_audio_transcript.done\";\n event_id?: string;\n response_id?: string;\n item_id?: string;\n output_index?: number;\n content_index?: number;\n transcript: string;\n }\n | {\n type: \"response.done\";\n event_id?: string;\n response?: unknown;\n }\n | {\n type: \"error\";\n event_id?: string;\n error: {\n code: string;\n message: string;\n retryable?: boolean;\n details?: unknown;\n };\n };\n\nexport type VoiceProviderEvent =\n | {\n kind: \"input_transcript.completed\";\n text: string;\n itemId?: string;\n startedAtMs?: number;\n endedAtMs?: number;\n transcriptionSource?: string;\n metadata?: Record<string, unknown>;\n }\n | {\n kind: \"server_event\";\n event: VoiceBrowserServerEvent;\n }\n | {\n kind: \"runtime_events\";\n events: RuntimeEvent[];\n }\n | {\n kind: \"error\";\n code?: string;\n message: string;\n retryable?: boolean;\n details?: unknown;\n };\n\ntype VoiceInputTranscriptEvent = Extract<VoiceProviderEvent, { kind: \"input_transcript.completed\" }>;\n\nexport interface VoiceProviderConnectInput {\n session: VoiceSocketSession;\n profile?: VoiceProfile;\n control?: VoiceControlSurface;\n signal: AbortSignal;\n onEvent(event: VoiceProviderEvent): Promise<void> | void;\n}\n\nexport interface VoiceProviderSession {\n send(event: VoiceBrowserClientEvent): Promise<void> | void;\n speak(input: { text: string; result?: HandleVoiceUserMessageResult }): Promise<void> | void;\n preamble?(input: { text: string }): Promise<void> | void;\n close(): Promise<void> | void;\n}\n\nexport interface VoiceProvider {\n readonly id: string;\n connect(input: VoiceProviderConnectInput): Promise<VoiceProviderSession>;\n}\n\nexport interface VoiceControlTool {\n name: string;\n description?: string;\n parameters?: unknown;\n}\n\nexport interface VoiceControlToolCall {\n session: VoiceSocketSession;\n name: string;\n arguments: unknown;\n callId: string;\n itemId?: string;\n responseId?: string;\n signal: AbortSignal;\n notify?(notification: VoiceControlNotification): Promise<void>;\n}\n\nexport interface VoiceControlToolResult {\n output: unknown;\n events?: RuntimeEvent[];\n}\n\nexport interface VoiceControlNotification {\n message: string;\n events?: RuntimeEvent[];\n responseInstructions?: string;\n createResponse?: boolean;\n}\n\nexport interface VoiceControlSurface {\n tools: VoiceControlTool[];\n instructions?: string;\n createSessionInstructions?(input: { session: VoiceSocketSession }): Promise<string> | string;\n handleToolCall(input: VoiceControlToolCall): Promise<VoiceControlToolResult> | VoiceControlToolResult;\n}\n\nexport interface VoiceSocketLike {\n send(data: string): void;\n close(code?: number, reason?: string): void;\n on(event: \"message\", listener: (data: string | ArrayBuffer | Uint8Array) => void): void;\n on(event: \"close\", listener: (code?: number, reason?: string) => void): void;\n on(event: \"error\", listener: (error: unknown) => void): void;\n}\n\nexport interface VoiceRecorder {\n onAudio?(input: {\n session: VoiceSocketSession;\n speaker: \"user\" | \"assistant\";\n audio: string;\n sequence?: number;\n }): Promise<void> | void;\n onTranscript?(input: {\n session: VoiceSocketSession;\n speaker: \"user\" | \"assistant\";\n text: string;\n runtimeEvent?: RuntimeEvent;\n }): Promise<void> | void;\n}\n\nexport interface VoiceSocketSession {\n id: string;\n conversation: StartVoiceResult[\"conversation\"];\n channelSegment: StartVoiceResult[\"channelSegment\"];\n connection: StartVoiceResult[\"connection\"];\n events: RuntimeEvent[];\n createdAt: string;\n updatedAt: string;\n status: \"pending\" | \"connected\" | \"reconnecting\" | \"ended\";\n lastAckSequence: number;\n reconnectGraceUntil?: string;\n}\n\nexport interface VoiceSocketToken {\n token: string;\n connectionId: string;\n sessionId: string;\n purpose: \"start\" | \"reconnect\";\n expiresAt: string;\n consumedAt?: string;\n}\n\nexport interface VoiceSessionStore {\n createSession(input: {\n result: StartVoiceResult;\n tokenTtlMs: number;\n now?: Date;\n }): Promise<{ session: VoiceSocketSession; socket: VoiceSocketMetadata }>;\n claimToken(input: {\n connectionId: string;\n token: string;\n now?: Date;\n }): Promise<{ session: VoiceSocketSession; token: VoiceSocketToken; reconnect: boolean } | null>;\n issueReconnectToken(input: {\n sessionId: string;\n ttlMs: number;\n now?: Date;\n }): Promise<VoiceSocketToken>;\n acknowledgeAudio(input: {\n sessionId: string;\n sequence: number;\n now?: Date;\n }): Promise<VoiceSocketSession>;\n markConnected(sessionId: string, now?: Date): Promise<VoiceSocketSession>;\n markReconnecting(sessionId: string, now?: Date, graceMs?: number): Promise<VoiceSocketSession>;\n markEnded(sessionId: string, now?: Date): Promise<VoiceSocketSession>;\n getSession(sessionId: string): Promise<VoiceSocketSession | null>;\n}\n\nexport interface InMemoryVoiceSessionStoreOptions {\n createToken?: () => string;\n}\n\nexport function createInMemoryVoiceSessionStore(\n options: InMemoryVoiceSessionStoreOptions = {},\n): VoiceSessionStore {\n const sessions = new Map<string, VoiceSocketSession>();\n const tokens = new Map<string, VoiceSocketToken>();\n const createToken = options.createToken ?? (() => createId(\"voice_socket_token\"));\n\n return {\n async createSession(input) {\n const now = input.now ?? new Date();\n const result = input.result;\n const session: VoiceSocketSession = {\n id: result.connection.id,\n conversation: result.conversation,\n channelSegment: result.channelSegment,\n connection: result.connection,\n events: result.events,\n createdAt: now.toISOString(),\n updatedAt: now.toISOString(),\n status: \"pending\",\n lastAckSequence: 0,\n };\n sessions.set(session.id, session);\n const token = createTokenRecord({\n createToken,\n connectionId: session.connection.id,\n sessionId: session.id,\n purpose: \"start\",\n ttlMs: input.tokenTtlMs,\n now,\n });\n tokens.set(token.token, token);\n return {\n session,\n socket: {\n url: \"\",\n token: token.token,\n expiresAt: token.expiresAt,\n protocol: COGNIDESK_VOICE_PROTOCOL,\n },\n };\n },\n\n async claimToken(input) {\n const now = input.now ?? new Date();\n const token = tokens.get(input.token);\n if (!token) return null;\n if (token.connectionId !== input.connectionId) return null;\n if (token.consumedAt) return null;\n if (Date.parse(token.expiresAt) <= now.getTime()) return null;\n const session = sessions.get(token.sessionId);\n if (!session || session.status === \"ended\") return null;\n token.consumedAt = now.toISOString();\n tokens.set(token.token, token);\n return {\n session,\n token,\n reconnect: token.purpose === \"reconnect\",\n };\n },\n\n async issueReconnectToken(input) {\n const session = sessions.get(input.sessionId);\n if (!session) throw new Error(`Voice session '${input.sessionId}' was not found.`);\n const token = createTokenRecord({\n createToken,\n connectionId: session.connection.id,\n sessionId: session.id,\n purpose: \"reconnect\",\n ttlMs: input.ttlMs,\n now: input.now ?? new Date(),\n });\n tokens.set(token.token, token);\n return token;\n },\n\n async acknowledgeAudio(input) {\n const session = requireSession(sessions, input.sessionId);\n if (input.sequence > session.lastAckSequence) {\n session.lastAckSequence = input.sequence;\n session.updatedAt = (input.now ?? new Date()).toISOString();\n sessions.set(session.id, session);\n }\n return session;\n },\n\n async markConnected(sessionId, now = new Date()) {\n const session = requireSession(sessions, sessionId);\n session.status = \"connected\";\n session.updatedAt = now.toISOString();\n delete session.reconnectGraceUntil;\n sessions.set(session.id, session);\n return session;\n },\n\n async markReconnecting(sessionId, now = new Date(), graceMs = 30_000) {\n const session = requireSession(sessions, sessionId);\n session.status = \"reconnecting\";\n session.updatedAt = now.toISOString();\n session.reconnectGraceUntil = new Date(now.getTime() + graceMs).toISOString();\n sessions.set(session.id, session);\n return session;\n },\n\n async markEnded(sessionId, now = new Date()) {\n const session = requireSession(sessions, sessionId);\n session.status = \"ended\";\n session.updatedAt = now.toISOString();\n sessions.set(session.id, session);\n return session;\n },\n\n async getSession(sessionId) {\n return sessions.get(sessionId) ?? null;\n },\n };\n}\n\nexport interface VoiceSocketHandshakeOptions {\n store: VoiceSessionStore;\n tokenTtlMs?: number;\n pathPrefix?: string;\n baseUrl?: string;\n}\n\nexport function createVoiceSocketHandshake(options: VoiceSocketHandshakeOptions) {\n const tokenTtlMs = options.tokenTtlMs ?? 60_000;\n const pathPrefix = normalizePathPrefix(options.pathPrefix ?? \"/voice/connections\");\n\n return {\n async createSocket(input: {\n result: StartVoiceResult;\n request: Request;\n basePath: string;\n }): Promise<VoiceSocketMetadata> {\n const created = await options.store.createSession({\n result: input.result,\n tokenTtlMs,\n });\n return {\n ...created.socket,\n url: buildSocketUrl({\n requestUrl: input.request.url,\n basePath: input.basePath,\n pathPrefix,\n connectionId: input.result.connection.id,\n token: created.socket.token,\n ...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),\n }),\n };\n },\n };\n}\n\nexport interface VoiceRuntime {\n handleVoiceUserMessage<TTurn = unknown>(\n input: HandleVoiceUserMessageInput<TTurn>,\n ): Promise<HandleVoiceUserMessageResult>;\n commitVoiceTranscript?(input: {\n conversationId: string;\n channelSegmentId: string;\n speaker: \"user\" | \"assistant\";\n text: string;\n recordingReferenceId?: string;\n startedAtMs?: number;\n endedAtMs?: number;\n transcriptionSource?: string;\n metadata?: Record<string, unknown>;\n }): Promise<{ events: RuntimeEvent[]; event: RuntimeEvent; message: RuntimeEvent }>;\n recordVoiceInterruption(input: {\n conversationId: string;\n channelSegmentId: string;\n connectionId?: string;\n interruptedMessageId?: string;\n source?: \"userSpeech\" | \"adapter\" | \"provider\";\n reason?: string;\n recordingReferenceId?: string;\n offsetMs?: number;\n }): Promise<RuntimeEvent>;\n endVoiceSegment(input: {\n conversationId: string;\n channelSegmentId: string;\n connectionId?: string;\n reason?: string;\n }): Promise<RuntimeEvent>;\n}\n\nexport interface HandleVoiceSocketOptions {\n socket: VoiceSocketLike;\n connectionId: string;\n token: string;\n store: VoiceSessionStore;\n runtime: VoiceRuntime;\n provider: VoiceProvider;\n control?: VoiceControlSurface;\n profile?: VoiceProfile;\n recorder?: VoiceRecorder;\n initialGreeting?: string;\n reconnectTokenTtlMs?: number;\n reconnectGraceMs?: number;\n inputTranscriptDebounceMs?: number;\n turnPreambleMs?: number;\n signal?: AbortSignal;\n}\n\nexport async function handleVoiceSocket(options: HandleVoiceSocketOptions): Promise<void> {\n const claimed = await options.store.claimToken({\n connectionId: options.connectionId,\n token: options.token,\n });\n if (!claimed) {\n send(options.socket, {\n type: \"error\",\n event_id: createId(\"voice_event\"),\n error: {\n code: \"invalid_voice_socket_token\",\n message: \"Voice socket token is invalid, expired, or already used.\",\n },\n });\n options.socket.close(4401, \"Invalid voice socket token\");\n return;\n }\n\n const controller = new AbortController();\n const abort = () => controller.abort();\n options.signal?.addEventListener(\"abort\", abort, { once: true });\n const session = await options.store.markConnected(claimed.session.id);\n let providerSession: VoiceProviderSession | null = null;\n let closed = false;\n const inputTranscriptDebounceMs = Math.max(0, options.inputTranscriptDebounceMs ?? 350);\n const turnPreambleMs = Math.max(0, options.turnPreambleMs ?? 1_200);\n const useRealtimeControl = Boolean(options.control);\n let pendingInputTranscript: VoiceInputTranscriptEvent | null = null;\n let pendingInputTranscriptTimer: ReturnType<typeof setTimeout> | null = null;\n let turnPreambleTimer: ReturnType<typeof setTimeout> | null = null;\n let inputTranscriptQueue = Promise.resolve();\n let speechQueue = Promise.resolve();\n let speechGeneration = 0;\n\n const sendRuntimeEvents = (events: RuntimeEvent[]) => {\n for (const event of events) {\n send(options.socket, {\n type: \"cognidesk.runtime_event\",\n event_id: createId(\"voice_event\"),\n event,\n });\n }\n };\n\n const issueReconnect = async () => {\n const token = await options.store.issueReconnectToken({\n sessionId: session.id,\n ttlMs: options.reconnectTokenTtlMs ?? 30_000,\n });\n send(options.socket, {\n type: \"cognidesk.connection.reconnect_token\",\n event_id: createId(\"voice_event\"),\n token: token.token,\n expiresAt: token.expiresAt,\n });\n };\n\n const clearTurnPreambleTimer = () => {\n if (!turnPreambleTimer) return;\n clearTimeout(turnPreambleTimer);\n turnPreambleTimer = null;\n };\n\n const queueSpeechAction = (\n generation: number,\n action: () => Promise<void> | void,\n ) => {\n const queued = speechQueue.catch(() => undefined).then(async () => {\n if (closed || generation !== speechGeneration) return;\n await action();\n });\n speechQueue = queued.catch((error) => {\n send(options.socket, {\n type: \"error\",\n event_id: createId(\"voice_event\"),\n error: {\n code: \"voice_speech_failed\",\n message: error instanceof Error ? error.message : \"Failed to queue voice speech.\",\n },\n });\n });\n };\n\n const startTurnPreambleTimer = (text: string, generation: number) => {\n clearTurnPreambleTimer();\n if (!providerSession?.preamble) return;\n if (turnPreambleMs === 0) return;\n turnPreambleTimer = setTimeout(() => {\n turnPreambleTimer = null;\n queueSpeechAction(generation, () => providerSession?.preamble?.({ text }));\n }, turnPreambleMs);\n };\n\n const handleProviderEvent = async (event: VoiceProviderEvent) => {\n if (event.kind === \"runtime_events\") {\n sendRuntimeEvents(event.events);\n return;\n }\n if (event.kind === \"server_event\") {\n if (isAgentResponseSignal(event.event)) clearTurnPreambleTimer();\n send(options.socket, event.event);\n if (event.event.type === \"response.output_audio.delta\") {\n await options.recorder?.onAudio?.({\n session,\n speaker: \"assistant\",\n audio: event.event.delta,\n });\n }\n if (useRealtimeControl && event.event.type === \"response.output_audio_transcript.done\") {\n await commitControlAssistantTranscript(event.event.transcript, \"openai-realtime\");\n }\n return;\n }\n if (event.kind === \"error\") {\n send(options.socket, {\n type: \"error\",\n event_id: createId(\"voice_event\"),\n error: {\n code: event.code ?? \"voice_provider_error\",\n message: event.message,\n ...(event.retryable !== undefined ? { retryable: event.retryable } : {}),\n ...(event.details !== undefined ? { details: event.details } : {}),\n },\n });\n return;\n }\n scheduleInputTranscript(event);\n };\n\n const scheduleInputTranscript = (event: VoiceInputTranscriptEvent) => {\n const text = event.text.trim();\n if (!text) return;\n sendInputTranscriptCompleted(event, text);\n pendingInputTranscript = mergeInputTranscript(\n pendingInputTranscript,\n {\n ...event,\n text,\n },\n );\n if (pendingInputTranscriptTimer) clearTimeout(pendingInputTranscriptTimer);\n if (inputTranscriptDebounceMs === 0) {\n const transcript = pendingInputTranscript;\n pendingInputTranscript = null;\n if (transcript) queueInputTranscript(transcript);\n return;\n }\n const waitMs = debounceMsForTranscript(pendingInputTranscript.text, inputTranscriptDebounceMs);\n pendingInputTranscriptTimer = setTimeout(() => {\n const transcript = pendingInputTranscript;\n pendingInputTranscript = null;\n pendingInputTranscriptTimer = null;\n if (transcript) queueInputTranscript(transcript);\n }, waitMs);\n };\n\n const sendInputTranscriptCompleted = (event: VoiceInputTranscriptEvent, text: string) => {\n send(options.socket, {\n type: \"input_audio_transcription.completed\",\n event_id: createId(\"voice_event\"),\n text,\n ...optionalStringField(\"item_id\", event.itemId),\n ...optionalNumberField(\"startedAtMs\", event.startedAtMs),\n ...optionalNumberField(\"endedAtMs\", event.endedAtMs),\n ...optionalStringField(\"transcriptionSource\", event.transcriptionSource),\n ...(event.metadata !== undefined ? { metadata: event.metadata } : {}),\n });\n };\n\n const queueInputTranscript = (event: VoiceInputTranscriptEvent) => {\n inputTranscriptQueue = inputTranscriptQueue\n .then(() => useRealtimeControl ? commitControlInputTranscript(event) : commitInputTranscript(event))\n .catch((error) => {\n send(options.socket, {\n type: \"error\",\n event_id: createId(\"voice_event\"),\n error: {\n code: \"voice_transcript_commit_failed\",\n message: error instanceof Error ? error.message : \"Failed to commit voice transcript.\",\n },\n });\n });\n };\n\n const flushPendingInputTranscript = async () => {\n if (pendingInputTranscriptTimer) {\n clearTimeout(pendingInputTranscriptTimer);\n pendingInputTranscriptTimer = null;\n }\n const transcript = pendingInputTranscript;\n pendingInputTranscript = null;\n if (transcript) queueInputTranscript(transcript);\n await inputTranscriptQueue;\n };\n\n const commitControlInputTranscript = async (event: VoiceInputTranscriptEvent) => {\n if (!options.runtime.commitVoiceTranscript) return;\n const committed = await options.runtime.commitVoiceTranscript({\n conversationId: session.conversation.id,\n channelSegmentId: session.channelSegment.id,\n speaker: \"user\",\n text: event.text,\n transcriptionSource: event.transcriptionSource ?? \"provider\",\n ...optionalNumberField(\"startedAtMs\", event.startedAtMs),\n ...optionalNumberField(\"endedAtMs\", event.endedAtMs),\n ...(event.metadata !== undefined ? { metadata: event.metadata } : {}),\n });\n sendRuntimeEvents(committed.events);\n await options.recorder?.onTranscript?.({\n session,\n speaker: \"user\",\n text: event.text,\n runtimeEvent: committed.event,\n });\n };\n\n const commitControlAssistantTranscript = async (text: string | undefined, transcriptionSource: string) => {\n const normalized = normalizeSpeechText(text ?? \"\");\n if (!normalized || !options.runtime.commitVoiceTranscript) return;\n await flushPendingInputTranscript();\n const committed = await options.runtime.commitVoiceTranscript({\n conversationId: session.conversation.id,\n channelSegmentId: session.channelSegment.id,\n speaker: \"assistant\",\n text: normalized,\n transcriptionSource,\n });\n sendRuntimeEvents(committed.events);\n await options.recorder?.onTranscript?.({\n session,\n speaker: \"assistant\",\n text: normalized,\n runtimeEvent: committed.event,\n });\n send(options.socket, {\n type: \"cognidesk.turn.completed\",\n event_id: createId(\"voice_event\"),\n text: normalized,\n });\n };\n\n const controlSurface = options.control\n ? {\n ...options.control,\n handleToolCall: async (call: VoiceControlToolCall) => {\n await flushPendingInputTranscript();\n return options.control!.handleToolCall(call);\n },\n }\n : undefined;\n\n const commitInputTranscript = async (event: VoiceInputTranscriptEvent) => {\n const generation = ++speechGeneration;\n let assistantSpeechBuffer = \"\";\n let assistantSpeechQueued = false;\n const queueAssistantSpeech = (text: string, result?: HandleVoiceUserMessageResult) => {\n const normalized = normalizeSpeechText(text);\n if (!normalized) return;\n clearTurnPreambleTimer();\n assistantSpeechQueued = true;\n queueSpeechAction(generation, () => providerSession?.speak({ text: normalized, ...(result ? { result } : {}) }));\n };\n const flushAssistantSpeech = (force: boolean) => {\n while (true) {\n const chunk = takeSpeakablePrefix(assistantSpeechBuffer, force);\n if (!chunk) return;\n assistantSpeechBuffer = assistantSpeechBuffer.slice(chunk.consumed).trimStart();\n queueAssistantSpeech(chunk.text);\n if (!force) return;\n }\n };\n startTurnPreambleTimer(event.text, generation);\n const result = await options.runtime.handleVoiceUserMessage({\n conversationId: session.conversation.id,\n channelSegmentId: session.channelSegment.id,\n connectionId: session.connection.id,\n text: event.text,\n transcriptionSource: event.transcriptionSource ?? \"provider\",\n ...optionalNumberField(\"startedAtMs\", event.startedAtMs),\n ...optionalNumberField(\"endedAtMs\", event.endedAtMs),\n ...(event.metadata !== undefined ? { metadata: event.metadata } : {}),\n onAssistantTextDelta: (textDelta) => {\n assistantSpeechBuffer += textDelta;\n flushAssistantSpeech(false);\n },\n });\n clearTurnPreambleTimer();\n flushAssistantSpeech(true);\n if (!assistantSpeechQueued) {\n queueAssistantSpeech(result.text, result);\n }\n sendRuntimeEvents(result.events);\n const userRuntimeEvent = result.voiceEvents.find((candidate) =>\n candidate.type === \"voice.transcript.committed\" && candidate.data.speaker === \"user\"\n );\n await options.recorder?.onTranscript?.({\n session,\n speaker: \"user\",\n text: event.text,\n ...(userRuntimeEvent ? { runtimeEvent: userRuntimeEvent } : {}),\n });\n const assistantRuntimeEvent = result.voiceEvents.find((candidate) =>\n candidate.type === \"voice.transcript.committed\" && candidate.data.speaker === \"assistant\"\n );\n await options.recorder?.onTranscript?.({\n session,\n speaker: \"assistant\",\n text: result.text,\n ...(assistantRuntimeEvent ? { runtimeEvent: assistantRuntimeEvent } : {}),\n });\n send(options.socket, {\n type: \"cognidesk.turn.completed\",\n event_id: createId(\"voice_event\"),\n text: result.text,\n ...(result.activeJourneyId ? { activeJourneyId: result.activeJourneyId } : {}),\n });\n };\n\n try {\n const controlInstructions = await options.control?.createSessionInstructions?.({ session });\n providerSession = await options.provider.connect({\n session,\n ...(options.profile ? { profile: options.profile } : {}),\n ...(controlSurface ? {\n control: {\n ...controlSurface,\n instructions: [\n controlSurface.instructions,\n controlInstructions,\n ].filter(Boolean).join(\"\\n\\n\"),\n },\n } : {}),\n signal: controller.signal,\n onEvent: handleProviderEvent,\n });\n } catch (error) {\n send(options.socket, {\n type: \"error\",\n event_id: createId(\"voice_event\"),\n error: {\n code: \"voice_provider_connect_failed\",\n message: error instanceof Error ? error.message : \"Voice provider connection failed.\",\n },\n });\n options.socket.close(1011, \"Voice provider connection failed\");\n return;\n }\n\n send(options.socket, {\n type: \"cognidesk.connection.ready\",\n event_id: createId(\"voice_event\"),\n protocol: COGNIDESK_VOICE_PROTOCOL,\n conversation: session.conversation,\n channelSegment: session.channelSegment,\n connection: session.connection,\n lastAckSequence: session.lastAckSequence,\n });\n await issueReconnect();\n if (options.initialGreeting?.trim()) {\n queueSpeechAction(speechGeneration, () => providerSession?.speak({ text: options.initialGreeting!.trim() }));\n }\n\n options.socket.on(\"message\", (data) => {\n void handleClientMessage(String(data)).catch((error) => {\n send(options.socket, {\n type: \"error\",\n event_id: createId(\"voice_event\"),\n error: {\n code: \"voice_socket_message_failed\",\n message: error instanceof Error ? error.message : \"Failed to handle voice socket message.\",\n },\n });\n });\n });\n\n options.socket.on(\"error\", (error) => {\n send(options.socket, {\n type: \"error\",\n event_id: createId(\"voice_event\"),\n error: {\n code: \"voice_socket_error\",\n message: error instanceof Error ? error.message : \"Voice socket error.\",\n },\n });\n });\n\n options.socket.on(\"close\", (code) => {\n if (closed) return;\n closed = true;\n speechGeneration++;\n if (pendingInputTranscriptTimer) clearTimeout(pendingInputTranscriptTimer);\n clearTurnPreambleTimer();\n pendingInputTranscript = null;\n controller.abort();\n options.signal?.removeEventListener(\"abort\", abort);\n void providerSession?.close();\n const normalClose = code === undefined || code === 1000 || code === 1001;\n if (normalClose) {\n void options.store.markEnded(session.id).then(() =>\n options.runtime.endVoiceSegment({\n conversationId: session.conversation.id,\n channelSegmentId: session.channelSegment.id,\n connectionId: session.connection.id,\n reason: \"socket_closed\",\n })\n );\n } else {\n void options.store.markReconnecting(\n session.id,\n new Date(),\n options.reconnectGraceMs ?? 30_000,\n );\n }\n });\n\n async function handleClientMessage(raw: string) {\n const event = parseClientEvent(raw);\n if (event.type === \"input_audio_buffer.append\") {\n assertBase64Audio(event.audio);\n const sequence = event.sequence;\n const previousAckSequence = session.lastAckSequence;\n if (sequence !== undefined) {\n await options.store.acknowledgeAudio({ sessionId: session.id, sequence });\n send(options.socket, {\n type: \"cognidesk.audio.ack\",\n event_id: createId(\"voice_event\"),\n sequence,\n });\n }\n await options.recorder?.onAudio?.({\n session,\n speaker: \"user\",\n audio: event.audio,\n ...(sequence !== undefined ? { sequence } : {}),\n });\n if (sequence === undefined || sequence > previousAckSequence) {\n await providerSession?.send(event);\n }\n return;\n }\n if (event.type === \"response.cancel\") {\n speechGeneration++;\n clearTurnPreambleTimer();\n await providerSession?.send(event);\n const interruption = await options.runtime.recordVoiceInterruption({\n conversationId: session.conversation.id,\n channelSegmentId: session.channelSegment.id,\n connectionId: session.connection.id,\n source: \"userSpeech\",\n reason: event.reason ?? \"client_cancelled_response\",\n ...optionalStringField(\"interruptedMessageId\", event.interruptedMessageId),\n ...optionalNumberField(\"offsetMs\", event.playedUntilMs ?? event.audioEndMs),\n });\n send(options.socket, {\n type: \"cognidesk.interruption.recorded\",\n event_id: createId(\"voice_event\"),\n event: interruption,\n });\n sendRuntimeEvents([interruption]);\n return;\n }\n await providerSession?.send(event);\n }\n}\n\nexport interface AttachNodeVoiceWebSocketAdapterOptions {\n server: HttpServer;\n store: VoiceSessionStore;\n runtime: VoiceRuntime;\n provider: VoiceProvider;\n control?: VoiceControlSurface;\n profile?: VoiceProfile;\n recorder?: VoiceRecorder;\n pathPrefix?: string;\n initialGreeting?: string;\n reconnectTokenTtlMs?: number;\n reconnectGraceMs?: number;\n turnPreambleMs?: number;\n}\n\nexport function attachNodeVoiceWebSocketAdapter(options: AttachNodeVoiceWebSocketAdapterOptions) {\n const pathPrefix = normalizePathPrefix(options.pathPrefix ?? \"/voice/connections\");\n const webSocketServer = new WebSocketServer({ noServer: true });\n\n const upgradeListener = (request: IncomingMessage, socket: Duplex, head: Buffer) => {\n const parsed = parseVoiceSocketRequest(request, pathPrefix);\n if (!parsed) return;\n webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {\n webSocketServer.emit(\"connection\", webSocket, request, parsed);\n });\n };\n\n options.server.on(\"upgrade\", upgradeListener);\n webSocketServer.on(\"connection\", (webSocket: WebSocket, _request: IncomingMessage, parsed: VoiceSocketRequest) => {\n void handleVoiceSocket({\n socket: adaptNodeWebSocket(webSocket),\n connectionId: parsed.connectionId,\n token: parsed.token,\n store: options.store,\n runtime: options.runtime,\n provider: options.provider,\n ...(options.control ? { control: options.control } : {}),\n ...(options.profile ? { profile: options.profile } : {}),\n ...(options.recorder ? { recorder: options.recorder } : {}),\n ...(options.initialGreeting !== undefined ? { initialGreeting: options.initialGreeting } : {}),\n ...(options.reconnectTokenTtlMs !== undefined ? { reconnectTokenTtlMs: options.reconnectTokenTtlMs } : {}),\n ...(options.reconnectGraceMs !== undefined ? { reconnectGraceMs: options.reconnectGraceMs } : {}),\n ...(options.turnPreambleMs !== undefined ? { turnPreambleMs: options.turnPreambleMs } : {}),\n });\n });\n\n return {\n close() {\n options.server.off(\"upgrade\", upgradeListener);\n webSocketServer.close();\n },\n webSocketServer,\n };\n}\n\ninterface VoiceSocketRequest {\n connectionId: string;\n token: string;\n}\n\nfunction parseVoiceSocketRequest(request: IncomingMessage, pathPrefix: string): VoiceSocketRequest | null {\n if (!request.url) return null;\n const url = new URL(request.url, \"http://localhost\");\n const expectedPrefix = `${pathPrefix}/`;\n if (!url.pathname.startsWith(expectedPrefix) || !url.pathname.endsWith(\"/socket\")) return null;\n const connectionId = decodeURIComponent(url.pathname.slice(expectedPrefix.length, -\"/socket\".length));\n if (!connectionId) return null;\n const token = url.searchParams.get(\"token\") ?? parseTokenFromProtocol(request.headers[\"sec-websocket-protocol\"]);\n if (!token) return null;\n return { connectionId, token };\n}\n\nfunction adaptNodeWebSocket(socket: WebSocket): VoiceSocketLike {\n return {\n send(data) {\n socket.send(data);\n },\n close(code, reason) {\n socket.close(code, reason);\n },\n on(event, listener) {\n if (event === \"message\") {\n socket.on(\"message\", (data: RawData) => {\n (listener as (data: string) => void)(rawDataToString(data));\n });\n return;\n }\n if (event === \"close\") {\n socket.on(\"close\", (code, reason) => {\n (listener as (code?: number, reason?: string) => void)(code, reason.toString(\"utf8\"));\n });\n return;\n }\n socket.on(\"error\", listener);\n },\n };\n}\n\nfunction parseClientEvent(raw: string): VoiceBrowserClientEvent {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\"Voice socket message must be valid JSON.\");\n }\n if (!isRecord(parsed)) throw new Error(\"Voice socket message must be a JSON object.\");\n const type = parsed.type;\n if (typeof type !== \"string\") throw new Error(\"Voice socket message type is required.\");\n switch (type) {\n case \"session.update\":\n return { type, ...optionalEventId(parsed), ...(isRecord(parsed.session) ? { session: parsed.session } : {}) };\n case \"input_audio_buffer.append\": {\n const audio = requiredString(parsed, \"audio\");\n const sequence = optionalInteger(parsed, \"sequence\");\n return { type, audio, ...optionalEventId(parsed), ...(sequence !== undefined ? { sequence } : {}) };\n }\n case \"input_audio_buffer.commit\":\n case \"input_audio_buffer.clear\":\n return { type, ...optionalEventId(parsed) };\n case \"response.cancel\":\n return {\n type,\n ...optionalEventId(parsed),\n ...optionalStringField(\"response_id\", optionalString(parsed, \"response_id\")),\n ...optionalStringField(\"interruptedMessageId\", optionalString(parsed, \"interruptedMessageId\")),\n ...optionalStringField(\"reason\", optionalString(parsed, \"reason\")),\n ...optionalNumberField(\"playedUntilMs\", optionalInteger(parsed, \"playedUntilMs\")),\n ...optionalNumberField(\"audioEndMs\", optionalInteger(parsed, \"audioEndMs\")),\n };\n case \"conversation.item.truncate\":\n return {\n type,\n ...optionalEventId(parsed),\n ...optionalStringField(\"item_id\", optionalString(parsed, \"item_id\")),\n ...optionalNumberField(\"content_index\", optionalInteger(parsed, \"content_index\")),\n ...optionalNumberField(\"audio_end_ms\", optionalInteger(parsed, \"audio_end_ms\")),\n };\n default:\n throw new Error(`Unsupported voice socket event '${type}'.`);\n }\n}\n\nfunction buildSocketUrl(input: {\n requestUrl: string;\n baseUrl?: string;\n basePath: string;\n pathPrefix: string;\n connectionId: string;\n token: string;\n}) {\n const requestUrl = new URL(input.requestUrl);\n const base = input.baseUrl ? new URL(input.baseUrl) : requestUrl;\n const protocol = base.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const url = new URL(`${input.basePath}${input.pathPrefix}/${encodeURIComponent(input.connectionId)}/socket`, base);\n url.protocol = protocol;\n url.searchParams.set(\"token\", input.token);\n return url.toString();\n}\n\nfunction normalizePathPrefix(path: string) {\n const withSlash = path.startsWith(\"/\") ? path : `/${path}`;\n return withSlash.endsWith(\"/\") ? withSlash.slice(0, -1) : withSlash;\n}\n\nfunction createTokenRecord(input: {\n createToken: () => string;\n connectionId: string;\n sessionId: string;\n purpose: \"start\" | \"reconnect\";\n ttlMs: number;\n now: Date;\n}): VoiceSocketToken {\n return {\n token: input.createToken(),\n connectionId: input.connectionId,\n sessionId: input.sessionId,\n purpose: input.purpose,\n expiresAt: new Date(input.now.getTime() + input.ttlMs).toISOString(),\n };\n}\n\nfunction requireSession(sessions: Map<string, VoiceSocketSession>, sessionId: string) {\n const session = sessions.get(sessionId);\n if (!session) throw new Error(`Voice session '${sessionId}' was not found.`);\n return session;\n}\n\nfunction send(socket: VoiceSocketLike, event: VoiceBrowserServerEvent) {\n socket.send(JSON.stringify(event));\n}\n\nfunction mergeInputTranscript(\n current: VoiceInputTranscriptEvent | null,\n next: VoiceInputTranscriptEvent,\n): VoiceInputTranscriptEvent {\n if (!current) return next;\n const merged: VoiceInputTranscriptEvent = {\n kind: \"input_transcript.completed\",\n text: `${current.text} ${next.text}`.trim(),\n };\n const itemId = next.itemId ?? current.itemId;\n if (itemId) merged.itemId = itemId;\n const startedAtMs = current.startedAtMs ?? next.startedAtMs;\n if (startedAtMs !== undefined) merged.startedAtMs = startedAtMs;\n const endedAtMs = next.endedAtMs ?? current.endedAtMs;\n if (endedAtMs !== undefined) merged.endedAtMs = endedAtMs;\n const transcriptionSource = next.transcriptionSource ?? current.transcriptionSource;\n if (transcriptionSource) merged.transcriptionSource = transcriptionSource;\n const metadata = {\n ...(current.metadata ?? {}),\n ...(next.metadata ?? {}),\n };\n if (Object.keys(metadata).length > 0) merged.metadata = metadata;\n return merged;\n}\n\nfunction isAgentResponseSignal(event: VoiceBrowserServerEvent) {\n return event.type === \"response.output_audio.delta\"\n || event.type === \"response.output_audio_transcript.delta\"\n || event.type === \"response.output_audio_transcript.done\"\n || event.type === \"response.done\";\n}\n\nfunction takeSpeakablePrefix(text: string, force: boolean): { text: string; consumed: number } | null {\n if (!text.trim()) return null;\n if (force) return { text: normalizeSpeechText(text), consumed: text.length };\n const sentenceBoundary = findLastSentenceBoundary(text);\n if (sentenceBoundary > 0) {\n return {\n text: normalizeSpeechText(text.slice(0, sentenceBoundary)),\n consumed: sentenceBoundary,\n };\n }\n if (text.length < 180) return null;\n const softBoundary = findSoftBoundary(text, 140);\n if (softBoundary <= 0) return null;\n return {\n text: normalizeSpeechText(text.slice(0, softBoundary)),\n consumed: softBoundary,\n };\n}\n\nfunction findLastSentenceBoundary(text: string) {\n let boundary = -1;\n const pattern = /[.!?。!?](?:[\"')\\]]+)?\\s+/g;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(text)) !== null) {\n boundary = match.index + match[0].length;\n }\n return boundary;\n}\n\nfunction findSoftBoundary(text: string, minIndex: number) {\n const candidates = [\", \", \"; \", \": \", \"\\n\", \" \"];\n for (const candidate of candidates) {\n const boundary = text.lastIndexOf(candidate);\n if (boundary >= minIndex) return boundary + candidate.length;\n }\n return -1;\n}\n\nfunction normalizeSpeechText(text: string) {\n return text.replace(/\\s+/g, \" \").trim();\n}\n\nfunction debounceMsForTranscript(text: string, baseMs: number) {\n const wordCount = text.trim().split(\" \").filter(Boolean).length;\n return wordCount <= 2 ? Math.max(baseMs, 900) : baseMs;\n}\n\nfunction rawDataToString(data: RawData) {\n if (typeof data === \"string\") return data;\n if (Buffer.isBuffer(data)) return data.toString(\"utf8\");\n if (Array.isArray(data)) return Buffer.concat(data).toString(\"utf8\");\n return Buffer.from(data).toString(\"utf8\");\n}\n\nfunction parseTokenFromProtocol(value: string | string[] | undefined) {\n const raw = Array.isArray(value) ? value.join(\",\") : value;\n if (!raw) return undefined;\n const protocols = raw.split(\",\").map((candidate) => candidate.trim()).filter(Boolean);\n const bearer = protocols.find((candidate) => candidate.startsWith(\"cognidesk.voice.token.\"));\n return bearer?.slice(\"cognidesk.voice.token.\".length);\n}\n\nfunction assertBase64Audio(value: string) {\n if (value.length === 0) throw new Error(\"audio must not be empty.\");\n if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value)) throw new Error(\"audio must be base64 encoded.\");\n}\n\nfunction optionalEventId(value: Record<string, unknown>) {\n return optionalStringField(\"event_id\", optionalString(value, \"event_id\"));\n}\n\nfunction requiredString(value: Record<string, unknown>, key: string) {\n const result = optionalString(value, key);\n if (!result) throw new Error(`${key} must be a non-empty string.`);\n return result;\n}\n\nfunction optionalString(value: Record<string, unknown>, key: string) {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) return undefined;\n if (typeof candidate !== \"string\") throw new Error(`${key} must be a string.`);\n const trimmed = candidate.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction optionalInteger(value: Record<string, unknown>, key: string) {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) return undefined;\n if (typeof candidate !== \"number\" || !Number.isSafeInteger(candidate) || candidate < 0) {\n throw new Error(`${key} must be a non-negative integer.`);\n }\n return candidate;\n}\n\nfunction optionalStringField<TKey extends string>(key: TKey, value: string | undefined) {\n return value ? { [key]: value } as Record<TKey, string> : {};\n}\n\nfunction optionalNumberField<TKey extends string>(key: TKey, value: number | undefined) {\n return value !== undefined ? { [key]: value } as Record<TKey, number> : {};\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value && typeof value === \"object\" && !Array.isArray(value));\n}\n\nfunction createId(prefix: string) {\n const random = globalThis.crypto?.randomUUID?.()\n ?? Math.random().toString(36).slice(2);\n return `${prefix}_${random}`;\n}\n"],"mappings":"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","names":["result"]}
|