@webex/contact-center 3.11.0 → 3.12.0-mobius-socket.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.
Files changed (126) hide show
  1. package/dist/cc.js +121 -28
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +5 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/index.js +7 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/metrics/behavioral-events.js +13 -0
  8. package/dist/metrics/behavioral-events.js.map +1 -1
  9. package/dist/metrics/constants.js +9 -1
  10. package/dist/metrics/constants.js.map +1 -1
  11. package/dist/services/ApiAiAssistant.js +173 -0
  12. package/dist/services/ApiAiAssistant.js.map +1 -0
  13. package/dist/services/agent/types.js.map +1 -1
  14. package/dist/services/config/Util.js +6 -2
  15. package/dist/services/config/Util.js.map +1 -1
  16. package/dist/services/config/constants.js +12 -0
  17. package/dist/services/config/constants.js.map +1 -1
  18. package/dist/services/config/index.js +41 -2
  19. package/dist/services/config/index.js.map +1 -1
  20. package/dist/services/config/types.js +19 -1
  21. package/dist/services/config/types.js.map +1 -1
  22. package/dist/services/constants.js +27 -1
  23. package/dist/services/constants.js.map +1 -1
  24. package/dist/services/core/Err.js.map +1 -1
  25. package/dist/services/core/Utils.js +28 -6
  26. package/dist/services/core/Utils.js.map +1 -1
  27. package/dist/services/core/aqm-reqs.js +92 -17
  28. package/dist/services/core/aqm-reqs.js.map +1 -1
  29. package/dist/services/core/websocket/WebSocketManager.js +20 -5
  30. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  31. package/dist/services/core/websocket/connection-service.js +3 -1
  32. package/dist/services/core/websocket/connection-service.js.map +1 -1
  33. package/dist/services/index.js +6 -0
  34. package/dist/services/index.js.map +1 -1
  35. package/dist/services/task/TaskManager.js +117 -24
  36. package/dist/services/task/TaskManager.js.map +1 -1
  37. package/dist/services/task/TaskUtils.js +16 -3
  38. package/dist/services/task/TaskUtils.js.map +1 -1
  39. package/dist/services/task/constants.js +15 -1
  40. package/dist/services/task/constants.js.map +1 -1
  41. package/dist/services/task/dialer.js +51 -0
  42. package/dist/services/task/dialer.js.map +1 -1
  43. package/dist/services/task/types.js +15 -0
  44. package/dist/services/task/types.js.map +1 -1
  45. package/dist/types/cc.d.ts +801 -0
  46. package/dist/types/config.d.ts +66 -0
  47. package/dist/types/constants.d.ts +50 -0
  48. package/dist/types/index.d.ts +184 -0
  49. package/dist/types/logger-proxy.d.ts +71 -0
  50. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  51. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  52. package/dist/types/metrics/constants.d.ts +161 -0
  53. package/dist/types/services/AddressBook.d.ts +74 -0
  54. package/dist/types/services/ApiAiAssistant.d.ts +31 -0
  55. package/dist/types/services/EntryPoint.d.ts +67 -0
  56. package/dist/types/services/Queue.d.ts +76 -0
  57. package/dist/types/services/WebCallingService.d.ts +1 -0
  58. package/dist/types/services/agent/index.d.ts +46 -0
  59. package/dist/types/services/agent/types.d.ts +413 -0
  60. package/dist/types/services/config/Util.d.ts +20 -0
  61. package/dist/types/services/config/constants.d.ts +249 -0
  62. package/dist/types/services/config/index.d.ts +177 -0
  63. package/dist/types/services/config/types.d.ts +1207 -0
  64. package/dist/types/services/constants.d.ts +110 -0
  65. package/dist/types/services/core/Err.d.ts +121 -0
  66. package/dist/types/services/core/GlobalTypes.d.ts +58 -0
  67. package/dist/types/services/core/Utils.d.ts +101 -0
  68. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  69. package/dist/types/services/core/aqm-reqs.d.ts +65 -0
  70. package/dist/types/services/core/constants.d.ts +99 -0
  71. package/dist/types/services/core/types.d.ts +47 -0
  72. package/dist/types/services/core/websocket/WebSocketManager.d.ts +35 -0
  73. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  74. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  75. package/dist/types/services/core/websocket/types.d.ts +37 -0
  76. package/dist/types/services/index.d.ts +54 -0
  77. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  78. package/dist/types/services/task/TaskManager.d.ts +1 -0
  79. package/dist/types/services/task/TaskUtils.d.ts +92 -0
  80. package/dist/types/services/task/constants.d.ts +84 -0
  81. package/dist/types/services/task/contact.d.ts +69 -0
  82. package/dist/types/services/task/dialer.d.ts +43 -0
  83. package/dist/types/services/task/index.d.ts +650 -0
  84. package/dist/types/services/task/types.d.ts +1319 -0
  85. package/dist/types/types.d.ts +643 -0
  86. package/dist/types/utils/PageCache.d.ts +173 -0
  87. package/dist/types/webex-config.d.ts +53 -0
  88. package/dist/types/webex.d.ts +7 -0
  89. package/dist/types.js +14 -1
  90. package/dist/types.js.map +1 -1
  91. package/dist/webex.js +1 -1
  92. package/package.json +9 -9
  93. package/src/cc.ts +157 -30
  94. package/src/constants.ts +4 -0
  95. package/src/index.ts +1 -0
  96. package/src/metrics/behavioral-events.ts +14 -0
  97. package/src/metrics/constants.ts +11 -0
  98. package/src/services/ApiAiAssistant.ts +217 -0
  99. package/src/services/agent/types.ts +1 -1
  100. package/src/services/config/Util.ts +8 -0
  101. package/src/services/config/constants.ts +12 -0
  102. package/src/services/config/index.ts +45 -1
  103. package/src/services/config/types.ts +67 -0
  104. package/src/services/constants.ts +29 -0
  105. package/src/services/core/Err.ts +1 -0
  106. package/src/services/core/Utils.ts +32 -5
  107. package/src/services/core/aqm-reqs.ts +100 -22
  108. package/src/services/core/websocket/WebSocketManager.ts +21 -6
  109. package/src/services/core/websocket/connection-service.ts +5 -1
  110. package/src/services/index.ts +4 -0
  111. package/src/services/task/TaskManager.ts +174 -27
  112. package/src/services/task/TaskUtils.ts +12 -0
  113. package/src/services/task/constants.ts +16 -0
  114. package/src/services/task/dialer.ts +56 -1
  115. package/src/services/task/types.ts +24 -0
  116. package/src/types.ts +40 -1
  117. package/test/unit/spec/cc.ts +163 -23
  118. package/test/unit/spec/services/ApiAiAssistant.ts +115 -0
  119. package/test/unit/spec/services/config/index.ts +56 -0
  120. package/test/unit/spec/services/core/Utils.ts +63 -1
  121. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +82 -12
  122. package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
  123. package/test/unit/spec/services/task/TaskManager.ts +1119 -251
  124. package/test/unit/spec/services/task/dialer.ts +198 -112
  125. package/umd/contact-center.min.js +2 -2
  126. package/umd/contact-center.min.js.map +1 -1
