@gravity-rail/sdk 0.8.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/{chunk-ML55PVP7.js → chunk-2BJG4GYM.js} +641 -541
- package/dist/chunk-2BJG4GYM.js.map +1 -0
- package/dist/chunk-FSSXKQR5.js +471 -0
- package/dist/chunk-FSSXKQR5.js.map +1 -0
- package/dist/{chunk-QS6ZRQUZ.js → chunk-TJT7A4CO.js} +8 -1
- package/dist/{chunk-QS6ZRQUZ.js.map → chunk-TJT7A4CO.js.map} +1 -1
- package/dist/{client-C2aWAalu.d.cts → client-BffM3KcA.d.cts} +1190 -313
- package/dist/{client-CLGovEox.d.ts → client-DfjAYbt8.d.ts} +1190 -313
- package/dist/helpers.cjs +178 -2
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +82 -3
- package/dist/helpers.d.ts +82 -3
- package/dist/helpers.js +156 -1
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +851 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -8
- package/dist/index.d.ts +14 -8
- package/dist/index.js +144 -20
- package/dist/index.js.map +1 -1
- package/dist/{inquiries-CmECZnkl.d.cts → inquiries-UcWNYo-Q.d.cts} +564 -79
- package/dist/{inquiries-CmECZnkl.d.ts → inquiries-UcWNYo-Q.d.ts} +564 -79
- package/dist/internal.cjs +784 -153
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +241 -5
- package/dist/internal.d.ts +241 -5
- package/dist/internal.js +71 -7
- package/dist/internal.js.map +1 -1
- package/dist/realtime.cjs +1 -0
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.js +1 -0
- package/dist/realtime.js.map +1 -1
- package/dist/schemas.cjs +29 -7
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +106 -51
- package/dist/schemas.d.ts +106 -51
- package/dist/schemas.js +29 -8
- package/dist/schemas.js.map +1 -1
- package/dist/{scopes-D6a_Mvqr.d.ts → scopes-CjSvITpL.d.cts} +53 -8
- package/dist/{scopes-CDaJBQVT.d.cts → scopes-CmNKWrzb.d.ts} +53 -8
- package/package.json +5 -5
- package/dist/chunk-ML55PVP7.js.map +0 -1
package/dist/internal.js
CHANGED
|
@@ -11,14 +11,12 @@ import {
|
|
|
11
11
|
AccessLevel,
|
|
12
12
|
AgentTasksApi,
|
|
13
13
|
AgentsApi,
|
|
14
|
-
ApiError,
|
|
15
14
|
ApiKeysApi,
|
|
16
15
|
AppConnectionsApi,
|
|
17
16
|
AppsApi,
|
|
18
17
|
AssignmentsApi,
|
|
19
18
|
AuthApi,
|
|
20
19
|
BUILTIN_ASSISTANT_CHAT_TYPES,
|
|
21
|
-
BaseApiClient,
|
|
22
20
|
BuildersApi,
|
|
23
21
|
BulkMemberResultStatus,
|
|
24
22
|
BusinessIndustry,
|
|
@@ -67,6 +65,8 @@ import {
|
|
|
67
65
|
McpServerState,
|
|
68
66
|
McpServersApi,
|
|
69
67
|
MemberFeature,
|
|
68
|
+
MemberImportMatchInfoMatchType,
|
|
69
|
+
MemberImportPreviewRowAction,
|
|
70
70
|
MemberType,
|
|
71
71
|
MembersApi,
|
|
72
72
|
MenuItemResponseItemType,
|
|
@@ -93,11 +93,14 @@ import {
|
|
|
93
93
|
PERMISSION_NONE,
|
|
94
94
|
PERMISSION_USE,
|
|
95
95
|
PERMISSION_VERIFY,
|
|
96
|
+
PIDS_DISPOSITIONS,
|
|
97
|
+
PIDS_SOURCES,
|
|
96
98
|
PersonasApi,
|
|
97
99
|
PhoneNumbersApi,
|
|
98
100
|
ProductType,
|
|
99
101
|
PromptsApi,
|
|
100
102
|
PronunciationsApi,
|
|
103
|
+
QUERY_VALUELESS_OPS,
|
|
101
104
|
QualificationsApi,
|
|
102
105
|
REALTIME_MODEL_CUSTOM,
|
|
103
106
|
REALTIME_MODEL_GEMINI,
|
|
@@ -135,7 +138,6 @@ import {
|
|
|
135
138
|
TTS_MODEL_ELEVENLABS_FLASH,
|
|
136
139
|
TTS_MODEL_ELEVENLABS_MULTILINGUAL,
|
|
137
140
|
TTS_MODEL_ELEVENLABS_TURBO,
|
|
138
|
-
TTS_MODEL_POCKET,
|
|
139
141
|
TTS_PREVIEW_PROVIDERS,
|
|
140
142
|
TasksApi,
|
|
141
143
|
TemplatesApi,
|
|
@@ -174,6 +176,7 @@ import {
|
|
|
174
176
|
bindPromptsMethods,
|
|
175
177
|
bindPronunciationsMethods,
|
|
176
178
|
bindQualificationsMethods,
|
|
179
|
+
bindQueriesMethods,
|
|
177
180
|
bindResellersMethods,
|
|
178
181
|
bindSitesMethods,
|
|
179
182
|
bindSupportRequestMethods,
|
|
@@ -194,7 +197,6 @@ import {
|
|
|
194
197
|
getApplicableResourceSlugs,
|
|
195
198
|
getApplicableResources,
|
|
196
199
|
getAvailableFeaturesForUpgrade,
|
|
197
|
-
getCookieValue,
|
|
198
200
|
getFeature,
|
|
199
201
|
getFeaturesByCategory,
|
|
200
202
|
getFeaturesByVisibility,
|
|
@@ -212,7 +214,7 @@ import {
|
|
|
212
214
|
requiresAssistantParticipant,
|
|
213
215
|
usesBuiltinAssistant,
|
|
214
216
|
validateFeatureDependencies
|
|
215
|
-
} from "./chunk-
|
|
217
|
+
} from "./chunk-2BJG4GYM.js";
|
|
216
218
|
import {
|
|
217
219
|
AttendeeResponseStatus,
|
|
218
220
|
AttendeeStatus,
|
|
@@ -223,10 +225,60 @@ import {
|
|
|
223
225
|
CalendarSource,
|
|
224
226
|
DataCategory,
|
|
225
227
|
DataFieldType,
|
|
228
|
+
DataRecordRetirementResponseReason,
|
|
226
229
|
DataSensitivity,
|
|
227
230
|
DataTypeRecordScope,
|
|
228
231
|
IndexStatus
|
|
229
|
-
} from "./chunk-
|
|
232
|
+
} from "./chunk-TJT7A4CO.js";
|
|
233
|
+
import {
|
|
234
|
+
ApiError,
|
|
235
|
+
BaseApiClient,
|
|
236
|
+
getCookieValue
|
|
237
|
+
} from "./chunk-FSSXKQR5.js";
|
|
238
|
+
|
|
239
|
+
// src/types/generated/customer_activity.ts
|
|
240
|
+
var CustomerActivityClassifierDetectorName = {
|
|
241
|
+
assignment_chat_classifier: "assignment_chat_classifier",
|
|
242
|
+
manager_chat_classifier: "manager_chat_classifier",
|
|
243
|
+
concierge_chat_classifier: "concierge_chat_classifier",
|
|
244
|
+
chat_review: "chat_review"
|
|
245
|
+
};
|
|
246
|
+
var CustomerActivityClassifierLabel = {
|
|
247
|
+
user_distress: "user_distress",
|
|
248
|
+
user_confusion: "user_confusion",
|
|
249
|
+
health_topic_mentioned: "health_topic_mentioned",
|
|
250
|
+
health_advice_given: "health_advice_given",
|
|
251
|
+
escalation_failure: "escalation_failure",
|
|
252
|
+
repeated_tool_failure: "repeated_tool_failure",
|
|
253
|
+
transcription_quality: "transcription_quality",
|
|
254
|
+
repetitive_loop: "repetitive_loop"
|
|
255
|
+
};
|
|
256
|
+
var CustomerActivityEventSource = {
|
|
257
|
+
system: "system",
|
|
258
|
+
customer: "customer",
|
|
259
|
+
classifier: "classifier",
|
|
260
|
+
operator: "operator"
|
|
261
|
+
};
|
|
262
|
+
var CustomerActivityEventStatus = {
|
|
263
|
+
new: "new",
|
|
264
|
+
reviewed: "reviewed"
|
|
265
|
+
};
|
|
266
|
+
var CustomerActivityReviewOutcome = {
|
|
267
|
+
no_action_needed: "no_action_needed",
|
|
268
|
+
support_request_created: "support_request_created",
|
|
269
|
+
bug_filed: "bug_filed",
|
|
270
|
+
linked_existing_work: "linked_existing_work",
|
|
271
|
+
classifier_feedback: "classifier_feedback",
|
|
272
|
+
test_activity: "test_activity"
|
|
273
|
+
};
|
|
274
|
+
var CustomerActivitySeverity = {
|
|
275
|
+
none: "none",
|
|
276
|
+
minimal: "minimal",
|
|
277
|
+
low: "low",
|
|
278
|
+
medium: "medium",
|
|
279
|
+
high: "high",
|
|
280
|
+
critical: "critical"
|
|
281
|
+
};
|
|
230
282
|
export {
|
|
231
283
|
AIModelsApi,
|
|
232
284
|
AbilitiesApi,
|
|
@@ -269,6 +321,12 @@ export {
|
|
|
269
321
|
ConsentMethod,
|
|
270
322
|
CriterionType,
|
|
271
323
|
CustomToolkitsApi,
|
|
324
|
+
CustomerActivityClassifierDetectorName,
|
|
325
|
+
CustomerActivityClassifierLabel,
|
|
326
|
+
CustomerActivityEventSource,
|
|
327
|
+
CustomerActivityEventStatus,
|
|
328
|
+
CustomerActivityReviewOutcome,
|
|
329
|
+
CustomerActivitySeverity,
|
|
272
330
|
CustomersApi,
|
|
273
331
|
DEFAULT_CHAT_MODEL,
|
|
274
332
|
DEFAULT_CUSTOM_VOICE_MODEL,
|
|
@@ -276,6 +334,7 @@ export {
|
|
|
276
334
|
DEFAULT_VOICE_INFERENCE_MODEL,
|
|
277
335
|
DataCategory,
|
|
278
336
|
DataFieldType,
|
|
337
|
+
DataRecordRetirementResponseReason,
|
|
279
338
|
DataSensitivity,
|
|
280
339
|
DataTypeRecordScope,
|
|
281
340
|
DataTypesApi,
|
|
@@ -308,6 +367,8 @@ export {
|
|
|
308
367
|
McpServerState,
|
|
309
368
|
McpServersApi,
|
|
310
369
|
MemberFeature,
|
|
370
|
+
MemberImportMatchInfoMatchType,
|
|
371
|
+
MemberImportPreviewRowAction,
|
|
311
372
|
MemberType,
|
|
312
373
|
MembersApi,
|
|
313
374
|
MenuItemResponseItemType,
|
|
@@ -335,11 +396,14 @@ export {
|
|
|
335
396
|
PERMISSION_NONE,
|
|
336
397
|
PERMISSION_USE,
|
|
337
398
|
PERMISSION_VERIFY,
|
|
399
|
+
PIDS_DISPOSITIONS,
|
|
400
|
+
PIDS_SOURCES,
|
|
338
401
|
PersonasApi,
|
|
339
402
|
PhoneNumbersApi,
|
|
340
403
|
ProductType,
|
|
341
404
|
PromptsApi,
|
|
342
405
|
PronunciationsApi,
|
|
406
|
+
QUERY_VALUELESS_OPS,
|
|
343
407
|
QualificationsApi,
|
|
344
408
|
REALTIME_MODEL_CUSTOM,
|
|
345
409
|
REALTIME_MODEL_GEMINI,
|
|
@@ -377,7 +441,6 @@ export {
|
|
|
377
441
|
TTS_MODEL_ELEVENLABS_FLASH,
|
|
378
442
|
TTS_MODEL_ELEVENLABS_MULTILINGUAL,
|
|
379
443
|
TTS_MODEL_ELEVENLABS_TURBO,
|
|
380
|
-
TTS_MODEL_POCKET,
|
|
381
444
|
TTS_PREVIEW_PROVIDERS,
|
|
382
445
|
TasksApi,
|
|
383
446
|
TemplatesApi,
|
|
@@ -417,6 +480,7 @@ export {
|
|
|
417
480
|
bindPromptsMethods,
|
|
418
481
|
bindPronunciationsMethods,
|
|
419
482
|
bindQualificationsMethods,
|
|
483
|
+
bindQueriesMethods,
|
|
420
484
|
bindResellersMethods,
|
|
421
485
|
bindSitesMethods,
|
|
422
486
|
bindSupportRequestMethods,
|
package/dist/internal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/types/generated/customer_activity.ts"],"sourcesContent":["/**\n * Generated by Orval (filtered by scripts/types-codegen/allowlist.txt and\n * grouped by scripts/types-codegen/group_generated.ts). Do not edit manually.\n * Group: customer_activity\n */\n\n\nexport interface CustomerActivityClassifierDefinitionResponse {\n detectorName: CustomerActivityClassifierDetectorName;\n subjectType: string;\n labels: CustomerActivityClassifierLabel[];\n}\n\nexport type CustomerActivityClassifierDetectorName = typeof CustomerActivityClassifierDetectorName[keyof typeof CustomerActivityClassifierDetectorName];\n\n\nexport const CustomerActivityClassifierDetectorName = {\n assignment_chat_classifier: 'assignment_chat_classifier',\n manager_chat_classifier: 'manager_chat_classifier',\n concierge_chat_classifier: 'concierge_chat_classifier',\n chat_review: 'chat_review',\n} as const;\n\nexport type CustomerActivityClassifierLabel = typeof CustomerActivityClassifierLabel[keyof typeof CustomerActivityClassifierLabel];\n\n\nexport const CustomerActivityClassifierLabel = {\n user_distress: 'user_distress',\n user_confusion: 'user_confusion',\n health_topic_mentioned: 'health_topic_mentioned',\n health_advice_given: 'health_advice_given',\n escalation_failure: 'escalation_failure',\n repeated_tool_failure: 'repeated_tool_failure',\n transcription_quality: 'transcription_quality',\n repetitive_loop: 'repetitive_loop',\n} as const;\n\nexport interface CustomerActivityClassifiersResponse {\n assignmentChatClassifier: CustomerActivityClassifierDefinitionResponse;\n managerChatClassifier: CustomerActivityClassifierDefinitionResponse;\n conciergeChatClassifier: CustomerActivityClassifierDefinitionResponse;\n}\n\nexport interface CustomerActivityEventListResponse {\n items: CustomerActivityEventResponse[];\n total: number;\n page: number;\n pageSize: number;\n totalPages: number;\n}\n\n/**\n * Admin/operator view of one customer-activity event.\n *\n * The list response remains free of operator-authored text. The single-event\n * detail response may include ``reviewNote`` and ``classifierFeedback`` for the\n * internal review workflow.\n */\nexport interface CustomerActivityEventResponse {\n id: number;\n uuid: string;\n accountId?: number | null;\n accountName?: string | null;\n organizationId?: number | null;\n organizationName?: string | null;\n workspaceId?: number | null;\n workspaceName?: string | null;\n subjectType: string;\n subjectRef: string;\n title?: string | null;\n labels: string[];\n severity: CustomerActivitySeverity;\n confidence?: number | null;\n score?: number | null;\n source: CustomerActivityEventSource;\n dedupeKey?: string | null;\n detectorName?: CustomerActivityClassifierDetectorName | null;\n detectorVersion?: string | null;\n modelId?: string | null;\n promptVersion?: string | null;\n status: CustomerActivityEventStatus;\n reviewOutcome?: CustomerActivityReviewOutcome | null;\n reviewNote?: string | null;\n classifierFeedback?: CustomerActivityEventResponseClassifierFeedback;\n linkUrl?: string | null;\n reviewedByAccountId?: number | null;\n reviewedByName?: string | null;\n reviewedAt?: string | null;\n supportRequestId?: number | null;\n resourceId?: number | null;\n workspaceUuid?: string | null;\n chatClassifierPrompt?: string | null;\n subjectTitle?: string | null;\n subjectSubtitle?: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport type CustomerActivityEventResponseClassifierFeedback = { [key: string]: unknown } | null;\n\nexport type CustomerActivityEventSource = typeof CustomerActivityEventSource[keyof typeof CustomerActivityEventSource];\n\n\nexport const CustomerActivityEventSource = {\n system: 'system',\n customer: 'customer',\n classifier: 'classifier',\n operator: 'operator',\n} as const;\n\nexport type CustomerActivityEventStatus = typeof CustomerActivityEventStatus[keyof typeof CustomerActivityEventStatus];\n\n\nexport const CustomerActivityEventStatus = {\n new: 'new',\n reviewed: 'reviewed',\n} as const;\n\nexport type CustomerActivityReviewOutcome = typeof CustomerActivityReviewOutcome[keyof typeof CustomerActivityReviewOutcome];\n\n\nexport const CustomerActivityReviewOutcome = {\n no_action_needed: 'no_action_needed',\n support_request_created: 'support_request_created',\n bug_filed: 'bug_filed',\n linked_existing_work: 'linked_existing_work',\n classifier_feedback: 'classifier_feedback',\n test_activity: 'test_activity',\n} as const;\n\nexport type CustomerActivitySeverity = typeof CustomerActivitySeverity[keyof typeof CustomerActivitySeverity];\n\n\nexport const CustomerActivitySeverity = {\n none: 'none',\n minimal: 'minimal',\n low: 'low',\n medium: 'medium',\n high: 'high',\n critical: 'critical',\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBO,IAAM,yCAAyC;AAAA,EACpD,4BAA4B;AAAA,EAC5B,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,aAAa;AACf;AAKO,IAAM,kCAAkC;AAAA,EAC7C,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,wBAAwB;AAAA,EACxB,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,iBAAiB;AACnB;AAoEO,IAAM,8BAA8B;AAAA,EACzC,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,UAAU;AACZ;AAKO,IAAM,8BAA8B;AAAA,EACzC,KAAK;AAAA,EACL,UAAU;AACZ;AAKO,IAAM,gCAAgC;AAAA,EAC3C,kBAAkB;AAAA,EAClB,yBAAyB;AAAA,EACzB,WAAW;AAAA,EACX,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,eAAe;AACjB;AAKO,IAAM,2BAA2B;AAAA,EACtC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AACZ;","names":[]}
|
package/dist/realtime.cjs
CHANGED
package/dist/realtime.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/realtime/index.ts","../src/realtime/types.ts","../src/realtime/workspace-client.ts","../src/audio/pcm.ts","../src/realtime/operator-session-client.ts"],"sourcesContent":["export {\n type ChannelEvent,\n type ChannelListener,\n type RealtimeAuthSession,\n type OperatorSessionState,\n type OperatorCallPhase,\n type Unsubscribe,\n isActiveCallPhase,\n} from './types';\n\nexport {\n WorkspaceRealtimeClient,\n buildWorkspaceWsUrl,\n initWorkspaceSocket,\n subscribeWorkspaceChannel,\n onWorkspaceSocketConnection,\n isWorkspaceSocketConnected,\n} from './workspace-client';\n\nexport { OperatorSessionClient, type OperatorSessionHandlers } from './operator-session-client';\n","/** Event received from a subscribed workspace realtime channel. */\nexport interface ChannelEvent {\n type: string;\n [key: string]: unknown;\n}\n\nexport type ChannelListener = (event: ChannelEvent) => void;\nexport type Unsubscribe = () => void;\n\n/** Auth context for workspace realtime WebSocket connections. */\nexport interface RealtimeAuthSession {\n apiUrl: string;\n workspaceUuid: string;\n accessToken: string;\n}\n\nexport type OperatorSessionState = 'idle' | 'connecting' | 'connected' | 'error';\n\nexport type OperatorCallPhase =\n | 'idle'\n | 'connecting'\n | 'ready'\n | 'dialing'\n | 'live'\n | 'onHold'\n | 'ended'\n | 'failed';\n\n/** Phases where Hold / Forward / Hang up apply to an active phone leg. */\nexport function isActiveCallPhase(phase: OperatorCallPhase): boolean {\n return phase === 'dialing' || phase === 'live' || phase === 'onHold';\n}\n","/**\n * Unified workspace WebSocket client for real-time events.\n *\n * Mirrors `/api/v2/w/{wid}/ws/realtime`: login with OAuth access token,\n * subscribe to channels, receive AGUI events.\n *\n * Protocol:\n * Client → Server: subscribe, unsubscribe, heartbeat, message\n * Server → Client: protocol.init, subscribed, unsubscribed, event, error\n */\n\nimport type { ChannelEvent, ChannelListener, RealtimeAuthSession, Unsubscribe } from './types';\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 3_000;\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst CLOSE_DELAY_MS = 1_000;\n\ninterface SocketState {\n ws: WebSocket | null;\n connected: boolean;\n connecting: boolean;\n listeners: Map<string, Set<ChannelListener>>;\n subscribedChannels: Set<string>;\n reconnectAttempts: number;\n reconnectTimer: ReturnType<typeof setTimeout> | null;\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n closeTimer: ReturnType<typeof setTimeout> | null;\n intentionalClose: boolean;\n}\n\nfunction createSocketState(): SocketState {\n return {\n ws: null,\n connected: false,\n connecting: false,\n listeners: new Map(),\n subscribedChannels: new Set(),\n reconnectAttempts: 0,\n reconnectTimer: null,\n heartbeatTimer: null,\n closeTimer: null,\n intentionalClose: false,\n };\n}\n\n/** Build the workspace realtime WebSocket URL from session config. */\nexport function buildWorkspaceWsUrl(session: RealtimeAuthSession): string {\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n return `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/ws/realtime`;\n}\n\nexport class WorkspaceRealtimeClient {\n private session: RealtimeAuthSession | null = null;\n private readonly getSessionFn: (() => RealtimeAuthSession | null) | null;\n private readonly state: SocketState = createSocketState();\n private readonly connectionListeners = new Set<(connected: boolean) => void>();\n\n constructor(config?: RealtimeAuthSession | (() => RealtimeAuthSession | null)) {\n if (typeof config === 'function') {\n this.getSessionFn = config;\n } else {\n this.getSessionFn = null;\n if (config) {\n this.session = config;\n }\n }\n }\n\n private resolveSession(): RealtimeAuthSession | null {\n return this.session ?? this.getSessionFn?.() ?? null;\n }\n\n private notifyConnectionListeners(): void {\n for (const listener of this.connectionListeners) {\n listener(this.state.connected);\n }\n }\n\n private clearCloseTimer(): void {\n if (this.state.closeTimer) {\n clearTimeout(this.state.closeTimer);\n this.state.closeTimer = null;\n }\n }\n\n setSession(session: RealtimeAuthSession): void {\n const changed =\n this.session?.accessToken !== session.accessToken ||\n this.session?.workspaceUuid !== session.workspaceUuid ||\n this.session?.apiUrl !== session.apiUrl;\n\n this.session = session;\n if (changed && this.state.ws) {\n this.closeConnection();\n }\n if (this.state.listeners.size > 0) {\n this.ensureConnection();\n }\n }\n\n subscribe(channel: string, listener: ChannelListener): Unsubscribe {\n this.clearCloseTimer();\n\n let listeners = this.state.listeners.get(channel);\n if (!listeners) {\n listeners = new Set();\n this.state.listeners.set(channel, listeners);\n }\n listeners.add(listener);\n\n this.ensureConnection();\n\n if (\n this.state.connected &&\n this.state.ws?.readyState === WebSocket.OPEN &&\n !this.state.subscribedChannels.has(channel)\n ) {\n this.state.ws.send(JSON.stringify({ type: 'subscribe', channels: [channel] }));\n }\n\n return () => {\n const channelListeners = this.state.listeners.get(channel);\n channelListeners?.delete(listener);\n if (channelListeners && channelListeners.size === 0) {\n this.state.listeners.delete(channel);\n this.state.subscribedChannels.delete(channel);\n if (this.state.connected && this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'unsubscribe', channels: [channel] }));\n }\n }\n\n if (this.state.listeners.size === 0) {\n this.state.closeTimer = setTimeout(() => {\n if (this.state.listeners.size === 0) {\n this.closeConnection();\n }\n }, CLOSE_DELAY_MS);\n }\n };\n }\n\n send(channel: string, data: unknown): void {\n if (!this.state.connected || !this.state.ws) return;\n this.state.ws.send(JSON.stringify({ type: 'message', channel, data }));\n }\n\n onConnection(listener: (connected: boolean) => void): () => void {\n this.connectionListeners.add(listener);\n listener(this.state.connected);\n return () => {\n this.connectionListeners.delete(listener);\n };\n }\n\n isConnected(): boolean {\n return this.state.connected;\n }\n\n private ensureConnection(): void {\n const session = this.resolveSession();\n if (!session) return;\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') return;\n if (this.state.ws || this.state.connecting) return;\n\n this.clearCloseTimer();\n this.state.connecting = true;\n this.state.intentionalClose = false;\n\n const ws = new WebSocket(buildWorkspaceWsUrl(session));\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event: MessageEvent) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n channels?: string[];\n channel?: string;\n event?: ChannelEvent;\n };\n\n if (msg.type === 'protocol.init' && !this.state.connected) {\n this.state.connected = true;\n this.state.connecting = false;\n this.state.reconnectAttempts = 0;\n\n if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = setInterval(() => {\n if (this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n if (this.state.listeners.size > 0) {\n ws.send(\n JSON.stringify({\n type: 'subscribe',\n channels: Array.from(this.state.listeners.keys()),\n })\n );\n }\n\n this.notifyConnectionListeners();\n return;\n }\n\n if (msg.type === 'subscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.add(ch);\n }\n return;\n }\n\n if (msg.type === 'unsubscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.delete(ch);\n }\n return;\n }\n\n if (msg.type === 'event' && msg.channel && msg.event) {\n const channelListeners = this.state.listeners.get(msg.channel);\n if (channelListeners) {\n for (const channelListener of channelListeners) {\n try {\n channelListener(msg.event);\n } catch (err) {\n console.error('Channel listener error:', err);\n }\n }\n }\n }\n } catch {\n // Ignore malformed frames.\n }\n };\n\n ws.onclose = () => {\n this.state.connected = false;\n this.state.connecting = false;\n this.state.ws = null;\n this.state.subscribedChannels.clear();\n\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n\n this.notifyConnectionListeners();\n\n if (\n !this.state.intentionalClose &&\n this.state.listeners.size > 0 &&\n this.state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS\n ) {\n this.state.reconnectAttempts += 1;\n this.state.reconnectTimer = setTimeout(() => {\n this.ensureConnection();\n }, RECONNECT_DELAY_MS * this.state.reconnectAttempts);\n }\n };\n\n ws.onerror = () => {\n // onclose will fire after this\n };\n\n this.state.ws = ws;\n }\n\n private closeConnection(): void {\n this.state.intentionalClose = true;\n this.clearCloseTimer();\n if (this.state.reconnectTimer) {\n clearTimeout(this.state.reconnectTimer);\n this.state.reconnectTimer = null;\n }\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n this.state.ws?.close();\n this.state.ws = null;\n this.state.connected = false;\n this.state.connecting = false;\n this.state.subscribedChannels.clear();\n this.notifyConnectionListeners();\n }\n}\n\nconst defaultClient = new WorkspaceRealtimeClient();\n\nexport function initWorkspaceSocket(session: RealtimeAuthSession): void {\n defaultClient.setSession(session);\n}\n\nexport function subscribeWorkspaceChannel(channel: string, listener: ChannelListener): Unsubscribe {\n return defaultClient.subscribe(channel, listener);\n}\n\nexport function onWorkspaceSocketConnection(listener: (connected: boolean) => void): () => void {\n return defaultClient.onConnection(listener);\n}\n\nexport function isWorkspaceSocketConnected(): boolean {\n return defaultClient.isConnected();\n}\n","/** PCM16 helpers aligned with main-app web voice (24 kHz). */\n\nexport const SERVER_SAMPLE_RATE = 24000;\nexport const TARGET_SAMPLE_RATE = 24000;\nexport const MONITOR_SAMPLE_RATE = 16000;\n\nexport function float32ArrayToBase64(float32Array: Float32Array): string {\n const int16Array = new Int16Array(float32Array.length);\n for (let i = 0; i < float32Array.length; i += 1) {\n const sample = Math.max(-1, Math.min(1, float32Array[i]));\n int16Array[i] = Math.round(sample * 32767);\n }\n const uint8Array = new Uint8Array(int16Array.buffer);\n let binaryString = '';\n for (let i = 0; i < uint8Array.length; i += 1) {\n binaryString += String.fromCharCode(uint8Array[i]);\n }\n return btoa(binaryString);\n}\n\nexport function base64ToFloat32Array(base64: string): Float32Array {\n const binaryString = atob(base64);\n const len = binaryString.length / 2;\n const float32Array = new Float32Array(len);\n for (let i = 0; i < len; i += 1) {\n const lo = binaryString.charCodeAt(i * 2);\n const hi = binaryString.charCodeAt(i * 2 + 1);\n let int16 = (hi << 8) | lo;\n if (int16 >= 0x8000) int16 -= 0x10000;\n float32Array[i] = int16 / 32768;\n }\n return float32Array;\n}\n\nexport function concatFloat32Arrays(arrays: Float32Array[]): Float32Array {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Float32Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n\nexport function downsampleAudio(\n audioData: Float32Array,\n originalSampleRate: number,\n targetSampleRate: number\n): Float32Array {\n if (originalSampleRate === targetSampleRate) return audioData;\n const ratio = originalSampleRate / targetSampleRate;\n const newLength = Math.round(audioData.length / ratio);\n const result = new Float32Array(newLength);\n for (let i = 0; i < newLength; i += 1) {\n const srcIndex = Math.floor(i * ratio);\n result[i] = audioData[srcIndex];\n }\n return result;\n}\n","/**\n * Human operator session WebSocket client.\n *\n * Connects to `/api/v2/w/{wid}/operator/chats/{chatId}/session-ws` — AGUI\n * events, bidirectional PCM, and call control. No AI inference.\n */\n\nimport {\n base64ToFloat32Array,\n float32ArrayToBase64,\n SERVER_SAMPLE_RATE,\n TARGET_SAMPLE_RATE,\n} from '../audio/pcm';\nimport type {\n ChannelEvent,\n OperatorCallPhase,\n OperatorSessionState,\n RealtimeAuthSession,\n} from './types';\n\nexport interface OperatorSessionHandlers {\n onSessionStateChange?: (state: OperatorSessionState) => void;\n onCallPhaseChange?: (phase: OperatorCallPhase) => void;\n onAguiEvent?: (event: ChannelEvent) => void;\n onError?: (message: string) => void;\n /** Fired when the server closes the session (e.g. callee hung up). */\n onSessionClosed?: (reason?: string) => void;\n}\n\nexport class OperatorSessionClient {\n private ws: WebSocket | null = null;\n private audioContext: AudioContext | null = null;\n private mediaStream: MediaStream | null = null;\n private processor: ScriptProcessorNode | null = null;\n private sessionState: OperatorSessionState = 'idle';\n private callPhase: OperatorCallPhase = 'idle';\n private nextPlaybackTime = 0;\n private playbackSources: AudioBufferSourceNode[] = [];\n private micActive = false;\n private onHold = false;\n private receivedRemoteAudio = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private sessionCloseNotified = false;\n private intentionalDisconnect = false;\n\n constructor(private handlers: OperatorSessionHandlers = {}) {}\n\n getSessionState(): OperatorSessionState {\n return this.sessionState;\n }\n\n getCallPhase(): OperatorCallPhase {\n return this.callPhase;\n }\n\n private setSessionState(next: OperatorSessionState): void {\n this.sessionState = next;\n this.handlers.onSessionStateChange?.(next);\n }\n\n private setCallPhase(next: OperatorCallPhase): void {\n this.callPhase = next;\n this.handlers.onCallPhaseChange?.(next);\n }\n\n private notifySessionClosed(reason?: string): void {\n if (this.sessionCloseNotified) return;\n this.sessionCloseNotified = true;\n this.handlers.onSessionClosed?.(reason);\n }\n\n async connect(session: RealtimeAuthSession, chatId: number): Promise<void> {\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') {\n throw new Error('Operator session WebSocket is not available in this environment');\n }\n\n await this.disconnect();\n this.sessionCloseNotified = false;\n this.setSessionState('connecting');\n this.setCallPhase('connecting');\n\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n const wsUrl = `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/operator/chats/${chatId}/session-ws`;\n\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(wsUrl);\n this.ws = ws;\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n };\n this.handleMessage(msg, resolve, reject);\n } catch {\n /* ignore malformed frames */\n }\n };\n\n ws.onerror = () => {\n const message = 'Operator session WebSocket error';\n const wasConnecting = this.sessionState === 'connecting';\n this.setSessionState('error');\n if (wasConnecting) {\n this.setCallPhase('idle');\n reject(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n };\n\n ws.onclose = () => {\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n const wasConnected = this.sessionState === 'connected';\n this.setSessionState('idle');\n this.setCallPhase('idle');\n if (wasConnected && !this.intentionalDisconnect) {\n this.notifySessionClosed('disconnect');\n }\n this.intentionalDisconnect = false;\n };\n });\n }\n\n private handleMessage(\n msg: {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n },\n resolveLogin: () => void,\n rejectLogin: (err: Error) => void\n ): void {\n if (msg.type === 'RUN_ERROR' || msg.type === 'error') {\n const message = msg.message ?? 'Operator session error';\n if (this.sessionState === 'connecting') {\n this.setSessionState('error');\n rejectLogin(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n return;\n }\n\n if (msg.type === 'protocol.init' && this.sessionState === 'connecting') {\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.session_ready') {\n this.setSessionState('connected');\n this.setCallPhase('ready');\n this.startHeartbeat();\n resolveLogin();\n return;\n }\n\n if (msg.type === 'event' && msg.event) {\n if (this.handleRemoteVoiceAudio(msg.event)) {\n return;\n }\n this.handlers.onAguiEvent?.(msg.event);\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.started') {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.ended') {\n this.onHold = false;\n this.receivedRemoteAudio = false;\n this.stopMicrophone();\n this.stopPlayback();\n const endSession = msg.value?.end_session === true;\n const reason = typeof msg.value?.reason === 'string' ? msg.value.reason : undefined;\n if (endSession) {\n this.setCallPhase('idle');\n this.notifySessionClosed(reason ?? 'peer_hangup');\n } else {\n this.setCallPhase('ready');\n }\n return;\n }\n }\n\n /** Play callee PCM from wrapped chat-stream events; returns true if handled. */\n private handleRemoteVoiceAudio(event: ChannelEvent): boolean {\n if (event.type !== 'CUSTOM' || event.name !== 'voice.audio') {\n return false;\n }\n if (this.onHold) return true;\n\n const value = event.value;\n if (!value || typeof value !== 'object') return true;\n\n const data = (value as Record<string, unknown>).data;\n if (typeof data !== 'string' || !data) return true;\n\n if (\n !this.receivedRemoteAudio &&\n (this.callPhase === 'dialing' || this.callPhase === 'live')\n ) {\n this.receivedRemoteAudio = true;\n this.setCallPhase('live');\n }\n void this.playPcm16(base64ToFloat32Array(data));\n return true;\n }\n\n private startHeartbeat(): void {\n this.clearHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, 30_000);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private sendCustom(name: string, value: Record<string, unknown> = {}): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n this.ws.send(JSON.stringify({ type: 'CUSTOM', name, value }));\n }\n\n async placeCall(memberId: number): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n if (this.sessionState !== 'connected') {\n throw new Error('Operator session is not ready');\n }\n await this.startMicrophone();\n this.sendCustom('operator.call.place', { member_id: memberId });\n }\n\n hangUp(): void {\n try {\n this.sendCustom('operator.call.hangup');\n } catch {\n /* socket already closed */\n }\n this.onHold = false;\n this.receivedRemoteAudio = false;\n }\n\n forwardCall(phoneNumber: string): void {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n this.sendCustom('operator.call.forward', { phone_number: phoneNumber });\n }\n\n setHold(onHold: boolean): void {\n this.onHold = onHold;\n this.sendCustom('operator.call.hold', { on_hold: onHold });\n if (this.callPhase === 'live' || this.callPhase === 'onHold') {\n this.setCallPhase(onHold ? 'onHold' : 'live');\n }\n if (onHold) {\n this.stopPlayback();\n }\n }\n\n async startMicrophone(): Promise<void> {\n if (typeof window === 'undefined') return;\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not available in this browser');\n }\n if (this.micActive) return;\n\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio not supported');\n\n this.audioContext = new Ctx();\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const source = this.audioContext.createMediaStreamSource(this.mediaStream);\n const processor = this.audioContext.createScriptProcessor(4096, 1, 1);\n this.processor = processor;\n\n processor.onaudioprocess = (event) => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n if (this.onHold) return;\n if (\n this.callPhase !== 'dialing' &&\n this.callPhase !== 'live' &&\n this.callPhase !== 'onHold'\n ) {\n return;\n }\n\n const input = event.inputBuffer.getChannelData(0);\n const copy = new Float32Array(input.length);\n copy.set(input);\n\n const sampleRate = this.audioContext?.sampleRate ?? TARGET_SAMPLE_RATE;\n let pcm = copy;\n if (sampleRate !== TARGET_SAMPLE_RATE) {\n const ratio = sampleRate / TARGET_SAMPLE_RATE;\n const newLen = Math.floor(copy.length / ratio);\n const down = new Float32Array(newLen);\n for (let i = 0; i < newLen; i += 1) {\n down[i] = copy[Math.floor(i * ratio)];\n }\n pcm = down;\n }\n\n this.ws.send(\n JSON.stringify({\n type: 'CUSTOM',\n name: 'voice.audio',\n value: {\n data: float32ArrayToBase64(pcm),\n format: 'pcm16',\n sample_rate: TARGET_SAMPLE_RATE,\n },\n })\n );\n };\n\n source.connect(processor);\n processor.connect(this.audioContext.destination);\n this.micActive = true;\n }\n\n stopMicrophone(): void {\n this.processor?.disconnect();\n this.processor = null;\n for (const track of this.mediaStream?.getTracks() ?? []) {\n track.stop();\n }\n this.mediaStream = null;\n this.micActive = false;\n }\n\n private async playPcm16(samples: Float32Array): Promise<void> {\n if (typeof window === 'undefined') return;\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n\n if (!this.audioContext || this.audioContext.state === 'closed') {\n this.audioContext = new Ctx();\n }\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const buffer = this.audioContext.createBuffer(1, samples.length, SERVER_SAMPLE_RATE);\n buffer.copyToChannel(new Float32Array(samples), 0);\n\n const source = this.audioContext.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioContext.destination);\n\n const now = this.audioContext.currentTime;\n const startAt = this.nextPlaybackTime < now + 0.02 ? now + 0.02 : this.nextPlaybackTime;\n source.start(startAt);\n this.nextPlaybackTime = startAt + buffer.duration;\n this.playbackSources.push(source);\n source.onended = () => {\n const idx = this.playbackSources.indexOf(source);\n if (idx >= 0) this.playbackSources.splice(idx, 1);\n };\n }\n\n private stopPlayback(): void {\n for (const src of this.playbackSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n this.playbackSources = [];\n this.nextPlaybackTime = 0;\n }\n\n async disconnect(): Promise<void> {\n this.intentionalDisconnect = true;\n if (\n this.callPhase === 'dialing' ||\n this.callPhase === 'live' ||\n this.callPhase === 'onHold'\n ) {\n this.hangUp();\n }\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n if (this.audioContext && this.audioContext.state !== 'closed') {\n await this.audioContext.close().catch(() => undefined);\n }\n this.audioContext = null;\n this.onHold = false;\n this.receivedRemoteAudio = false;\n this.setSessionState('idle');\n this.setCallPhase('idle');\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/realtime/index.ts","../src/realtime/types.ts","../src/realtime/workspace-client.ts","../src/audio/pcm.ts","../src/realtime/operator-session-client.ts"],"sourcesContent":["export {\n type ChannelEvent,\n type ChannelListener,\n type RealtimeAuthSession,\n type OperatorSessionState,\n type OperatorCallPhase,\n type Unsubscribe,\n isActiveCallPhase,\n} from './types';\n\nexport {\n WorkspaceRealtimeClient,\n buildWorkspaceWsUrl,\n initWorkspaceSocket,\n subscribeWorkspaceChannel,\n onWorkspaceSocketConnection,\n isWorkspaceSocketConnected,\n} from './workspace-client';\n\nexport { OperatorSessionClient, type OperatorSessionHandlers } from './operator-session-client';\n","/** Event received from a subscribed workspace realtime channel. */\nexport interface ChannelEvent {\n type: string;\n [key: string]: unknown;\n}\n\nexport type ChannelListener = (event: ChannelEvent) => void;\nexport type Unsubscribe = () => void;\n\n/** Auth context for workspace realtime WebSocket connections. */\nexport interface RealtimeAuthSession {\n apiUrl: string;\n workspaceUuid: string;\n accessToken: string;\n}\n\nexport type OperatorSessionState = 'idle' | 'connecting' | 'connected' | 'error';\n\nexport type OperatorCallPhase =\n | 'idle'\n | 'connecting'\n | 'ready'\n | 'dialing'\n | 'live'\n | 'onHold'\n | 'ended'\n | 'failed';\n\n/** Phases where Hold / Forward / Hang up apply to an active phone leg. */\nexport function isActiveCallPhase(phase: OperatorCallPhase): boolean {\n return phase === 'dialing' || phase === 'live' || phase === 'onHold';\n}\n","/**\n * Unified workspace WebSocket client for real-time events.\n *\n * Mirrors `/api/v2/w/{wid}/ws/realtime`: login with OAuth access token,\n * subscribe to channels, receive AGUI events.\n *\n * Protocol:\n * Client → Server: subscribe, unsubscribe, heartbeat, message\n * Server → Client: protocol.init, subscribed, unsubscribed, event, error\n */\n\nimport type { ChannelEvent, ChannelListener, RealtimeAuthSession, Unsubscribe } from './types';\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 3_000;\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst CLOSE_DELAY_MS = 1_000;\n\ninterface SocketState {\n ws: WebSocket | null;\n connected: boolean;\n connecting: boolean;\n listeners: Map<string, Set<ChannelListener>>;\n subscribedChannels: Set<string>;\n reconnectAttempts: number;\n reconnectTimer: ReturnType<typeof setTimeout> | null;\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n closeTimer: ReturnType<typeof setTimeout> | null;\n intentionalClose: boolean;\n}\n\nfunction createSocketState(): SocketState {\n return {\n ws: null,\n connected: false,\n connecting: false,\n listeners: new Map(),\n subscribedChannels: new Set(),\n reconnectAttempts: 0,\n reconnectTimer: null,\n heartbeatTimer: null,\n closeTimer: null,\n intentionalClose: false,\n };\n}\n\n/** Build the workspace realtime WebSocket URL from session config. */\nexport function buildWorkspaceWsUrl(session: RealtimeAuthSession): string {\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n return `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/ws/realtime`;\n}\n\nexport class WorkspaceRealtimeClient {\n private session: RealtimeAuthSession | null = null;\n private readonly getSessionFn: (() => RealtimeAuthSession | null) | null;\n private readonly state: SocketState = createSocketState();\n private readonly connectionListeners = new Set<(connected: boolean) => void>();\n\n constructor(config?: RealtimeAuthSession | (() => RealtimeAuthSession | null)) {\n if (typeof config === 'function') {\n this.getSessionFn = config;\n } else {\n this.getSessionFn = null;\n if (config) {\n this.session = config;\n }\n }\n }\n\n private resolveSession(): RealtimeAuthSession | null {\n return this.session ?? this.getSessionFn?.() ?? null;\n }\n\n private notifyConnectionListeners(): void {\n for (const listener of this.connectionListeners) {\n listener(this.state.connected);\n }\n }\n\n private clearCloseTimer(): void {\n if (this.state.closeTimer) {\n clearTimeout(this.state.closeTimer);\n this.state.closeTimer = null;\n }\n }\n\n setSession(session: RealtimeAuthSession): void {\n const changed =\n this.session?.accessToken !== session.accessToken ||\n this.session?.workspaceUuid !== session.workspaceUuid ||\n this.session?.apiUrl !== session.apiUrl;\n\n this.session = session;\n if (changed && this.state.ws) {\n this.closeConnection();\n }\n if (this.state.listeners.size > 0) {\n this.ensureConnection();\n }\n }\n\n subscribe(channel: string, listener: ChannelListener): Unsubscribe {\n this.clearCloseTimer();\n\n let listeners = this.state.listeners.get(channel);\n if (!listeners) {\n listeners = new Set();\n this.state.listeners.set(channel, listeners);\n }\n listeners.add(listener);\n\n this.ensureConnection();\n\n if (\n this.state.connected &&\n this.state.ws?.readyState === WebSocket.OPEN &&\n !this.state.subscribedChannels.has(channel)\n ) {\n this.state.ws.send(JSON.stringify({ type: 'subscribe', channels: [channel] }));\n }\n\n return () => {\n const channelListeners = this.state.listeners.get(channel);\n channelListeners?.delete(listener);\n if (channelListeners && channelListeners.size === 0) {\n this.state.listeners.delete(channel);\n this.state.subscribedChannels.delete(channel);\n if (this.state.connected && this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'unsubscribe', channels: [channel] }));\n }\n }\n\n if (this.state.listeners.size === 0) {\n this.state.closeTimer = setTimeout(() => {\n if (this.state.listeners.size === 0) {\n this.closeConnection();\n }\n }, CLOSE_DELAY_MS);\n }\n };\n }\n\n send(channel: string, data: unknown): void {\n if (!this.state.connected || !this.state.ws) return;\n this.state.ws.send(JSON.stringify({ type: 'message', channel, data }));\n }\n\n onConnection(listener: (connected: boolean) => void): () => void {\n this.connectionListeners.add(listener);\n listener(this.state.connected);\n return () => {\n this.connectionListeners.delete(listener);\n };\n }\n\n isConnected(): boolean {\n return this.state.connected;\n }\n\n private ensureConnection(): void {\n const session = this.resolveSession();\n if (!session) return;\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') return;\n if (this.state.ws || this.state.connecting) return;\n\n this.clearCloseTimer();\n this.state.connecting = true;\n this.state.intentionalClose = false;\n\n const ws = new WebSocket(buildWorkspaceWsUrl(session));\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event: MessageEvent) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n channels?: string[];\n channel?: string;\n event?: ChannelEvent;\n };\n\n if (msg.type === 'protocol.init' && !this.state.connected) {\n this.state.connected = true;\n this.state.connecting = false;\n this.state.reconnectAttempts = 0;\n\n if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = setInterval(() => {\n if (this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n if (this.state.listeners.size > 0) {\n ws.send(\n JSON.stringify({\n type: 'subscribe',\n channels: Array.from(this.state.listeners.keys()),\n })\n );\n }\n\n this.notifyConnectionListeners();\n return;\n }\n\n if (msg.type === 'subscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.add(ch);\n }\n return;\n }\n\n if (msg.type === 'unsubscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.delete(ch);\n }\n return;\n }\n\n if (msg.type === 'event' && msg.channel && msg.event) {\n const channelListeners = this.state.listeners.get(msg.channel);\n if (channelListeners) {\n for (const channelListener of channelListeners) {\n try {\n channelListener(msg.event);\n } catch (err) {\n console.error('Channel listener error:', err);\n }\n }\n }\n }\n } catch {\n // Ignore malformed frames.\n }\n };\n\n ws.onclose = () => {\n this.state.connected = false;\n this.state.connecting = false;\n this.state.ws = null;\n this.state.subscribedChannels.clear();\n\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n\n this.notifyConnectionListeners();\n\n if (\n !this.state.intentionalClose &&\n this.state.listeners.size > 0 &&\n this.state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS\n ) {\n this.state.reconnectAttempts += 1;\n this.state.reconnectTimer = setTimeout(() => {\n this.ensureConnection();\n }, RECONNECT_DELAY_MS * this.state.reconnectAttempts);\n }\n };\n\n ws.onerror = () => {\n // onclose will fire after this\n };\n\n this.state.ws = ws;\n }\n\n private closeConnection(): void {\n this.state.intentionalClose = true;\n this.clearCloseTimer();\n if (this.state.reconnectTimer) {\n clearTimeout(this.state.reconnectTimer);\n this.state.reconnectTimer = null;\n }\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n this.state.ws?.close();\n this.state.ws = null;\n this.state.connected = false;\n this.state.connecting = false;\n this.state.subscribedChannels.clear();\n this.notifyConnectionListeners();\n }\n}\n\nconst defaultClient = new WorkspaceRealtimeClient();\n\nexport function initWorkspaceSocket(session: RealtimeAuthSession): void {\n defaultClient.setSession(session);\n}\n\nexport function subscribeWorkspaceChannel(channel: string, listener: ChannelListener): Unsubscribe {\n return defaultClient.subscribe(channel, listener);\n}\n\nexport function onWorkspaceSocketConnection(listener: (connected: boolean) => void): () => void {\n return defaultClient.onConnection(listener);\n}\n\nexport function isWorkspaceSocketConnected(): boolean {\n return defaultClient.isConnected();\n}\n","/** PCM16 helpers aligned with main-app web voice (24 kHz). */\n\nexport const SERVER_SAMPLE_RATE = 24000;\nexport const TARGET_SAMPLE_RATE = 24000;\nexport const MONITOR_SAMPLE_RATE = 16000;\n\nexport function float32ArrayToBase64(float32Array: Float32Array): string {\n const int16Array = new Int16Array(float32Array.length);\n for (let i = 0; i < float32Array.length; i += 1) {\n const sample = Math.max(-1, Math.min(1, float32Array[i]));\n int16Array[i] = Math.round(sample * 32767);\n }\n const uint8Array = new Uint8Array(int16Array.buffer);\n let binaryString = '';\n for (let i = 0; i < uint8Array.length; i += 1) {\n binaryString += String.fromCharCode(uint8Array[i]);\n }\n return btoa(binaryString);\n}\n\nexport function base64ToFloat32Array(base64: string): Float32Array {\n const binaryString = atob(base64);\n const len = binaryString.length / 2;\n const float32Array = new Float32Array(len);\n for (let i = 0; i < len; i += 1) {\n const lo = binaryString.charCodeAt(i * 2);\n const hi = binaryString.charCodeAt(i * 2 + 1);\n let int16 = (hi << 8) | lo;\n if (int16 >= 0x8000) int16 -= 0x10000;\n float32Array[i] = int16 / 32768;\n }\n return float32Array;\n}\n\nexport function concatFloat32Arrays(arrays: Float32Array[]): Float32Array {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Float32Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n\nexport function downsampleAudio(\n audioData: Float32Array,\n originalSampleRate: number,\n targetSampleRate: number\n): Float32Array {\n if (originalSampleRate === targetSampleRate) return audioData;\n const ratio = originalSampleRate / targetSampleRate;\n const newLength = Math.round(audioData.length / ratio);\n const result = new Float32Array(newLength);\n for (let i = 0; i < newLength; i += 1) {\n const srcIndex = Math.floor(i * ratio);\n result[i] = audioData[srcIndex];\n }\n return result;\n}\n","/**\n * Human operator session WebSocket client.\n *\n * Connects to `/api/v2/w/{wid}/operator/chats/{chatId}/session-ws` — AGUI\n * events, bidirectional PCM, and call control. No AI inference.\n */\n\nimport {\n base64ToFloat32Array,\n float32ArrayToBase64,\n SERVER_SAMPLE_RATE,\n TARGET_SAMPLE_RATE,\n} from '../audio/pcm';\nimport type {\n ChannelEvent,\n OperatorCallPhase,\n OperatorSessionState,\n RealtimeAuthSession,\n} from './types';\n\nexport interface OperatorSessionHandlers {\n onSessionStateChange?: (state: OperatorSessionState) => void;\n onCallPhaseChange?: (phase: OperatorCallPhase) => void;\n onAguiEvent?: (event: ChannelEvent) => void;\n onError?: (message: string) => void;\n /** Fired when the server closes the session (e.g. callee hung up). */\n onSessionClosed?: (reason?: string) => void;\n}\n\nexport class OperatorSessionClient {\n private ws: WebSocket | null = null;\n private audioContext: AudioContext | null = null;\n private mediaStream: MediaStream | null = null;\n private processor: ScriptProcessorNode | null = null;\n private sessionState: OperatorSessionState = 'idle';\n private callPhase: OperatorCallPhase = 'idle';\n private nextPlaybackTime = 0;\n private playbackSources: AudioBufferSourceNode[] = [];\n private micActive = false;\n private onHold = false;\n private receivedRemoteAudio = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private sessionCloseNotified = false;\n private intentionalDisconnect = false;\n\n constructor(private handlers: OperatorSessionHandlers = {}) {}\n\n getSessionState(): OperatorSessionState {\n return this.sessionState;\n }\n\n getCallPhase(): OperatorCallPhase {\n return this.callPhase;\n }\n\n private setSessionState(next: OperatorSessionState): void {\n this.sessionState = next;\n this.handlers.onSessionStateChange?.(next);\n }\n\n private setCallPhase(next: OperatorCallPhase): void {\n this.callPhase = next;\n this.handlers.onCallPhaseChange?.(next);\n }\n\n private notifySessionClosed(reason?: string): void {\n if (this.sessionCloseNotified) return;\n this.sessionCloseNotified = true;\n this.handlers.onSessionClosed?.(reason);\n }\n\n async connect(session: RealtimeAuthSession, chatId: number): Promise<void> {\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') {\n throw new Error('Operator session WebSocket is not available in this environment');\n }\n\n await this.disconnect();\n this.sessionCloseNotified = false;\n this.setSessionState('connecting');\n this.setCallPhase('connecting');\n\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n const wsUrl = `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/operator/chats/${chatId}/session-ws`;\n\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(wsUrl);\n this.ws = ws;\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n };\n this.handleMessage(msg, resolve, reject);\n } catch {\n /* ignore malformed frames */\n }\n };\n\n ws.onerror = () => {\n const message = 'Operator session WebSocket error';\n const wasConnecting = this.sessionState === 'connecting';\n this.setSessionState('error');\n if (wasConnecting) {\n this.setCallPhase('idle');\n reject(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n };\n\n ws.onclose = () => {\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n const wasConnected = this.sessionState === 'connected';\n this.setSessionState('idle');\n this.setCallPhase('idle');\n if (wasConnected && !this.intentionalDisconnect) {\n this.notifySessionClosed('disconnect');\n }\n this.intentionalDisconnect = false;\n };\n });\n }\n\n private handleMessage(\n msg: {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n },\n resolveLogin: () => void,\n rejectLogin: (err: Error) => void\n ): void {\n if (msg.type === 'RUN_ERROR' || msg.type === 'error') {\n const message = msg.message ?? 'Operator session error';\n if (this.sessionState === 'connecting') {\n this.setSessionState('error');\n rejectLogin(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n return;\n }\n\n if (msg.type === 'protocol.init' && this.sessionState === 'connecting') {\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.session_ready') {\n this.setSessionState('connected');\n this.setCallPhase('ready');\n this.startHeartbeat();\n resolveLogin();\n return;\n }\n\n if (msg.type === 'event' && msg.event) {\n if (this.handleRemoteVoiceAudio(msg.event)) {\n return;\n }\n this.handlers.onAguiEvent?.(msg.event);\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.started') {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.ended') {\n this.onHold = false;\n this.receivedRemoteAudio = false;\n this.stopMicrophone();\n this.stopPlayback();\n const endSession = msg.value?.end_session === true;\n const reason = typeof msg.value?.reason === 'string' ? msg.value.reason : undefined;\n if (endSession) {\n this.setCallPhase('idle');\n this.notifySessionClosed(reason ?? 'peer_hangup');\n } else {\n this.setCallPhase('ready');\n }\n return;\n }\n }\n\n /** Play callee PCM from wrapped chat-stream events; returns true if handled. */\n private handleRemoteVoiceAudio(event: ChannelEvent): boolean {\n if (event.type !== 'CUSTOM' || event.name !== 'voice.audio') {\n return false;\n }\n if (this.onHold) return true;\n\n const value = event.value;\n if (!value || typeof value !== 'object') return true;\n\n const data = (value as Record<string, unknown>).data;\n if (typeof data !== 'string' || !data) return true;\n\n if (\n !this.receivedRemoteAudio &&\n (this.callPhase === 'dialing' || this.callPhase === 'live')\n ) {\n this.receivedRemoteAudio = true;\n this.setCallPhase('live');\n }\n void this.playPcm16(base64ToFloat32Array(data));\n return true;\n }\n\n private startHeartbeat(): void {\n this.clearHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, 30_000);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private sendCustom(name: string, value: Record<string, unknown> = {}): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n this.ws.send(JSON.stringify({ type: 'CUSTOM', name, value }));\n }\n\n async placeCall(memberId: number): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n if (this.sessionState !== 'connected') {\n throw new Error('Operator session is not ready');\n }\n await this.startMicrophone();\n this.sendCustom('operator.call.place', { member_id: memberId });\n }\n\n hangUp(): void {\n try {\n this.sendCustom('operator.call.hangup');\n } catch {\n /* socket already closed */\n }\n this.onHold = false;\n this.receivedRemoteAudio = false;\n }\n\n forwardCall(phoneNumber: string): void {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n this.sendCustom('operator.call.forward', { phone_number: phoneNumber });\n }\n\n setHold(onHold: boolean): void {\n this.onHold = onHold;\n this.sendCustom('operator.call.hold', { on_hold: onHold });\n if (this.callPhase === 'live' || this.callPhase === 'onHold') {\n this.setCallPhase(onHold ? 'onHold' : 'live');\n }\n if (onHold) {\n this.stopPlayback();\n }\n }\n\n async startMicrophone(): Promise<void> {\n if (typeof window === 'undefined') return;\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not available in this browser');\n }\n if (this.micActive) return;\n\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio not supported');\n\n this.audioContext = new Ctx();\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const source = this.audioContext.createMediaStreamSource(this.mediaStream);\n const processor = this.audioContext.createScriptProcessor(4096, 1, 1);\n this.processor = processor;\n\n processor.onaudioprocess = (event) => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n if (this.onHold) return;\n if (\n this.callPhase !== 'dialing' &&\n this.callPhase !== 'live' &&\n this.callPhase !== 'onHold'\n ) {\n return;\n }\n\n const input = event.inputBuffer.getChannelData(0);\n const copy = new Float32Array(input.length);\n copy.set(input);\n\n const sampleRate = this.audioContext?.sampleRate ?? TARGET_SAMPLE_RATE;\n let pcm = copy;\n if (sampleRate !== TARGET_SAMPLE_RATE) {\n const ratio = sampleRate / TARGET_SAMPLE_RATE;\n const newLen = Math.floor(copy.length / ratio);\n const down = new Float32Array(newLen);\n for (let i = 0; i < newLen; i += 1) {\n down[i] = copy[Math.floor(i * ratio)];\n }\n pcm = down;\n }\n\n this.ws.send(\n JSON.stringify({\n type: 'CUSTOM',\n name: 'voice.audio',\n value: {\n data: float32ArrayToBase64(pcm),\n format: 'pcm16',\n sample_rate: TARGET_SAMPLE_RATE,\n },\n })\n );\n };\n\n source.connect(processor);\n processor.connect(this.audioContext.destination);\n this.micActive = true;\n }\n\n stopMicrophone(): void {\n this.processor?.disconnect();\n this.processor = null;\n for (const track of this.mediaStream?.getTracks() ?? []) {\n track.stop();\n }\n this.mediaStream = null;\n this.micActive = false;\n }\n\n private async playPcm16(samples: Float32Array): Promise<void> {\n if (typeof window === 'undefined') return;\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n\n if (!this.audioContext || this.audioContext.state === 'closed') {\n this.audioContext = new Ctx();\n }\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const buffer = this.audioContext.createBuffer(1, samples.length, SERVER_SAMPLE_RATE);\n buffer.copyToChannel(new Float32Array(samples), 0);\n\n const source = this.audioContext.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioContext.destination);\n\n const now = this.audioContext.currentTime;\n const startAt = this.nextPlaybackTime < now + 0.02 ? now + 0.02 : this.nextPlaybackTime;\n source.start(startAt);\n this.nextPlaybackTime = startAt + buffer.duration;\n this.playbackSources.push(source);\n source.onended = () => {\n const idx = this.playbackSources.indexOf(source);\n if (idx >= 0) this.playbackSources.splice(idx, 1);\n };\n }\n\n private stopPlayback(): void {\n for (const src of this.playbackSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n this.playbackSources = [];\n this.nextPlaybackTime = 0;\n }\n\n async disconnect(): Promise<void> {\n this.intentionalDisconnect = true;\n if (\n this.callPhase === 'dialing' ||\n this.callPhase === 'live' ||\n this.callPhase === 'onHold'\n ) {\n this.hangUp();\n }\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n if (this.audioContext && this.audioContext.state !== 'closed') {\n await this.audioContext.close().catch(() => undefined);\n }\n this.audioContext = null;\n this.onHold = false;\n this.receivedRemoteAudio = false;\n this.setSessionState('idle');\n this.setCallPhase('idle');\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC6BO,SAAS,kBAAkB,OAAmC;AAClE,SAAO,UAAU,aAAa,UAAU,UAAU,UAAU;AAC/D;;;AClBA,IAAM,wBAAwB;AAC9B,IAAM,qBAAqB;AAC3B,IAAM,yBAAyB;AAC/B,IAAM,iBAAiB;AAevB,SAAS,oBAAiC;AACvC,SAAO;AAAA,IACJ,IAAI;AAAA,IACJ,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,WAAW,oBAAI,IAAI;AAAA,IACnB,oBAAoB,oBAAI,IAAI;AAAA,IAC5B,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,kBAAkB;AAAA,EACrB;AACH;AAGO,SAAS,oBAAoB,SAAsC;AACvE,QAAM,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAC/C,QAAM,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,EAAE;AACtD,SAAO,GAAG,QAAQ,QAAQ,IAAI,MAAM,IAAI,aAAa,QAAQ,aAAa;AAC7E;AAEO,IAAM,0BAAN,MAA8B;AAAA,EAC1B,UAAsC;AAAA,EAC7B;AAAA,EACA,QAAqB,kBAAkB;AAAA,EACvC,sBAAsB,oBAAI,IAAkC;AAAA,EAE7E,YAAY,QAAmE;AAC5E,QAAI,OAAO,WAAW,YAAY;AAC/B,WAAK,eAAe;AAAA,IACvB,OAAO;AACJ,WAAK,eAAe;AACpB,UAAI,QAAQ;AACT,aAAK,UAAU;AAAA,MAClB;AAAA,IACH;AAAA,EACH;AAAA,EAEQ,iBAA6C;AAClD,WAAO,KAAK,WAAW,KAAK,eAAe,KAAK;AAAA,EACnD;AAAA,EAEQ,4BAAkC;AACvC,eAAW,YAAY,KAAK,qBAAqB;AAC9C,eAAS,KAAK,MAAM,SAAS;AAAA,IAChC;AAAA,EACH;AAAA,EAEQ,kBAAwB;AAC7B,QAAI,KAAK,MAAM,YAAY;AACxB,mBAAa,KAAK,MAAM,UAAU;AAClC,WAAK,MAAM,aAAa;AAAA,IAC3B;AAAA,EACH;AAAA,EAEA,WAAW,SAAoC;AAC5C,UAAM,UACH,KAAK,SAAS,gBAAgB,QAAQ,eACtC,KAAK,SAAS,kBAAkB,QAAQ,iBACxC,KAAK,SAAS,WAAW,QAAQ;AAEpC,SAAK,UAAU;AACf,QAAI,WAAW,KAAK,MAAM,IAAI;AAC3B,WAAK,gBAAgB;AAAA,IACxB;AACA,QAAI,KAAK,MAAM,UAAU,OAAO,GAAG;AAChC,WAAK,iBAAiB;AAAA,IACzB;AAAA,EACH;AAAA,EAEA,UAAU,SAAiB,UAAwC;AAChE,SAAK,gBAAgB;AAErB,QAAI,YAAY,KAAK,MAAM,UAAU,IAAI,OAAO;AAChD,QAAI,CAAC,WAAW;AACb,kBAAY,oBAAI,IAAI;AACpB,WAAK,MAAM,UAAU,IAAI,SAAS,SAAS;AAAA,IAC9C;AACA,cAAU,IAAI,QAAQ;AAEtB,SAAK,iBAAiB;AAEtB,QACG,KAAK,MAAM,aACX,KAAK,MAAM,IAAI,eAAe,UAAU,QACxC,CAAC,KAAK,MAAM,mBAAmB,IAAI,OAAO,GAC3C;AACC,WAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,aAAa,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;AAAA,IAChF;AAEA,WAAO,MAAM;AACV,YAAM,mBAAmB,KAAK,MAAM,UAAU,IAAI,OAAO;AACzD,wBAAkB,OAAO,QAAQ;AACjC,UAAI,oBAAoB,iBAAiB,SAAS,GAAG;AAClD,aAAK,MAAM,UAAU,OAAO,OAAO;AACnC,aAAK,MAAM,mBAAmB,OAAO,OAAO;AAC5C,YAAI,KAAK,MAAM,aAAa,KAAK,MAAM,IAAI,eAAe,UAAU,MAAM;AACvE,eAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,eAAe,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;AAAA,QAClF;AAAA,MACH;AAEA,UAAI,KAAK,MAAM,UAAU,SAAS,GAAG;AAClC,aAAK,MAAM,aAAa,WAAW,MAAM;AACtC,cAAI,KAAK,MAAM,UAAU,SAAS,GAAG;AAClC,iBAAK,gBAAgB;AAAA,UACxB;AAAA,QACH,GAAG,cAAc;AAAA,MACpB;AAAA,IACH;AAAA,EACH;AAAA,EAEA,KAAK,SAAiB,MAAqB;AACxC,QAAI,CAAC,KAAK,MAAM,aAAa,CAAC,KAAK,MAAM,GAAI;AAC7C,SAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,WAAW,SAAS,KAAK,CAAC,CAAC;AAAA,EACxE;AAAA,EAEA,aAAa,UAAoD;AAC9D,SAAK,oBAAoB,IAAI,QAAQ;AACrC,aAAS,KAAK,MAAM,SAAS;AAC7B,WAAO,MAAM;AACV,WAAK,oBAAoB,OAAO,QAAQ;AAAA,IAC3C;AAAA,EACH;AAAA,EAEA,cAAuB;AACpB,WAAO,KAAK,MAAM;AAAA,EACrB;AAAA,EAEQ,mBAAyB;AAC9B,UAAM,UAAU,KAAK,eAAe;AACpC,QAAI,CAAC,QAAS;AACd,QAAI,OAAO,WAAW,eAAe,OAAO,cAAc,YAAa;AACvE,QAAI,KAAK,MAAM,MAAM,KAAK,MAAM,WAAY;AAE5C,SAAK,gBAAgB;AACrB,SAAK,MAAM,aAAa;AACxB,SAAK,MAAM,mBAAmB;AAE9B,UAAM,KAAK,IAAI,UAAU,oBAAoB,OAAO,CAAC;AAErD,OAAG,SAAS,MAAM;AACf,SAAG,KAAK,KAAK,UAAU,EAAE,MAAM,SAAS,OAAO,QAAQ,YAAY,CAAC,CAAC;AAAA,IACxE;AAEA,OAAG,YAAY,CAAC,UAAwB;AACrC,UAAI;AACD,cAAM,MAAM,KAAK,MAAM,OAAO,MAAM,IAAI,CAAC;AAOzC,YAAI,IAAI,SAAS,mBAAmB,CAAC,KAAK,MAAM,WAAW;AACxD,eAAK,MAAM,YAAY;AACvB,eAAK,MAAM,aAAa;AACxB,eAAK,MAAM,oBAAoB;AAE/B,cAAI,KAAK,MAAM,eAAgB,eAAc,KAAK,MAAM,cAAc;AACtE,eAAK,MAAM,iBAAiB,YAAY,MAAM;AAC3C,gBAAI,KAAK,MAAM,IAAI,eAAe,UAAU,MAAM;AAC/C,mBAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC;AAAA,YAC3D;AAAA,UACH,GAAG,qBAAqB;AAExB,cAAI,KAAK,MAAM,UAAU,OAAO,GAAG;AAChC,eAAG;AAAA,cACA,KAAK,UAAU;AAAA,gBACZ,MAAM;AAAA,gBACN,UAAU,MAAM,KAAK,KAAK,MAAM,UAAU,KAAK,CAAC;AAAA,cACnD,CAAC;AAAA,YACJ;AAAA,UACH;AAEA,eAAK,0BAA0B;AAC/B;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,gBAAgB,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAC3D,qBAAW,MAAM,IAAI,UAAU;AAC5B,iBAAK,MAAM,mBAAmB,IAAI,EAAE;AAAA,UACvC;AACA;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,kBAAkB,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7D,qBAAW,MAAM,IAAI,UAAU;AAC5B,iBAAK,MAAM,mBAAmB,OAAO,EAAE;AAAA,UAC1C;AACA;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,WAAW,IAAI,WAAW,IAAI,OAAO;AACnD,gBAAM,mBAAmB,KAAK,MAAM,UAAU,IAAI,IAAI,OAAO;AAC7D,cAAI,kBAAkB;AACnB,uBAAW,mBAAmB,kBAAkB;AAC7C,kBAAI;AACD,gCAAgB,IAAI,KAAK;AAAA,cAC5B,SAAS,KAAK;AACX,wBAAQ,MAAM,2BAA2B,GAAG;AAAA,cAC/C;AAAA,YACH;AAAA,UACH;AAAA,QACH;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACH;AAEA,OAAG,UAAU,MAAM;AAChB,WAAK,MAAM,YAAY;AACvB,WAAK,MAAM,aAAa;AACxB,WAAK,MAAM,KAAK;AAChB,WAAK,MAAM,mBAAmB,MAAM;AAEpC,UAAI,KAAK,MAAM,gBAAgB;AAC5B,sBAAc,KAAK,MAAM,cAAc;AACvC,aAAK,MAAM,iBAAiB;AAAA,MAC/B;AAEA,WAAK,0BAA0B;AAE/B,UACG,CAAC,KAAK,MAAM,oBACZ,KAAK,MAAM,UAAU,OAAO,KAC5B,KAAK,MAAM,oBAAoB,wBAChC;AACC,aAAK,MAAM,qBAAqB;AAChC,aAAK,MAAM,iBAAiB,WAAW,MAAM;AAC1C,eAAK,iBAAiB;AAAA,QACzB,GAAG,qBAAqB,KAAK,MAAM,iBAAiB;AAAA,MACvD;AAAA,IACH;AAEA,OAAG,UAAU,MAAM;AAAA,IAEnB;AAEA,SAAK,MAAM,KAAK;AAAA,EACnB;AAAA,EAEQ,kBAAwB;AAC7B,SAAK,MAAM,mBAAmB;AAC9B,SAAK,gBAAgB;AACrB,QAAI,KAAK,MAAM,gBAAgB;AAC5B,mBAAa,KAAK,MAAM,cAAc;AACtC,WAAK,MAAM,iBAAiB;AAAA,IAC/B;AACA,QAAI,KAAK,MAAM,gBAAgB;AAC5B,oBAAc,KAAK,MAAM,cAAc;AACvC,WAAK,MAAM,iBAAiB;AAAA,IAC/B;AACA,SAAK,MAAM,IAAI,MAAM;AACrB,SAAK,MAAM,KAAK;AAChB,SAAK,MAAM,YAAY;AACvB,SAAK,MAAM,aAAa;AACxB,SAAK,MAAM,mBAAmB,MAAM;AACpC,SAAK,0BAA0B;AAAA,EAClC;AACH;AAEA,IAAM,gBAAgB,IAAI,wBAAwB;AAE3C,SAAS,oBAAoB,SAAoC;AACrE,gBAAc,WAAW,OAAO;AACnC;AAEO,SAAS,0BAA0B,SAAiB,UAAwC;AAChG,SAAO,cAAc,UAAU,SAAS,QAAQ;AACnD;AAEO,SAAS,4BAA4B,UAAoD;AAC7F,SAAO,cAAc,aAAa,QAAQ;AAC7C;AAEO,SAAS,6BAAsC;AACnD,SAAO,cAAc,YAAY;AACpC;;;ACnTO,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAG3B,SAAS,qBAAqB,cAAoC;AACtE,QAAM,aAAa,IAAI,WAAW,aAAa,MAAM;AACrD,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,GAAG;AAC9C,UAAM,SAAS,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC;AACxD,eAAW,CAAC,IAAI,KAAK,MAAM,SAAS,KAAK;AAAA,EAC5C;AACA,QAAM,aAAa,IAAI,WAAW,WAAW,MAAM;AACnD,MAAI,eAAe;AACnB,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK,GAAG;AAC5C,oBAAgB,OAAO,aAAa,WAAW,CAAC,CAAC;AAAA,EACpD;AACA,SAAO,KAAK,YAAY;AAC3B;AAEO,SAAS,qBAAqB,QAA8B;AAChE,QAAM,eAAe,KAAK,MAAM;AAChC,QAAM,MAAM,aAAa,SAAS;AAClC,QAAM,eAAe,IAAI,aAAa,GAAG;AACzC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK,GAAG;AAC9B,UAAM,KAAK,aAAa,WAAW,IAAI,CAAC;AACxC,UAAM,KAAK,aAAa,WAAW,IAAI,IAAI,CAAC;AAC5C,QAAI,QAAS,MAAM,IAAK;AACxB,QAAI,SAAS,MAAQ,UAAS;AAC9B,iBAAa,CAAC,IAAI,QAAQ;AAAA,EAC7B;AACA,SAAO;AACV;;;ACHO,IAAM,wBAAN,MAA4B;AAAA,EAgBhC,YAAoB,WAAoC,CAAC,GAAG;AAAxC;AAAA,EAAyC;AAAA,EAAzC;AAAA,EAfZ,KAAuB;AAAA,EACvB,eAAoC;AAAA,EACpC,cAAkC;AAAA,EAClC,YAAwC;AAAA,EACxC,eAAqC;AAAA,EACrC,YAA+B;AAAA,EAC/B,mBAAmB;AAAA,EACnB,kBAA2C,CAAC;AAAA,EAC5C,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,iBAAwD;AAAA,EACxD,uBAAuB;AAAA,EACvB,wBAAwB;AAAA,EAIhC,kBAAwC;AACrC,WAAO,KAAK;AAAA,EACf;AAAA,EAEA,eAAkC;AAC/B,WAAO,KAAK;AAAA,EACf;AAAA,EAEQ,gBAAgB,MAAkC;AACvD,SAAK,eAAe;AACpB,SAAK,SAAS,uBAAuB,IAAI;AAAA,EAC5C;AAAA,EAEQ,aAAa,MAA+B;AACjD,SAAK,YAAY;AACjB,SAAK,SAAS,oBAAoB,IAAI;AAAA,EACzC;AAAA,EAEQ,oBAAoB,QAAuB;AAChD,QAAI,KAAK,qBAAsB;AAC/B,SAAK,uBAAuB;AAC5B,SAAK,SAAS,kBAAkB,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,QAAQ,SAA8B,QAA+B;AACxE,QAAI,OAAO,WAAW,eAAe,OAAO,cAAc,aAAa;AACpE,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACpF;AAEA,UAAM,KAAK,WAAW;AACtB,SAAK,uBAAuB;AAC5B,SAAK,gBAAgB,YAAY;AACjC,SAAK,aAAa,YAAY;AAE9B,UAAM,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAC/C,UAAM,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,EAAE;AACtD,UAAM,QAAQ,GAAG,QAAQ,QAAQ,IAAI,MAAM,IAAI,aAAa,QAAQ,aAAa,mBAAmB,MAAM;AAE1G,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC1C,YAAM,KAAK,IAAI,UAAU,KAAK;AAC9B,WAAK,KAAK;AAEV,SAAG,SAAS,MAAM;AACf,WAAG,KAAK,KAAK,UAAU,EAAE,MAAM,SAAS,OAAO,QAAQ,YAAY,CAAC,CAAC;AAAA,MACxE;AAEA,SAAG,YAAY,CAAC,UAAU;AACvB,YAAI;AACD,gBAAM,MAAM,KAAK,MAAM,OAAO,MAAM,IAAI,CAAC;AAQzC,eAAK,cAAc,KAAK,SAAS,MAAM;AAAA,QAC1C,QAAQ;AAAA,QAER;AAAA,MACH;AAEA,SAAG,UAAU,MAAM;AAChB,cAAM,UAAU;AAChB,cAAM,gBAAgB,KAAK,iBAAiB;AAC5C,aAAK,gBAAgB,OAAO;AAC5B,YAAI,eAAe;AAChB,eAAK,aAAa,MAAM;AACxB,iBAAO,IAAI,MAAM,OAAO,CAAC;AAAA,QAC5B,OAAO;AACJ,eAAK,aAAa,QAAQ;AAC1B,eAAK,SAAS,UAAU,OAAO;AAAA,QAClC;AAAA,MACH;AAEA,SAAG,UAAU,MAAM;AAChB,aAAK,eAAe;AACpB,aAAK,eAAe;AACpB,aAAK,aAAa;AAClB,cAAM,eAAe,KAAK,iBAAiB;AAC3C,aAAK,gBAAgB,MAAM;AAC3B,aAAK,aAAa,MAAM;AACxB,YAAI,gBAAgB,CAAC,KAAK,uBAAuB;AAC9C,eAAK,oBAAoB,YAAY;AAAA,QACxC;AACA,aAAK,wBAAwB;AAAA,MAChC;AAAA,IACH,CAAC;AAAA,EACJ;AAAA,EAEQ,cACL,KAQA,cACA,aACK;AACL,QAAI,IAAI,SAAS,eAAe,IAAI,SAAS,SAAS;AACnD,YAAM,UAAU,IAAI,WAAW;AAC/B,UAAI,KAAK,iBAAiB,cAAc;AACrC,aAAK,gBAAgB,OAAO;AAC5B,oBAAY,IAAI,MAAM,OAAO,CAAC;AAAA,MACjC,OAAO;AACJ,aAAK,aAAa,QAAQ;AAC1B,aAAK,SAAS,UAAU,OAAO;AAAA,MAClC;AACA;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,mBAAmB,KAAK,iBAAiB,cAAc;AACrE;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,0BAA0B;AACjE,WAAK,gBAAgB,WAAW;AAChC,WAAK,aAAa,OAAO;AACzB,WAAK,eAAe;AACpB,mBAAa;AACb;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,WAAW,IAAI,OAAO;AACpC,UAAI,KAAK,uBAAuB,IAAI,KAAK,GAAG;AACzC;AAAA,MACH;AACA,WAAK,SAAS,cAAc,IAAI,KAAK;AACrC;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,yBAAyB;AAChE,WAAK,sBAAsB;AAC3B,WAAK,aAAa,SAAS;AAC3B;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,uBAAuB;AAC9D,WAAK,SAAS;AACd,WAAK,sBAAsB;AAC3B,WAAK,eAAe;AACpB,WAAK,aAAa;AAClB,YAAM,aAAa,IAAI,OAAO,gBAAgB;AAC9C,YAAM,SAAS,OAAO,IAAI,OAAO,WAAW,WAAW,IAAI,MAAM,SAAS;AAC1E,UAAI,YAAY;AACb,aAAK,aAAa,MAAM;AACxB,aAAK,oBAAoB,UAAU,aAAa;AAAA,MACnD,OAAO;AACJ,aAAK,aAAa,OAAO;AAAA,MAC5B;AACA;AAAA,IACH;AAAA,EACH;AAAA;AAAA,EAGQ,uBAAuB,OAA8B;AAC1D,QAAI,MAAM,SAAS,YAAY,MAAM,SAAS,eAAe;AAC1D,aAAO;AAAA,IACV;AACA,QAAI,KAAK,OAAQ,QAAO;AAExB,UAAM,QAAQ,MAAM;AACpB,QAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,UAAM,OAAQ,MAAkC;AAChD,QAAI,OAAO,SAAS,YAAY,CAAC,KAAM,QAAO;AAE9C,QACG,CAAC,KAAK,wBACL,KAAK,cAAc,aAAa,KAAK,cAAc,SACrD;AACC,WAAK,sBAAsB;AAC3B,WAAK,aAAa,MAAM;AAAA,IAC3B;AACA,SAAK,KAAK,UAAU,qBAAqB,IAAI,CAAC;AAC9C,WAAO;AAAA,EACV;AAAA,EAEQ,iBAAuB;AAC5B,SAAK,eAAe;AACpB,SAAK,iBAAiB,YAAY,MAAM;AACrC,UAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AACzC,aAAK,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC;AAAA,MACrD;AAAA,IACH,GAAG,GAAM;AAAA,EACZ;AAAA,EAEQ,iBAAuB;AAC5B,QAAI,KAAK,gBAAgB;AACtB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACzB;AAAA,EACH;AAAA,EAEQ,WAAW,MAAc,QAAiC,CAAC,GAAS;AACzE,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAChE;AACA,SAAK,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EAC/D;AAAA,EAEA,MAAM,UAAU,UAAiC;AAC9C,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAChE;AACA,QAAI,KAAK,iBAAiB,aAAa;AACpC,YAAM,IAAI,MAAM,+BAA+B;AAAA,IAClD;AACA,UAAM,KAAK,gBAAgB;AAC3B,SAAK,WAAW,uBAAuB,EAAE,WAAW,SAAS,CAAC;AAAA,EACjE;AAAA,EAEA,SAAe;AACZ,QAAI;AACD,WAAK,WAAW,sBAAsB;AAAA,IACzC,QAAQ;AAAA,IAER;AACA,SAAK,SAAS;AACd,SAAK,sBAAsB;AAAA,EAC9B;AAAA,EAEA,YAAY,aAA2B;AACpC,SAAK,sBAAsB;AAC3B,SAAK,aAAa,SAAS;AAC3B,SAAK,WAAW,yBAAyB,EAAE,cAAc,YAAY,CAAC;AAAA,EACzE;AAAA,EAEA,QAAQ,QAAuB;AAC5B,SAAK,SAAS;AACd,SAAK,WAAW,sBAAsB,EAAE,SAAS,OAAO,CAAC;AACzD,QAAI,KAAK,cAAc,UAAU,KAAK,cAAc,UAAU;AAC3D,WAAK,aAAa,SAAS,WAAW,MAAM;AAAA,IAC/C;AACA,QAAI,QAAQ;AACT,WAAK,aAAa;AAAA,IACrB;AAAA,EACH;AAAA,EAEA,MAAM,kBAAiC;AACpC,QAAI,OAAO,WAAW,YAAa;AACnC,QAAI,CAAC,UAAU,cAAc,cAAc;AACxC,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC7D;AACA,QAAI,KAAK,UAAW;AAEpB,SAAK,cAAc,MAAM,UAAU,aAAa,aAAa;AAAA,MAC1D,OAAO;AAAA,QACJ,kBAAkB;AAAA,QAClB,kBAAkB;AAAA,QAClB,iBAAiB;AAAA,MACpB;AAAA,IACH,CAAC;AAED,UAAM,MACH,OAAO,gBACN,OAAmE;AACvE,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,yBAAyB;AAEnD,SAAK,eAAe,IAAI,IAAI;AAC5B,QAAI,KAAK,aAAa,UAAU,aAAa;AAC1C,YAAM,KAAK,aAAa,OAAO;AAAA,IAClC;AAEA,UAAM,SAAS,KAAK,aAAa,wBAAwB,KAAK,WAAW;AACzE,UAAM,YAAY,KAAK,aAAa,sBAAsB,MAAM,GAAG,CAAC;AACpE,SAAK,YAAY;AAEjB,cAAU,iBAAiB,CAAC,UAAU;AACnC,UAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,KAAM;AACvD,UAAI,KAAK,OAAQ;AACjB,UACG,KAAK,cAAc,aACnB,KAAK,cAAc,UACnB,KAAK,cAAc,UACpB;AACC;AAAA,MACH;AAEA,YAAM,QAAQ,MAAM,YAAY,eAAe,CAAC;AAChD,YAAM,OAAO,IAAI,aAAa,MAAM,MAAM;AAC1C,WAAK,IAAI,KAAK;AAEd,YAAM,aAAa,KAAK,cAAc,cAAc;AACpD,UAAI,MAAM;AACV,UAAI,eAAe,oBAAoB;AACpC,cAAM,QAAQ,aAAa;AAC3B,cAAM,SAAS,KAAK,MAAM,KAAK,SAAS,KAAK;AAC7C,cAAM,OAAO,IAAI,aAAa,MAAM;AACpC,iBAAS,IAAI,GAAG,IAAI,QAAQ,KAAK,GAAG;AACjC,eAAK,CAAC,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC;AAAA,QACvC;AACA,cAAM;AAAA,MACT;AAEA,WAAK,GAAG;AAAA,QACL,KAAK,UAAU;AAAA,UACZ,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,YACJ,MAAM,qBAAqB,GAAG;AAAA,YAC9B,QAAQ;AAAA,YACR,aAAa;AAAA,UAChB;AAAA,QACH,CAAC;AAAA,MACJ;AAAA,IACH;AAEA,WAAO,QAAQ,SAAS;AACxB,cAAU,QAAQ,KAAK,aAAa,WAAW;AAC/C,SAAK,YAAY;AAAA,EACpB;AAAA,EAEA,iBAAuB;AACpB,SAAK,WAAW,WAAW;AAC3B,SAAK,YAAY;AACjB,eAAW,SAAS,KAAK,aAAa,UAAU,KAAK,CAAC,GAAG;AACtD,YAAM,KAAK;AAAA,IACd;AACA,SAAK,cAAc;AACnB,SAAK,YAAY;AAAA,EACpB;AAAA,EAEA,MAAc,UAAU,SAAsC;AAC3D,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,MACH,OAAO,gBACN,OAAmE;AACvE,QAAI,CAAC,IAAK;AAEV,QAAI,CAAC,KAAK,gBAAgB,KAAK,aAAa,UAAU,UAAU;AAC7D,WAAK,eAAe,IAAI,IAAI;AAAA,IAC/B;AACA,QAAI,KAAK,aAAa,UAAU,aAAa;AAC1C,YAAM,KAAK,aAAa,OAAO;AAAA,IAClC;AAEA,UAAM,SAAS,KAAK,aAAa,aAAa,GAAG,QAAQ,QAAQ,kBAAkB;AACnF,WAAO,cAAc,IAAI,aAAa,OAAO,GAAG,CAAC;AAEjD,UAAM,SAAS,KAAK,aAAa,mBAAmB;AACpD,WAAO,SAAS;AAChB,WAAO,QAAQ,KAAK,aAAa,WAAW;AAE5C,UAAM,MAAM,KAAK,aAAa;AAC9B,UAAM,UAAU,KAAK,mBAAmB,MAAM,OAAO,MAAM,OAAO,KAAK;AACvE,WAAO,MAAM,OAAO;AACpB,SAAK,mBAAmB,UAAU,OAAO;AACzC,SAAK,gBAAgB,KAAK,MAAM;AAChC,WAAO,UAAU,MAAM;AACpB,YAAM,MAAM,KAAK,gBAAgB,QAAQ,MAAM;AAC/C,UAAI,OAAO,EAAG,MAAK,gBAAgB,OAAO,KAAK,CAAC;AAAA,IACnD;AAAA,EACH;AAAA,EAEQ,eAAqB;AAC1B,eAAW,OAAO,KAAK,iBAAiB;AACrC,UAAI;AACD,YAAI,KAAK;AAAA,MACZ,QAAQ;AAAA,MAER;AAAA,IACH;AACA,SAAK,kBAAkB,CAAC;AACxB,SAAK,mBAAmB;AAAA,EAC3B;AAAA,EAEA,MAAM,aAA4B;AAC/B,SAAK,wBAAwB;AAC7B,QACG,KAAK,cAAc,aACnB,KAAK,cAAc,UACnB,KAAK,cAAc,UACpB;AACC,WAAK,OAAO;AAAA,IACf;AACA,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,QAAI,KAAK,IAAI;AACV,WAAK,GAAG,MAAM;AACd,WAAK,KAAK;AAAA,IACb;AACA,QAAI,KAAK,gBAAgB,KAAK,aAAa,UAAU,UAAU;AAC5D,YAAM,KAAK,aAAa,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,IACxD;AACA,SAAK,eAAe;AACpB,SAAK,SAAS;AACd,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB,MAAM;AAC3B,SAAK,aAAa,MAAM;AAAA,EAC3B;AACH;","names":[]}
|
package/dist/realtime.js
CHANGED
package/dist/realtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/realtime/types.ts","../src/realtime/workspace-client.ts","../src/realtime/operator-session-client.ts"],"sourcesContent":["/** Event received from a subscribed workspace realtime channel. */\nexport interface ChannelEvent {\n type: string;\n [key: string]: unknown;\n}\n\nexport type ChannelListener = (event: ChannelEvent) => void;\nexport type Unsubscribe = () => void;\n\n/** Auth context for workspace realtime WebSocket connections. */\nexport interface RealtimeAuthSession {\n apiUrl: string;\n workspaceUuid: string;\n accessToken: string;\n}\n\nexport type OperatorSessionState = 'idle' | 'connecting' | 'connected' | 'error';\n\nexport type OperatorCallPhase =\n | 'idle'\n | 'connecting'\n | 'ready'\n | 'dialing'\n | 'live'\n | 'onHold'\n | 'ended'\n | 'failed';\n\n/** Phases where Hold / Forward / Hang up apply to an active phone leg. */\nexport function isActiveCallPhase(phase: OperatorCallPhase): boolean {\n return phase === 'dialing' || phase === 'live' || phase === 'onHold';\n}\n","/**\n * Unified workspace WebSocket client for real-time events.\n *\n * Mirrors `/api/v2/w/{wid}/ws/realtime`: login with OAuth access token,\n * subscribe to channels, receive AGUI events.\n *\n * Protocol:\n * Client → Server: subscribe, unsubscribe, heartbeat, message\n * Server → Client: protocol.init, subscribed, unsubscribed, event, error\n */\n\nimport type { ChannelEvent, ChannelListener, RealtimeAuthSession, Unsubscribe } from './types';\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 3_000;\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst CLOSE_DELAY_MS = 1_000;\n\ninterface SocketState {\n ws: WebSocket | null;\n connected: boolean;\n connecting: boolean;\n listeners: Map<string, Set<ChannelListener>>;\n subscribedChannels: Set<string>;\n reconnectAttempts: number;\n reconnectTimer: ReturnType<typeof setTimeout> | null;\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n closeTimer: ReturnType<typeof setTimeout> | null;\n intentionalClose: boolean;\n}\n\nfunction createSocketState(): SocketState {\n return {\n ws: null,\n connected: false,\n connecting: false,\n listeners: new Map(),\n subscribedChannels: new Set(),\n reconnectAttempts: 0,\n reconnectTimer: null,\n heartbeatTimer: null,\n closeTimer: null,\n intentionalClose: false,\n };\n}\n\n/** Build the workspace realtime WebSocket URL from session config. */\nexport function buildWorkspaceWsUrl(session: RealtimeAuthSession): string {\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n return `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/ws/realtime`;\n}\n\nexport class WorkspaceRealtimeClient {\n private session: RealtimeAuthSession | null = null;\n private readonly getSessionFn: (() => RealtimeAuthSession | null) | null;\n private readonly state: SocketState = createSocketState();\n private readonly connectionListeners = new Set<(connected: boolean) => void>();\n\n constructor(config?: RealtimeAuthSession | (() => RealtimeAuthSession | null)) {\n if (typeof config === 'function') {\n this.getSessionFn = config;\n } else {\n this.getSessionFn = null;\n if (config) {\n this.session = config;\n }\n }\n }\n\n private resolveSession(): RealtimeAuthSession | null {\n return this.session ?? this.getSessionFn?.() ?? null;\n }\n\n private notifyConnectionListeners(): void {\n for (const listener of this.connectionListeners) {\n listener(this.state.connected);\n }\n }\n\n private clearCloseTimer(): void {\n if (this.state.closeTimer) {\n clearTimeout(this.state.closeTimer);\n this.state.closeTimer = null;\n }\n }\n\n setSession(session: RealtimeAuthSession): void {\n const changed =\n this.session?.accessToken !== session.accessToken ||\n this.session?.workspaceUuid !== session.workspaceUuid ||\n this.session?.apiUrl !== session.apiUrl;\n\n this.session = session;\n if (changed && this.state.ws) {\n this.closeConnection();\n }\n if (this.state.listeners.size > 0) {\n this.ensureConnection();\n }\n }\n\n subscribe(channel: string, listener: ChannelListener): Unsubscribe {\n this.clearCloseTimer();\n\n let listeners = this.state.listeners.get(channel);\n if (!listeners) {\n listeners = new Set();\n this.state.listeners.set(channel, listeners);\n }\n listeners.add(listener);\n\n this.ensureConnection();\n\n if (\n this.state.connected &&\n this.state.ws?.readyState === WebSocket.OPEN &&\n !this.state.subscribedChannels.has(channel)\n ) {\n this.state.ws.send(JSON.stringify({ type: 'subscribe', channels: [channel] }));\n }\n\n return () => {\n const channelListeners = this.state.listeners.get(channel);\n channelListeners?.delete(listener);\n if (channelListeners && channelListeners.size === 0) {\n this.state.listeners.delete(channel);\n this.state.subscribedChannels.delete(channel);\n if (this.state.connected && this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'unsubscribe', channels: [channel] }));\n }\n }\n\n if (this.state.listeners.size === 0) {\n this.state.closeTimer = setTimeout(() => {\n if (this.state.listeners.size === 0) {\n this.closeConnection();\n }\n }, CLOSE_DELAY_MS);\n }\n };\n }\n\n send(channel: string, data: unknown): void {\n if (!this.state.connected || !this.state.ws) return;\n this.state.ws.send(JSON.stringify({ type: 'message', channel, data }));\n }\n\n onConnection(listener: (connected: boolean) => void): () => void {\n this.connectionListeners.add(listener);\n listener(this.state.connected);\n return () => {\n this.connectionListeners.delete(listener);\n };\n }\n\n isConnected(): boolean {\n return this.state.connected;\n }\n\n private ensureConnection(): void {\n const session = this.resolveSession();\n if (!session) return;\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') return;\n if (this.state.ws || this.state.connecting) return;\n\n this.clearCloseTimer();\n this.state.connecting = true;\n this.state.intentionalClose = false;\n\n const ws = new WebSocket(buildWorkspaceWsUrl(session));\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event: MessageEvent) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n channels?: string[];\n channel?: string;\n event?: ChannelEvent;\n };\n\n if (msg.type === 'protocol.init' && !this.state.connected) {\n this.state.connected = true;\n this.state.connecting = false;\n this.state.reconnectAttempts = 0;\n\n if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = setInterval(() => {\n if (this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n if (this.state.listeners.size > 0) {\n ws.send(\n JSON.stringify({\n type: 'subscribe',\n channels: Array.from(this.state.listeners.keys()),\n })\n );\n }\n\n this.notifyConnectionListeners();\n return;\n }\n\n if (msg.type === 'subscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.add(ch);\n }\n return;\n }\n\n if (msg.type === 'unsubscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.delete(ch);\n }\n return;\n }\n\n if (msg.type === 'event' && msg.channel && msg.event) {\n const channelListeners = this.state.listeners.get(msg.channel);\n if (channelListeners) {\n for (const channelListener of channelListeners) {\n try {\n channelListener(msg.event);\n } catch (err) {\n console.error('Channel listener error:', err);\n }\n }\n }\n }\n } catch {\n // Ignore malformed frames.\n }\n };\n\n ws.onclose = () => {\n this.state.connected = false;\n this.state.connecting = false;\n this.state.ws = null;\n this.state.subscribedChannels.clear();\n\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n\n this.notifyConnectionListeners();\n\n if (\n !this.state.intentionalClose &&\n this.state.listeners.size > 0 &&\n this.state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS\n ) {\n this.state.reconnectAttempts += 1;\n this.state.reconnectTimer = setTimeout(() => {\n this.ensureConnection();\n }, RECONNECT_DELAY_MS * this.state.reconnectAttempts);\n }\n };\n\n ws.onerror = () => {\n // onclose will fire after this\n };\n\n this.state.ws = ws;\n }\n\n private closeConnection(): void {\n this.state.intentionalClose = true;\n this.clearCloseTimer();\n if (this.state.reconnectTimer) {\n clearTimeout(this.state.reconnectTimer);\n this.state.reconnectTimer = null;\n }\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n this.state.ws?.close();\n this.state.ws = null;\n this.state.connected = false;\n this.state.connecting = false;\n this.state.subscribedChannels.clear();\n this.notifyConnectionListeners();\n }\n}\n\nconst defaultClient = new WorkspaceRealtimeClient();\n\nexport function initWorkspaceSocket(session: RealtimeAuthSession): void {\n defaultClient.setSession(session);\n}\n\nexport function subscribeWorkspaceChannel(channel: string, listener: ChannelListener): Unsubscribe {\n return defaultClient.subscribe(channel, listener);\n}\n\nexport function onWorkspaceSocketConnection(listener: (connected: boolean) => void): () => void {\n return defaultClient.onConnection(listener);\n}\n\nexport function isWorkspaceSocketConnected(): boolean {\n return defaultClient.isConnected();\n}\n","/**\n * Human operator session WebSocket client.\n *\n * Connects to `/api/v2/w/{wid}/operator/chats/{chatId}/session-ws` — AGUI\n * events, bidirectional PCM, and call control. No AI inference.\n */\n\nimport {\n base64ToFloat32Array,\n float32ArrayToBase64,\n SERVER_SAMPLE_RATE,\n TARGET_SAMPLE_RATE,\n} from '../audio/pcm';\nimport type {\n ChannelEvent,\n OperatorCallPhase,\n OperatorSessionState,\n RealtimeAuthSession,\n} from './types';\n\nexport interface OperatorSessionHandlers {\n onSessionStateChange?: (state: OperatorSessionState) => void;\n onCallPhaseChange?: (phase: OperatorCallPhase) => void;\n onAguiEvent?: (event: ChannelEvent) => void;\n onError?: (message: string) => void;\n /** Fired when the server closes the session (e.g. callee hung up). */\n onSessionClosed?: (reason?: string) => void;\n}\n\nexport class OperatorSessionClient {\n private ws: WebSocket | null = null;\n private audioContext: AudioContext | null = null;\n private mediaStream: MediaStream | null = null;\n private processor: ScriptProcessorNode | null = null;\n private sessionState: OperatorSessionState = 'idle';\n private callPhase: OperatorCallPhase = 'idle';\n private nextPlaybackTime = 0;\n private playbackSources: AudioBufferSourceNode[] = [];\n private micActive = false;\n private onHold = false;\n private receivedRemoteAudio = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private sessionCloseNotified = false;\n private intentionalDisconnect = false;\n\n constructor(private handlers: OperatorSessionHandlers = {}) {}\n\n getSessionState(): OperatorSessionState {\n return this.sessionState;\n }\n\n getCallPhase(): OperatorCallPhase {\n return this.callPhase;\n }\n\n private setSessionState(next: OperatorSessionState): void {\n this.sessionState = next;\n this.handlers.onSessionStateChange?.(next);\n }\n\n private setCallPhase(next: OperatorCallPhase): void {\n this.callPhase = next;\n this.handlers.onCallPhaseChange?.(next);\n }\n\n private notifySessionClosed(reason?: string): void {\n if (this.sessionCloseNotified) return;\n this.sessionCloseNotified = true;\n this.handlers.onSessionClosed?.(reason);\n }\n\n async connect(session: RealtimeAuthSession, chatId: number): Promise<void> {\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') {\n throw new Error('Operator session WebSocket is not available in this environment');\n }\n\n await this.disconnect();\n this.sessionCloseNotified = false;\n this.setSessionState('connecting');\n this.setCallPhase('connecting');\n\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n const wsUrl = `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/operator/chats/${chatId}/session-ws`;\n\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(wsUrl);\n this.ws = ws;\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n };\n this.handleMessage(msg, resolve, reject);\n } catch {\n /* ignore malformed frames */\n }\n };\n\n ws.onerror = () => {\n const message = 'Operator session WebSocket error';\n const wasConnecting = this.sessionState === 'connecting';\n this.setSessionState('error');\n if (wasConnecting) {\n this.setCallPhase('idle');\n reject(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n };\n\n ws.onclose = () => {\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n const wasConnected = this.sessionState === 'connected';\n this.setSessionState('idle');\n this.setCallPhase('idle');\n if (wasConnected && !this.intentionalDisconnect) {\n this.notifySessionClosed('disconnect');\n }\n this.intentionalDisconnect = false;\n };\n });\n }\n\n private handleMessage(\n msg: {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n },\n resolveLogin: () => void,\n rejectLogin: (err: Error) => void\n ): void {\n if (msg.type === 'RUN_ERROR' || msg.type === 'error') {\n const message = msg.message ?? 'Operator session error';\n if (this.sessionState === 'connecting') {\n this.setSessionState('error');\n rejectLogin(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n return;\n }\n\n if (msg.type === 'protocol.init' && this.sessionState === 'connecting') {\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.session_ready') {\n this.setSessionState('connected');\n this.setCallPhase('ready');\n this.startHeartbeat();\n resolveLogin();\n return;\n }\n\n if (msg.type === 'event' && msg.event) {\n if (this.handleRemoteVoiceAudio(msg.event)) {\n return;\n }\n this.handlers.onAguiEvent?.(msg.event);\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.started') {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.ended') {\n this.onHold = false;\n this.receivedRemoteAudio = false;\n this.stopMicrophone();\n this.stopPlayback();\n const endSession = msg.value?.end_session === true;\n const reason = typeof msg.value?.reason === 'string' ? msg.value.reason : undefined;\n if (endSession) {\n this.setCallPhase('idle');\n this.notifySessionClosed(reason ?? 'peer_hangup');\n } else {\n this.setCallPhase('ready');\n }\n return;\n }\n }\n\n /** Play callee PCM from wrapped chat-stream events; returns true if handled. */\n private handleRemoteVoiceAudio(event: ChannelEvent): boolean {\n if (event.type !== 'CUSTOM' || event.name !== 'voice.audio') {\n return false;\n }\n if (this.onHold) return true;\n\n const value = event.value;\n if (!value || typeof value !== 'object') return true;\n\n const data = (value as Record<string, unknown>).data;\n if (typeof data !== 'string' || !data) return true;\n\n if (\n !this.receivedRemoteAudio &&\n (this.callPhase === 'dialing' || this.callPhase === 'live')\n ) {\n this.receivedRemoteAudio = true;\n this.setCallPhase('live');\n }\n void this.playPcm16(base64ToFloat32Array(data));\n return true;\n }\n\n private startHeartbeat(): void {\n this.clearHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, 30_000);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private sendCustom(name: string, value: Record<string, unknown> = {}): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n this.ws.send(JSON.stringify({ type: 'CUSTOM', name, value }));\n }\n\n async placeCall(memberId: number): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n if (this.sessionState !== 'connected') {\n throw new Error('Operator session is not ready');\n }\n await this.startMicrophone();\n this.sendCustom('operator.call.place', { member_id: memberId });\n }\n\n hangUp(): void {\n try {\n this.sendCustom('operator.call.hangup');\n } catch {\n /* socket already closed */\n }\n this.onHold = false;\n this.receivedRemoteAudio = false;\n }\n\n forwardCall(phoneNumber: string): void {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n this.sendCustom('operator.call.forward', { phone_number: phoneNumber });\n }\n\n setHold(onHold: boolean): void {\n this.onHold = onHold;\n this.sendCustom('operator.call.hold', { on_hold: onHold });\n if (this.callPhase === 'live' || this.callPhase === 'onHold') {\n this.setCallPhase(onHold ? 'onHold' : 'live');\n }\n if (onHold) {\n this.stopPlayback();\n }\n }\n\n async startMicrophone(): Promise<void> {\n if (typeof window === 'undefined') return;\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not available in this browser');\n }\n if (this.micActive) return;\n\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio not supported');\n\n this.audioContext = new Ctx();\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const source = this.audioContext.createMediaStreamSource(this.mediaStream);\n const processor = this.audioContext.createScriptProcessor(4096, 1, 1);\n this.processor = processor;\n\n processor.onaudioprocess = (event) => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n if (this.onHold) return;\n if (\n this.callPhase !== 'dialing' &&\n this.callPhase !== 'live' &&\n this.callPhase !== 'onHold'\n ) {\n return;\n }\n\n const input = event.inputBuffer.getChannelData(0);\n const copy = new Float32Array(input.length);\n copy.set(input);\n\n const sampleRate = this.audioContext?.sampleRate ?? TARGET_SAMPLE_RATE;\n let pcm = copy;\n if (sampleRate !== TARGET_SAMPLE_RATE) {\n const ratio = sampleRate / TARGET_SAMPLE_RATE;\n const newLen = Math.floor(copy.length / ratio);\n const down = new Float32Array(newLen);\n for (let i = 0; i < newLen; i += 1) {\n down[i] = copy[Math.floor(i * ratio)];\n }\n pcm = down;\n }\n\n this.ws.send(\n JSON.stringify({\n type: 'CUSTOM',\n name: 'voice.audio',\n value: {\n data: float32ArrayToBase64(pcm),\n format: 'pcm16',\n sample_rate: TARGET_SAMPLE_RATE,\n },\n })\n );\n };\n\n source.connect(processor);\n processor.connect(this.audioContext.destination);\n this.micActive = true;\n }\n\n stopMicrophone(): void {\n this.processor?.disconnect();\n this.processor = null;\n for (const track of this.mediaStream?.getTracks() ?? []) {\n track.stop();\n }\n this.mediaStream = null;\n this.micActive = false;\n }\n\n private async playPcm16(samples: Float32Array): Promise<void> {\n if (typeof window === 'undefined') return;\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n\n if (!this.audioContext || this.audioContext.state === 'closed') {\n this.audioContext = new Ctx();\n }\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const buffer = this.audioContext.createBuffer(1, samples.length, SERVER_SAMPLE_RATE);\n buffer.copyToChannel(new Float32Array(samples), 0);\n\n const source = this.audioContext.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioContext.destination);\n\n const now = this.audioContext.currentTime;\n const startAt = this.nextPlaybackTime < now + 0.02 ? now + 0.02 : this.nextPlaybackTime;\n source.start(startAt);\n this.nextPlaybackTime = startAt + buffer.duration;\n this.playbackSources.push(source);\n source.onended = () => {\n const idx = this.playbackSources.indexOf(source);\n if (idx >= 0) this.playbackSources.splice(idx, 1);\n };\n }\n\n private stopPlayback(): void {\n for (const src of this.playbackSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n this.playbackSources = [];\n this.nextPlaybackTime = 0;\n }\n\n async disconnect(): Promise<void> {\n this.intentionalDisconnect = true;\n if (\n this.callPhase === 'dialing' ||\n this.callPhase === 'live' ||\n this.callPhase === 'onHold'\n ) {\n this.hangUp();\n }\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n if (this.audioContext && this.audioContext.state !== 'closed') {\n await this.audioContext.close().catch(() => undefined);\n }\n this.audioContext = null;\n this.onHold = false;\n this.receivedRemoteAudio = false;\n this.setSessionState('idle');\n this.setCallPhase('idle');\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/realtime/types.ts","../src/realtime/workspace-client.ts","../src/realtime/operator-session-client.ts"],"sourcesContent":["/** Event received from a subscribed workspace realtime channel. */\nexport interface ChannelEvent {\n type: string;\n [key: string]: unknown;\n}\n\nexport type ChannelListener = (event: ChannelEvent) => void;\nexport type Unsubscribe = () => void;\n\n/** Auth context for workspace realtime WebSocket connections. */\nexport interface RealtimeAuthSession {\n apiUrl: string;\n workspaceUuid: string;\n accessToken: string;\n}\n\nexport type OperatorSessionState = 'idle' | 'connecting' | 'connected' | 'error';\n\nexport type OperatorCallPhase =\n | 'idle'\n | 'connecting'\n | 'ready'\n | 'dialing'\n | 'live'\n | 'onHold'\n | 'ended'\n | 'failed';\n\n/** Phases where Hold / Forward / Hang up apply to an active phone leg. */\nexport function isActiveCallPhase(phase: OperatorCallPhase): boolean {\n return phase === 'dialing' || phase === 'live' || phase === 'onHold';\n}\n","/**\n * Unified workspace WebSocket client for real-time events.\n *\n * Mirrors `/api/v2/w/{wid}/ws/realtime`: login with OAuth access token,\n * subscribe to channels, receive AGUI events.\n *\n * Protocol:\n * Client → Server: subscribe, unsubscribe, heartbeat, message\n * Server → Client: protocol.init, subscribed, unsubscribed, event, error\n */\n\nimport type { ChannelEvent, ChannelListener, RealtimeAuthSession, Unsubscribe } from './types';\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 3_000;\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst CLOSE_DELAY_MS = 1_000;\n\ninterface SocketState {\n ws: WebSocket | null;\n connected: boolean;\n connecting: boolean;\n listeners: Map<string, Set<ChannelListener>>;\n subscribedChannels: Set<string>;\n reconnectAttempts: number;\n reconnectTimer: ReturnType<typeof setTimeout> | null;\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n closeTimer: ReturnType<typeof setTimeout> | null;\n intentionalClose: boolean;\n}\n\nfunction createSocketState(): SocketState {\n return {\n ws: null,\n connected: false,\n connecting: false,\n listeners: new Map(),\n subscribedChannels: new Set(),\n reconnectAttempts: 0,\n reconnectTimer: null,\n heartbeatTimer: null,\n closeTimer: null,\n intentionalClose: false,\n };\n}\n\n/** Build the workspace realtime WebSocket URL from session config. */\nexport function buildWorkspaceWsUrl(session: RealtimeAuthSession): string {\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n return `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/ws/realtime`;\n}\n\nexport class WorkspaceRealtimeClient {\n private session: RealtimeAuthSession | null = null;\n private readonly getSessionFn: (() => RealtimeAuthSession | null) | null;\n private readonly state: SocketState = createSocketState();\n private readonly connectionListeners = new Set<(connected: boolean) => void>();\n\n constructor(config?: RealtimeAuthSession | (() => RealtimeAuthSession | null)) {\n if (typeof config === 'function') {\n this.getSessionFn = config;\n } else {\n this.getSessionFn = null;\n if (config) {\n this.session = config;\n }\n }\n }\n\n private resolveSession(): RealtimeAuthSession | null {\n return this.session ?? this.getSessionFn?.() ?? null;\n }\n\n private notifyConnectionListeners(): void {\n for (const listener of this.connectionListeners) {\n listener(this.state.connected);\n }\n }\n\n private clearCloseTimer(): void {\n if (this.state.closeTimer) {\n clearTimeout(this.state.closeTimer);\n this.state.closeTimer = null;\n }\n }\n\n setSession(session: RealtimeAuthSession): void {\n const changed =\n this.session?.accessToken !== session.accessToken ||\n this.session?.workspaceUuid !== session.workspaceUuid ||\n this.session?.apiUrl !== session.apiUrl;\n\n this.session = session;\n if (changed && this.state.ws) {\n this.closeConnection();\n }\n if (this.state.listeners.size > 0) {\n this.ensureConnection();\n }\n }\n\n subscribe(channel: string, listener: ChannelListener): Unsubscribe {\n this.clearCloseTimer();\n\n let listeners = this.state.listeners.get(channel);\n if (!listeners) {\n listeners = new Set();\n this.state.listeners.set(channel, listeners);\n }\n listeners.add(listener);\n\n this.ensureConnection();\n\n if (\n this.state.connected &&\n this.state.ws?.readyState === WebSocket.OPEN &&\n !this.state.subscribedChannels.has(channel)\n ) {\n this.state.ws.send(JSON.stringify({ type: 'subscribe', channels: [channel] }));\n }\n\n return () => {\n const channelListeners = this.state.listeners.get(channel);\n channelListeners?.delete(listener);\n if (channelListeners && channelListeners.size === 0) {\n this.state.listeners.delete(channel);\n this.state.subscribedChannels.delete(channel);\n if (this.state.connected && this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'unsubscribe', channels: [channel] }));\n }\n }\n\n if (this.state.listeners.size === 0) {\n this.state.closeTimer = setTimeout(() => {\n if (this.state.listeners.size === 0) {\n this.closeConnection();\n }\n }, CLOSE_DELAY_MS);\n }\n };\n }\n\n send(channel: string, data: unknown): void {\n if (!this.state.connected || !this.state.ws) return;\n this.state.ws.send(JSON.stringify({ type: 'message', channel, data }));\n }\n\n onConnection(listener: (connected: boolean) => void): () => void {\n this.connectionListeners.add(listener);\n listener(this.state.connected);\n return () => {\n this.connectionListeners.delete(listener);\n };\n }\n\n isConnected(): boolean {\n return this.state.connected;\n }\n\n private ensureConnection(): void {\n const session = this.resolveSession();\n if (!session) return;\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') return;\n if (this.state.ws || this.state.connecting) return;\n\n this.clearCloseTimer();\n this.state.connecting = true;\n this.state.intentionalClose = false;\n\n const ws = new WebSocket(buildWorkspaceWsUrl(session));\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event: MessageEvent) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n channels?: string[];\n channel?: string;\n event?: ChannelEvent;\n };\n\n if (msg.type === 'protocol.init' && !this.state.connected) {\n this.state.connected = true;\n this.state.connecting = false;\n this.state.reconnectAttempts = 0;\n\n if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = setInterval(() => {\n if (this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n if (this.state.listeners.size > 0) {\n ws.send(\n JSON.stringify({\n type: 'subscribe',\n channels: Array.from(this.state.listeners.keys()),\n })\n );\n }\n\n this.notifyConnectionListeners();\n return;\n }\n\n if (msg.type === 'subscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.add(ch);\n }\n return;\n }\n\n if (msg.type === 'unsubscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.delete(ch);\n }\n return;\n }\n\n if (msg.type === 'event' && msg.channel && msg.event) {\n const channelListeners = this.state.listeners.get(msg.channel);\n if (channelListeners) {\n for (const channelListener of channelListeners) {\n try {\n channelListener(msg.event);\n } catch (err) {\n console.error('Channel listener error:', err);\n }\n }\n }\n }\n } catch {\n // Ignore malformed frames.\n }\n };\n\n ws.onclose = () => {\n this.state.connected = false;\n this.state.connecting = false;\n this.state.ws = null;\n this.state.subscribedChannels.clear();\n\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n\n this.notifyConnectionListeners();\n\n if (\n !this.state.intentionalClose &&\n this.state.listeners.size > 0 &&\n this.state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS\n ) {\n this.state.reconnectAttempts += 1;\n this.state.reconnectTimer = setTimeout(() => {\n this.ensureConnection();\n }, RECONNECT_DELAY_MS * this.state.reconnectAttempts);\n }\n };\n\n ws.onerror = () => {\n // onclose will fire after this\n };\n\n this.state.ws = ws;\n }\n\n private closeConnection(): void {\n this.state.intentionalClose = true;\n this.clearCloseTimer();\n if (this.state.reconnectTimer) {\n clearTimeout(this.state.reconnectTimer);\n this.state.reconnectTimer = null;\n }\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n this.state.ws?.close();\n this.state.ws = null;\n this.state.connected = false;\n this.state.connecting = false;\n this.state.subscribedChannels.clear();\n this.notifyConnectionListeners();\n }\n}\n\nconst defaultClient = new WorkspaceRealtimeClient();\n\nexport function initWorkspaceSocket(session: RealtimeAuthSession): void {\n defaultClient.setSession(session);\n}\n\nexport function subscribeWorkspaceChannel(channel: string, listener: ChannelListener): Unsubscribe {\n return defaultClient.subscribe(channel, listener);\n}\n\nexport function onWorkspaceSocketConnection(listener: (connected: boolean) => void): () => void {\n return defaultClient.onConnection(listener);\n}\n\nexport function isWorkspaceSocketConnected(): boolean {\n return defaultClient.isConnected();\n}\n","/**\n * Human operator session WebSocket client.\n *\n * Connects to `/api/v2/w/{wid}/operator/chats/{chatId}/session-ws` — AGUI\n * events, bidirectional PCM, and call control. No AI inference.\n */\n\nimport {\n base64ToFloat32Array,\n float32ArrayToBase64,\n SERVER_SAMPLE_RATE,\n TARGET_SAMPLE_RATE,\n} from '../audio/pcm';\nimport type {\n ChannelEvent,\n OperatorCallPhase,\n OperatorSessionState,\n RealtimeAuthSession,\n} from './types';\n\nexport interface OperatorSessionHandlers {\n onSessionStateChange?: (state: OperatorSessionState) => void;\n onCallPhaseChange?: (phase: OperatorCallPhase) => void;\n onAguiEvent?: (event: ChannelEvent) => void;\n onError?: (message: string) => void;\n /** Fired when the server closes the session (e.g. callee hung up). */\n onSessionClosed?: (reason?: string) => void;\n}\n\nexport class OperatorSessionClient {\n private ws: WebSocket | null = null;\n private audioContext: AudioContext | null = null;\n private mediaStream: MediaStream | null = null;\n private processor: ScriptProcessorNode | null = null;\n private sessionState: OperatorSessionState = 'idle';\n private callPhase: OperatorCallPhase = 'idle';\n private nextPlaybackTime = 0;\n private playbackSources: AudioBufferSourceNode[] = [];\n private micActive = false;\n private onHold = false;\n private receivedRemoteAudio = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private sessionCloseNotified = false;\n private intentionalDisconnect = false;\n\n constructor(private handlers: OperatorSessionHandlers = {}) {}\n\n getSessionState(): OperatorSessionState {\n return this.sessionState;\n }\n\n getCallPhase(): OperatorCallPhase {\n return this.callPhase;\n }\n\n private setSessionState(next: OperatorSessionState): void {\n this.sessionState = next;\n this.handlers.onSessionStateChange?.(next);\n }\n\n private setCallPhase(next: OperatorCallPhase): void {\n this.callPhase = next;\n this.handlers.onCallPhaseChange?.(next);\n }\n\n private notifySessionClosed(reason?: string): void {\n if (this.sessionCloseNotified) return;\n this.sessionCloseNotified = true;\n this.handlers.onSessionClosed?.(reason);\n }\n\n async connect(session: RealtimeAuthSession, chatId: number): Promise<void> {\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') {\n throw new Error('Operator session WebSocket is not available in this environment');\n }\n\n await this.disconnect();\n this.sessionCloseNotified = false;\n this.setSessionState('connecting');\n this.setCallPhase('connecting');\n\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n const wsUrl = `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/operator/chats/${chatId}/session-ws`;\n\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(wsUrl);\n this.ws = ws;\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n };\n this.handleMessage(msg, resolve, reject);\n } catch {\n /* ignore malformed frames */\n }\n };\n\n ws.onerror = () => {\n const message = 'Operator session WebSocket error';\n const wasConnecting = this.sessionState === 'connecting';\n this.setSessionState('error');\n if (wasConnecting) {\n this.setCallPhase('idle');\n reject(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n };\n\n ws.onclose = () => {\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n const wasConnected = this.sessionState === 'connected';\n this.setSessionState('idle');\n this.setCallPhase('idle');\n if (wasConnected && !this.intentionalDisconnect) {\n this.notifySessionClosed('disconnect');\n }\n this.intentionalDisconnect = false;\n };\n });\n }\n\n private handleMessage(\n msg: {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n },\n resolveLogin: () => void,\n rejectLogin: (err: Error) => void\n ): void {\n if (msg.type === 'RUN_ERROR' || msg.type === 'error') {\n const message = msg.message ?? 'Operator session error';\n if (this.sessionState === 'connecting') {\n this.setSessionState('error');\n rejectLogin(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n return;\n }\n\n if (msg.type === 'protocol.init' && this.sessionState === 'connecting') {\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.session_ready') {\n this.setSessionState('connected');\n this.setCallPhase('ready');\n this.startHeartbeat();\n resolveLogin();\n return;\n }\n\n if (msg.type === 'event' && msg.event) {\n if (this.handleRemoteVoiceAudio(msg.event)) {\n return;\n }\n this.handlers.onAguiEvent?.(msg.event);\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.started') {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.ended') {\n this.onHold = false;\n this.receivedRemoteAudio = false;\n this.stopMicrophone();\n this.stopPlayback();\n const endSession = msg.value?.end_session === true;\n const reason = typeof msg.value?.reason === 'string' ? msg.value.reason : undefined;\n if (endSession) {\n this.setCallPhase('idle');\n this.notifySessionClosed(reason ?? 'peer_hangup');\n } else {\n this.setCallPhase('ready');\n }\n return;\n }\n }\n\n /** Play callee PCM from wrapped chat-stream events; returns true if handled. */\n private handleRemoteVoiceAudio(event: ChannelEvent): boolean {\n if (event.type !== 'CUSTOM' || event.name !== 'voice.audio') {\n return false;\n }\n if (this.onHold) return true;\n\n const value = event.value;\n if (!value || typeof value !== 'object') return true;\n\n const data = (value as Record<string, unknown>).data;\n if (typeof data !== 'string' || !data) return true;\n\n if (\n !this.receivedRemoteAudio &&\n (this.callPhase === 'dialing' || this.callPhase === 'live')\n ) {\n this.receivedRemoteAudio = true;\n this.setCallPhase('live');\n }\n void this.playPcm16(base64ToFloat32Array(data));\n return true;\n }\n\n private startHeartbeat(): void {\n this.clearHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, 30_000);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private sendCustom(name: string, value: Record<string, unknown> = {}): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n this.ws.send(JSON.stringify({ type: 'CUSTOM', name, value }));\n }\n\n async placeCall(memberId: number): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n if (this.sessionState !== 'connected') {\n throw new Error('Operator session is not ready');\n }\n await this.startMicrophone();\n this.sendCustom('operator.call.place', { member_id: memberId });\n }\n\n hangUp(): void {\n try {\n this.sendCustom('operator.call.hangup');\n } catch {\n /* socket already closed */\n }\n this.onHold = false;\n this.receivedRemoteAudio = false;\n }\n\n forwardCall(phoneNumber: string): void {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n this.sendCustom('operator.call.forward', { phone_number: phoneNumber });\n }\n\n setHold(onHold: boolean): void {\n this.onHold = onHold;\n this.sendCustom('operator.call.hold', { on_hold: onHold });\n if (this.callPhase === 'live' || this.callPhase === 'onHold') {\n this.setCallPhase(onHold ? 'onHold' : 'live');\n }\n if (onHold) {\n this.stopPlayback();\n }\n }\n\n async startMicrophone(): Promise<void> {\n if (typeof window === 'undefined') return;\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not available in this browser');\n }\n if (this.micActive) return;\n\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio not supported');\n\n this.audioContext = new Ctx();\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const source = this.audioContext.createMediaStreamSource(this.mediaStream);\n const processor = this.audioContext.createScriptProcessor(4096, 1, 1);\n this.processor = processor;\n\n processor.onaudioprocess = (event) => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n if (this.onHold) return;\n if (\n this.callPhase !== 'dialing' &&\n this.callPhase !== 'live' &&\n this.callPhase !== 'onHold'\n ) {\n return;\n }\n\n const input = event.inputBuffer.getChannelData(0);\n const copy = new Float32Array(input.length);\n copy.set(input);\n\n const sampleRate = this.audioContext?.sampleRate ?? TARGET_SAMPLE_RATE;\n let pcm = copy;\n if (sampleRate !== TARGET_SAMPLE_RATE) {\n const ratio = sampleRate / TARGET_SAMPLE_RATE;\n const newLen = Math.floor(copy.length / ratio);\n const down = new Float32Array(newLen);\n for (let i = 0; i < newLen; i += 1) {\n down[i] = copy[Math.floor(i * ratio)];\n }\n pcm = down;\n }\n\n this.ws.send(\n JSON.stringify({\n type: 'CUSTOM',\n name: 'voice.audio',\n value: {\n data: float32ArrayToBase64(pcm),\n format: 'pcm16',\n sample_rate: TARGET_SAMPLE_RATE,\n },\n })\n );\n };\n\n source.connect(processor);\n processor.connect(this.audioContext.destination);\n this.micActive = true;\n }\n\n stopMicrophone(): void {\n this.processor?.disconnect();\n this.processor = null;\n for (const track of this.mediaStream?.getTracks() ?? []) {\n track.stop();\n }\n this.mediaStream = null;\n this.micActive = false;\n }\n\n private async playPcm16(samples: Float32Array): Promise<void> {\n if (typeof window === 'undefined') return;\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n\n if (!this.audioContext || this.audioContext.state === 'closed') {\n this.audioContext = new Ctx();\n }\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const buffer = this.audioContext.createBuffer(1, samples.length, SERVER_SAMPLE_RATE);\n buffer.copyToChannel(new Float32Array(samples), 0);\n\n const source = this.audioContext.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioContext.destination);\n\n const now = this.audioContext.currentTime;\n const startAt = this.nextPlaybackTime < now + 0.02 ? now + 0.02 : this.nextPlaybackTime;\n source.start(startAt);\n this.nextPlaybackTime = startAt + buffer.duration;\n this.playbackSources.push(source);\n source.onended = () => {\n const idx = this.playbackSources.indexOf(source);\n if (idx >= 0) this.playbackSources.splice(idx, 1);\n };\n }\n\n private stopPlayback(): void {\n for (const src of this.playbackSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n this.playbackSources = [];\n this.nextPlaybackTime = 0;\n }\n\n async disconnect(): Promise<void> {\n this.intentionalDisconnect = true;\n if (\n this.callPhase === 'dialing' ||\n this.callPhase === 'live' ||\n this.callPhase === 'onHold'\n ) {\n this.hangUp();\n }\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n if (this.audioContext && this.audioContext.state !== 'closed') {\n await this.audioContext.close().catch(() => undefined);\n }\n this.audioContext = null;\n this.onHold = false;\n this.receivedRemoteAudio = false;\n this.setSessionState('idle');\n this.setCallPhase('idle');\n }\n}\n"],"mappings":"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","names":[]}
|