@webex/contact-center 3.12.0-next.73 → 3.12.0-next.74

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 (188) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +53 -38
  24. package/dist/cc.js.map +1 -1
  25. package/dist/config.js +6 -0
  26. package/dist/config.js.map +1 -1
  27. package/dist/constants.js +14 -1
  28. package/dist/constants.js.map +1 -1
  29. package/dist/index.js +13 -5
  30. package/dist/index.js.map +1 -1
  31. package/dist/metrics/behavioral-events.js +26 -0
  32. package/dist/metrics/behavioral-events.js.map +1 -1
  33. package/dist/metrics/constants.js +9 -3
  34. package/dist/metrics/constants.js.map +1 -1
  35. package/dist/services/ApiAiAssistant.js +74 -3
  36. package/dist/services/ApiAiAssistant.js.map +1 -1
  37. package/dist/services/config/Util.js +2 -2
  38. package/dist/services/config/Util.js.map +1 -1
  39. package/dist/services/config/types.js +25 -9
  40. package/dist/services/config/types.js.map +1 -1
  41. package/dist/services/core/Utils.js +74 -27
  42. package/dist/services/core/Utils.js.map +1 -1
  43. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  44. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  45. package/dist/services/core/websocket/types.js.map +1 -1
  46. package/dist/services/index.js +1 -1
  47. package/dist/services/index.js.map +1 -1
  48. package/dist/services/task/Task.js +688 -0
  49. package/dist/services/task/Task.js.map +1 -0
  50. package/dist/services/task/TaskFactory.js +45 -0
  51. package/dist/services/task/TaskFactory.js.map +1 -0
  52. package/dist/services/task/TaskManager.js +725 -612
  53. package/dist/services/task/TaskManager.js.map +1 -1
  54. package/dist/services/task/TaskUtils.js +162 -26
  55. package/dist/services/task/TaskUtils.js.map +1 -1
  56. package/dist/services/task/constants.js +6 -1
  57. package/dist/services/task/constants.js.map +1 -1
  58. package/dist/services/task/digital/Digital.js +77 -0
  59. package/dist/services/task/digital/Digital.js.map +1 -0
  60. package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
  61. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  62. package/dist/services/task/state-machine/actions.js +543 -0
  63. package/dist/services/task/state-machine/actions.js.map +1 -0
  64. package/dist/services/task/state-machine/constants.js +161 -0
  65. package/dist/services/task/state-machine/constants.js.map +1 -0
  66. package/dist/services/task/state-machine/guards.js +340 -0
  67. package/dist/services/task/state-machine/guards.js.map +1 -0
  68. package/dist/services/task/state-machine/index.js +53 -0
  69. package/dist/services/task/state-machine/index.js.map +1 -0
  70. package/dist/services/task/state-machine/types.js +54 -0
  71. package/dist/services/task/state-machine/types.js.map +1 -0
  72. package/dist/services/task/state-machine/uiControlsComputer.js +553 -0
  73. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  74. package/dist/services/task/taskDataNormalizer.js +99 -0
  75. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  76. package/dist/services/task/types.js +168 -16
  77. package/dist/services/task/types.js.map +1 -1
  78. package/dist/services/task/voice/Voice.js +1042 -0
  79. package/dist/services/task/voice/Voice.js.map +1 -0
  80. package/dist/services/task/voice/WebRTC.js +149 -0
  81. package/dist/services/task/voice/WebRTC.js.map +1 -0
  82. package/dist/types/cc.d.ts +19 -18
  83. package/dist/types/config.d.ts +6 -0
  84. package/dist/types/constants.d.ts +14 -1
  85. package/dist/types/index.d.ts +11 -5
  86. package/dist/types/metrics/constants.d.ts +7 -1
  87. package/dist/types/services/ApiAiAssistant.d.ts +11 -3
  88. package/dist/types/services/config/types.d.ts +113 -9
  89. package/dist/types/services/core/Utils.d.ts +23 -10
  90. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  91. package/dist/types/services/core/websocket/types.d.ts +1 -1
  92. package/dist/types/services/index.d.ts +1 -1
  93. package/dist/types/services/task/Task.d.ts +157 -0
  94. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  95. package/dist/types/services/task/TaskUtils.d.ts +46 -2
  96. package/dist/types/services/task/constants.d.ts +5 -0
  97. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  98. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
  99. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  100. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  101. package/dist/types/services/task/state-machine/guards.d.ts +90 -0
  102. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  103. package/dist/types/services/task/state-machine/types.d.ts +267 -0
  104. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  105. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  106. package/dist/types/services/task/types.d.ts +551 -78
  107. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  108. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  109. package/dist/types/types.d.ts +92 -0
  110. package/dist/types/webex.d.ts +1 -0
  111. package/dist/types.js +85 -0
  112. package/dist/types.js.map +1 -1
  113. package/dist/webex.js +14 -2
  114. package/dist/webex.js.map +1 -1
  115. package/package.json +15 -12
  116. package/src/cc.ts +63 -43
  117. package/src/config.ts +6 -0
  118. package/src/constants.ts +14 -1
  119. package/src/index.ts +14 -5
  120. package/src/metrics/ai-docs/AGENTS.md +348 -0
  121. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  122. package/src/metrics/behavioral-events.ts +28 -0
  123. package/src/metrics/constants.ts +9 -4
  124. package/src/services/ApiAiAssistant.ts +104 -3
  125. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  126. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  127. package/src/services/ai-docs/AGENTS.md +384 -0
  128. package/src/services/config/Util.ts +2 -2
  129. package/src/services/config/ai-docs/AGENTS.md +253 -0
  130. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  131. package/src/services/config/types.ts +116 -10
  132. package/src/services/core/Utils.ts +85 -34
  133. package/src/services/core/ai-docs/AGENTS.md +379 -0
  134. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  135. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  136. package/src/services/core/websocket/types.ts +1 -1
  137. package/src/services/index.ts +1 -1
  138. package/src/services/task/Task.ts +837 -0
  139. package/src/services/task/TaskFactory.ts +55 -0
  140. package/src/services/task/TaskManager.ts +738 -697
  141. package/src/services/task/TaskUtils.ts +205 -25
  142. package/src/services/task/ai-docs/AGENTS.md +455 -0
  143. package/src/services/task/ai-docs/ARCHITECTURE.md +585 -0
  144. package/src/services/task/constants.ts +5 -0
  145. package/src/services/task/digital/Digital.ts +95 -0
  146. package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
  147. package/src/services/task/state-machine/actions.ts +685 -0
  148. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  149. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  150. package/src/services/task/state-machine/constants.ts +172 -0
  151. package/src/services/task/state-machine/guards.ts +406 -0
  152. package/src/services/task/state-machine/index.ts +28 -0
  153. package/src/services/task/state-machine/types.ts +241 -0
  154. package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
  155. package/src/services/task/taskDataNormalizer.ts +137 -0
  156. package/src/services/task/types.ts +654 -85
  157. package/src/services/task/voice/Voice.ts +1267 -0
  158. package/src/services/task/voice/WebRTC.ts +187 -0
  159. package/src/types.ts +112 -1
  160. package/src/utils/AGENTS.md +276 -0
  161. package/src/webex.js +2 -0
  162. package/test/unit/spec/cc.ts +133 -3
  163. package/test/unit/spec/logger-proxy.ts +70 -0
  164. package/test/unit/spec/services/ApiAiAssistant.ts +122 -17
  165. package/test/unit/spec/services/WebCallingService.ts +7 -1
  166. package/test/unit/spec/services/config/index.ts +27 -27
  167. package/test/unit/spec/services/core/Utils.ts +335 -1
  168. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  169. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  170. package/test/unit/spec/services/task/Task.ts +477 -0
  171. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  172. package/test/unit/spec/services/task/TaskManager.ts +821 -1936
  173. package/test/unit/spec/services/task/TaskUtils.ts +206 -0
  174. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  175. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
  176. package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
  177. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  178. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
  179. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  180. package/test/unit/spec/services/task/voice/Voice.ts +631 -0
  181. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  182. package/umd/contact-center.min.js +2 -2
  183. package/umd/contact-center.min.js.map +1 -1
  184. package/dist/services/task/index.js +0 -1530
  185. package/dist/services/task/index.js.map +0 -1
  186. package/dist/types/services/task/index.d.ts +0 -650
  187. package/src/services/task/index.ts +0 -1806
  188. package/test/unit/spec/services/task/index.ts +0 -2205
