@multiplayer-app/ai-agent-node 0.1.0-beta.73 → 0.1.0-beta.75

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 (71) hide show
  1. package/dist/cjs/helpers/AIHelper.cjs +5 -3
  2. package/dist/cjs/helpers/AIHelper.cjs.map +1 -1
  3. package/dist/cjs/helpers/AIHelper.d.ts +2 -1
  4. package/dist/cjs/helpers/AIHelper.d.ts.map +1 -1
  5. package/dist/cjs/processors/ActivityProcessor.cjs +16 -2
  6. package/dist/cjs/processors/ActivityProcessor.cjs.map +1 -1
  7. package/dist/cjs/processors/ActivityProcessor.d.ts +2 -0
  8. package/dist/cjs/processors/ActivityProcessor.d.ts.map +1 -1
  9. package/dist/cjs/processors/ActivityProcessor.test.cjs +63 -8
  10. package/dist/cjs/processors/ActivityProcessor.test.cjs.map +1 -1
  11. package/dist/cjs/processors/AgentProcessor.cjs +4 -3
  12. package/dist/cjs/processors/AgentProcessor.cjs.map +1 -1
  13. package/dist/cjs/processors/AgentProcessor.d.ts.map +1 -1
  14. package/dist/cjs/processors/ChatProcessor.cjs +330 -32
  15. package/dist/cjs/processors/ChatProcessor.cjs.map +1 -1
  16. package/dist/cjs/processors/ChatProcessor.d.ts +12 -2
  17. package/dist/cjs/processors/ChatProcessor.d.ts.map +1 -1
  18. package/dist/cjs/processors/ChatProcessor.test.cjs +41 -0
  19. package/dist/cjs/processors/ChatProcessor.test.cjs.map +1 -1
  20. package/dist/cjs/services/AIService.cjs +25 -20
  21. package/dist/cjs/services/AIService.cjs.map +1 -1
  22. package/dist/cjs/services/AIService.d.ts.map +1 -1
  23. package/dist/cjs/store/AgentStore.cjs +11 -0
  24. package/dist/cjs/store/AgentStore.cjs.map +1 -1
  25. package/dist/cjs/store/AgentStore.d.ts +1 -0
  26. package/dist/cjs/store/AgentStore.d.ts.map +1 -1
  27. package/dist/cjs/store/ConfigStore.cjs +7 -0
  28. package/dist/cjs/store/ConfigStore.cjs.map +1 -1
  29. package/dist/cjs/store/ConfigStore.d.ts +1 -0
  30. package/dist/cjs/store/ConfigStore.d.ts.map +1 -1
  31. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  32. package/dist/cjs/utils/utils.cjs +31 -0
  33. package/dist/cjs/utils/utils.cjs.map +1 -0
  34. package/dist/cjs/utils/utils.d.ts +5 -0
  35. package/dist/cjs/utils/utils.d.ts.map +1 -0
  36. package/dist/esm/helpers/AIHelper.d.ts +2 -1
  37. package/dist/esm/helpers/AIHelper.d.ts.map +1 -1
  38. package/dist/esm/helpers/AIHelper.js +5 -3
  39. package/dist/esm/helpers/AIHelper.js.map +1 -1
  40. package/dist/esm/processors/ActivityProcessor.d.ts +2 -0
  41. package/dist/esm/processors/ActivityProcessor.d.ts.map +1 -1
  42. package/dist/esm/processors/ActivityProcessor.js +16 -2
  43. package/dist/esm/processors/ActivityProcessor.js.map +1 -1
  44. package/dist/esm/processors/ActivityProcessor.test.js +63 -8
  45. package/dist/esm/processors/ActivityProcessor.test.js.map +1 -1
  46. package/dist/esm/processors/AgentProcessor.d.ts.map +1 -1
  47. package/dist/esm/processors/AgentProcessor.js +4 -3
  48. package/dist/esm/processors/AgentProcessor.js.map +1 -1
  49. package/dist/esm/processors/ChatProcessor.d.ts +12 -2
  50. package/dist/esm/processors/ChatProcessor.d.ts.map +1 -1
  51. package/dist/esm/processors/ChatProcessor.js +330 -32
  52. package/dist/esm/processors/ChatProcessor.js.map +1 -1
  53. package/dist/esm/processors/ChatProcessor.test.js +41 -0
  54. package/dist/esm/processors/ChatProcessor.test.js.map +1 -1
  55. package/dist/esm/services/AIService.d.ts.map +1 -1
  56. package/dist/esm/services/AIService.js +25 -20
  57. package/dist/esm/services/AIService.js.map +1 -1
  58. package/dist/esm/store/AgentStore.d.ts +1 -0
  59. package/dist/esm/store/AgentStore.d.ts.map +1 -1
  60. package/dist/esm/store/AgentStore.js +11 -0
  61. package/dist/esm/store/AgentStore.js.map +1 -1
  62. package/dist/esm/store/ConfigStore.d.ts +1 -0
  63. package/dist/esm/store/ConfigStore.d.ts.map +1 -1
  64. package/dist/esm/store/ConfigStore.js +7 -0
  65. package/dist/esm/store/ConfigStore.js.map +1 -1
  66. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  67. package/dist/esm/utils/utils.d.ts +5 -0
  68. package/dist/esm/utils/utils.d.ts.map +1 -0
  69. package/dist/esm/utils/utils.js +26 -0
  70. package/dist/esm/utils/utils.js.map +1 -0
  71. package/package.json +3 -3
