@webex/contact-center 3.12.0-next.147 → 3.12.0-next.149

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 (82) hide show
  1. package/.sdd/manifest.json +4 -2
  2. package/ai-docs/CONTRACTS.md +2 -0
  3. package/dist/cc.js +3 -1
  4. package/dist/cc.js.map +1 -1
  5. package/dist/constants.js +10 -1
  6. package/dist/constants.js.map +1 -1
  7. package/dist/index.js +9 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/metrics/constants.js +9 -0
  10. package/dist/metrics/constants.js.map +1 -1
  11. package/dist/services/ApiAiAssistant.js +87 -10
  12. package/dist/services/ApiAiAssistant.js.map +1 -1
  13. package/dist/services/config/types.js +10 -4
  14. package/dist/services/config/types.js.map +1 -1
  15. package/dist/services/core/types.js.map +1 -1
  16. package/dist/services/task/Task.js +202 -1
  17. package/dist/services/task/Task.js.map +1 -1
  18. package/dist/services/task/TaskFactory.js +4 -4
  19. package/dist/services/task/TaskFactory.js.map +1 -1
  20. package/dist/services/task/TaskManager.js +113 -26
  21. package/dist/services/task/TaskManager.js.map +1 -1
  22. package/dist/services/task/TaskUtils.js +11 -1
  23. package/dist/services/task/TaskUtils.js.map +1 -1
  24. package/dist/services/task/constants.js +11 -2
  25. package/dist/services/task/constants.js.map +1 -1
  26. package/dist/services/task/digital/Digital.js +2 -2
  27. package/dist/services/task/digital/Digital.js.map +1 -1
  28. package/dist/services/task/types.js +31 -0
  29. package/dist/services/task/types.js.map +1 -1
  30. package/dist/services/task/voice/Voice.js +2 -2
  31. package/dist/services/task/voice/Voice.js.map +1 -1
  32. package/dist/services/task/voice/WebRTC.js +2 -2
  33. package/dist/services/task/voice/WebRTC.js.map +1 -1
  34. package/dist/types/constants.d.ts +9 -0
  35. package/dist/types/index.d.ts +4 -5
  36. package/dist/types/metrics/constants.d.ts +8 -0
  37. package/dist/types/services/ApiAiAssistant.d.ts +4 -2
  38. package/dist/types/services/config/types.d.ts +17 -7
  39. package/dist/types/services/task/Task.d.ts +15 -4
  40. package/dist/types/services/task/TaskFactory.d.ts +3 -2
  41. package/dist/types/services/task/TaskUtils.d.ts +6 -0
  42. package/dist/types/services/task/constants.d.ts +9 -0
  43. package/dist/types/services/task/digital/Digital.d.ts +3 -2
  44. package/dist/types/services/task/types.d.ts +109 -1
  45. package/dist/types/services/task/voice/Voice.d.ts +3 -2
  46. package/dist/types/services/task/voice/WebRTC.d.ts +3 -2
  47. package/dist/types/types.d.ts +10 -4
  48. package/dist/types.js +8 -4
  49. package/dist/types.js.map +1 -1
  50. package/dist/webex.js +1 -1
  51. package/package.json +3 -3
  52. package/src/cc.ts +4 -0
  53. package/src/constants.ts +12 -0
  54. package/src/index.ts +10 -2
  55. package/src/metrics/ai-docs/metrics-spec.md +12 -4
  56. package/src/metrics/constants.ts +10 -0
  57. package/src/services/ApiAiAssistant.ts +120 -12
  58. package/src/services/config/types.ts +10 -4
  59. package/src/services/core/types.ts +31 -1
  60. package/src/services/task/Task.ts +308 -5
  61. package/src/services/task/TaskFactory.ts +26 -5
  62. package/src/services/task/TaskManager.ts +148 -29
  63. package/src/services/task/TaskUtils.ts +13 -0
  64. package/src/services/task/ai-docs/task-spec.md +30 -5
  65. package/src/services/task/constants.ts +10 -0
  66. package/src/services/task/digital/Digital.ts +8 -3
  67. package/src/services/task/types.ts +139 -0
  68. package/src/services/task/voice/Voice.ts +8 -3
  69. package/src/services/task/voice/WebRTC.ts +6 -3
  70. package/src/types.ts +10 -4
  71. package/test/unit/fixtures/aiSummaryTestUtils.ts +40 -0
  72. package/test/unit/spec/cc.ts +1314 -32
  73. package/test/unit/spec/index.ts +881 -0
  74. package/test/unit/spec/services/ApiAiAssistant.ts +122 -6
  75. package/test/unit/spec/services/core/WebexRequest.ts +5 -6
  76. package/test/unit/spec/services/task/Task.ts +1988 -10
  77. package/test/unit/spec/services/task/TaskFactory.ts +17 -0
  78. package/test/unit/spec/services/task/TaskManager.ts +2327 -180
  79. package/test/unit/spec/services/task/TaskUtils.ts +72 -0
  80. package/test/unit/spec/services/task/voice/Voice.ts +353 -18
  81. package/umd/contact-center.min.js +2 -2
  82. package/umd/contact-center.min.js.map +1 -1