@@ -59,6 +59,7 @@ global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
59
59
 
60
60
  describe('webex.cc', () => {
61
61
  let webex;
62
+ let mockApiAIAssistant;
62
63
  let mockContact;
63
64
  let mockTaskManager;
64
65
  let mockMetricsManager;
@@ -109,6 +110,14 @@ describe('webex.cc', () => {
109
110
  cancelCtq: jest.fn(),
110
111
  };
111
112
 
113
+ mockApiAIAssistant = {
114
+ sendEvent: jest.fn(),
115
+ getSuggestedResponse: jest.fn(),
116
+ fetchHistoricTranscripts: jest.fn(),
117
+ setAIFeatureFlags: jest.fn(),
118
+ setAgentId: jest.fn(),
119
+ };
120
+
112
121
  // Mock Services instance
113
122
  const mockServicesInstance = {
114
123
  agent: {
@@ -149,17 +158,17 @@ describe('webex.cc', () => {
149
158
  };
150
159
 
151
160
  mockTaskManager = {
161
+ apiAIAssistant: mockApiAIAssistant,
152
162
  contact: mockContact,
153
163
  call: undefined,
154
164
  taskCollection: {},
155
165
  webCallingService: undefined,
156
166
  webSocketManager: mockWebSocketManager,
157
167
  task: undefined,
168
+ setConfigFlags: jest.fn(),
158
169
  setWrapupData: jest.fn(),
159
170
  setAgentId: jest.fn(),
160
171
  setWebRtcEnabled: jest.fn(),
161
- handleRealtimeTranscriptEvent: jest.fn(),
162
- setApiAIAssistant: jest.fn(),
163
172
  registerIncomingCallEvent: jest.fn(),
164
173
  registerTaskListeners: jest.fn(),
165
174
  getTask: jest.fn(),
@@ -298,7 +307,7 @@ describe('webex.cc', () => {
298
307
  isAgentAvailableAfterOutdial: false,
299
308
  isCampaignManagementEnabled: false,
300
309
  outDialEp: '',
301
- isEndCallEnabled: false,
310
+ isEndTaskEnabled: false,
302
311
  isEndConsultEnabled: false,
303
312
  agentDbId: '',
304
313
  allowConsultToQueue: false,
@@ -389,6 +398,13 @@ describe('webex.cc', () => {
389
398
  module: CC_FILE,
390
399
  method: 'connectWebsocket',
391
400
  });
401
+ expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith({
402
+ isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
403
+ isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
404
+ webRtcEnabled: mockAgentProfile.webRtcEnabled,
405
+ autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
406
+ aiFeature: mockAgentProfile.aiFeature,
407
+ });
392
408
  expect(reloadSpy).toHaveBeenCalled();
393
409
  expect(result).toEqual(mockAgentProfile);
394
410
  expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
@@ -532,6 +548,36 @@ describe('webex.cc', () => {
532
548
  expect(result).toEqual(mockAgentProfile);
533
549
  });
534
550
 
551
+ it('should skip mercury connection when disableWebRTCRegistration is enabled', async () => {
552
+ webex.cc.$config = {
553
+ ...webex.cc.$config,
554
+ allowAutomatedRelogin: false,
555
+ disableWebRTCRegistration: true,
556
+ };
557
+ mockAgentProfile.webRtcEnabled = true;
558
+ const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
559
+ const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
560
+ const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
561
+ jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
562
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
563
+ agentId: 'agent123',
564
+ });
565
+
566
+ const result = await webex.cc.register();
567
+
568
+ expect(connectWebsocketSpy).toHaveBeenCalled();
569
+ expect(setupEventListenersSpy).toHaveBeenCalled();
570
+ expect(mercurySpy).not.toHaveBeenCalled();
571
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
572
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
573
+ {
574
+ module: CC_FILE,
575
+ method: 'connectWebsocket',
576
+ }
577
+ );
578
+ expect(result).toEqual(mockAgentProfile);
579
+ });
580
+
535
581
  it('should not attempt for mercury connection when webrtc is disabled', async () => {
536
582
  mockAgentProfile.webRtcEnabled = false;
537
583
  mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
@@ -729,6 +775,59 @@ describe('webex.cc', () => {
729
775
  );
730
776
  });
731
777
 
778
+ it('should skip web calling line registration when disableWebRTCRegistration is enabled', async () => {
779
+ const options = {
780
+ teamId: 'teamId',
781
+ loginOption: LoginOption.BROWSER,
782
+ };
783
+
784
+ webex.cc.$config = {
785
+ ...webex.cc.$config,
786
+ disableWebRTCRegistration: true,
787
+ };
788
+ webex.cc.agentConfig = {
789
+ agentId: 'agentId',
790
+ webRtcEnabled: true,
791
+ loginVoiceOptions: ['BROWSER', 'EXTENSION', 'AGENT_DN'],
792
+ };
793
+
794
+ const registerWebCallingLineSpy = jest.spyOn(
795
+ webex.cc.webCallingService,
796
+ 'registerWebCallingLine'
797
+ );
798
+
799
+ jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
800
+ data: {
801
+ loginOption: LoginOption.BROWSER,
802
+ agentId: 'agentId',
803
+ teamId: 'teamId',
804
+ siteId: 'siteId',
805
+ roles: [AGENT],
806
+ channelsMap: {
807
+ chat: [],
808
+ email: [],
809
+ social: [],
810
+ telephony: [],
811
+ },
812
+ },
813
+ trackingId: 'notifs_52628',
814
+ orgId: 'orgId',
815
+ type: 'StationLoginSuccess',
816
+ eventType: 'STATION_LOGIN',
817
+ } as unknown as StationLoginSuccess);
818
+
819
+ await webex.cc.stationLogin(options);
820
+
821
+ expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
822
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
823
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
824
+ {
825
+ module: CC_FILE,
826
+ method: 'stationLogin',
827
+ }
828
+ );
829
+ });
830
+
732
831
  it('should not attempt mobius registration for LoginOption.BROWSER if webrtc is disabled', async () => {
733
832
  const options = {
734
833
  teamId: 'teamId',
@@ -1543,6 +1642,27 @@ describe('webex.cc', () => {
1543
1642
  });
1544
1643
  });
1545
1644
 
1645
+ describe('getQueues', () => {
1646
+ it('delegates to the queue service when successful', async () => {
1647
+ const mockQueuesResponse = [{queueId: 'queue1', queueName: 'Queue 1'}];
1648
+ const queueSpy = jest
1649
+ .spyOn(webex.cc.queue, 'getQueues')
1650
+ .mockResolvedValue(mockQueuesResponse as any);
1651
+
1652
+ const result = await webex.cc.getQueues({page: 1});
1653
+
1654
+ expect(queueSpy).toHaveBeenCalledWith({page: 1});
1655
+ expect(result).toBe(mockQueuesResponse);
1656
+ });
1657
+
1658
+ it('propagates queue service errors', async () => {
1659
+ const error = new Error('Test error.');
1660
+ jest.spyOn(webex.cc.queue, 'getQueues').mockRejectedValue(error);
1661
+
1662
+ await expect(webex.cc.getQueues()).rejects.toThrow('Test error.');
1663
+ });
1664
+ });
1665
+
1546
1666
  describe('uploadLogs', () => {
1547
1667
  it('should upload logs successfully', async () => {
1548
1668
  const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
@@ -1572,6 +1692,7 @@ describe('webex.cc', () => {
1572
1692
 
1573
1693
  describe('unregister', () => {
1574
1694
  let mockWebSocketManager;
1695
+ let mockRTDWebSocketManager;
1575
1696
  let mercuryDisconnectSpy;
1576
1697
  let deviceUnregisterSpy;
1577
1698
 
@@ -1588,8 +1709,15 @@ describe('webex.cc', () => {
1588
1709
  off: jest.fn(),
1589
1710
  on: jest.fn(),
1590
1711
  };
1712
+ mockRTDWebSocketManager = {
1713
+ isSocketClosed: false,
1714
+ close: jest.fn(),
1715
+ off: jest.fn(),
1716
+ on: jest.fn(),
1717
+ };
1591
1718
 
1592
1719
  webex.cc.services.webSocketManager = mockWebSocketManager;
1720
+ webex.cc.services.rtdWebSocketManager = mockRTDWebSocketManager;
1593
1721
 
1594
1722
  webex.internal = webex.internal || {};
1595
1723
  webex.internal.mercury = {
@@ -1728,6 +1856,7 @@ describe('webex.cc', () => {
1728
1856
  expect(webex.internal.mercury.off).not.toHaveBeenCalled();
1729
1857
  expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
1730
1858
  expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1859
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1731
1860
  });
1732
1861
 
1733
1862
  it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
@@ -1745,6 +1874,7 @@ describe('webex.cc', () => {
1745
1874
  expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1746
1875
 
1747
1876
  expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1877
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1748
1878
  expect(webex.cc.agentConfig).toBeNull();
1749
1879
  });
1750
1880
 
@@ -0,0 +1,70 @@
1
+ import LoggerProxy from '../../../src/logger-proxy';
2
+
3
+ describe('LoggerProxy', () => {
4
+ beforeEach(() => {
5
+ // Reset between tests
6
+ (LoggerProxy as any).logger = undefined;
7
+ });
8
+
9
+ it('no-ops when logger is not initialized', () => {
10
+ expect(() => {
11
+ LoggerProxy.log('msg');
12
+ LoggerProxy.info('msg');
13
+ LoggerProxy.warn('msg');
14
+ LoggerProxy.trace('msg');
15
+ LoggerProxy.error('msg');
16
+ }).not.toThrow();
17
+ });
18
+
19
+ it('routes calls to the injected logger with formatted context', () => {
20
+ const logger = {
21
+ log: jest.fn(),
22
+ info: jest.fn(),
23
+ warn: jest.fn(),
24
+ trace: jest.fn(),
25
+ error: jest.fn(),
26
+ };
27
+
28
+ LoggerProxy.initialize(logger as any);
29
+
30
+ LoggerProxy.log('hello', {module: 'm', method: 'fn', interactionId: 'i', trackingId: 't'});
31
+ expect(logger.log).toHaveBeenCalled();
32
+ expect(String(logger.log.mock.calls[0][0])).toContain('module:m - method:fn');
33
+ expect(String(logger.log.mock.calls[0][0])).toContain('interactionId:i');
34
+ expect(String(logger.log.mock.calls[0][0])).toContain('trackingId:t');
35
+
36
+ LoggerProxy.info('hello', {module: 'm', method: 'fn'});
37
+ LoggerProxy.warn('hello', {module: 'm', method: 'fn'});
38
+ LoggerProxy.trace('hello', {module: 'm', method: 'fn'});
39
+ LoggerProxy.error('hello', {module: 'm', method: 'fn'});
40
+ expect(logger.info).toHaveBeenCalled();
41
+ expect(logger.warn).toHaveBeenCalled();
42
+ expect(logger.trace).toHaveBeenCalled();
43
+ expect(logger.error).toHaveBeenCalled();
44
+ });
45
+
46
+ it('handles unserializable data and different error shapes', () => {
47
+ const logger = {
48
+ log: jest.fn(),
49
+ info: jest.fn(),
50
+ warn: jest.fn(),
51
+ trace: jest.fn(),
52
+ error: jest.fn(),
53
+ };
54
+ LoggerProxy.initialize(logger as any);
55
+
56
+ const circular: any = {};
57
+ circular.self = circular;
58
+
59
+ LoggerProxy.info('msg', {module: 'm', method: 'fn', data: circular, error: 'oops'});
60
+ expect(String(logger.info.mock.calls[0][0])).toContain('data:[object]');
61
+ expect(String(logger.info.mock.calls[0][0])).toContain('error:oops');
62
+
63
+ const err = new Error('boom');
64
+ LoggerProxy.error('msg', {module: 'm', method: 'fn', error: err});
65
+ const errorLine = String(logger.error.mock.calls[0][0]);
66
+ expect(errorLine).toContain('error:Error:boom');
67
+ expect(errorLine).toContain('stack:');
68
+ });
69
+ });
70
+
@@ -1,6 +1,7 @@
1
1
  import ApiAIAssistant from '../../../../src/services/ApiAiAssistant';
2
2
  import MetricsManager from '../../../../src/metrics/MetricsManager';
3
3
  import LoggerProxy from '../../../../src/logger-proxy';
4
+ import WebexRequest from '../../../../src/services/core/WebexRequest';
4
5
  import {HTTP_METHODS, WebexSDK} from '../../../../src/types';
5
6
 
6
7
  jest.mock('../../../../src/metrics/MetricsManager');
@@ -13,6 +14,9 @@ describe('ApiAIAssistant', () => {
13
14
 
14
15
  beforeEach(() => {
15
16
  jest.clearAllMocks();
17
+ jest.spyOn(WebexRequest, 'getInstance').mockReturnValue({
18
+ uploadLogs: jest.fn(),
19
+ } as any);
16
20
 
17
21
  mockWebex = {
18
22
  credentials: {
@@ -53,23 +57,18 @@ describe('ApiAIAssistant', () => {
53
57
  'START'
54
58
  );
55
59
 
56
- expect(mockWebex.request).toHaveBeenCalledWith({
57
- uri: 'https://api-ai-assistant.produs1.ciscoccservice.com/event',
58
- method: HTTP_METHODS.POST,
59
- addAuthHeader: true,
60
- body: {
61
- agentId: 'test-agent-id',
62
- orgId: 'test-org-id',
63
- eventType: 'CUSTOM_EVENT',
64
- eventName: 'GET_TRANSCRIPTS',
65
- eventDetails: {
66
- data: expect.objectContaining({
67
- interactionId: 'interaction-1',
68
- action: 'START',
69
- }),
70
- },
71
- },
72
- });
60
+ expect(mockWebex.request).toHaveBeenCalledTimes(1);
61
+ const requestArgs = (mockWebex.request as jest.Mock).mock.calls[0][0];
62
+
63
+ expect(requestArgs.uri).toBe('https://api-ai-assistant.produs1.ciscoccservice.com/event');
64
+ expect(requestArgs.method).toBe(HTTP_METHODS.POST);
65
+ expect(requestArgs.addAuthHeader).toBe(true);
66
+ expect(requestArgs.body.agentId).toBe('test-agent-id');
67
+ expect(requestArgs.body.orgId).toBe('test-org-id');
68
+ expect(requestArgs.body.eventType).toBe('CUSTOM_EVENT');
69
+ expect(requestArgs.body.eventName).toBe('GET_TRANSCRIPTS');
70
+ expect(requestArgs.body.eventDetails.data.interactionId).toBe('interaction-1');
71
+ expect(requestArgs.body.eventDetails.data.action).toBe('START');
73
72
  expect(result).toEqual({ok: true});
74
73
  });
75
74
 
@@ -93,6 +92,83 @@ describe('ApiAIAssistant', () => {
93
92
  expect(result).toEqual(responseBody as any);
94
93
  });
95
94
 
95
+ it('should request suggested response without extra context using sendEvent', async () => {
96
+ const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
97
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
98
+
99
+ const result = await apiAIAssistant.getSuggestedResponse({
100
+ agentId: 'test-agent-id',
101
+ interactionId: 'interaction-1',
102
+ });
103
+
104
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
105
+ const [agentId, interactionId, eventType, eventName, action, context, languageCode, trackingId] =
106
+ sendEventSpy.mock.calls[0];
107
+
108
+ expect(agentId).toBe('test-agent-id');
109
+ expect(interactionId).toBe('interaction-1');
110
+ expect(eventType).toBe('CUSTOM_EVENT');
111
+ expect(eventName).toBe('GET_SUGGESTIONS');
112
+ expect(action).toBeUndefined();
113
+ expect(context).toBeUndefined();
114
+ expect(languageCode).toBe('en');
115
+ expect(typeof trackingId).toBe('string');
116
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
117
+ expect(result).toEqual({ok: true});
118
+ });
119
+
120
+ it('should request suggested response with extra context using sendEvent', async () => {
121
+ const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
122
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
123
+
124
+ const result = await apiAIAssistant.getSuggestedResponse({
125
+ agentId: 'test-agent-id',
126
+ interactionId: 'interaction-1',
127
+ context: 'Need assistance with credit card payment due date',
128
+ });
129
+
130
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
131
+ const [agentId, interactionId, eventType, eventName, action, context, languageCode, trackingId] =
132
+ sendEventSpy.mock.calls[0];
133
+
134
+ expect(agentId).toBe('test-agent-id');
135
+ expect(interactionId).toBe('interaction-1');
136
+ expect(eventType).toBe('CUSTOM_EVENT');
137
+ expect(eventName).toBe('ADD_SUGGESTIONS_EXTRA_CONTEXT');
138
+ expect(action).toBeUndefined();
139
+ expect(context).toBe('Need assistance with credit card payment due date');
140
+ expect(languageCode).toBe('en');
141
+ expect(typeof trackingId).toBe('string');
142
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
143
+ expect(result).toEqual({ok: true});
144
+ });
145
+
146
+ it('should treat whitespace-only context as GET_SUGGESTIONS', async () => {
147
+ const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
148
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
149
+
150
+ const result = await apiAIAssistant.getSuggestedResponse({
151
+ agentId: 'test-agent-id',
152
+ interactionId: 'interaction-1',
153
+ context: ' ',
154
+ });
155
+
156
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
157
+ const [agentId, interactionId, eventType, eventName, action, context, languageCode, trackingId] =
158
+ sendEventSpy.mock.calls[0];
159
+
160
+ expect(agentId).toBe('test-agent-id');
161
+ expect(interactionId).toBe('interaction-1');
162
+ expect(eventType).toBe('CUSTOM_EVENT');
163
+ expect(eventName).toBe('GET_SUGGESTIONS');
164
+ expect(action).toBeUndefined();
165
+ expect(context).toBe('');
166
+ expect(languageCode).toBe('en');
167
+ expect(typeof trackingId).toBe('string');
168
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
169
+ expect(result).toEqual({ok: true});
170
+ });
171
+
96
172
  it('should fail when base URL mapping is not available', async () => {
97
173
  (mockWebex.internal.services.get as jest.Mock).mockReturnValue('https://unknown-host.invalid');
98
174
 
@@ -112,4 +188,33 @@ describe('ApiAIAssistant', () => {
112
188
  expect(failed).toBe(true);
113
189
  expect(LoggerProxy.error).toHaveBeenCalled();
114
190
  });
191
+
192
+ it('should fail when realtime transcripts feature is disabled', async () => {
193
+ apiAIAssistant.setAIFeatureFlags({realtimeTranscripts: {enable: false}} as any);
194
+ let errorMessage = '';
195
+
196
+ try {
197
+ await apiAIAssistant.fetchHistoricTranscripts('test-agent-id', 'interaction-1');
198
+ } catch (error) {
199
+ errorMessage = (error as Error)?.message || '';
200
+ }
201
+
202
+ expect(errorMessage).toBe('Error while performing fetchHistoricTranscripts');
203
+ });
204
+
205
+ it('should fail when suggested responses feature is disabled', async () => {
206
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: false}} as any);
207
+ let errorMessage = '';
208
+
209
+ try {
210
+ await apiAIAssistant.getSuggestedResponse({
211
+ agentId: 'test-agent-id',
212
+ interactionId: 'interaction-1',
213
+ });
214
+ } catch (error) {
215
+ errorMessage = (error as Error)?.message || '';
216
+ }
217
+
218
+ expect(errorMessage).toBe('Error while performing getSuggestedResponse');
219
+ });
115
220
  });
@@ -200,11 +200,17 @@ describe('WebCallingService', () => {
200
200
 
201
201
  it('should reject if registration times out', async () => {
202
202
  line = callingClient.getLines().line1 as ILine;
203
+ jest.spyOn(global, 'setTimeout').mockImplementation(((handler: TimerHandler) => {
204
+ if (typeof handler === 'function') {
205
+ handler();
206
+ }
207
+ return 0 as unknown as NodeJS.Timeout;
208
+ }) as typeof setTimeout);
203
209
 
204
210
  const promise = webRTCCalling.registerWebCallingLine();
205
211
 
206
212
  await expect(promise).rejects.toThrow('WebCallingService Registration timed out');
207
- }, 20003); // Increased timeout to 20 seconds
213
+ });
208
214
 
209
215
  it('should handle incoming calls', async () => {
210
216
  line = callingClient.getLines().line1 as ILine;
@@ -66,7 +66,6 @@ describe('AgentConfigService', () => {
66
66
  method: 'GET',
67
67
  });
68
68
  expect(result).toEqual(mockResponse.body);
69
-
70
69
  expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching user data using CI', {
71
70
  module: CONFIG_FILE_NAME,
72
71
  method: 'getUserUsingCI',
@@ -124,7 +123,6 @@ describe('AgentConfigService', () => {
124
123
  method: 'GET',
125
124
  });
126
125
  expect(result).toEqual(mockResponse.body);
127
-
128
126
  expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching desktop profile', {
129
127
  module: CONFIG_FILE_NAME,
130
128
  method: 'getDesktopProfileById',
@@ -183,7 +181,6 @@ describe('AgentConfigService', () => {
183
181
  method: 'GET',
184
182
  });
185
183
  expect(result).toEqual(mockResponse.body);
186
-
187
184
  expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching list of teams', {
188
185
  module: CONFIG_FILE_NAME,
189
186
  method: 'getListOfTeams',
@@ -267,7 +264,6 @@ describe('AgentConfigService', () => {
267
264
  method: 'GET',
268
265
  });
269
266
  expect(result).toEqual(mockResponse.body);
270
-
271
267
  expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching list of aux codes', {
272
268
  module: CONFIG_FILE_NAME,
273
269
  method: 'getListOfAuxCodes',
@@ -495,7 +491,7 @@ describe('AgentConfigService', () => {
495
491
  const mockResponse = {
496
492
  statusCode: 200,
497
493
  body: {
498
- realtimeTranscripts: {enable: true},
494
+ data: [{realtimeTranscripts: {enable: true}}],
499
495
  },
500
496
  };
501
497
  mockWebexRequest.request.mockResolvedValue(mockResponse);
@@ -710,6 +706,32 @@ describe('AgentConfigService', () => {
710
706
  });
711
707
 
712
708
  describe('getAgentConfig', () => {
709
+ const mockTeamData = [
710
+ {id: 'team1', name: 'Support Team'},
711
+ {id: 'team2', name: 'Sales Team'},
712
+ ];
713
+
714
+ const mockOrgInfo = {
715
+ tenantId: 'tenant123',
716
+ timezone: 'GMT',
717
+ environment: 'produs1',
718
+ };
719
+
720
+ const mockSiteInfo = {
721
+ id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
722
+ name: 'Site-1',
723
+ multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
724
+ };
725
+
726
+ const mockURLMapping = [
727
+ {key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
728
+ {key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
729
+ ];
730
+
731
+ const mockAIFeatureFlags = {
732
+ data: [{realtimeTranscripts: {enable: true}}],
733
+ };
734
+
713
735
  beforeEach(() => {
714
736
  jest.clearAllMocks();
715
737
  });
@@ -770,12 +792,6 @@ describe('AgentConfigService', () => {
770
792
  maskSensitiveData: false,
771
793
  };
772
794
 
773
- const mockSiteInfo = {
774
- id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
775
- name: 'Site-1',
776
- multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
777
- };
778
-
779
795
  const mockTenantData = {
780
796
  timeoutDesktopInactivityEnabled: false,
781
797
  timeoutDesktopInactivityMins: 15,
@@ -789,11 +805,6 @@ describe('AgentConfigService', () => {
789
805
  callVariablesSuppressed: false,
790
806
  };
791
807
 
792
- const mockURLMapping = [
793
- {key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
794
- {key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
795
- ];
796
-
797
808
  const mockAuxCodes = [
798
809
  {id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1', isDefault: true},
799
810
  {id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1', isDefault: true},
@@ -915,12 +926,6 @@ describe('AgentConfigService', () => {
915
926
  maskSensitiveData: true,
916
927
  };
917
928
 
918
- const mockSiteInfo = {
919
- id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
920
- name: 'Site-1',
921
- multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
922
- };
923
-
924
929
  const mockTenantData = {
925
930
  timeoutDesktopInactivityEnabled: true,
926
931
  timeoutDesktopInactivityMins: 15,
@@ -935,11 +940,6 @@ describe('AgentConfigService', () => {
935
940
  lostConnectionRecoveryTimeout: 30,
936
941
  };
937
942
 
938
- const mockURLMapping = [
939
- {key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
940
- {key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
941
- ];
942
-
943
943
  const mockAuxCodes = [
944
944
  {id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1'},
945
945
  {id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1'},