@@ -1,11 +1,11 @@
1
- import { AgentStatus, AgentToolCallStatus, ChatType, MessageRole, SortOrder, StreamChunkType, ActivityOperationName, AgentToolType } from '@multiplayer-app/ai-agent-types';
1
+ import { ActivityOperationName, AgentStatus, AgentToolCallStatus, AgentToolType, ChatType, MessageRole, SortOrder, StreamChunkType } from '@multiplayer-app/ai-agent-types';
2
2
  import { z } from 'zod';
3
- import { AIHelper, FileHelper } from '../helpers';
4
- import { AgentProcessEventType } from '../store';
5
- import { ContextLimiter } from '../helpers';
3
+ import { AgentProcessEventType, ConfigStore } from '../store';
4
+ import { AIHelper, ContextLimiter, FileHelper } from '../helpers';
6
5
  import { PassThrough } from 'stream';
7
6
  import { logger } from '../libs/logger';
8
- import { ConfigStore } from '../store';
7
+ import { AgentSessionKind } from "@multiplayer-app/ai-agent-types";
8
+ import { getAgentToolName } from "../utils/utils";
9
9
  export class ChatProcessor {
10
10
  aiHelper;
11
11
  chatRepository;
@@ -50,6 +50,117 @@ export class ChatProcessor {
50
50
  }
51
51
  return payload.tenants ?? {};
52
52
  }