@@ -20,11 +20,22 @@ import {
20
20
  TASK_CHANNEL_TYPE,
21
21
  VOICE_VARIANT,
22
22
  CallId,
23
+ AISummaryAction,
24
+ AISummary,
25
+ AISummaryResponse,
26
+ AISummaryFeatureEnablement,
27
+ AISummaryCapabilities,
23
28
  TaskToggleMuteOptions,
24
29
  TaskTransmitDtmfOptions,
25
30
  } from './types';
26
- import {ENTRY_POINT_TRANSFER_DESTINATION_TYPE, METHODS} from './constants';
27
- import {CC_FILE, TASK_FILE} from '../../constants';
31
+ import {AI_SUMMARY_DURATION_MS, ENTRY_POINT_TRANSFER_DESTINATION_TYPE, METHODS} from './constants';
32
+ import {
33
+ AI_ASSISTANT_CLIENT_TYPE,
34
+ AI_SUMMARY_ERROR_CODES,
35
+ AI_SUMMARY_INVALID_RESPONSE_PAYLOAD,
36
+ CC_FILE,
37
+ TASK_FILE,
38
+ } from '../../constants';
28
39
  import {getErrorDetails} from '../core/Utils';
29
40
  import routingContact from './contact';
30
41
  import MetricsManager from '../../metrics/MetricsManager';
@@ -45,7 +56,10 @@ import {
45
56
  haveUIControlsChanged,
46
57
  } from './state-machine/uiControlsComputer';
47
58
  import AutoWrapup from './AutoWrapup';
48
- import {WrapupData} from '../config/types';
59
+ import type {WrapupData} from '../config/types';
60
+ import {AIAssistantEventName, AIAssistantEventType} from '../../types';
61
+ import {createSummaryError} from './TaskUtils';
62
+ import type ApiAIAssistant from '../ApiAiAssistant';
49
63
 
