@webex/contact-center 3.12.0-task-refactor.8 → 3.12.0-task-refactor.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cc.js +3 -4
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/metrics/constants.js +2 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +74 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/constants.js +1 -1
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/types.js +9 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/task/TaskManager.js +7 -2
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/metrics/constants.d.ts +2 -0
- package/dist/types/services/ApiAiAssistant.d.ts +10 -2
- package/dist/types/services/config/types.d.ts +16 -0
- package/dist/types/types.d.ts +24 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +1 -1
- package/src/cc.ts +6 -4
- package/src/constants.ts +1 -0
- package/src/metrics/constants.ts +2 -0
- package/src/services/ApiAiAssistant.ts +102 -2
- package/src/services/config/constants.ts +1 -1
- package/src/services/config/types.ts +8 -0
- package/src/services/task/TaskManager.ts +7 -2
- package/src/services/task/ai-docs/AGENTS.md +7 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +12 -0
- package/src/types.ts +25 -0
- package/test/unit/spec/cc.ts +2 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +105 -17
- package/test/unit/spec/services/config/index.ts +1 -1
- package/test/unit/spec/services/task/TaskManager.ts +42 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +99 -106
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
package/src/cc.ts
CHANGED
|
@@ -736,11 +736,13 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
|
|
|
736
736
|
this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
|
|
737
737
|
this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
|
|
738
738
|
/**
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
* If the latter is true, we need to update this condition.
|
|
739
|
+
* RTD websocket currently supports realtime transcripts and suggested responses.
|
|
740
|
+
* Extend this condition when additional AI RTD features are introduced.
|
|
742
741
|
*/
|
|
743
|
-
if (
|
|
742
|
+
if (
|
|
743
|
+
this.agentConfig.aiFeature?.realtimeTranscripts?.enable ||
|
|
744
|
+
this.agentConfig.aiFeature?.suggestedResponses?.enable
|
|
745
|
+
) {
|
|
744
746
|
LoggerProxy.info('Connecting to RTD websocket', {
|
|
745
747
|
module: CC_FILE,
|
|
746
748
|
method: METHODS.CONNECT_WEBSOCKET,
|
package/src/constants.ts
CHANGED
package/src/metrics/constants.ts
CHANGED
|
@@ -166,6 +166,8 @@ export const METRIC_EVENT_NAMES = {
|
|
|
166
166
|
// AI Assistant events
|
|
167
167
|
AI_ASSISTANT_SEND_EVENT_SUCCESS: 'AI Assistant Send Event Success',
|
|
168
168
|
AI_ASSISTANT_SEND_EVENT_FAILED: 'AI Assistant Send Event Failed',
|
|
169
|
+
AI_ASSISTANT_GET_SUGGESTED_RESPONSE_SUCCESS: 'AI Assistant Get Suggested Response Success',
|
|
170
|
+
AI_ASSISTANT_GET_SUGGESTED_RESPONSE_FAILED: 'AI Assistant Get Suggested Response Failed',
|
|
169
171
|
AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_SUCCESS:
|
|
170
172
|
'AI Assistant Fetch Historic Transcripts Success',
|
|
171
173
|
AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_FAILED: 'AI Assistant Fetch Historic Transcripts Failed',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {v4 as uuidv4} from 'uuid';
|
|
1
2
|
import LoggerProxy from '../logger-proxy';
|
|
2
3
|
import MetricsManager from '../metrics/MetricsManager';
|
|
3
4
|
import {METRIC_EVENT_NAMES} from '../metrics/constants';
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
AIAssistantEventType,
|
|
11
12
|
AIAssistantEventName,
|
|
12
13
|
HistoricTranscriptsResponse,
|
|
14
|
+
SuggestedResponseParams,
|
|
13
15
|
} from '../types';
|
|
14
16
|
import {getErrorDetails} from './core/Utils';
|
|
15
17
|
import {
|
|
@@ -83,13 +85,16 @@ export class ApiAIAssistant {
|
|
|
83
85
|
interactionId: string,
|
|
84
86
|
eventType: AIAssistantEventType,
|
|
85
87
|
eventName: AIAssistantEventName,
|
|
86
|
-
action
|
|
88
|
+
action?: TranscriptAction,
|
|
89
|
+
context?: string,
|
|
90
|
+
languageCode?: string,
|
|
91
|
+
trackingId?: string
|
|
87
92
|
): Promise<Record<string, unknown>> {
|
|
88
93
|
LoggerProxy.info('Sending event', {
|
|
89
94
|
module: CC_FILE,
|
|
90
95
|
method: METHODS.SEND_EVENT,
|
|
91
96
|
interactionId,
|
|
92
|
-
data: {eventType, eventName, action},
|
|
97
|
+
data: {eventType, eventName, action, context},
|
|
93
98
|
});
|
|
94
99
|
this.metricsManager.timeEvent([
|
|
95
100
|
METRIC_EVENT_NAMES.AI_ASSISTANT_SEND_EVENT_SUCCESS,
|
|
@@ -112,7 +117,10 @@ export class ApiAIAssistant {
|
|
|
112
117
|
data: {
|
|
113
118
|
interactionId,
|
|
114
119
|
action,
|
|
120
|
+
context,
|
|
115
121
|
actionTimeStamp: String(Date.now()),
|
|
122
|
+
languageCode,
|
|
123
|
+
trackingId,
|
|
116
124
|
},
|
|
117
125
|
},
|
|
118
126
|
},
|
|
@@ -143,6 +151,98 @@ export class ApiAIAssistant {
|
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Requests a suggested response for an interaction.
|
|
156
|
+
*
|
|
157
|
+
* @param params - Suggestion request parameters
|
|
158
|
+
* @returns HTTP response body from the AI Assistant event API
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
public async getSuggestedResponse(params: SuggestedResponseParams): Promise<any> {
|
|
162
|
+
const {agentId, interactionId, context} = params;
|
|
163
|
+
const trimmedContext = context?.trim();
|
|
164
|
+
const languageCode = params.languageCode ?? 'en';
|
|
165
|
+
const trackingId = `WX_CC_SDK_${uuidv4()}`;
|
|
166
|
+
const eventName = trimmedContext
|
|
167
|
+
? AIAssistantEventName.ADD_SUGGESTIONS_EXTRA_CONTEXT
|
|
168
|
+
: AIAssistantEventName.GET_SUGGESTIONS;
|
|
169
|
+
|
|
170
|
+
const loggerContext = {
|
|
171
|
+
module: CC_FILE,
|
|
172
|
+
method: METHODS.GET_SUGGESTED_RESPONSE,
|
|
173
|
+
interactionId,
|
|
174
|
+
trackingId,
|
|
175
|
+
data: {eventName},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
LoggerProxy.info('Requesting suggested response', loggerContext);
|
|
179
|
+
|
|
180
|
+
this.metricsManager.timeEvent([
|
|
181
|
+
METRIC_EVENT_NAMES.AI_ASSISTANT_GET_SUGGESTED_RESPONSE_SUCCESS,
|
|
182
|
+
METRIC_EVENT_NAMES.AI_ASSISTANT_GET_SUGGESTED_RESPONSE_FAILED,
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
if (!this.aiFeature?.suggestedResponses?.enable) {
|
|
187
|
+
const {error: detailedError} = getErrorDetails(
|
|
188
|
+
new Error('SUGGESTED_RESPONSES_NOT_ENABLED'),
|
|
189
|
+
METHODS.GET_SUGGESTED_RESPONSE,
|
|
190
|
+
CC_FILE
|
|
191
|
+
);
|
|
192
|
+
throw detailedError;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const orgId = this.webex.credentials.getOrgId();
|
|
196
|
+
|
|
197
|
+
const response = await this.sendEvent(
|
|
198
|
+
agentId,
|
|
199
|
+
interactionId,
|
|
200
|
+
AIAssistantEventType.CUSTOM_EVENT,
|
|
201
|
+
eventName,
|
|
202
|
+
undefined,
|
|
203
|
+
trimmedContext,
|
|
204
|
+
languageCode,
|
|
205
|
+
trackingId
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
this.metricsManager.trackEvent(
|
|
209
|
+
METRIC_EVENT_NAMES.AI_ASSISTANT_GET_SUGGESTED_RESPONSE_SUCCESS,
|
|
210
|
+
{
|
|
211
|
+
agentId,
|
|
212
|
+
orgId,
|
|
213
|
+
interactionId,
|
|
214
|
+
eventName,
|
|
215
|
+
trackingId,
|
|
216
|
+
context,
|
|
217
|
+
},
|
|
218
|
+
['operational']
|
|
219
|
+
);
|
|
220
|
+
LoggerProxy.log('Suggested response request succeeded', loggerContext);
|
|
221
|
+
|
|
222
|
+
return response;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
LoggerProxy.error('Suggested response request failed', {...loggerContext, error});
|
|
225
|
+
this.metricsManager.trackEvent(
|
|
226
|
+
METRIC_EVENT_NAMES.AI_ASSISTANT_GET_SUGGESTED_RESPONSE_FAILED,
|
|
227
|
+
{
|
|
228
|
+
agentId,
|
|
229
|
+
interactionId,
|
|
230
|
+
trackingId,
|
|
231
|
+
eventName,
|
|
232
|
+
error: error instanceof Error ? error.message : String(error),
|
|
233
|
+
},
|
|
234
|
+
['operational']
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const {error: detailedError} = getErrorDetails(
|
|
238
|
+
error,
|
|
239
|
+
METHODS.GET_SUGGESTED_RESPONSE,
|
|
240
|
+
CC_FILE
|
|
241
|
+
);
|
|
242
|
+
throw detailedError;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
146
246
|
/**
|
|
147
247
|
* Fetches historic transcripts for an interaction.
|
|
148
248
|
* This API is allowed only when real-time transcription feature is enabled.
|
|
@@ -166,7 +166,7 @@ export const endPointMap = {
|
|
|
166
166
|
) =>
|
|
167
167
|
`organization/${orgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}${
|
|
168
168
|
filter && filter.length > 0 ? `&filter=id=in=(${filter})` : ''
|
|
169
|
-
}&attributes=${attributes}`,
|
|
169
|
+
}&attributes=${attributes}&desktopProfileFilter=true`,
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
172
|
* Gets the endpoint for organization info.
|
|
@@ -121,6 +121,14 @@ export const CC_TASK_EVENTS = {
|
|
|
121
121
|
AGENT_INVITE_FAILED: 'AgentInviteFailed',
|
|
122
122
|
/** Event emitted when a real-time transcript chunk is received */
|
|
123
123
|
REAL_TIME_TRANSCRIPTION: 'REAL_TIME_TRANSCRIPTION',
|
|
124
|
+
/** Event emitted when an AI assistant suggested response is available */
|
|
125
|
+
SUGGESTED_RESPONSE: 'SUGGESTED_RESPONSE',
|
|
126
|
+
/** Event emitted when backend acknowledges it is listening for more context */
|
|
127
|
+
SUGGESTED_RESPONSE_ACKNOWLEDGE: 'SUGGESTED_RESPONSE_ACKNOWLEDGE',
|
|
128
|
+
/** Event emitted when a mid-call summary is available */
|
|
129
|
+
MID_CALL_SUMMARY: 'MID_CALL_SUMMARY',
|
|
130
|
+
/** Event emitted when a post-call summary is available */
|
|
131
|
+
POST_CALL_SUMMARY: 'POST_CALL_SUMMARY',
|
|
124
132
|
} as const;
|
|
125
133
|
|
|
126
134
|
/**
|
|
@@ -93,7 +93,12 @@ export default class TaskManager extends EventEmitter {
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
switch (payload.type) {
|
|
97
|
+
case CC_EVENTS.REAL_TIME_TRANSCRIPTION:
|
|
98
|
+
case CC_EVENTS.SUGGESTED_RESPONSE:
|
|
99
|
+
task.emit(payload.type, payload.data);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
97
102
|
} catch (error) {
|
|
98
103
|
LoggerProxy.error('Failed to parse RTD WebSocket message', {
|
|
99
104
|
module: TASK_MANAGER_FILE,
|
|
@@ -755,7 +760,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
755
760
|
private requestRealTimeTranscripts(eventType: string, interactionId: string): void {
|
|
756
761
|
const action = TRANSCRIPT_EVENT_MAP[eventType];
|
|
757
762
|
if (!action || !this.apiAIAssistant) return;
|
|
758
|
-
if (this.configFlags?.aiFeature?.realtimeTranscripts?.enable
|
|
763
|
+
if (this.configFlags?.aiFeature?.realtimeTranscripts?.enable !== true) return;
|
|
759
764
|
|
|
760
765
|
this.apiAIAssistant
|
|
761
766
|
.sendEvent(
|
|
@@ -217,6 +217,13 @@ cc.on('task:incoming', async (task) => {
|
|
|
217
217
|
|
|
218
218
|
> Full list is defined in `TASK_EVENTS` (`types.ts`).
|
|
219
219
|
|
|
220
|
+
### AI Assistant events on `task`
|
|
221
|
+
|
|
222
|
+
| Event | When Emitted |
|
|
223
|
+
| --- | --- |
|
|
224
|
+
| `REAL_TIME_TRANSCRIPTION` | A realtime transcript payload is received for the task interaction |
|
|
225
|
+
| `SUGGESTED_RESPONSE` | A final AI Assistant suggestion payload is received for the task interaction |
|
|
226
|
+
|
|
220
227
|
---
|
|
221
228
|
|
|
222
229
|
## API Reference
|
|
@@ -400,6 +400,18 @@ this.webSocketManager.on('message', (event) => {
|
|
|
400
400
|
});
|
|
401
401
|
```
|
|
402
402
|
|
|
403
|
+
### RTD / AI Assistant event routing
|
|
404
|
+
|
|
405
|
+
`TaskManager.handleRealtimeWebsocketEvent()` handles payloads arriving on the realtime subscription socket used for AI features. It:
|
|
406
|
+
|
|
407
|
+
1. Normalizes the websocket envelope (`payload.data` vs direct payload form)
|
|
408
|
+
2. Resolves the owning task via `conversationId`
|
|
409
|
+
3. Emits `REAL_TIME_TRANSCRIPTION` on the task for transcript payloads
|
|
410
|
+
4. Emits `SUGGESTED_RESPONSE` on the task only when the backend payload is a final suggestion (`data.type === 'SUGGESTION'`)
|
|
411
|
+
5. Ignores `SUGGESTED_RESPONSE_ACKNOWLEDGE` for public SDK emission
|
|
412
|
+
|
|
413
|
+
This keeps transcript and suggestion delivery aligned on the same per-task event surface.
|
|
414
|
+
|
|
403
415
|
---
|
|
404
416
|
|
|
405
417
|
## WebRTC Integration
|
package/src/types.ts
CHANGED
|
@@ -846,6 +846,27 @@ export type UpdateDeviceTypeResponse = Agent.DeviceTypeUpdateSuccess | Error;
|
|
|
846
846
|
*/
|
|
847
847
|
export type TranscriptAction = 'START' | 'STOP';
|
|
848
848
|
|
|
849
|
+
/**
|
|
850
|
+
* Parameters used to request an AI Assistant suggested response.
|
|
851
|
+
* @public
|
|
852
|
+
* @example
|
|
853
|
+
* const params: SuggestedResponseParams = {
|
|
854
|
+
* interactionId: 'interaction-123',
|
|
855
|
+
* actionTimeStamp: Date.now(),
|
|
856
|
+
* context: 'Need help with credit card payment due date',
|
|
857
|
+
* };
|
|
858
|
+
*/
|
|
859
|
+
export type SuggestedResponseParams = {
|
|
860
|
+
/** Agent identifier */
|
|
861
|
+
agentId: string;
|
|
862
|
+
/** Interaction identifier for which suggestion should be generated */
|
|
863
|
+
interactionId: string;
|
|
864
|
+
/** Optional additional context that should refine the suggestion */
|
|
865
|
+
context?: string;
|
|
866
|
+
/** Optional language code for suggestions (for example, 'en'). Defaults to 'en'. */
|
|
867
|
+
languageCode?: string;
|
|
868
|
+
};
|
|
869
|
+
|
|
849
870
|
/**
|
|
850
871
|
* Supported AI Assistant event categories.
|
|
851
872
|
* @public
|
|
@@ -879,6 +900,10 @@ export type AIAssistantEventType = Enum<typeof AIAssistantEventType>;
|
|
|
879
900
|
export const AIAssistantEventName = {
|
|
880
901
|
/** Request transcript streaming for an interaction */
|
|
881
902
|
GET_TRANSCRIPTS: 'GET_TRANSCRIPTS',
|
|
903
|
+
/** Request a suggested response for an interaction */
|
|
904
|
+
GET_SUGGESTIONS: 'GET_SUGGESTIONS',
|
|
905
|
+
/** Add extra context to refine a suggested response */
|
|
906
|
+
ADD_SUGGESTIONS_EXTRA_CONTEXT: 'ADD_SUGGESTIONS_EXTRA_CONTEXT',
|
|
882
907
|
/** Request mid-call summary generation */
|
|
883
908
|
GET_MID_CALL_SUMMARY: 'GET_MID_CALL_SUMMARY',
|
|
884
909
|
/** Request post-call summary generation */
|
package/test/unit/spec/cc.ts
CHANGED
|
@@ -110,8 +110,10 @@ describe('webex.cc', () => {
|
|
|
110
110
|
|
|
111
111
|
mockApiAIAssistant = {
|
|
112
112
|
sendEvent: jest.fn(),
|
|
113
|
+
getSuggestedResponse: jest.fn(),
|
|
113
114
|
fetchHistoricTranscripts: jest.fn(),
|
|
114
115
|
setAIFeatureFlags: jest.fn(),
|
|
116
|
+
setAgentId: jest.fn(),
|
|
115
117
|
};
|
|
116
118
|
|
|
117
119
|
// Mock Services instance
|
|
@@ -57,23 +57,18 @@ describe('ApiAIAssistant', () => {
|
|
|
57
57
|
'START'
|
|
58
58
|
);
|
|
59
59
|
|
|
60
|
-
expect(mockWebex.request).
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
action: 'START',
|
|
73
|
-
}),
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
});
|
|
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');
|
|
77
72
|
expect(result).toEqual({ok: true});
|
|
78
73
|
});
|
|
79
74
|
|
|
@@ -97,6 +92,83 @@ describe('ApiAIAssistant', () => {
|
|
|
97
92
|
expect(result).toEqual(responseBody as any);
|
|
98
93
|
});
|
|
99
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
|
+
|
|
100
172
|
it('should fail when base URL mapping is not available', async () => {
|
|
101
173
|
(mockWebex.internal.services.get as jest.Mock).mockReturnValue('https://unknown-host.invalid');
|
|
102
174
|
|
|
@@ -129,4 +201,20 @@ describe('ApiAIAssistant', () => {
|
|
|
129
201
|
|
|
130
202
|
expect(errorMessage).toBe('Error while performing fetchHistoricTranscripts');
|
|
131
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
|
+
});
|
|
132
220
|
});
|
|
@@ -260,7 +260,7 @@ describe('AgentConfigService', () => {
|
|
|
260
260
|
|
|
261
261
|
expect(mockWebexRequest.request).toHaveBeenCalledWith({
|
|
262
262
|
service: mockWccAPIURL,
|
|
263
|
-
resource: `organization/${mockOrgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}&filter=id=in=(${filter})&attributes=${attributes}`,
|
|
263
|
+
resource: `organization/${mockOrgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}&filter=id=in=(${filter})&attributes=${attributes}&desktopProfileFilter=true`,
|
|
264
264
|
method: 'GET',
|
|
265
265
|
});
|
|
266
266
|
expect(result).toEqual(mockResponse.body);
|
|
@@ -253,6 +253,19 @@ describe('TaskManager', () => {
|
|
|
253
253
|
});
|
|
254
254
|
|
|
255
255
|
it('should invoke sendEvent for configured start/stop backend events', () => {
|
|
256
|
+
taskManager.setConfigFlags({
|
|
257
|
+
isEndTaskEnabled: true,
|
|
258
|
+
isEndConsultEnabled: true,
|
|
259
|
+
webRtcEnabled: true,
|
|
260
|
+
autoWrapup: false,
|
|
261
|
+
aiFeature: {
|
|
262
|
+
id: 'ai-feature-1',
|
|
263
|
+
realtimeTranscripts: {
|
|
264
|
+
enable: true,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
256
269
|
const interactionId = taskId;
|
|
257
270
|
const message = (type: CC_EVENTS) =>
|
|
258
271
|
JSON.stringify({
|
|
@@ -316,6 +329,35 @@ describe('TaskManager', () => {
|
|
|
316
329
|
expect(mockApiAIAssistant.sendEvent).not.toHaveBeenCalled();
|
|
317
330
|
});
|
|
318
331
|
|
|
332
|
+
it('should not invoke sendEvent when realtime transcripts config is missing', () => {
|
|
333
|
+
taskManager.setConfigFlags({
|
|
334
|
+
isEndTaskEnabled: true,
|
|
335
|
+
isEndConsultEnabled: true,
|
|
336
|
+
webRtcEnabled: true,
|
|
337
|
+
autoWrapup: false,
|
|
338
|
+
aiFeature: {
|
|
339
|
+
id: 'ai-feature-1',
|
|
340
|
+
suggestedResponses: {
|
|
341
|
+
enable: true,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const message = (type: CC_EVENTS) =>
|
|
347
|
+
JSON.stringify({
|
|
348
|
+
data: {
|
|
349
|
+
...taskDataMock,
|
|
350
|
+
interactionId: taskId,
|
|
351
|
+
type,
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
356
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
357
|
+
|
|
358
|
+
expect(mockApiAIAssistant.sendEvent).not.toHaveBeenCalled();
|
|
359
|
+
});
|
|
360
|
+
|
|
319
361
|
it('should emit REAL_TIME_TRANSCRIPTION from RTD websocket payload', () => {
|
|
320
362
|
const task = taskManager.getTask(taskId);
|
|
321
363
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|