53
+ async updateChatAndEmit(chat, update, excludeSocketId) {
54
+ await this.chatRepository.update(chat.id, update);
55
+ const updatedChat = await this.chatRepository.findById(chat.id);
56
+ const chatToEmit = updatedChat ?? { ...chat, ...update };
57
+ this.socketService.emitChatUpdate(chat.userId, chatToEmit, excludeSocketId);
58
+ return chatToEmit;
59
+ }
60
+ async startSubagentProcess(params) {
61
+ const childChat = await this.chatRepository.create({
62
+ title: `${params.subAgentConfig.name} subagent`,
63
+ type: ChatType.Agent,
64
+ status: AgentStatus.Streaming,
65
+ sessionKind: AgentSessionKind.SUBAGENT,
66
+ parentChatId: params.parentChat.id,
67
+ parentMessageId: params.parentMessage.id,
68
+ parentToolCallId: params.parentToolCallId,
69
+ contextKey: params.parentChat.contextKey,
70
+ userId: params.parentChat.userId,
71
+ model: params.subAgentConfig.defaultModel || params.parentChat.model,
72
+ });
73
+ try {
74
+ const parentToolCall = params.parentMessage.toolCalls?.find((toolCall) => toolCall.id === params.parentToolCallId);
75
+ if (parentToolCall) {
76
+ parentToolCall.input = {
77
+ ...(parentToolCall.input ?? {}),
78
+ subagentChatId: childChat.id,
79
+ };
80
+ parentToolCall.subagentChatId = childChat.id;
81
+ const updatedParentMessage = await this.messageRepository.update(params.parentMessage.id, {
82
+ toolCalls: params.parentMessage.toolCalls,
83
+ });
84
+ if (updatedParentMessage) {
85
+ this.socketService.emitMessageUpdate(params.parentChat.userId, updatedParentMessage);
86
+ }
87
+ }
88
+ this.socketService.emitChatUpdate(params.parentChat.userId, childChat);
89
+ const executionTenants = this.getExecutionTenants(params);
90
+ const abortController = this.agentStore.registerSubAgentProcess(childChat.id, params.parentChat.id);
91
+ const userMessage = await this.createMessage(childChat, MessageRole.User, {
92
+ content: JSON.stringify(params.input),
93
+ agentName: undefined
94
+ });
95
+ await this.agentStore.shareAgentProcessEvent(childChat.id, {
96
+ type: AgentProcessEventType.Update,
97
+ data: userMessage
98
+ });
99
+ let assistantMessage = await this.createMessage(childChat, MessageRole.Assistant, {
100
+ content: '',
101
+ agentName: params.subAgentConfig.name
102
+ });
103
+ const parentActivity = await this.activityRepository.create({
104
+ ownerId: params.parentChat.userId,
105
+ groupId: params.parentChat.id,
106
+ name: ActivityOperationName.SUBAGENT,
107
+ tenants: executionTenants,
108
+ sourceId: assistantMessage.id,
109
+ sourceType: 'AgentMessage',
110
+ metadata: {
111
+ modelId: this.aiHelper.getLanguageModelId(childChat.model),
112
+ agentOptions: {
113
+ name: params.subAgentConfig.name,
114
+ modelId: this.aiHelper.getLanguageModelId(params.subAgentConfig.defaultModel),
115
+ temperature: params.subAgentConfig.temperature,
116
+ maxOutputTokens: params.subAgentConfig.maxOutputTokens,
117
+ topP: params.subAgentConfig.topP,
118
+ topK: params.subAgentConfig.topK,
119
+ presencePenalty: params.subAgentConfig.presencePenalty,
120
+ frequencyPenalty: params.subAgentConfig.frequencyPenalty,
121
+ stopSequences: params.subAgentConfig.stopSequences,
122
+ seed: params.subAgentConfig.seed,
123
+ },
124
+ },
125
+ });
126
+ const updatedAssistantMessage = await this.messageRepository.update(assistantMessage.id, {
127
+ activity: parentActivity.id
128
+ });
129
+ const agentOptions = this.aiHelper.getAgentOptionsFromConfig(params.subAgentConfig, params.context);
130
+ agentOptions.onStepFinish = (stepResult) => {
131
+ return this.storeStepActivity({
132
+ chat: params.parentChat,
133
+ parentId: assistantMessage.activity,
134
+ stepResult,
135
+ name: ActivityOperationName.STEP_FINISHED,
136
+ sourceId: assistantMessage.id,
137
+ sourceType: 'AgentMessage',
138
+ tenants: executionTenants,
139
+ });
140
+ };
141
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(childChat.userId, agentOptions.name);
142
+ agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
143
+ await this.streamMessageStep({
144
+ chat: childChat,
145
+ existingMessages: [userMessage, updatedAssistantMessage],
146
+ assistantMessage: updatedAssistantMessage,
147
+ agentOptions,
148
+ signal: abortController.signal,
149
+ tenants: executionTenants,
150
+ executionContext: params.executionContext,
151
+ context: params.context,
152
+ });
153
+ }
154
+ catch (error) {
155
+ await this.storeSubagentResponse({
156
+ subagentChat: childChat,
157
+ content: undefined,
158
+ error: error,
159
+ context: params.context,
160
+ executionContext: params.executionContext,
161
+ });
162
+ }
163
+ }
53
164
  mergeMissingUsageFields(target, source) {
54
165
  for (const [key, sourceValue] of Object.entries(source)) {
55
166
  const targetValue = target[key];
@@ -121,8 +232,12 @@ export class ChatProcessor {
121
232
  }
122
233
  }
123
234
  async listChats(params) {
124
- // Build filter object from params
125
- const filter = {};
235
+ // Build filter object from params. Always exclude subagent chats:
236
+ // the repository translates ROOT into `sessionKind !== SUBAGENT`, which
237
+ // also includes legacy chats where the field is unset.
238
+ const filter = {
239
+ sessionKind: AgentSessionKind.ROOT
240
+ };
126
241
  if (params?.userId) {
127
242
  filter.userId = params.userId;
128
243
  }
@@ -171,15 +286,48 @@ export class ChatProcessor {
171
286
  return this.messageRepository.findByChatIdPaginated(chatId, options);
172
287
  }
173
288
  async deleteChat(chatId) {
289
+ // Collect all subagent descendants BEFORE removing the parent, otherwise the
290
+ // `parentChatId` link is severed and we'd orphan the subtree. Subagents can
291
+ // themselves spawn subagents, so traversal must be recursive.
292
+ const descendantIds = await this.collectDescendantChatIds(chatId);
174
293
  const deleted = await this.chatRepository.delete(chatId);
175
294
  if (!deleted) {
176
295
  throw new Error('Chat not found');
177
296
  }
297
+ // Delete descendant chat rows in parallel. Use `Promise.allSettled` so a
298
+ // missing/already-deleted descendant doesn't abort cleanup of the rest.
299
+ if (descendantIds.length > 0) {
300
+ await Promise.allSettled(descendantIds.map(id => this.chatRepository.delete(id)));
301
+ }
302
+ const allIds = [chatId, ...descendantIds];
178
303
  await Promise.all([
179
- this.messageRepository.deleteByChatId(chatId),
180
- this.activityRepository.deleteByGroupId(chatId), // todo: discuss if this is needed
304
+ ...allIds.map(id => this.messageRepository.deleteByChatId(id)),
305
+ ...allIds.map(id => this.activityRepository.deleteByGroupId(id)),
181
306
  ]);
182
- this.artifactStore.deleteArtifacts(chatId);
307
+ for (const id of allIds) {
308
+ this.artifactStore.deleteArtifacts(id);
309
+ }
310
+ }
311
+ /**
312
+ * Walk the subagent tree rooted at `rootChatId` and return every descendant
313
+ * chat id (excluding the root itself). BFS, with parallel fan-out per level
314
+ * so deeply nested trees don't serialize one DB roundtrip per depth.
315
+ */
316
+ async collectDescendantChatIds(rootChatId) {
317
+ const descendants = [];
318
+ let frontier = [rootChatId];
319
+ while (frontier.length > 0) {
320
+ const childLists = await Promise.all(frontier.map(id => this.chatRepository.find({ parentChatId: id })));
321
+ const nextFrontier = [];
322
+ for (const list of childLists) {
323
+ for (const child of list) {
324
+ descendants.push(child.id);
325
+ nextFrontier.push(child.id);
326
+ }
327
+ }
328
+ frontier = nextFrontier;
329
+ }
330
+ return descendants;
183
331
  }
184
332
  async upsertAndGetChat(payload, excludeSocketId) {
185
333
  const targetUserId = payload.userId ?? 'guest';
@@ -242,6 +390,7 @@ export class ChatProcessor {
242
390
  title: this.getTemporaryTitle(contextKey),
243
391
  type: ChatType.Chat,
244
392
  status: AgentStatus.Streaming,
393
+ sessionKind: AgentSessionKind.ROOT,
245
394
  contextKey,
246
395
  userId: targetUserId,
247
396
  metadata,
@@ -313,8 +462,11 @@ export class ChatProcessor {
313
462
  * Update selected fields (input/output/status) on a specific tool call.
314
463
  */
315
464
  async updateToolCall(params) {
316
- if (params.input === undefined && params.output === undefined && params.status === undefined) {
317
- throw new Error('At least one of input, output, or status must be provided');
465
+ if (params.input === undefined &&
466
+ params.output === undefined &&
467
+ params.status === undefined &&
468
+ params.subagentChatId === undefined) {
469
+ throw new Error('At least one tool call update field must be provided');
318
470
  }
319
471
  const chat = await this.chatRepository.findById(params.chatId);
320
472
  if (!chat) {
@@ -342,6 +494,7 @@ export class ChatProcessor {
342
494
  ...(params.input !== undefined ? { input: params.input } : {}),
343
495
  ...(params.output !== undefined ? { output: params.output } : {}),
344
496
  ...(params.status !== undefined ? { status: params.status } : {}),
497
+ ...(params.subagentChatId !== undefined ? { subagentChatId: params.subagentChatId } : {}),
345
498
  });
346
499
  if (!updatedMessage) {
347
500
  throw new Error('Tool call not found in message');
@@ -351,8 +504,80 @@ export class ChatProcessor {
351
504
  this.socketService.emitMessageUpdate(chat.userId, updatedMessage, params.excludeSocketId);
352
505
  return updatedMessage;
353
506
  }
507
+ async storeSubagentResponse(params) {
508
+ const { subagentChat, content, error, context, executionContext } = params;
509
+ if (!subagentChat.parentChatId || !subagentChat.parentMessageId) {
510
+ throw new Error(`Parent chat or message not found for subagent ${subagentChat.id}`);
511
+ }
512
+ const chat = await this.chatRepository.findById(subagentChat.parentChatId);
513
+ if (!chat) {
514
+ throw new Error(`Parent chat not found for subagent ${subagentChat.id}`);
515
+ }
516
+ const assistantMessage = await this.messageRepository.findById(subagentChat.parentMessageId);
517
+ if (!assistantMessage || assistantMessage.chat !== subagentChat.parentChatId) {
518
+ throw new Error(`Assistant message with id ${subagentChat.parentMessageId} not found`);
519
+ }
520
+ const toolCall = assistantMessage.toolCalls?.find(tc => tc.id === subagentChat.parentToolCallId);
521
+ if (!toolCall) {
522
+ throw new Error(`Tool call with id ${subagentChat.parentToolCallId} not found`);
523
+ }
524
+ if (error) {
525
+ toolCall.output = {
526
+ type: 'error',
527
+ message: error,
528
+ };
529
+ toolCall.status = AgentToolCallStatus.Failed;
530
+ }
531
+ else {
532
+ toolCall.output = content;
533
+ toolCall.status = AgentToolCallStatus.Succeeded;
534
+ }
535
+ await this.messageRepository.update(assistantMessage.id, assistantMessage);
536
+ const agentOptions = await this.aiHelper.getAgentOptions({
537
+ agentName: assistantMessage.agentName,
538
+ context: context,
539
+ executionContext: executionContext,
540
+ });
541
+ const executionTenants = this.getExecutionTenants(params);
542
+ const abortController = this.agentStore.registerAgentProcess(chat.id);
543
+ const existingMessages = await this.messageRepository.findByChatId(chat.id);
544
+ // Limit context to prevent exceeding token limits
545
+ const limitedMessages = ContextLimiter.limitContext(existingMessages, {
546
+ maxMessages: this.config.ai.maxContextMessages,
547
+ keepFirstUserMessage: true,
548
+ keepSystemMessages: true,
549
+ });
550
+ agentOptions.onStepFinish = (stepResult) => {
551
+ return this.storeStepActivity({
552
+ chat,
553
+ parentId: assistantMessage.activity,
554
+ stepResult,
555
+ name: ActivityOperationName.STEP_FINISHED,
556
+ sourceId: assistantMessage.id,
557
+ sourceType: 'AgentMessage',
558
+ tenants: executionTenants,
559
+ });
560
+ };
561
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(chat.userId, agentOptions.name);
562
+ agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
563
+ await this.streamMessageStep({
564
+ chat,
565
+ existingMessages: limitedMessages,
566
+ assistantMessage,
567
+ agentOptions,
568
+ signal: abortController.signal,
569
+ tenants: executionTenants,
570
+ executionContext,
571
+ context,
572
+ });
573
+ }
354
574
  async streamMessageStep(params) {
355
575
  const { chat, assistantMessage, existingMessages, signal, excludeSocketId, agentOptions } = params;
576
+ const persistAndBroadcastToolCalls = async () => {
577
+ await this.messageRepository.update(assistantMessage.id, { toolCalls: assistantMessage.toolCalls ?? [] });
578
+ this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
579
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: assistantMessage });
580
+ };
356
581
  if (signal.aborted) {
357
582
  return;
358
583
  }
@@ -366,12 +591,21 @@ export class ChatProcessor {
366
591
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Error, data: chunk.error });
367
592
  assistantMessage.content = chunk.error.message || 'Sorry, I cannot process you request due to the error';
368
593
  await this.messageRepository.update(assistantMessage.id, assistantMessage);
369
- await this.chatRepository.update(chat.id, { status: AgentStatus.Error });
594
+ await this.updateChatAndEmit(chat, { status: AgentStatus.Error }, excludeSocketId);
595
+ if (chat.sessionKind === AgentSessionKind.SUBAGENT) {
596
+ await this.storeSubagentResponse({
597
+ subagentChat: chat,
598
+ content: undefined,
599
+ error: chunk.error,
600
+ context: params.context,
601
+ executionContext: params.executionContext,
602
+ });
603
+ }
370
604
  continue;
371
605
  }
372
606
  if (chunk.type === 'abort') {
373
607
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Aborted, data: undefined });
374
- await this.chatRepository.update(chat.id, { status: AgentStatus.Aborted });
608
+ await this.updateChatAndEmit(chat, { status: AgentStatus.Aborted }, excludeSocketId);
375
609
  await this.messageRepository.update(assistantMessage.id, assistantMessage);
376
610
  this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
377
611
  continue;
@@ -390,8 +624,9 @@ export class ChatProcessor {
390
624
  ((totalUsage.promptTokens ?? 0) + (totalUsage.completionTokens ?? 0));
391
625
  }
392
626
  await this.messageRepository.update(assistantMessage.id, { ...assistantMessage });
627
+ this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
393
628
  if (chunk.finishReason === 'stop') {
394
- await this.chatRepository.update(chat.id, { status: AgentStatus.Finished });
629
+ let nextChat = await this.updateChatAndEmit(chat, { status: AgentStatus.Finished }, excludeSocketId);
395
630
  if (chat.title && this.isTemporaryTitle(chat.title)) {
396
631
  const title = await this.aiHelper.generateTitleForMessage(chat.contextKey, existingMessages.filter(m => m.role === MessageRole.User), (stepResult) => {
397
632
  return this.storeStepActivity({
@@ -403,16 +638,51 @@ export class ChatProcessor {
403
638
  tenants: params.tenants || {},
404
639
  });
405
640
  }, params.executionContext);
406
- await this.chatRepository.update(chat.id, { title });
407
- this.socketService.emitChatUpdate(chat.userId, { ...chat, title });
641
+ nextChat = await this.updateChatAndEmit(nextChat, { title }, excludeSocketId);
642
+ }
643
+ if (chat.sessionKind === AgentSessionKind.SUBAGENT) {
644
+ await this.storeSubagentResponse({
645
+ subagentChat: chat,
646
+ content: assistantMessage.content,
647
+ error: undefined,
648
+ context: params.context,
649
+ executionContext: params.executionContext,
650
+ });
408
651
  }
409
652
  }
410
653
  if (chunk.finishReason === 'tool-calls') {
411
- if (assistantMessage.toolCalls?.some(toolCall => toolCall.requiresConfirmation && toolCall.approvalId && !toolCall.output)) {
412
- await this.chatRepository.update(chat.id, { status: AgentStatus.WaitingForUserAction });
654
+ const toolCall = assistantMessage.toolCalls?.find(toolCall => toolCall.output === undefined && toolCall.status === AgentToolCallStatus.Running);
655
+ if (!toolCall) {
413
656
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Finished, data: assistantMessage });
657
+ await this.storeSubagentResponse({
658
+ subagentChat: chat,
659
+ content: JSON.stringify(assistantMessage.toolCalls),
660
+ error: undefined,
661
+ context: params.context,
662
+ executionContext: params.executionContext,
663
+ });
414
664
  continue;
415
665
  }
666
+ const toolConfig = ConfigStore.getInstance().getToolConfig(assistantMessage.agentName || '', toolCall.name);
667
+ if (toolCall.requiresConfirmation && toolCall.approvalId) {
668
+ await this.updateChatAndEmit(chat, { status: AgentStatus.WaitingForUserAction }, excludeSocketId);
669
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Finished, data: assistantMessage });
670
+ }
671
+ else if (toolConfig && toolConfig.type === AgentToolType.SUBAGENT) {
672
+ void this.startSubagentProcess({
673
+ parentChat: chat,
674
+ parentMessage: assistantMessage,
675
+ parentToolCallId: toolCall.id,
676
+ toolName: toolCall.name,
677
+ input: toolCall.input,
678
+ subAgentConfig: toolConfig.data.subAgent,
679
+ context: params.context,
680
+ executionContext: params.executionContext,
681
+ }).catch(error => {
682
+ logger.error(error);
683
+ //todo: store error in response
684
+ });
685
+ }
416
686
  }
417
687
  //todo: Handle other finish reasons // length, content, error, other, undefined
418
688
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Finished, data: assistantMessage });
@@ -440,20 +710,43 @@ export class ChatProcessor {
440
710
  if (chunk.type === 'tool-input-start') {
441
711
  if (!assistantMessage.toolCalls)
442
712
  assistantMessage.toolCalls = [];
443
- assistantMessage.toolCalls.push({
444
- id: chunk.id,
445
- name: chunk.toolName,
446
- status: AgentToolCallStatus.Pending,
447
- input: {},
448
- });
713
+ const existingToolCall = assistantMessage.toolCalls.find((toolCall) => toolCall.id === chunk.id);
714
+ if (existingToolCall) {
715
+ existingToolCall.name = chunk.toolName;
716
+ if (!existingToolCall.status) {
717
+ existingToolCall.status = AgentToolCallStatus.Pending;
718
+ }
719
+ existingToolCall.input = existingToolCall.input ?? {};
720
+ }
721
+ else {
722
+ assistantMessage.toolCalls.push({
723
+ id: chunk.id,
724
+ name: chunk.toolName,
725
+ status: AgentToolCallStatus.Pending,
726
+ input: {},
727
+ });
728
+ }
729
+ await persistAndBroadcastToolCalls();
449
730
  continue;
450
731
  }
451
732
  if (chunk.type === 'tool-call') {
452
- const toolCall = assistantMessage.toolCalls?.find(toolCall => toolCall.id === chunk.toolCallId);
453
- if (toolCall) {
733
+ if (!assistantMessage.toolCalls)
734
+ assistantMessage.toolCalls = [];
735
+ let toolCall = assistantMessage.toolCalls.find(toolCall => toolCall.id === chunk.toolCallId);
736
+ if (!toolCall) {
737
+ toolCall = {
738
+ id: chunk.toolCallId,
739
+ name: chunk.toolName,
740
+ status: AgentToolCallStatus.Running,
741
+ input: chunk.input,
742
+ };
743
+ assistantMessage.toolCalls.push(toolCall);
744
+ }
745
+ else {
454
746
  toolCall.status = AgentToolCallStatus.Running;
455
747
  toolCall.input = chunk.input;
456
748
  }
749
+ await persistAndBroadcastToolCalls();
457
750
  continue;
458
751
  }
459
752
  if (chunk.type === 'tool-error') {
@@ -461,29 +754,33 @@ export class ChatProcessor {
461
754
  if (toolCall) {
462
755
  toolCall.status = AgentToolCallStatus.Failed;
463
756
  toolCall.error = chunk.error.message || JSON.stringify(chunk.error);
757
+ await persistAndBroadcastToolCalls();
464
758
  }
465
759
  continue;
466
760
  }
467
761
  if (chunk.type === 'tool-approval-request') {
468
762
  const toolCall = assistantMessage.toolCalls?.find(toolCall => toolCall.id === chunk.toolCall.toolCallId);
469
763
  if (toolCall) {
764
+ const toolConfig = ConfigStore.getInstance().getToolConfig(assistantMessage.agentName || '', toolCall.name);
765
+ if (toolConfig && toolConfig.type === AgentToolType.SUBAGENT) {
766
+ // TODO: allow needsApproval flow for the subagents too
767
+ continue;
768
+ }
470
769
  toolCall.requiresConfirmation = true;
471
770
  toolCall.approvalId = chunk.approvalId;
472
771
  if (toolCall.name === AgentToolType.REQUEST_CLARIFICATION) {
473
772
  toolCall.requiresUserAction = true;
474
773
  }
774
+ await persistAndBroadcastToolCalls();
475
775
  }
476
776
  continue;
477
777
  }
478
778
  if (chunk.type === 'tool-result') {
479
779
  const toolCall = assistantMessage.toolCalls?.find(toolCall => toolCall.id === chunk.toolCallId);
480
780
  if (toolCall) {
481
- if (chunk.output?.requiresApproval) {
482
- toolCall.requiresConfirmation = true;
483
- continue;
484
- }
485
781
  toolCall.status = AgentToolCallStatus.Succeeded;
486
782
  toolCall.output = chunk.output;
783
+ await persistAndBroadcastToolCalls();
487
784
  }
488
785
  continue;
489
786
  }
@@ -719,6 +1016,7 @@ export class ChatProcessor {
719
1016
  signal: abortController.signal,
720
1017
  tenants: executionTenants,
721
1018
  executionContext: payload.executionContext,
1019
+ context: payload.context,
722
1020
  });
723
1021
  }
724
1022
  catch (error) {
@@ -730,7 +1028,7 @@ export class ChatProcessor {
730
1028
  const availableToolsMap = Object.fromEntries(toolNames.map(key => [key, true]));
731
1029
  const agentsConfiguration = ConfigStore.getInstance().getAgentConfigByName(agentOptions.name);
732
1030
  agentsConfiguration.tools.forEach((tool) => {
733
- const toolTitle = tool.data?.title || tool.type;
1031
+ const toolTitle = getAgentToolName(tool);
734
1032
  if (tool.disabledByDefault) {
735
1033
  availableToolsMap[toolTitle] = false;
736
1034
  }