@scout9/app 1.0.0-alpha.0.1.91 → 1.0.0-alpha.0.1.92

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.
@@ -97,434 +97,474 @@
97
97
  */
98
98
  export const Spirits = {
99
99
 
100
- /**
101
- * Customer message
102
- * @param {ConversationData & CustomerSpiritCallbacks} input
103
- * @returns {Promise<ConversationEvent>}
104
- */
105
- customer: async function (input) {
106
- const {
107
- customer,
108
- config,
109
- parser,
110
- workflow,
111
- generator,
112
- idGenerator,
113
- progress = (message, level, type, payload) => {
114
- },
115
- message: messageBefore,
116
- context: contextBefore,
117
- messages: messagesBefore,
118
- conversation: conversationBefore
119
- } = input;
120
- let {conversation, messages, context, message} = input;
121
-
122
- // 0. Setup Helpers
123
- const updateConversation = (previousConversation, conversationUpdates) => {
124
- progress('Update conversation', 'info', 'UPDATE_CONVERSATION', conversationUpdates);
125
- return {
126
- ...previousConversation,
127
- ...conversationUpdates
128
- };
100
+ /**
101
+ * Customer message
102
+ * @param {ConversationData & CustomerSpiritCallbacks} input
103
+ * @returns {Promise<ConversationEvent>}
104
+ */
105
+ customer: async function (input) {
106
+ const {
107
+ customer,
108
+ config,
109
+ parser,
110
+ workflow,
111
+ generator,
112
+ idGenerator,
113
+ progress = (message, level, type, payload) => {
114
+ },
115
+ message: messageBefore,
116
+ context: contextBefore,
117
+ messages: messagesBefore,
118
+ conversation: conversationBefore
119
+ } = input;
120
+ let {conversation, messages, context, message} = input;
121
+
122
+ // 0. Setup Helpers
123
+ const updateConversation = (previousConversation, conversationUpdates) => {
124
+ progress('Update conversation', 'info', 'UPDATE_CONVERSATION', conversationUpdates);
125
+ return {
126
+ ...previousConversation,
127
+ ...conversationUpdates
128
+ };
129
+ };
130
+
131
+ const updateContext = (previousContext, newContext) => {
132
+ progress('Update context', 'info', 'UPDATE_CONTEXT', newContext);
133
+ return {
134
+ ...previousContext,
135
+ ...newContext
136
+ };
137
+ };
138
+
139
+ const userMessages = (_messages) => {
140
+ return _messages.filter(m => m.role === 'customer' || m.role === 'user');
141
+ };
142
+
143
+ const recentUserMessage = (_messages) => {
144
+ const _userMessages = userMessages(_messages);
145
+ return _userMessages[_userMessages.length - 1];
146
+ };
147
+
148
+ const lockConversation = (_conversation, reason) => {
149
+ return updateConversation(
150
+ _conversation,
151
+ {locked: true, lockedReason: conversation.lockedReason || reason || 'Unknown'}
152
+ );
153
+ };
154
+
155
+ const incrementLockAttempt = (_conversation, _config) => {
156
+ if (typeof _conversation.lockAttempts !== 'number') {
157
+ _conversation.lockAttempts = 0;
158
+ }
159
+ _conversation.lockAttempts++;
160
+ if (_conversation.lockAttempts > (_config?.maxLockAttempts || 3)) {
161
+ _conversation.locked = true;
162
+ _conversation.lockedReason = `Max lock attempts exceeded (${_conversation.lockAttempts} > ${(_config?.maxLockAttempts || 3)})`;
163
+ }
164
+ progress(
165
+ 'Incremented lock attempt',
166
+ 'info',
167
+ 'UPDATE_CONVERSATION',
168
+ {
169
+ lockAttempts: _conversation.lockAttempts,
170
+ locked: _conversation.locked,
171
+ lockedReason: _conversation.lockedReason || ''
129
172
  }
130
-
131
- const updateContext = (previousContext, newContext) => {
132
- progress('Update context', 'info', 'UPDATE_CONTEXT', newContext);
133
- return {
134
- ...previousContext,
135
- ...newContext
136
- };
173
+ );
174
+ return _conversation;
175
+ };
176
+
177
+ const _addInstruction = (
178
+ instruction,
179
+ _messages,
180
+ _conversation,
181
+ _config,
182
+ previousLockAttempt,
183
+ id
184
+ ) => {
185
+ const systemMessages = _messages.filter(m => m.role === 'system');
186
+ const lastSystemMessage = systemMessages[systemMessages.length - 1];
187
+ let addedMessage = false;
188
+ let changedConversation = false;
189
+
190
+ // If instruction does not equal previous system message, add it, otherwise lock attempt
191
+ if (!lastSystemMessage || instruction !== lastSystemMessage.content) {
192
+ _messages.push({
193
+ id,
194
+ role: 'system',
195
+ content: instruction,
196
+ time: new Date().toISOString()
197
+ });
198
+ addedMessage = true;
199
+ } else {
200
+ // Handle repeated instruction
201
+ // Increment lock attempt if instructions are repeated and we haven't already incremented lock attempt (for example if a forward is provided)
202
+ if (previousLockAttempt === (conversation.lockAttempts || 0)) {
203
+ _conversation = incrementLockAttempt(_conversation, _config);
204
+ changedConversation = true;
137
205
  }
206
+ }
207
+ return {
208
+ conversation: _conversation,
209
+ messages: _messages,
210
+ addedMessage,
211
+ changedConversation
212
+ };
213
+ };
214
+
215
+ const addInstruction = (instruction, previousLockAttempt, id = idGenerator('sys')) => {
216
+ const {
217
+ conversation: newConversation,
218
+ messages: newMessages,
219
+ addedMessage,
220
+ changedConversation
221
+ } = _addInstruction(instruction, messages, conversation, config, previousLockAttempt, id);
222
+ conversation = newConversation;
223
+ messages = newMessages;
224
+ if (addedMessage) {
225
+ progress('Added instruction', 'info', 'ADD_MESSAGE', newMessages[newMessages.length - 1]);
226
+ }
227
+ if (changedConversation) {
228
+ progress('Updated conversation', 'info', 'UPDATE_CONVERSATION', newConversation);
229
+ }
230
+ };
231
+
232
+ // 1. Check inputs
233
+ if (!conversation.$agent) {
234
+ throw new Error(`SpiritsError: No agent found in conversation, must define ".$agent" in the conversation`);
235
+ }
236
+ const persona = (config.persona || config.agents).find(p => p.id === conversation.$agent);
237
+ if (!persona) {
238
+ if ((config.persona || config.agents).some(a => !a.id)) {
239
+ throw new Error(`SpiritsError: No persona found ("${conversation.$agent}") in provided config, some persona's did not contain an "id" (Internal Mapping Error)`);
240
+ }
241
+ throw new Error(`SpiritsError: No persona found ("${conversation.$agent}") in provided config`);
242
+ }
243
+ if (!messages.every(m => !!m.id)) {
244
+ throw new Error(`SpiritsError: Every message must have an ".id", ensure all messages have an id assigned before running`);
245
+ }
246
+ if (!messages.every(m => m.role === 'customer' || m.role === 'agent' || m.role === 'system')) {
247
+ const invalidRoles = messages.filter(m => m.role !== 'customer' && m.role !== 'agent' && m.role !== 'system');
248
+ throw new Error(`SpiritsError: Every message must have a role of "customer", "agent", or "system". Got invalid roles: ${invalidRoles.map(
249
+ m => m.role).join(', ')}`);
250
+ }
251
+ // if message is not in messages, then add it
252
+ if (!messages.find(m => m.id === input.message.id)) {
253
+ messages.push(input.message);
254
+ }
138
255
 
139
- const userMessages = (_messages) => {
140
- return _messages.filter(m => m.role === 'customer' || m.role === 'user')
141
- }
256
+ // 2. Parse the message
257
+ progress('Parsing message', 'info', 'SET_PROCESSING', 'user');
258
+ const parsePayload = await parser(message.content, 'en');
259
+ if (parsePayload.intent) {
260
+ message.intent = parsePayload.intent;
261
+ }
262
+ if (typeof parsePayload.intentScore === 'number') {
263
+ message.intentScore = parsePayload.intentScore;
264
+ }
265
+ message.context = parsePayload.context;
266
+ const index = messages.findIndex(m => m.content === message.content || m.id === message.id);
267
+ if (index === -1) {
268
+ const _message = {
269
+ id: idGenerator('customer'),
270
+ role: 'customer',
271
+ content: message,
272
+ context: parsePayload.context,
273
+ time: new Date().toISOString()
274
+ };
275
+ if (parsePayload.intent) {
276
+ _message.intent = parsePayload.intent;
277
+ }
278
+ if (typeof parsePayload.intentScore === 'number') {
279
+ _message.intentScore = parsePayload.intentScore;
280
+ }
281
+ message = _message;
282
+ messages.push(_message);
283
+ progress('Added message', 'info', 'ADD_MESSAGE', _message);
284
+ } else {
285
+ messages[index].context = parsePayload.context;
286
+ if (parsePayload.intent) {
287
+ messages[index].intent = parsePayload.intent;
288
+ }
289
+ if (typeof parsePayload.intentScore === 'number') {
290
+ messages[index].intentScore = parsePayload.intentScore;
291
+ }
292
+ message = messages[index];
293
+ progress('Parsed message', 'success', 'UPDATE_MESSAGE', message);
294
+ }
295
+ // If this is the first user message, then update conversations intent
296
+ const previousUserMessages = messages.filter(m => m.role === 'customer' && m.content !== message.content);
297
+ if (!conversation.intent || previousUserMessages.length === 0 && parsePayload.intent) {
298
+ conversation.intent = parsePayload.intent;
299
+ conversation.intentScore = parsePayload?.intentScore || 0;
300
+ progress(
301
+ 'Updated conversation intent',
302
+ 'info',
303
+ 'UPDATE_CONVERSATION',
304
+ {intent: parsePayload.intent, intentScore: parsePayload?.intentScore || 0}
305
+ );
306
+ }
307
+ const oldKeyCount = Object.keys(context).length;
308
+ context = updateContext(context, parsePayload.context);
309
+ const newKeyCount = Object.keys(context).length;
310
+
311
+ if (!conversation.locked && (newKeyCount > oldKeyCount)) {
312
+ // Reset lock attempts
313
+ conversation.locked = false;
314
+ conversation.lockAttempts = 0;
315
+ conversation.lockedReason = '';
316
+ progress('Reset lock', 'info', 'UPDATE_CONVERSATION', {locked: false, lockAttempts: 0, lockedReason: ''});
317
+ }
142
318
 
143
- const recentUserMessage = (_messages) => {
144
- const _userMessages = userMessages(_messages);
145
- return _userMessages[_userMessages.length - 1];
146
- }
319
+ const noNewContext = Object.keys(parsePayload.context).length === 0;
320
+
321
+ // 3. Run the workflow
322
+ progress('Running workflow', 'info', 'SET_PROCESSING', 'system');
323
+ const slots = await workflow({
324
+ messages,
325
+ conversation,
326
+ context,
327
+ message,
328
+ agent: persona,
329
+ customer,
330
+ intent: {
331
+ current: recentUserMessage(messages)?.intent || null,
332
+ flow: messages.map(m => m.intent).filter(Boolean),
333
+ initial: conversation.intent || null
334
+ },
335
+ stagnationCount: conversation.lockAttempts || 0
336
+ }).then((res) => Array.isArray(res) ? res : [res]);
337
+ const hasNoInstructions = slots.every(s => !s.instructions || (Array.isArray(s) && s.instructions.length === 0));
338
+ const hasNoCustomMessage = slots.every(s => !s.message);
339
+ const previousLockAttempt = conversation.lockAttempts || 0; // Used to track
340
+
341
+ if (hasNoInstructions && noNewContext) {
342
+ conversation = incrementLockAttempt(conversation, config);
343
+ } else {
344
+ conversation.lockAttempts = 0;
345
+ conversation.locked = false;
346
+ conversation.lockedReason = '';
347
+ progress('Reset lock', 'info', 'UPDATE_CONVERSATION', {lockAttempts: 0, locked: false, lockedReason: ''});
348
+ }
147
349
 
148
- const lockConversation = (_conversation, reason) => {
149
- return updateConversation(_conversation, {locked: true, lockedReason: conversation.lockedReason || reason || 'Unknown'});
150
- }
350
+ let resettedIntent = false;
351
+ let _forward;
352
+ let _forwardNote;
353
+
354
+ for (const {
355
+ forward,
356
+ forwardNote,
357
+ instructions,
358
+ removeInstructions,
359
+ message: manualMessage,
360
+ scheduled,
361
+ resetIntent,
362
+ secondsDelay,
363
+ contextUpsert
364
+ } of slots) {
365
+
366
+ // Forward to agent or other agent
367
+ if (forward) {
368
+ conversation = lockConversation(conversation, 'App instructed forward');
369
+ _forward = forward;
370
+ _forwardNote = forwardNote;
371
+ if (typeof forward === 'string') {
372
+ updateConversation(conversation, {forwarded: forward});
373
+ messages.push({
374
+ id: idGenerator('sys'),
375
+ role: 'system',
376
+ content: `forwarded to "${forward}"`,
377
+ time: new Date().toISOString()
378
+ });
379
+ progress(`Forwarded to "${forward}"`, 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
380
+ } else if (typeof forward === 'boolean') {
381
+ updateConversation(conversation, {forwarded: conversation.$agent});
382
+ messages.push({
383
+ id: idGenerator('sys'),
384
+ role: 'system',
385
+ content: `forwarded to "${forward}"`,
386
+ time: new Date().toISOString()
387
+ });
388
+ progress(`Forwarded to agent`, 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
151
389
 
152
- const incrementLockAttempt = (_conversation, _config) => {
153
- if (typeof _conversation.lockAttempts !== 'number') {
154
- _conversation.lockAttempts = 0;
155
- }
156
- _conversation.lockAttempts++;
157
- if (_conversation.lockAttempts > (_config?.maxLockAttempts || 3)) {
158
- _conversation.locked = true;
159
- _conversation.lockedReason = `Max lock attempts exceeded (${_conversation.lockAttempts} > ${(_config?.maxLockAttempts || 3)})`;
160
- }
161
- progress('Incremented lock attempt', 'info', 'UPDATE_CONVERSATION', {lockAttempts: _conversation.lockAttempts, locked: _conversation.locked, lockedReason: _conversation.lockedReason || ''});
162
- return _conversation;
390
+ } else {
391
+ messages.push({
392
+ id: idGenerator('sys'),
393
+ role: 'system',
394
+ content: `forwarded to "${forward.to}" ${forward.mode ? ' (' + forward.mode + ')' : ''}`,
395
+ time: new Date().toISOString()
396
+ });
397
+ progress(
398
+ `Forwarded to "${forward.to}" ${forward.mode ? ' (' + forward.mode + ')' : ''}`,
399
+ 'info',
400
+ 'ADD_MESSAGE',
401
+ messages[messages.length - 1]
402
+ );
403
+ updateConversation(conversation, {forwarded: forward.to});
163
404
  }
164
-
165
- const _addInstruction = (
166
- instruction,
167
- _messages,
168
- _conversation,
169
- _config,
170
- previousLockAttempt,
171
- id
172
- ) => {
173
- const systemMessages = _messages.filter(m => m.role === 'system');
174
- const lastSystemMessage = systemMessages[systemMessages.length - 1];
175
- let addedMessage = false;
176
- let changedConversation = false;
177
-
178
- // If instruction does not equal previous system message, add it, otherwise lock attempt
179
- if (!lastSystemMessage || instruction !== lastSystemMessage.content) {
180
- _messages.push({
181
- id,
182
- role: 'system',
183
- content: instruction,
184
- time: new Date().toISOString()
185
- });
186
- addedMessage = true;
405
+ }
406
+
407
+ // Insert instructions context
408
+ if (instructions) {
409
+ if (typeof instructions === 'string') {
410
+ addInstruction(instructions, previousLockAttempt);
411
+ } else if (Array.isArray(instructions)) {
412
+ for (const instruction of instructions) {
413
+ if (typeof instruction === 'string') {
414
+ addInstruction(instruction, previousLockAttempt);
187
415
  } else {
188
- // Handle repeated instruction
189
- // Increment lock attempt if instructions are repeated and we haven't already incremented lock attempt (for example if a forward is provided)
190
- if (previousLockAttempt === (conversation.lockAttempts || 0)) {
191
- _conversation = incrementLockAttempt(_conversation, _config);
192
- changedConversation = true;
193
- }
194
- }
195
- return {
196
- conversation: _conversation,
197
- messages: _messages,
198
- addedMessage,
199
- changedConversation
200
- }
201
- }
202
-
203
- const addInstruction = (instruction, previousLockAttempt, id = idGenerator('sys')) => {
204
- const {
205
- conversation: newConversation,
206
- messages: newMessages,
207
- addedMessage,
208
- changedConversation
209
- } = _addInstruction(instruction, messages, conversation, config, previousLockAttempt, id);
210
- conversation = newConversation;
211
- messages = newMessages;
212
- if (addedMessage) {
213
- progress('Added instruction', 'info', 'ADD_MESSAGE', newMessages[newMessages.length - 1]);
214
- }
215
- if (changedConversation) {
216
- progress('Updated conversation', 'info', 'UPDATE_CONVERSATION', newConversation);
217
- }
218
- }
219
-
220
- // 1. Check inputs
221
- if (!conversation.$agent) {
222
- throw new Error(`No agent found in conversation, must define ".$agent" in the conversation`);
223
- }
224
- const persona = (config.persona || config.agents).find(p => p.id === conversation.$agent);
225
- if (!persona) {
226
- throw new Error(`No persona found ("${conversation.$agent}") in provided config`);
227
- }
228
- if (!messages.every(m => !!m.id)) {
229
- throw new Error(`Every message must have an ".id", ensure all messages have an id assigned before running`);
230
- }
231
- if (!messages.every(m => m.role === 'customer' || m.role === 'agent' || m.role === 'system')) {
232
- const invalidRoles = messages.filter(m => m.role !== 'customer' && m.role !== 'agent' && m.role !== 'system');
233
- throw new Error(`Every message must have a role of "customer", "agent", or "system". Got invalid roles: ${invalidRoles.map(m => m.role).join(', ')}`);
234
- }
235
- // if message is not in messages, then add it
236
- if (!messages.find(m => m.id === input.message.id)) {
237
- messages.push(input.message);
238
- }
239
-
240
- // 2. Parse the message
241
- progress('Parsing message', 'info', 'SET_PROCESSING', 'user');
242
- const parsePayload = await parser(message.content, 'en');
243
- if (parsePayload.intent) {
244
- message.intent = parsePayload.intent;
245
- }
246
- if (typeof parsePayload.intentScore === 'number') {
247
- message.intentScore = parsePayload.intentScore;
248
- }
249
- message.context = parsePayload.context;
250
- const index = messages.findIndex(m => m.content === message.content || m.id === message.id);
251
- if (index === -1) {
252
- const _message = {
253
- id: idGenerator('customer'),
254
- role: 'customer',
255
- content: message,
256
- context: parsePayload.context,
257
- time: new Date().toISOString(),
258
- };
259
- if (parsePayload.intent) {
260
- _message.intent = parsePayload.intent;
416
+ addInstruction(instruction.content, previousLockAttempt, instruction.id);
261
417
  }
262
- if (typeof parsePayload.intentScore === 'number') {
263
- _message.intentScore = parsePayload.intentScore;
264
- }
265
- message = _message;
266
- messages.push(_message);
267
- progress('Added message', 'info', 'ADD_MESSAGE', _message);
418
+ }
419
+ } else if (typeof instructions === 'object' && 'content' in instructions && 'id' in instructions) {
420
+ addInstruction(instructions.content, previousLockAttempt, instructions.id);
268
421
  } else {
269
- messages[index].context = parsePayload.context;
270
- if (parsePayload.intent) {
271
- messages[index].intent = parsePayload.intent;
272
- }
273
- if (typeof parsePayload.intentScore === 'number') {
274
- messages[index].intentScore = parsePayload.intentScore;
275
- }
276
- message = messages[index];
277
- progress('Parsed message', 'success', 'UPDATE_MESSAGE', message);
422
+ throw new Error('SpiritsError: instructions must be a string or array or {content: "", id: ""}');
278
423
  }
279
- // If this is the first user message, then update conversations intent
280
- const previousUserMessages = messages.filter(m => m.role === 'customer' && m.content !== message.content);
281
- if (!conversation.intent || previousUserMessages.length === 0 && parsePayload.intent) {
282
- conversation.intent = parsePayload.intent;
283
- conversation.intentScore = parsePayload?.intentScore || 0;
284
- progress('Updated conversation intent', 'info', 'UPDATE_CONVERSATION', {intent: parsePayload.intent, intentScore: parsePayload?.intentScore || 0});
424
+ }
425
+
426
+
427
+ // Remove messages that have the given ids
428
+ if (removeInstructions) {
429
+ for (const instructionId of removeInstructions) {
430
+ const index = messages.findIndex(m => m.id === instructionId);
431
+ if (index > -1) {
432
+ messages.splice(index, 1);
433
+ progress('Remove instruction', 'info', 'REMOVE_MESSAGE', instructionId);
434
+ } else {
435
+ console.log('instruction not found', instructionId);
436
+ }
285
437
  }
286
- const oldKeyCount = Object.keys(context).length;
287
- context = updateContext(context, parsePayload.context);
288
- const newKeyCount = Object.keys(context).length;
289
-
290
- if (!conversation.locked && (newKeyCount > oldKeyCount)) {
291
- // Reset lock attempts
292
- conversation.locked = false;
293
- conversation.lockAttempts = 0;
294
- conversation.lockedReason = '';
295
- progress('Reset lock', 'info', 'UPDATE_CONVERSATION', {locked: false, lockAttempts: 0, lockedReason: ''});
438
+ }
439
+
440
+ if (manualMessage) {
441
+ let manualMessageObj = {
442
+ id: idGenerator('agent'),
443
+ role: 'agent',
444
+ content: manualMessage,
445
+ time: new Date().toISOString()
446
+ };
447
+ if (typeof manualMessage !== 'string') {
448
+ throw new Error('Manual message must be of type "string"');
296
449
  }
297
-
298
- const noNewContext = Object.keys(parsePayload.context).length === 0;
299
-
300
- // 3. Run the workflow
301
- progress('Running workflow', 'info', 'SET_PROCESSING', 'system');
302
- const slots = await workflow({
303
- messages,
304
- conversation,
305
- context,
306
- message,
307
- agent: persona,
308
- customer,
309
- intent: {
310
- current: recentUserMessage(messages)?.intent || null,
311
- flow: messages.map(m => m.intent).filter(Boolean),
312
- initial: conversation.intent || null
313
- },
314
- stagnationCount: conversation.lockAttempts || 0
315
- }).then((res) => Array.isArray(res) ? res : [res]);
316
- const hasNoInstructions = slots.every(s => !s.instructions || (Array.isArray(s) && s.instructions.length === 0));
317
- const hasNoCustomMessage = slots.every(s => !s.message);
318
- const previousLockAttempt = conversation.lockAttempts || 0; // Used to track
319
-
320
- if (hasNoInstructions && noNewContext) {
321
- conversation = incrementLockAttempt(conversation, config);
322
- } else {
323
- conversation.lockAttempts = 0;
324
- conversation.locked = false;
325
- conversation.lockedReason = '';
326
- progress('Reset lock', 'info', 'UPDATE_CONVERSATION', {lockAttempts: 0, locked: false, lockedReason: ''});
450
+ if (scheduled) {
451
+ manualMessageObj.time = new Date(scheduled * 1000).toISOString();
452
+ manualMessageObj.scheduled = manualMessageObj.time;
453
+ } else if (secondsDelay) {
454
+ const now = new Date();
455
+ now.setSeconds(now.getSeconds() + secondsDelay);
456
+ manualMessageObj.time = now.toISOString();
457
+ manualMessageObj.delayInSeconds = secondsDelay;
327
458
  }
459
+ messages.push(manualMessageObj);
460
+ progress('Added manual message', 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
461
+ }
328
462
 
329
- let resettedIntent = false;
330
- let _forward;
331
- let _forwardNote;
332
-
333
- for (const {
334
- forward,
335
- forwardNote,
336
- instructions,
337
- removeInstructions,
338
- message: manualMessage,
339
- scheduled,
340
- resetIntent,
341
- secondsDelay,
342
- contextUpsert
343
- } of slots) {
344
-
345
- // Forward to agent or other agent
346
- if (forward) {
347
- conversation = lockConversation(conversation, 'App instructed forward');
348
- _forward = forward;
349
- _forwardNote = forwardNote;
350
- if (typeof forward === 'string') {
351
- updateConversation(conversation, {forwarded: forward});
352
- messages.push({
353
- id: idGenerator('sys'),
354
- role: 'system',
355
- content: `forwarded to "${forward}"`,
356
- time: new Date().toISOString()
357
- });
358
- progress(`Forwarded to "${forward}"`, 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
359
- } else if (typeof forward === 'boolean') {
360
- updateConversation(conversation, {forwarded: conversation.$agent});
361
- messages.push({
362
- id: idGenerator('sys'),
363
- role: 'system',
364
- content: `forwarded to "${forward}"`,
365
- time: new Date().toISOString()
366
- });
367
- progress(`Forwarded to agent`, 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
368
-
369
- } else {
370
- messages.push({
371
- id: idGenerator('sys'),
372
- role: 'system',
373
- content: `forwarded to "${forward.to}" ${forward.mode ? ' (' + forward.mode + ')' : ''}`,
374
- time: new Date().toISOString()
375
- });
376
- progress(`Forwarded to "${forward.to}" ${forward.mode ? ' (' + forward.mode + ')' : ''}`, 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
377
- updateConversation(conversation, {forwarded: forward.to});
378
- }
379
- }
380
-
381
- // Insert instructions context
382
- if (instructions) {
383
- if (typeof instructions === 'string') {
384
- addInstruction(instructions, previousLockAttempt)
385
- } else if (Array.isArray(instructions)) {
386
- for (const instruction of instructions) {
387
- if (typeof instruction === 'string') {
388
- addInstruction(instruction, previousLockAttempt)
389
- } else {
390
- addInstruction(instruction.content, previousLockAttempt, instruction.id)
391
- }
392
- }
393
- } else if (typeof instructions === 'object' && 'content' in instructions && 'id' in instructions) {
394
- addInstruction(instructions.content, previousLockAttempt, instructions.id)
395
- } else {
396
- throw new Error('instructions must be a string or array or {content: "", id: ""}');
397
- }
398
- }
463
+ if (contextUpsert) {
464
+ context = updateContext(context, contextUpsert);
465
+ progress('Upserted context', 'info', 'UPDATE_CONTEXT', contextUpsert);
466
+ }
399
467
 
468
+ if (resetIntent) {
469
+ resettedIntent = true;
470
+ }
400
471
 
401
- // Remove messages that have the given ids
402
- if (removeInstructions) {
403
- for (const instructionId of removeInstructions) {
404
- const index = messages.findIndex(m => m.id === instructionId);
405
- if (index > -1) {
406
- messages.splice(index, 1);
407
- progress('Remove instruction', 'info', 'REMOVE_MESSAGE', instructionId);
408
- } else {
409
- console.log('instruction not found', instructionId);
410
- }
411
- }
412
- }
472
+ }
413
473
 
414
- if (manualMessage) {
415
- let manualMessageObj = {
416
- id: idGenerator('agent'),
417
- role: 'agent',
418
- content: message,
419
- time: new Date().toISOString()
420
- };
421
- if (scheduled) {
422
- manualMessageObj.time = new Date(scheduled * 1000).toISOString();
423
- manualMessageObj.scheduled = manualMessageObj.time;
424
- } else if (secondsDelay) {
425
- const now = new Date();
426
- now.setSeconds(now.getSeconds() + secondsDelay);
427
- manualMessageObj.time = now.toISOString();
428
- manualMessageObj.delayInSeconds = secondsDelay;
429
- }
430
- messages.push(manualMessageObj);
431
- progress('Added manual message', 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
432
- }
474
+ if (resettedIntent && !_forward) {
475
+ conversation.intent = null;
476
+ conversation.intentScore = null;
477
+ conversation.locked = false;
478
+ conversation.lockedReason = '';
479
+ conversation.lockAttempts = 0;
480
+ progress(
481
+ 'Reset conversation intent',
482
+ 'info',
483
+ 'UPDATE_CONVERSATION',
484
+ {intent: null, intentScore: null, locked: false, lockAttempts: 0, lockedReason: ''}
485
+ );
486
+ }
433
487
 
434
- if (contextUpsert) {
435
- context = updateContext(context, contextUpsert);
436
- progress('Upserted context', 'info', 'UPDATE_CONTEXT', contextUpsert);
488
+ // 4. Generate response
489
+ // If conversation previously locked, don't generate
490
+ if (!input.conversation.locked) {
491
+ // If conversation is newly locked, don't generate, unless instructions were provided and no custom messages were provided
492
+ if ((!conversation.locked || !hasNoInstructions) && !!hasNoCustomMessage) {
493
+ try {
494
+ progress('Parsing message', 'info', 'SET_PROCESSING', 'system');
495
+ const generatorPayload = await generator({
496
+ messages,
497
+ persona,
498
+ context,
499
+ llm: config.llm,
500
+ pmt: config.pmt
501
+ });
502
+ if (!generatorPayload.send) {
503
+ progress('Generated response', 'failed', undefined, {error: generatorPayload.error || 'Unknown Reason'});
504
+ console.error(
505
+ `Locking conversation, api returned send false: ${generatorPayload.message}`,
506
+ generatorPayload.error || 'Unknown Reason'
507
+ );
508
+ conversation = lockConversation(conversation, 'API: ' + generatorPayload.error || 'Unknown Reason');
509
+ } else {
510
+ progress('Generated response', 'success', undefined, undefined);
511
+ // Check if already had message
512
+ const agentMessages = messages.filter(m => m.role === 'agent');
513
+ const lastAgentMessage = agentMessages[agentMessages.length - 1];
514
+ if (lastAgentMessage && lastAgentMessage.content === generatorPayload.message) {
515
+ // Error should not have happened
516
+ conversation = lockConversation(conversation, 'Duplicate message');
517
+ } else {
518
+ messages.push({
519
+ id: idGenerator('agent'),
520
+ role: 'agent',
521
+ content: generatorPayload.message,
522
+ time: new Date().toISOString()
523
+ });
524
+ progress('Added agent message', 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
437
525
  }
438
526
 
439
- if (resetIntent) {
440
- resettedIntent = true;
527
+ // Check if conversation was marked for forward (generator message still allowed to be sent)
528
+ if (generatorPayload.forward) {
529
+ conversation = lockConversation(
530
+ conversation,
531
+ 'API: ' + generatorPayload.forwardNote || 'Forwarded by API'
532
+ );
533
+ if (!_forward) {
534
+ _forward = generatorPayload.forward;
535
+ _forwardNote = generatorPayload.forwardNote;
536
+ }
441
537
  }
538
+ }
442
539
 
540
+ } catch (e) {
541
+ console.error(`Locking conversation, error generating response: ${e.message}`);
542
+ conversation = lockConversation(conversation, 'API: ' + e.message);
443
543
  }
444
-
445
- if (resettedIntent && !_forward) {
446
- conversation.intent = null;
447
- conversation.intentScore = null;
448
- conversation.locked = false;
449
- conversation.lockedReason = '';
450
- conversation.lockAttempts = 0;
451
- progress('Reset conversation intent', 'info', 'UPDATE_CONVERSATION', {intent: null, intentScore: null, locked: false, lockAttempts: 0, lockedReason: ''});
452
- }
453
-
454
- // 4. Generate response
455
- // If conversation previously locked, don't generate
456
- if (!input.conversation.locked) {
457
- // If conversation is newly locked, don't generate, unless instructions were provided and no custom messages were provided
458
- if ((!conversation.locked || !hasNoInstructions) && !!hasNoCustomMessage) {
459
- try {
460
- progress('Parsing message', 'info', 'SET_PROCESSING', 'system');
461
- const generatorPayload = await generator({
462
- messages,
463
- persona,
464
- context,
465
- llm: config.llm,
466
- pmt: config.pmt,
467
- });
468
- if (!generatorPayload.send) {
469
- progress('Generated response', 'failed', undefined, {error: generatorPayload.error || 'Unknown Reason'});
470
- console.error(`Locking conversation, api returned send false: ${generatorPayload.message}`, generatorPayload.error || 'Unknown Reason');
471
- conversation = lockConversation(conversation, 'API: ' + generatorPayload.error || 'Unknown Reason');
472
- } else {
473
- progress('Generated response', 'success', undefined, undefined);
474
- // Check if already had message
475
- const agentMessages = messages.filter(m => m.role === 'agent');
476
- const lastAgentMessage = agentMessages[agentMessages.length - 1];
477
- if (lastAgentMessage && lastAgentMessage.content === generatorPayload.message) {
478
- // Error should not have happened
479
- conversation = lockConversation(conversation, 'Duplicate message');
480
- } else {
481
- messages.push({
482
- id: idGenerator('agent'),
483
- role: 'agent',
484
- content: generatorPayload.message,
485
- time: new Date().toISOString()
486
- });
487
- progress('Added agent message', 'info', 'ADD_MESSAGE', messages[messages.length - 1]);
488
- }
489
-
490
- // Check if conversation was marked for forward (generator message still allowed to be sent)
491
- if (generatorPayload.forward) {
492
- conversation = lockConversation(conversation, 'API: ' + generatorPayload.forwardNote || 'Forwarded by API');
493
- if (!_forward) {
494
- _forward = generatorPayload.forward;
495
- _forwardNote = generatorPayload.forwardNote;
496
- }
497
- }
498
- }
499
-
500
- } catch (e) {
501
- console.error(`Locking conversation, error generating response: ${e.message}`);
502
- conversation = lockConversation(conversation, 'API: ' + e.message);
503
- }
504
- }
505
- }
506
-
507
- progress('Parsing message', 'info', 'SET_PROCESSING', null);
508
-
509
- return {
510
- conversation: {
511
- before: conversationBefore,
512
- after: conversation,
513
- forward: _forward || null,
514
- forwardNote: _forwardNote || '',
515
- },
516
- messages: {
517
- before: messagesBefore,
518
- after: messages
519
- },
520
- message: {
521
- before: messageBefore,
522
- after: message
523
- },
524
- context: {
525
- before: contextBefore,
526
- after: context
527
- }
528
- }
544
+ }
529
545
  }
530
- }
546
+
547
+ progress('Parsing message', 'info', 'SET_PROCESSING', null);
548
+
549
+ return {
550
+ conversation: {
551
+ before: conversationBefore,
552
+ after: conversation,
553
+ forward: _forward || null,
554
+ forwardNote: _forwardNote || ''
555
+ },
556
+ messages: {
557
+ before: messagesBefore,
558
+ after: messages
559
+ },
560
+ message: {
561
+ before: messageBefore,
562
+ after: message
563
+ },
564
+ context: {
565
+ before: contextBefore,
566
+ after: context
567
+ }
568
+ };
569
+ }
570
+ };