@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 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
- import { StartVoiceResult, RuntimeEvent, VoiceSocketMetadata, HandleVoiceUserMessageInput, HandleVoiceUserMessageResult, VoiceProfile } from '@cognidesk/core';
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?: Record<string, unknown>;
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?: Record<string, unknown>;
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?: unknown;
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?: unknown;
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?: Record<string, unknown>;
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?: unknown;
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?: unknown;
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: unknown;
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: unknown;
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> | 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?: Record<string, unknown>;
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/index.ts
2
- import { WebSocketServer } from "ws";
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.get(input.sessionId);
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",
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 --sourcemap",
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.3",
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"]}