package/src/cc.ts CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  METHODS,
40
40
  } from './constants';
41
41
  import {AGENT_STATE_AVAILABLE, AGENT_STATE_AVAILABLE_ID} from './services/config/constants';
42
- import {AGENT, WEB_RTC_PREFIX} from './services/constants';
42
+ import {AGENT, RTD_SUBSCRIBE_API, SUBSCRIBE_API, WEB_RTC_PREFIX} from './services/constants';
43
43
  import Services from './services';
44
44
  import WebexRequest from './services/core/WebexRequest';
45
45
  import LoggerProxy from './logger-proxy';
@@ -55,13 +55,20 @@ import {
55
55
  import {ConnectionLostDetails} from './services/core/websocket/types';
56
56
  import TaskManager from './services/task/TaskManager';
57
57
  import WebCallingService from './services/WebCallingService';
58
- import {ITask, TASK_EVENTS, TaskResponse, DialerPayload} from './services/task/types';
58
+ import {
59
+ ITask,
60
+ TASK_EVENTS,
61
+ TaskResponse,
62
+ DialerPayload,
63
+ PreviewContactPayload,
64
+ } from './services/task/types';
59
65
  import MetricsManager from './metrics/MetricsManager';
60
66
  import {METRIC_EVENT_NAMES} from './metrics/constants';
61
67
  import {Failure} from './services/core/GlobalTypes';
62
- import EntryPoint from './services/EntryPoint';
63
- import AddressBook from './services/AddressBook';
64
- import Queue from './services/Queue';
68
+ import {EntryPoint} from './services/EntryPoint';
69
+ import {AddressBook} from './services/AddressBook';
70
+ import {Queue} from './services/Queue';
71
+ import {ApiAIAssistant} from './services/ApiAiAssistant';
65
72
  import type {
66
73
  EntryPointListResponse,
67
74
  EntryPointSearchParams,
@@ -119,6 +126,7 @@ import type {
119
126
  * - `task:established` - Task/call has been connected
120
127
  * - `task:ended` - Task/call has ended
121
128
  * - `task:error` - An error occurred during task handling
129
+ * - `task:campaignPreviewReservation` - Campaign preview contact offered to agent
122
130
  *
123
131
  * @public
124
132
  *
@@ -319,6 +327,13 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
319
327
  */
320
328
  private queue: Queue;
321
329
 
330
+ /**
331
+ * API instance for AI Assistant operations such as transcript controls.
332
+ * @type {ApiAIAssistant}
333
+ * @public
334
+ */
335
+ public apiAIAssistant: ApiAIAssistant;
336
+
322
337
  /**
323
338
  * Logger utility for Contact Center plugin
324
339
  * Provides consistent logging across the plugin
@@ -357,8 +372,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
357
372
  this.services.webSocketManager.on('message', this.handleWebsocketMessage);
358
373
 
359
374
  this.webCallingService = new WebCallingService(this.$webex);
375
+ this.apiAIAssistant = new ApiAIAssistant(this.$webex);
360
376
  this.metricsManager = MetricsManager.getInstance({webex: this.$webex});
361
377
  this.taskManager = TaskManager.getTaskManager(
378
+ this.apiAIAssistant,
362
379
  this.services.contact,
363
380
  this.webCallingService,
364
381
  this.services.webSocketManager
@@ -370,8 +387,6 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
370
387
  this.entryPoint = new EntryPoint(this.$webex);
371
388
  this.addressBook = new AddressBook(this.$webex, () => this.agentConfig?.addressBookId);
372
389
  this.queue = new Queue(this.$webex);
373
-
374
- // Initialize logger
375
390
  LoggerProxy.initialize(this.$webex.logger);
376
391
  });
377
392
  }
@@ -406,6 +421,20 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
406
421
  this.trigger(TASK_EVENTS.TASK_MERGED, task);
407
422
  };
408
423
 
424
+ /**
425
+ * Handles campaign preview reservation events when a contact is offered to the agent
426
+ * @private
427
+ * @param {ITask} task The campaign reservation task
428
+ */
429
+ private handleCampaignPreviewReservation = (task: ITask) => {
430
+ // @ts-ignore
431
+ this.trigger(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
432
+ };
433
+
434
+ private handleRTDWebsocketMessage = (payload: string) => {
435
+ this.taskManager.handleRealtimeWebsocketEvent(payload);
436
+ };
437
+
409
438
  /**
410
439
  * Sets up event listeners for incoming tasks and task hydration
411
440
  * Subscribes to task events from the task manager
@@ -415,6 +444,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
415
444
  this.taskManager.on(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
416
445
  this.taskManager.on(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
417
446
  this.taskManager.on(TASK_EVENTS.TASK_MERGED, this.handleTaskMerged);
447
+ this.taskManager.on(
448
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
449
+ this.handleCampaignPreviewReservation
450
+ );
418
451
  }
419
452
 
420
453
  /**
@@ -543,9 +576,14 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
543
576
 
544
577
  this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
545
578
  this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
579
+ this.taskManager.off(
580
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
581
+ this.handleCampaignPreviewReservation
582
+ );
546
583
  this.taskManager.unregisterIncomingCallEvent();
547
584
 
548
585
  this.services.webSocketManager.off('message', this.handleWebsocketMessage);
586
+ this.services.rtdWebSocketManager.off('message', this.handleRTDWebsocketMessage);
549
587
  this.services.connectionService.off('connectionLost', this.handleConnectionLost);
550
588
 
551
589
  if (
@@ -569,6 +607,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
569
607
  this.services.webSocketManager.close(false, 'Unregistering the SDK');
570
608
  }
571
609
 
610
+ if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
611
+ this.services.rtdWebSocketManager.close(false, 'Unregistering the RTD websocket');
612
+ }
613
+
572
614
  // Clear any cached agent configuration
573
615
  this.agentConfig = null;
574
616
 
@@ -697,6 +739,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
697
739
  try {
698
740
  const data = (await this.services.webSocketManager.initWebSocket({
699
741
  body: this.getConnectionConfig(),
742
+ resource: SUBSCRIBE_API,
700
743
  })) as WelcomeEvent;
701
744
 
702
745
  const agentId = data.agentId;
@@ -712,6 +755,33 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
712
755
  this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
713
756
  this.taskManager.setAgentId(this.agentConfig.agentId);
714
757
  this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
758
+ this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
759
+
760
+ if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable) {
761
+ LoggerProxy.info('Connecting to RTD websocket', {
762
+ module: CC_FILE,
763
+ method: METHODS.CONNECT_WEBSOCKET,
764
+ });
765
+
766
+ await this.services.rtdWebSocketManager
767
+ .initWebSocket({
768
+ body: this.getConnectionConfig(),
769
+ resource: RTD_SUBSCRIBE_API,
770
+ })
771
+ .then(() => {
772
+ LoggerProxy.log('RTD websocket connected successfully', {
773
+ module: CC_FILE,
774
+ method: METHODS.CONNECT_WEBSOCKET,
775
+ });
776
+ this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
777
+ })
778
+ .catch((error) => {
779
+ LoggerProxy.error(`Error during RTD websocket setup: ${error}`, {
780
+ module: CC_FILE,
781
+ method: METHODS.CONNECT_WEBSOCKET,
782
+ });
783
+ });
784
+ }
715
785
 
716
786
  if (
717
787
  this.agentConfig.webRtcEnabled &&
@@ -785,7 +855,11 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
785
855
  METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
786
856
  ]);
787
857
 
788
- if (data.loginOption === LoginOption.AGENT_DN && !isValidDialNumber(data.dialNumber)) {
858
+ const dialPlanEntries = this.agentConfig?.dialPlan?.dialPlanEntity ?? [];
859
+ if (
860
+ data.loginOption === LoginOption.AGENT_DN &&
861
+ !isValidDialNumber(data.dialNumber, dialPlanEntries)
862
+ ) {
789
863
  const error = new Error('INVALID_DIAL_NUMBER');
790
864
  // @ts-ignore - adding custom key to the error object
791
865
  error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
@@ -1509,6 +1583,79 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1509
1583
  }
1510
1584
  }
1511
1585
 
1586
+ /**
1587
+ * Accepts a campaign preview contact, initiating the outbound call.
1588
+ *
1589
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1590
+ * `AgentOfferCampaignReservation` event. The agent can then accept the preview contact
1591
+ * to initiate the outbound call.
1592
+ *
1593
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1594
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1595
+ * @throws {Error} If the operation fails (network error, customer unavailable, etc.)
1596
+ *
1597
+ * @example
1598
+ * ```typescript
1599
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1600
+ * const { interactionId } = task.data;
1601
+ * // campaignId is the campaign name (e.g. "MyCampaign"), not a UUID
1602
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1603
+ *
1604
+ * const result = await webex.cc.acceptPreviewContact({ interactionId, campaignId });
1605
+ * });
1606
+ * ```
1607
+ */
1608
+ public async acceptPreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
1609
+ LoggerProxy.info('Accepting campaign preview contact', {
1610
+ module: CC_FILE,
1611
+ method: METHODS.ACCEPT_PREVIEW_CONTACT,
1612
+ });
1613
+ try {
1614
+ this.metricsManager.timeEvent([
1615
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS,
1616
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
1617
+ ]);
1618
+
1619
+ const result = await this.services.dialer.acceptPreviewContact({data: payload});
1620
+
1621
+ this.metricsManager.trackEvent(
1622
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS,
1623
+ {
1624
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1625
+ interactionId: payload.interactionId,
1626
+ campaignId: payload.campaignId,
1627
+ },
1628
+ ['behavioral', 'business', 'operational']
1629
+ );
1630
+
1631
+ LoggerProxy.log('Campaign preview contact accepted successfully', {
1632
+ module: CC_FILE,
1633
+ method: METHODS.ACCEPT_PREVIEW_CONTACT,
1634
+ trackingId: result.trackingId,
1635
+ interactionId: payload.interactionId,
1636
+ });
1637
+
1638
+ return result;
1639
+ } catch (error) {
1640
+ const failure = error.details as Failure;
1641
+ this.metricsManager.trackEvent(
1642
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
1643
+ {
1644
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
1645
+ interactionId: payload.interactionId,
1646
+ campaignId: payload.campaignId,
1647
+ },
1648
+ ['behavioral', 'business', 'operational']
1649
+ );
1650
+ const {error: detailedError} = getErrorDetails(
1651
+ error,
1652
+ METHODS.ACCEPT_PREVIEW_CONTACT,
1653
+ CC_FILE
1654
+ );
1655
+ throw detailedError;
1656
+ }
1657
+ }
1658
+
1512
1659
  /**
1513
1660
  * Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
1514
1661
  *
@@ -1656,6 +1803,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1656
1803
  /**
1657
1804
  * Updates the agent device type and login configuration.
1658
1805
  * Use this method to change how an agent connects to the contact center system (e.g., switching from browser-based calling to a desk phone extension).
1806
+ * Change to any field of the profile is allowed;
1659
1807
  *
1660
1808
  * @param {AgentDeviceUpdate} data Configuration containing:
1661
1809
  * - loginOption: New device type ('BROWSER', 'EXTENSION', 'AGENT_DN')
@@ -1695,29 +1843,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1695
1843
  });
1696
1844
 
1697
1845
  try {
1698
- // Only block if both loginOption AND teamId remain unchanged
1699
- if (
1700
- this.webCallingService?.loginOption === data.loginOption &&
1701
- data.teamId === this.agentConfig.currentTeamId
1702
- ) {
1703
- const message =
1704
- 'Will not proceed with device update as new Device type is same as current device type and teamId is same as current teamId';
1705
- const err = new Error(message) as GenericError;
1706
- err.details = {
1707
- type: 'Identical Device Change Failure',
1708
- orgId: this.$webex.credentials.getOrgId(),
1709
- trackingId,
1710
- data: {
1711
- agentId: this.agentConfig.agentId,
1712
- reasonCode: 'R002',
1713
- reason: message,
1714
- },
1715
- };
1716
- throw err;
1717
- }
1718
-
1719
1846
  await this.stationLogout({
1720
- logoutReason: 'User requested agent device change',
1847
+ logoutReason: 'User requested agent profile update',
1721
1848
  });
1722
1849
 
1723
1850
  const loginPayload: AgentLogin = {
package/src/constants.ts CHANGED
@@ -49,4 +49,8 @@ export const METHODS = {
49
49
  HANDLE_INCOMING_TASK: 'handleIncomingTask',
50
50
  HANDLE_TASK_HYDRATE: 'handleTaskHydrate',
51
51
  INCOMING_TASK_LISTENER: 'incomingTaskListener',
52
+ ACCEPT_PREVIEW_CONTACT: 'acceptPreviewContact',
53
+ GET_BASE_URL: 'getBaseUrl',
54
+ SEND_EVENT: 'sendEvent',
55
+ FETCH_HISTORIC_TRANSCRIPTS: 'fetchHistoricTranscripts',
52
56
  };
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ export {default as routingAgent} from './services/agent';
28
28
 
29
29
  // API exports (AddressBook is public, EntryPoint and Queue are accessed via cc wrappers)
30
30
  export {default as AddressBook} from './services/AddressBook';
31
+ export {default as ApiAIAssistant} from './services/ApiAiAssistant';
31
32
 
32
33
  /** EntryPoint API types */
33
34
  export type {
@@ -435,6 +435,20 @@ const eventTaxonomyMap: Record<string, BehavioralEventTaxonomy> = {
435
435
  target: 'outdial_ani_ep_fetch',
436
436
  verb: 'fail',
437
437
  },
438
+
439
+ // Campaign Preview API Events
440
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS]: {
441
+ product,
442
+ agent: 'user',
443
+ target: 'campaign_preview_accept',
444
+ verb: 'complete',
445
+ },
446
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED]: {
447
+ product,
448
+ agent: 'user',
449
+ target: 'campaign_preview_accept',
450
+ verb: 'fail',
451
+ },
438
452
  };
439
453
 
440
454
  /**
@@ -159,6 +159,17 @@ export const METRIC_EVENT_NAMES = {
159
159
  // Outdial ANI Entries API Events
160
160
  OUTDIAL_ANI_EP_FETCH_SUCCESS: 'Outdial ANI Entries Fetch Success',
161
161
  OUTDIAL_ANI_EP_FETCH_FAILED: 'Outdial ANI Entries Fetch Failed',
162
+
163
+ // Campaign Preview API Events
164
+ CAMPAIGN_PREVIEW_ACCEPT_SUCCESS: 'Campaign Preview Accept Success',
165
+ CAMPAIGN_PREVIEW_ACCEPT_FAILED: 'Campaign Preview Accept Failed',
166
+
167
+ // AI Assistant transcript events
168
+ AI_ASSISTANT_SEND_EVENT_SUCCESS: 'AI Assistant Send Event Success',
169
+ AI_ASSISTANT_SEND_EVENT_FAILED: 'AI Assistant Send Event Failed',
170
+ AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_SUCCESS:
171
+ 'AI Assistant Fetch Historic Transcripts Success',
172
+ AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_FAILED: 'AI Assistant Fetch Historic Transcripts Failed',
162
173
  } as const;
163
174
 
164
175
  /**
@@ -0,0 +1,217 @@
1
+ import LoggerProxy from '../logger-proxy';
2
+ import MetricsManager from '../metrics/MetricsManager';
3
+ import {METRIC_EVENT_NAMES} from '../metrics/constants';
4
+ import {CC_FILE, METHODS} from '../constants';
5
+ import {
6
+ HTTP_METHODS,
7
+ WebexSDK,
8
+ IHttpResponse,
9
+ TranscriptAction,
10
+ AIAssistantEventType,
11
+ AIAssistantEventName,
12
+ HistoricTranscriptsResponse,
13
+ } from '../types';
14
+ import {getErrorDetails} from './core/Utils';
15
+ import {
16
+ AI_ASSISTANT_BASE_URL_TEMPLATE,
17
+ AI_ASSISTANT_ENV_MAP,
18
+ AI_ASSISTANT_API_URLS,
19
+ WCC_API_GATEWAY,
20
+ } from './constants';
21
+ import {AIFeatureFlags} from './config/types';
22
+
23
+ /**
24
+ * ApiAIAssistant provides AI Assistant APIs for transcript controls.
25
+ * @public
26
+ */
27
+ export class ApiAIAssistant {
28
+ private webex: WebexSDK;
29
+ private metricsManager: MetricsManager;
30
+ public aiFeature: AIFeatureFlags;
31
+
32
+ constructor(webex: WebexSDK) {
33
+ this.webex = webex;
34
+ this.metricsManager = MetricsManager.getInstance({webex});
35
+ }
36
+
37
+ public setAIFeatureFlags(aiFeature: AIFeatureFlags): void {
38
+ this.aiFeature = aiFeature;
39
+ }
40
+
41
+ private getBaseUrl(): string {
42
+ const wccApiGatewayUrl = this.webex.internal.services.get(WCC_API_GATEWAY) || '';
43
+
44
+ if (!wccApiGatewayUrl) {
45
+ const {error: detailedError} = getErrorDetails(
46
+ new Error('AI_ASSISTANT_BASE_URL_NOT_AVAILABLE'),
47
+ METHODS.GET_BASE_URL,
48
+ CC_FILE
49
+ );
50
+ throw detailedError;
51
+ }
52
+
53
+ let hostname = '';
54
+ try {
55
+ hostname = new URL(wccApiGatewayUrl).hostname.toLowerCase();
56
+ } catch (error) {
57
+ hostname = wccApiGatewayUrl.toLowerCase();
58
+ }
59
+
60
+ const resolvedEnv = AI_ASSISTANT_ENV_MAP[hostname];
61
+ if (!resolvedEnv) {
62
+ const {error: detailedError} = getErrorDetails(
63
+ new Error('AI_ASSISTANT_BASE_URL_NOT_AVAILABLE'),
64
+ METHODS.GET_BASE_URL,
65
+ CC_FILE
66
+ );
67
+ throw detailedError;
68
+ }
69
+
70
+ return AI_ASSISTANT_BASE_URL_TEMPLATE.replace('%s', resolvedEnv);
71
+ }
72
+
73
+ /**
74
+ * Sends an event to the AI Assistant service.
75
+ * @param agentId - agent identifier
76
+ * @param interactionId - interaction/conversation identifier
77
+ * @param eventType - the type of event (e.g. 'CUSTOM_EVENT')
78
+ * @param eventName - the name of the event (e.g. 'GET_TRANSCRIPTS')
79
+ * @param action - action within eventDetails (e.g. 'START' or 'STOP')
80
+ */
81
+ public async sendEvent(
82
+ agentId: string,
83
+ interactionId: string,
84
+ eventType: AIAssistantEventType,
85
+ eventName: AIAssistantEventName,
86
+ action: TranscriptAction
87
+ ): Promise<Record<string, unknown>> {
88
+ LoggerProxy.info('Sending event', {
89
+ module: CC_FILE,
90
+ method: METHODS.SEND_EVENT,
91
+ interactionId,
92
+ data: {eventType, eventName, action},
93
+ });
94
+ this.metricsManager.timeEvent([
95
+ METRIC_EVENT_NAMES.AI_ASSISTANT_SEND_EVENT_SUCCESS,
96
+ METRIC_EVENT_NAMES.AI_ASSISTANT_SEND_EVENT_FAILED,
97
+ ]);
98
+
99
+ try {
100
+ const baseUrl = this.getBaseUrl();
101
+ const orgId = this.webex.credentials.getOrgId();
102
+ const response = (await this.webex.request({
103
+ uri: `${baseUrl}${AI_ASSISTANT_API_URLS.EVENT}`,
104
+ method: HTTP_METHODS.POST,
105
+ addAuthHeader: true,
106
+ body: {
107
+ agentId,
108
+ orgId,
109
+ eventType,
110
+ eventName,
111
+ eventDetails: {
112
+ data: {
113
+ interactionId,
114
+ action,
115
+ actionTimeStamp: String(Date.now()),
116
+ },
117
+ },
118
+ },
119
+ })) as IHttpResponse;
120
+
121
+ this.metricsManager.trackEvent(
122
+ METRIC_EVENT_NAMES.AI_ASSISTANT_SEND_EVENT_SUCCESS,
123
+ {agentId, orgId, interactionId, eventType, eventName, action},
124
+ ['operational']
125
+ );
126
+
127
+ return response?.body || {};
128
+ } catch (error) {
129
+ this.metricsManager.trackEvent(
130
+ METRIC_EVENT_NAMES.AI_ASSISTANT_SEND_EVENT_FAILED,
131
+ {
132
+ interactionId,
133
+ eventType,
134
+ eventName,
135
+ action,
136
+ error: error instanceof Error ? error.message : String(error),
137
+ },
138
+ ['operational']
139
+ );
140
+
141
+ const {error: detailedError} = getErrorDetails(error, METHODS.SEND_EVENT, CC_FILE);
142
+ throw detailedError;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Fetches historic transcripts for an interaction.
148
+ * This API is allowed only when real-time transcription feature is enabled.
149
+ *
150
+ * @param interactionId - interaction/conversation identifier
151
+ */
152
+ public async fetchHistoricTranscripts(
153
+ agentId: string,
154
+ interactionId: string
155
+ ): Promise<HistoricTranscriptsResponse> {
156
+ LoggerProxy.info('Fetching historic transcripts', {
157
+ module: CC_FILE,
158
+ method: METHODS.FETCH_HISTORIC_TRANSCRIPTS,
159
+ interactionId,
160
+ });
161
+ this.metricsManager.timeEvent([
162
+ METRIC_EVENT_NAMES.AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_SUCCESS,
163
+ METRIC_EVENT_NAMES.AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_FAILED,
164
+ ]);
165
+ if (!this.aiFeature?.realtimeTranscripts?.enable) {
166
+ const {error: detailedError} = getErrorDetails(
167
+ new Error('REAL_TIME_TRANSCRIPTION_NOT_ENABLED'),
168
+ METHODS.FETCH_HISTORIC_TRANSCRIPTS,
169
+ CC_FILE
170
+ );
171
+ throw detailedError;
172
+ }
173
+
174
+ try {
175
+ const baseUrl = this.getBaseUrl();
176
+ const orgId = this.webex.credentials.getOrgId();
177
+ const response = (await this.webex.request({
178
+ uri: `${baseUrl}${AI_ASSISTANT_API_URLS.TRANSCRIPTS_LIST}`,
179
+ method: HTTP_METHODS.POST,
180
+ addAuthHeader: true,
181
+ body: {
182
+ agentId,
183
+ orgId,
184
+ interactionId,
185
+ },
186
+ })) as IHttpResponse;
187
+
188
+ this.metricsManager.trackEvent(
189
+ METRIC_EVENT_NAMES.AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_SUCCESS,
190
+ {agentId, orgId, interactionId},
191
+ ['operational']
192
+ );
193
+
194
+ return response.body as HistoricTranscriptsResponse;
195
+ } catch (error) {
196
+ this.metricsManager.trackEvent(
197
+ METRIC_EVENT_NAMES.AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_FAILED,
198
+ {
199
+ interactionId,
200
+ error: error instanceof Error ? error.message : String(error),
201
+ },
202
+ ['operational']
203
+ );
204
+ if (error instanceof Error) {
205
+ throw error;
206
+ }
207
+ const {error: detailedError} = getErrorDetails(
208
+ error,
209
+ METHODS.FETCH_HISTORIC_TRANSCRIPTS,
210
+ CC_FILE
211
+ );
212
+ throw detailedError;
213
+ }
214
+ }
215
+ }
216
+
217
+ export default ApiAIAssistant;
@@ -259,7 +259,7 @@ export type Logout = {
259
259
  logoutReason?:
260
260
  | 'User requested logout'
261
261
  | 'Inactivity Logout'
262
- | 'User requested agent device change';
262
+ | 'User requested agent profile update';
263
263
  };
264
264
 
265
265
  /**
@@ -14,6 +14,8 @@ import {
14
14
  TenantData,
15
15
  URLMapping,
16
16
  WRAP_UP_CODE,
17
+ AIFeatureFlagsResponse,
18
+ AIFeatureFlags,
17
19
  } from './types';
18
20
 
19
21
  /**
@@ -140,6 +142,7 @@ function parseAgentConfigs(profileData: {
140
142
  dialPlanData: DialPlanEntity[];
141
143
  urlMapping: URLMapping[];
142
144
  multimediaProfileId: string;
145
+ aiFeatureFlags: AIFeatureFlagsResponse;
143
146
  }): Profile {
144
147
  const {
145
148
  userData,
@@ -151,6 +154,7 @@ function parseAgentConfigs(profileData: {
151
154
  agentProfileData,
152
155
  dialPlanData,
153
156
  urlMapping,
157
+ aiFeatureFlags,
154
158
  } = profileData;
155
159
 
156
160
  const tenantDataTimeout = tenantData.timeoutDesktopInactivityEnabled
@@ -180,6 +184,8 @@ function parseAgentConfigs(profileData: {
180
184
  }); // pushing available state to idle codes
181
185
 
182
186
  const defaultWrapUpData = getDefaultWrapUpCode(wrapupCodes);
187
+ const aiFeature: AIFeatureFlags | undefined =
188
+ aiFeatureFlags?.data && aiFeatureFlags.data.length > 0 ? aiFeatureFlags.data[0] : undefined;
183
189
 
184
190
  const finalData = {
185
191
  teams: teamData,
@@ -204,6 +210,7 @@ function parseAgentConfigs(profileData: {
204
210
  siteId: userData.siteId,
205
211
  enterpriseId: orgInfoData.tenantId,
206
212
  tenantTimezone: orgInfoData.timezone,
213
+ environment: orgInfoData.environment,
207
214
  privacyShieldVisible: tenantData.privacyShieldVisible,
208
215
  organizationIdleCodes: [], // TODO: for supervisor, getOrgFilteredIdleCodes(auxCodes, false),
209
216
  idleCodesAccess: agentProfileData.accessIdleCode as 'ALL' | 'SPECIFIC',
@@ -253,6 +260,7 @@ function parseAgentConfigs(profileData: {
253
260
  webexConfig: getWebexConfig(agentProfileData),
254
261
  lostConnectionRecoveryTimeout:
255
262
  tenantData.lostConnectionRecoveryTimeout || LOST_CONNECTION_RECOVERY_TIMEOUT,
263
+ aiFeature,
256
264
  };
257
265
 
258
266
  return finalData;
@@ -67,6 +67,8 @@ export const METHODS = {
67
67
  GET_TENANT_DATA: 'getTenantData',
68
68
  GET_URL_MAPPING: 'getURLMapping',
69
69
  GET_DIAL_PLAN_DATA: 'getDialPlanData',
70
+ GET_AI_FEATURE_FLAGS: 'getAIFeatureFlags',
71
+ GET_QUEUES: 'getQueues',
70
72
 
71
73
  // Util methods
72
74
  PARSE_AGENT_CONFIGS: 'parseAgentConfigs',
@@ -232,6 +234,16 @@ export const endPointMap = {
232
234
  * @ignore
233
235
  */
234
236
  dialPlan: (orgId: string) => `organization/${orgId}/dial-plan?agentView=true`,
237
+ /**
238
+ * Gets the endpoint for listing AI feature flags.
239
+ * @param orgId - Organization ID.
240
+ * @returns The endpoint URL string.
241
+ * @public
242
+ * @example
243
+ * const url = endPointMap.aiFeatureFlags('org123');
244
+ * @ignore
245
+ */
246
+ aiFeature: (orgId: string) => `organization/${orgId}/v2/ai-feature?page=0&pageSize=100`,
235
247
 
236
248
  /**
237
249
  * Gets the endpoint for the queue list with custom query parameters.