50
64
  type UIControlConfigInput = Omit<UIControlConfig, 'channelType'> & {
51
65
  channelType?: UIControlConfig['channelType'];
@@ -64,13 +78,21 @@ export default abstract class Task extends EventEmitter implements ITask {
64
78
  protected wrapupData?: WrapupData;
65
79
  public autoWrapup?: AutoWrapup;
66
80
  protected agentId?: string;
81
+ protected agentName?: string;
82
+ private apiAIAssistant?: ApiAIAssistant;
83
+ public aiSummaryCapabilities: Readonly<AISummaryCapabilities> = {
84
+ midCallEnabled: false,
85
+ postCallEnabled: false,
86
+ };
67
87
 
68
88
  constructor(
69
89
  contact: ReturnType<typeof routingContact>,
70
90
  data: TaskData,
71
91
  uiControlConfig: UIControlConfigInput,
72
92
  wrapupData?: WrapupData,
73
- agentId?: string
93
+ agentId?: string,
94
+ agentName?: string,
95
+ apiAIAssistant?: ApiAIAssistant
74
96
  ) {
75
97
  super();
76
98
  this.contact = contact;
@@ -80,6 +102,8 @@ export default abstract class Task extends EventEmitter implements ITask {
80
102
  this.uiControlConfig = {...uiControlConfig, channelType, agentId};
81
103
  this.wrapupData = wrapupData;
82
104
  this.agentId = agentId;
105
+ this.agentName = agentName;
106
+ this.apiAIAssistant = apiAIAssistant;
83
107
  this.metricsManager = MetricsManager.getInstance();
84
108
  this.webCallMap = {};
85
109
  this.currentUiControls = getDefaultUIControls();
@@ -216,6 +240,285 @@ export default abstract class Task extends EventEmitter implements ITask {
216
240
  this.unsupportedMethodError('holdResume');
217
241
  }
218
242
 
243
+ public setFeatureEnablement(enablement: AISummaryFeatureEnablement): void {
244
+ if (enablement.interactionId !== this.data.interactionId) {
245
+ return;
246
+ }
247
+
248
+ this.aiSummaryCapabilities = {
249
+ midCallEnabled: enablement.midCallEnabled === true,
250
+ postCallEnabled: enablement.postCallEnabled === true,
251
+ };
252
+ }
253
+
254
+ public clearFeatureEnablement(): void {
255
+ this.aiSummaryCapabilities = {
256
+ midCallEnabled: false,
257
+ postCallEnabled: false,
258
+ };
259
+ }
260
+
261
+ public async requestPostCallSummary(): Promise<AISummary> {
262
+ const metricFields: Record<string, unknown> = {operation: METHODS.REQUEST_POST_CALL_SUMMARY};
263
+
264
+ try {
265
+ this.metricsManager.timeEvent([
266
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_SUCCESS,
267
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_FAILED,
268
+ ]);
269
+
270
+ const interactionId = this.data.interactionId;
271
+ const conversationId = this.data.interaction?.mainInteractionId || interactionId;
272
+ Object.assign(metricFields, {conversationId, interactionId});
273
+
274
+ if (
275
+ !this.apiAIAssistant ||
276
+ this.apiAIAssistant.aiFeature?.generatedSummaries?.wrapUpSummariesEnabled !== true ||
277
+ !this.aiSummaryCapabilities.postCallEnabled
278
+ ) {
279
+ throw createSummaryError(AI_SUMMARY_ERROR_CODES.POST_CALL_SUMMARY_DISABLED);
280
+ }
281
+
282
+ const result = await this.apiAIAssistant.requestAndWaitForRtd<AISummary>({
283
+ correlationId: conversationId,
284
+ rtdEventType: 'POST_CALL_SUMMARY',
285
+ timeoutMs: AI_SUMMARY_DURATION_MS,
286
+ createTimeoutError: () =>
287
+ createSummaryError(AI_SUMMARY_ERROR_CODES.POST_CALL_SUMMARY_TIMEOUT),
288
+ agentId: this.agentId as string,
289
+ interactionId,
290
+ eventType: AIAssistantEventType.CTI_EVENT,
291
+ eventName: AIAssistantEventName.GET_POST_CALL_SUMMARY,
292
+ eventMetaData: {
293
+ conversationId,
294
+ clientType: AI_ASSISTANT_CLIENT_TYPE,
295
+ },
296
+ timeout: AI_SUMMARY_DURATION_MS,
297
+ });
298
+
299
+ this.metricsManager.trackEvent(
300
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_SUCCESS,
301
+ {taskId: this.data?.interactionId, ...metricFields},
302
+ ['operational']
303
+ );
304
+
305
+ return result;
306
+ } catch (error) {
307
+ this.metricsManager.trackEvent(
308
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_FAILED,
309
+ {
310
+ taskId: this.data?.interactionId,
311
+ ...metricFields,
312
+ failureCode: Task.getAISummaryFailureCode(error),
313
+ },
314
+ ['operational']
315
+ );
316
+ throw error;
317
+ }
318
+ }
319
+
320
+ public async sendPostCallSummaryResponse(response: AISummaryResponse): Promise<void> {
321
+ const metricFields: Record<string, unknown> = {
322
+ operation: METHODS.SEND_POST_CALL_SUMMARY_RESPONSE,
323
+ };
324
+
325
+ try {
326
+ this.metricsManager.timeEvent([
327
+ METRIC_EVENT_NAMES.AI_SUMMARY_POST_CALL_RESPONSE_SUCCESS,
328
+ METRIC_EVENT_NAMES.AI_SUMMARY_POST_CALL_RESPONSE_FAILED,
329
+ ]);
330
+ const interactionId = this.data.interactionId;
331
+ const conversationId = this.data.interaction?.mainInteractionId || interactionId;
332
+ Object.assign(metricFields, {conversationId, interactionId});
333
+
334
+ const publishTimestamp = Date.now();
335
+ const eventName = AIAssistantEventName.POST_CALL_SUMMARY_RESPONSE;
336
+ const eventPayload = {
337
+ conversationId,
338
+ clientType: AI_ASSISTANT_CLIENT_TYPE,
339
+ action: eventName,
340
+ summary: response.summary,
341
+ numberOfTimesViewed: response.numberOfTimesViewed,
342
+ numberOfTimesEdited: response.numberOfTimesEdited,
343
+ numberOfTimesCopied: response.numberOfTimesCopied,
344
+ feedback: response.feedback,
345
+ state: response.state,
346
+ wrapUpCode: response.wrapUpCode,
347
+ };
348
+ await this.apiAIAssistant.sendEvent(
349
+ this.agentId as string,
350
+ interactionId,
351
+ AIAssistantEventType.CTI_EVENT,
352
+ eventName,
353
+ eventPayload,
354
+ undefined,
355
+ undefined,
356
+ publishTimestamp,
357
+ AI_SUMMARY_DURATION_MS
358
+ );
359
+
360
+ this.metricsManager.trackEvent(
361
+ METRIC_EVENT_NAMES.AI_SUMMARY_POST_CALL_RESPONSE_SUCCESS,
362
+ {taskId: this.data?.interactionId, ...metricFields},
363
+ ['operational']
364
+ );
365
+ } catch (error) {
366
+ this.metricsManager.trackEvent(
367
+ METRIC_EVENT_NAMES.AI_SUMMARY_POST_CALL_RESPONSE_FAILED,
368
+ {
369
+ taskId: this.data?.interactionId,
370
+ ...metricFields,
371
+ failureCode: Task.getAISummaryFailureCode(error),
372
+ },
373
+ ['operational']
374
+ );
375
+ throw error;
376
+ }
377
+ }
378
+
379
+ public async requestMidCallSummary(action: AISummaryAction): Promise<AISummary> {
380
+ const metricFields: Record<string, unknown> = {
381
+ operation: METHODS.REQUEST_MID_CALL_SUMMARY,
382
+ actionType: action,
383
+ };
384
+
385
+ try {
386
+ this.metricsManager.timeEvent([
387
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_SUCCESS,
388
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_FAILED,
389
+ ]);
390
+
391
+ const interactionId = this.data.interactionId;
392
+ const conversationId = this.data.interaction?.mainInteractionId || interactionId;
393
+ Object.assign(metricFields, {conversationId, interactionId});
394
+
395
+ if (
396
+ !this.apiAIAssistant ||
397
+ this.apiAIAssistant.aiFeature?.generatedSummaries?.consultTransferSummariesEnabled !==
398
+ true ||
399
+ !this.aiSummaryCapabilities.midCallEnabled
400
+ ) {
401
+ throw createSummaryError(AI_SUMMARY_ERROR_CODES.MID_CALL_SUMMARY_DISABLED);
402
+ }
403
+
404
+ const eventName =
405
+ action === 'CONSULT'
406
+ ? AIAssistantEventName.GET_MID_CALL_CONSULT_SUMMARY
407
+ : AIAssistantEventName.GET_MID_CALL_TRANSFER_SUMMARY;
408
+ const result = await this.apiAIAssistant.requestAndWaitForRtd<AISummary>({
409
+ correlationId: conversationId,
410
+ rtdEventType: 'MID_CALL_SUMMARY',
411
+ timeoutMs: AI_SUMMARY_DURATION_MS,
412
+ createTimeoutError: () =>
413
+ createSummaryError(AI_SUMMARY_ERROR_CODES.MID_CALL_SUMMARY_TIMEOUT),
414
+ agentId: this.agentId as string,
415
+ interactionId,
416
+ eventType: AIAssistantEventType.CTI_EVENT,
417
+ eventName,
418
+ eventMetaData: {
419
+ conversationId,
420
+ clientType: AI_ASSISTANT_CLIENT_TYPE,
421
+ },
422
+ timeout: AI_SUMMARY_DURATION_MS,
423
+ });
424
+
425
+ this.metricsManager.trackEvent(
426
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_SUCCESS,
427
+ {taskId: this.data?.interactionId, ...metricFields},
428
+ ['operational']
429
+ );
430
+
431
+ return result;
432
+ } catch (error) {
433
+ this.metricsManager.trackEvent(
434
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_FAILED,
435
+ {
436
+ taskId: this.data?.interactionId,
437
+ ...metricFields,
438
+ failureCode: Task.getAISummaryFailureCode(error),
439
+ },
440
+ ['operational']
441
+ );
442
+ throw error;
443
+ }
444
+ }
445
+
446
+ public async sendMidCallSummaryResponse(
447
+ response: AISummaryResponse,
448
+ action: AISummaryAction
449
+ ): Promise<void> {
450
+ const metricFields: Record<string, unknown> = {
451
+ operation: METHODS.SEND_MID_CALL_SUMMARY_RESPONSE,
452
+ actionType: action,
453
+ };
454
+
455
+ try {
456
+ this.metricsManager.timeEvent([
457
+ METRIC_EVENT_NAMES.AI_SUMMARY_MID_CALL_RESPONSE_SUCCESS,
458
+ METRIC_EVENT_NAMES.AI_SUMMARY_MID_CALL_RESPONSE_FAILED,
459
+ ]);
460
+ const interactionId = this.data.interactionId;
461
+ const conversationId = this.data.interaction?.mainInteractionId || interactionId;
462
+ Object.assign(metricFields, {conversationId, interactionId});
463
+
464
+ const publishTimestamp = Date.now();
465
+ const eventName =
466
+ action === 'CONSULT'
467
+ ? AIAssistantEventName.MID_CALL_CONSULT_SUMMARY_RESPONSE
468
+ : AIAssistantEventName.MID_CALL_TRANSFER_SUMMARY_RESPONSE;
469
+ const eventPayload = {
470
+ conversationId,
471
+ clientType: AI_ASSISTANT_CLIENT_TYPE,
472
+ action: eventName,
473
+ summary: response.summary,
474
+ numberOfTimesViewed: response.numberOfTimesViewed,
475
+ numberOfTimesEdited: response.numberOfTimesEdited,
476
+ numberOfTimesCopied: response.numberOfTimesCopied,
477
+ feedback: response.feedback,
478
+ state: response.state,
479
+ agentName: this.agentName ?? '',
480
+ };
481
+ await this.apiAIAssistant.sendEvent(
482
+ this.agentId as string,
483
+ interactionId,
484
+ AIAssistantEventType.CTI_EVENT,
485
+ eventName,
486
+ eventPayload,
487
+ undefined,
488
+ undefined,
489
+ publishTimestamp,
490
+ AI_SUMMARY_DURATION_MS
491
+ );
492
+
493
+ this.metricsManager.trackEvent(
494
+ METRIC_EVENT_NAMES.AI_SUMMARY_MID_CALL_RESPONSE_SUCCESS,
495
+ {taskId: this.data?.interactionId, ...metricFields},
496
+ ['operational']
497
+ );
498
+ } catch (error) {
499
+ this.metricsManager.trackEvent(
500
+ METRIC_EVENT_NAMES.AI_SUMMARY_MID_CALL_RESPONSE_FAILED,
501
+ {
502
+ taskId: this.data?.interactionId,
503
+ ...metricFields,
504
+ failureCode: Task.getAISummaryFailureCode(error),
505
+ },
506
+ ['operational']
507
+ );
508
+ throw error;
509
+ }
510
+ }
511
+
512
+ private static getAISummaryFailureCode(error: unknown): string {
513
+ const errorCode = (error as {data?: {errorCode?: unknown}})?.data?.errorCode;
514
+
515
+ if (typeof errorCode === 'string') {
516
+ return errorCode;
517
+ }
518
+
519
+ return AI_SUMMARY_INVALID_RESPONSE_PAYLOAD;
520
+ }
521
+
219
522
  /**
220
523
  * Latest UI controls derived from state machine state and context.
221
524
  */
@@ -650,7 +953,7 @@ export default abstract class Task extends EventEmitter implements ITask {
650
953
  * @param methodName - The name of the method that is unsupported
651
954
  * @throws Error
652
955
  */
653
- protected unsupportedMethodError(methodName: string) {
956
+ protected unsupportedMethodError(methodName: string): never {
654
957
  LoggerProxy.error(`Unsupported operation`, {
655
958
  module: 'TASK',
656
959
  method: methodName,
@@ -7,7 +7,8 @@ import WebRTC from './voice/WebRTC';
7
7
  import Digital from './digital/Digital';
8
8
  import {MEDIA_CHANNEL, TaskData} from './types';
9
9
  import {ConfigFlags, LoginOption} from '../../types';
10
- import {WrapupData} from '../config/types';
10
+ import type {WrapupData} from '../config/types';
11
+ import type ApiAIAssistant from '../ApiAiAssistant';
11
12
 
12
13
  export default class TaskFactory {
13
14
  /**
@@ -20,7 +21,9 @@ export default class TaskFactory {
20
21
  configFlags: ConfigFlags,
21
22
  wrapupData?: WrapupData,
22
23
  agentId?: string,
23
- answerCallOnWebexService?: AnswerCallOnWebexService
24
+ agentName?: string,
25
+ answerCallOnWebexService?: AnswerCallOnWebexService,
26
+ apiAIAssistant?: ApiAIAssistant
24
27
  ): Task {
25
28
  const mediaType = data.interaction.mediaType ?? MEDIA_CHANNEL.TELEPHONY;
26
29
  const {isEndTaskEnabled, isEndConsultEnabled, consultTransfer} = configFlags;
@@ -43,16 +46,34 @@ export default class TaskFactory {
43
46
  data,
44
47
  voiceControlOptions,
45
48
  wrapupData,
46
- agentId
49
+ agentId,
50
+ agentName,
51
+ apiAIAssistant
47
52
  );
48
53
  }
49
54
 
50
- return new Voice(contact, data, voiceControlOptions, wrapupData, agentId);
55
+ return new Voice(
56
+ contact,
57
+ data,
58
+ voiceControlOptions,
59
+ wrapupData,
60
+ agentId,
61
+ agentName,
62
+ apiAIAssistant
63
+ );
51
64
 
52
65
  case MEDIA_CHANNEL.CHAT:
53
66
  case MEDIA_CHANNEL.EMAIL:
54
67
  case MEDIA_CHANNEL.SOCIAL:
55
- return new Digital(contact, data, wrapupData, agentId, consultTransfer);
68
+ return new Digital(
69
+ contact,
70
+ data,
71
+ wrapupData,
72
+ agentId,
73
+ consultTransfer,
74
+ agentName,
75
+ apiAIAssistant
76
+ );
56
77
 
57
78
  default:
58
79
  throw new Error(`Unknown media type: ${mediaType}`);