@wundr.io/autogen-orchestrator 1.0.3

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.
@@ -0,0 +1,980 @@
1
+ /**
2
+ * GroupChatManager - Core orchestration for AutoGen-style multi-agent conversations
3
+ *
4
+ * Implements conversational patterns for coordinating multiple AI agents in a
5
+ * group chat setting with configurable speaker selection and termination conditions.
6
+ */
7
+
8
+ import { EventEmitter } from 'eventemitter3';
9
+ import { v4 as uuidv4 } from 'uuid';
10
+
11
+ import { NestedChatManager } from './nested-chat';
12
+ import { SpeakerSelectionManager } from './speaker-selection';
13
+ import { TerminationManager } from './termination';
14
+ import { GroupChatConfigSchema } from './types';
15
+
16
+ import type { NestedChatResult } from './nested-chat';
17
+ import type {
18
+ Message,
19
+ ChatParticipant,
20
+ ChatContext,
21
+ ChatResult,
22
+ ChatStatus,
23
+ ChatMetrics,
24
+ ChatError,
25
+ ChatEvent,
26
+ ChatEventType,
27
+ ChatEventDataMap,
28
+ GroupChatConfig,
29
+ TerminationCondition,
30
+ NestedChatConfig,
31
+ CreateMessageOptions,
32
+ AddParticipantOptions,
33
+ StartChatOptions,
34
+ ParticipantStatus,
35
+ } from './types';
36
+
37
+ /**
38
+ * Events emitted by the GroupChatManager
39
+ */
40
+ export interface GroupChatEvents {
41
+ 'chat:started': (data: { chatId: string; config: GroupChatConfig }) => void;
42
+ 'chat:ended': (data: { chatId: string; result: ChatResult }) => void;
43
+ 'chat:error': (data: { chatId: string; error: ChatError }) => void;
44
+ 'message:sent': (data: { chatId: string; message: Message }) => void;
45
+ 'message:received': (data: { chatId: string; message: Message }) => void;
46
+ 'speaker:selected': (data: {
47
+ chatId: string;
48
+ speaker: string;
49
+ reason?: string;
50
+ }) => void;
51
+ 'round:started': (data: { chatId: string; round: number }) => void;
52
+ 'round:ended': (data: { chatId: string; round: number }) => void;
53
+ 'termination:triggered': (data: { chatId: string; reason: string }) => void;
54
+ 'nested:started': (data: { chatId: string; nestedChatId: string }) => void;
55
+ 'nested:ended': (data: {
56
+ chatId: string;
57
+ nestedChatId: string;
58
+ result: NestedChatResult;
59
+ }) => void;
60
+ }
61
+
62
+ /**
63
+ * Response generator function type
64
+ * Used to generate responses for participants
65
+ */
66
+ export type ResponseGenerator = (
67
+ participant: ChatParticipant,
68
+ messages: Message[],
69
+ context: ChatContext
70
+ ) => Promise<string>;
71
+
72
+ /**
73
+ * GroupChatManager - Orchestrates multi-agent conversations
74
+ */
75
+ export class GroupChatManager extends EventEmitter<GroupChatEvents> {
76
+ private config: GroupChatConfig;
77
+ private participants: Map<string, ChatParticipant> = new Map();
78
+ private messages: Message[] = [];
79
+ private context: ChatContext;
80
+ private status: ChatStatus = 'initializing';
81
+ private startTime?: Date;
82
+ private endTime?: Date;
83
+ private chatId: string;
84
+
85
+ private speakerManager: SpeakerSelectionManager;
86
+ private terminationManager: TerminationManager;
87
+ private nestedChatManager: NestedChatManager;
88
+
89
+ private responseGenerator?: ResponseGenerator;
90
+ private metrics: ChatMetrics;
91
+ private nestedResults: NestedChatResult[] = [];
92
+
93
+ /**
94
+ * Create a new GroupChatManager
95
+ * @param config - Group chat configuration
96
+ */
97
+ constructor(config: GroupChatConfig) {
98
+ super();
99
+
100
+ // Validate configuration
101
+ const validationResult = GroupChatConfigSchema.safeParse(config);
102
+ if (!validationResult.success) {
103
+ throw new Error(
104
+ `Invalid GroupChatConfig: ${validationResult.error.message}`,
105
+ );
106
+ }
107
+
108
+ this.config = config;
109
+ this.chatId = config.id || uuidv4();
110
+
111
+ // Initialize participants
112
+ for (const participant of config.participants) {
113
+ this.participants.set(participant.name, { ...participant });
114
+ }
115
+
116
+ // Initialize context
117
+ this.context = {
118
+ chatId: this.chatId,
119
+ currentRound: 0,
120
+ messageCount: 0,
121
+ activeParticipants: config.participants.map(p => p.name),
122
+ startTime: new Date(),
123
+ state: {},
124
+ };
125
+
126
+ // Initialize managers
127
+ this.speakerManager = new SpeakerSelectionManager(
128
+ config.speakerSelectionMethod,
129
+ );
130
+ this.terminationManager = new TerminationManager(
131
+ config.terminationConditions || [],
132
+ );
133
+ this.nestedChatManager = new NestedChatManager(
134
+ config.nestedChatConfigs || [],
135
+ );
136
+
137
+ // Initialize metrics
138
+ this.metrics = {
139
+ totalTokens: 0,
140
+ avgResponseTimeMs: 0,
141
+ messagesPerParticipant: {},
142
+ tokensPerParticipant: {},
143
+ successfulResponses: 0,
144
+ failedResponses: 0,
145
+ };
146
+
147
+ // Setup nested chat event forwarding
148
+ this.setupNestedChatEvents();
149
+ }
150
+
151
+ /**
152
+ * Setup event forwarding from nested chat manager
153
+ */
154
+ private setupNestedChatEvents(): void {
155
+ this.nestedChatManager.on('nested:started', ({ nestedChatId }) => {
156
+ this.emit('nested:started', { chatId: this.chatId, nestedChatId });
157
+ });
158
+
159
+ this.nestedChatManager.on(
160
+ 'nested:completed',
161
+ ({ nestedChatId, result }) => {
162
+ const nestedResult: NestedChatResult = {
163
+ nestedChatId,
164
+ configId: '', // Will be filled properly
165
+ result,
166
+ parentMessageId: '',
167
+ };
168
+ this.nestedResults.push(nestedResult);
169
+ this.emit('nested:ended', {
170
+ chatId: this.chatId,
171
+ nestedChatId,
172
+ result: nestedResult,
173
+ });
174
+ },
175
+ );
176
+ }
177
+
178
+ /**
179
+ * Set the response generator function
180
+ * @param generator - Function to generate participant responses
181
+ */
182
+ setResponseGenerator(generator: ResponseGenerator): void {
183
+ this.responseGenerator = generator;
184
+ }
185
+
186
+ /**
187
+ * Start the group chat
188
+ * @param options - Optional start options
189
+ * @returns Chat result when completed
190
+ */
191
+ async start(options: StartChatOptions = {}): Promise<ChatResult> {
192
+ if (this.status !== 'initializing') {
193
+ throw new Error(`Cannot start chat in status: ${this.status}`);
194
+ }
195
+
196
+ this.status = 'active';
197
+ this.startTime = new Date();
198
+ this.context.startTime = this.startTime;
199
+
200
+ // Initialize state if provided
201
+ if (options.initialState) {
202
+ this.context.state = { ...options.initialState };
203
+ }
204
+
205
+ this.emitEvent('chat_started', { config: this.config });
206
+ this.emit('chat:started', { chatId: this.chatId, config: this.config });
207
+
208
+ try {
209
+ // Add initial message if provided
210
+ if (options.initialMessage) {
211
+ const senderName =
212
+ options.initialSender || this.config.adminName || 'user';
213
+ await this.addMessage({
214
+ role: 'user',
215
+ content: options.initialMessage,
216
+ name: senderName,
217
+ });
218
+ }
219
+
220
+ // Run the conversation loop
221
+ const result = await this.runConversationLoop(options);
222
+
223
+ return result;
224
+ } catch (error) {
225
+ return this.handleError(error);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Run the main conversation loop
231
+ * @param options - Start options
232
+ * @returns Chat result
233
+ */
234
+ private async runConversationLoop(
235
+ options: StartChatOptions,
236
+ ): Promise<ChatResult> {
237
+ const maxRounds = this.config.maxRounds || 100;
238
+ const maxMessages = this.config.maxMessages || 1000;
239
+
240
+ while (this.status === 'active') {
241
+ // Check termination conditions
242
+ const terminationResult = await this.terminationManager.evaluate(
243
+ this.messages,
244
+ Array.from(this.participants.values()),
245
+ this.context,
246
+ );
247
+
248
+ if (terminationResult.shouldTerminate) {
249
+ return this.endChat('completed', terminationResult.reason);
250
+ }
251
+
252
+ // Check limits
253
+ if (this.context.currentRound >= maxRounds) {
254
+ return this.endChat(
255
+ 'terminated',
256
+ `Maximum rounds reached: ${maxRounds}`,
257
+ );
258
+ }
259
+
260
+ if (this.messages.length >= maxMessages) {
261
+ return this.endChat(
262
+ 'terminated',
263
+ `Maximum messages reached: ${maxMessages}`,
264
+ );
265
+ }
266
+
267
+ // Start new round
268
+ this.context.currentRound++;
269
+ this.emitEvent('round_started', { round: this.context.currentRound });
270
+ this.emit('round:started', {
271
+ chatId: this.chatId,
272
+ round: this.context.currentRound,
273
+ });
274
+
275
+ // Select next speaker
276
+ const skipSelection =
277
+ options.skipInitialSelection && this.context.currentRound === 1;
278
+
279
+ if (!skipSelection) {
280
+ const selectionResult = await this.speakerManager.selectSpeaker(
281
+ Array.from(this.participants.values()),
282
+ this.messages,
283
+ this.context,
284
+ this.config.speakerSelectionConfig,
285
+ );
286
+
287
+ this.context.previousSpeaker = this.context.currentSpeaker;
288
+ this.context.currentSpeaker = selectionResult.speaker;
289
+
290
+ this.emitEvent('speaker_selected', {
291
+ speaker: selectionResult.speaker,
292
+ reason: selectionResult.reason,
293
+ });
294
+ this.emit('speaker:selected', {
295
+ chatId: this.chatId,
296
+ speaker: selectionResult.speaker,
297
+ reason: selectionResult.reason,
298
+ });
299
+
300
+ // Generate and add response
301
+ const participant = this.participants.get(selectionResult.speaker);
302
+ if (participant) {
303
+ const response = await this.generateResponse(participant);
304
+
305
+ if (response) {
306
+ const message = await this.addMessage({
307
+ role: 'assistant',
308
+ content: response,
309
+ name: participant.name,
310
+ });
311
+
312
+ // Check for nested chat triggers
313
+ if (this.config.allowNestedChats) {
314
+ await this.checkNestedChatTriggers(message);
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ // End round
321
+ this.emitEvent('round_ended', { round: this.context.currentRound });
322
+ this.emit('round:ended', {
323
+ chatId: this.chatId,
324
+ round: this.context.currentRound,
325
+ });
326
+ }
327
+
328
+ // If we exit the loop due to status change
329
+ return this.endChat(this.status as ChatStatus, 'Chat stopped');
330
+ }
331
+
332
+ /**
333
+ * Generate a response for a participant
334
+ * @param participant - Participant to generate response for
335
+ * @returns Generated response content
336
+ */
337
+ private async generateResponse(
338
+ participant: ChatParticipant,
339
+ ): Promise<string | null> {
340
+ const startTime = Date.now();
341
+
342
+ try {
343
+ // Update participant status
344
+ participant.status = 'busy';
345
+
346
+ let response: string;
347
+
348
+ if (this.responseGenerator) {
349
+ response = await this.responseGenerator(
350
+ participant,
351
+ this.messages,
352
+ this.context,
353
+ );
354
+ } else {
355
+ // Default placeholder response
356
+ response = this.generatePlaceholderResponse(participant);
357
+ }
358
+
359
+ // Update metrics
360
+ const latency = Date.now() - startTime;
361
+ this.updateMetrics(participant.name, latency, response.length);
362
+
363
+ participant.status = 'idle';
364
+ this.metrics.successfulResponses++;
365
+
366
+ return response;
367
+ } catch (error) {
368
+ participant.status = 'error';
369
+ this.metrics.failedResponses++;
370
+
371
+ const errorMessage =
372
+ error instanceof Error ? error.message : String(error);
373
+ console.error(
374
+ `Error generating response for ${participant.name}: ${errorMessage}`,
375
+ );
376
+
377
+ return null;
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Generate a placeholder response when no generator is set
383
+ * @param participant - Participant to generate for
384
+ * @returns Placeholder response
385
+ */
386
+ private generatePlaceholderResponse(participant: ChatParticipant): string {
387
+ const prompts = [
388
+ `[${participant.name}]: I acknowledge the message and am ready to contribute.`,
389
+ `[${participant.name}]: Based on my expertise in ${participant.capabilities.join(', ')}, I suggest we proceed.`,
390
+ `[${participant.name}]: Let me analyze this from my perspective.`,
391
+ ];
392
+
393
+ return prompts[Math.floor(Math.random() * prompts.length)]!;
394
+ }
395
+
396
+ /**
397
+ * Update metrics after a response
398
+ * @param participantName - Name of the participant
399
+ * @param latencyMs - Response latency
400
+ * @param tokenEstimate - Estimated token count
401
+ */
402
+ private updateMetrics(
403
+ participantName: string,
404
+ latencyMs: number,
405
+ tokenEstimate: number,
406
+ ): void {
407
+ // Update per-participant metrics
408
+ this.metrics.messagesPerParticipant[participantName] =
409
+ (this.metrics.messagesPerParticipant[participantName] || 0) + 1;
410
+
411
+ const estimatedTokens = Math.ceil(tokenEstimate / 4); // Rough estimate
412
+ this.metrics.tokensPerParticipant[participantName] =
413
+ (this.metrics.tokensPerParticipant[participantName] || 0) +
414
+ estimatedTokens;
415
+
416
+ this.metrics.totalTokens += estimatedTokens;
417
+
418
+ // Update average response time
419
+ const totalResponses =
420
+ this.metrics.successfulResponses + this.metrics.failedResponses;
421
+ this.metrics.avgResponseTimeMs =
422
+ (this.metrics.avgResponseTimeMs * (totalResponses - 1) + latencyMs) /
423
+ totalResponses;
424
+ }
425
+
426
+ /**
427
+ * Check if a message triggers a nested chat
428
+ * @param message - Message to check
429
+ */
430
+ private async checkNestedChatTriggers(message: Message): Promise<void> {
431
+ const triggeredConfig = this.nestedChatManager.checkTrigger(
432
+ message,
433
+ Array.from(this.participants.values()),
434
+ this.context,
435
+ );
436
+
437
+ if (triggeredConfig) {
438
+ await this.runNestedChat(triggeredConfig, message.id);
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Run a nested chat session
444
+ * @param config - Nested chat configuration
445
+ * @param triggerMessageId - ID of the triggering message
446
+ */
447
+ private async runNestedChat(
448
+ config: NestedChatConfig,
449
+ triggerMessageId: string,
450
+ ): Promise<void> {
451
+ const nestedChatId = this.nestedChatManager.startNestedChat(
452
+ config,
453
+ this.chatId,
454
+ triggerMessageId,
455
+ Array.from(this.participants.values()),
456
+ this.context,
457
+ );
458
+
459
+ // Run nested chat rounds
460
+ const maxRounds = config.maxRounds || 5;
461
+ let round = 0;
462
+
463
+ while (round < maxRounds && this.nestedChatManager.hasActiveChats()) {
464
+ const nestedContext = this.nestedChatManager.getContext(nestedChatId);
465
+ if (!nestedContext) {
466
+ break;
467
+ }
468
+
469
+ // Select speaker for nested chat
470
+ const nestedState = this.nestedChatManager.getActiveChat(nestedChatId);
471
+ if (!nestedState) {
472
+ break;
473
+ }
474
+
475
+ const selectionResult = await this.speakerManager.selectSpeaker(
476
+ nestedState.participants,
477
+ nestedState.messages,
478
+ nestedContext,
479
+ );
480
+
481
+ // Generate response
482
+ const participant = nestedState.participants.find(
483
+ p => p.name === selectionResult.speaker,
484
+ );
485
+
486
+ if (participant && this.responseGenerator) {
487
+ const response = await this.responseGenerator(
488
+ participant,
489
+ nestedState.messages,
490
+ nestedContext,
491
+ );
492
+
493
+ if (response) {
494
+ this.nestedChatManager.addMessage(nestedChatId, {
495
+ id: uuidv4(),
496
+ role: 'assistant',
497
+ content: response,
498
+ name: participant.name,
499
+ timestamp: new Date(),
500
+ status: 'delivered',
501
+ });
502
+ }
503
+ }
504
+
505
+ this.nestedChatManager.incrementRound(nestedChatId);
506
+ round++;
507
+ }
508
+
509
+ // End nested chat
510
+ const result = await this.nestedChatManager.endNestedChat(
511
+ nestedChatId,
512
+ 'completed',
513
+ `Completed after ${round} rounds`,
514
+ );
515
+
516
+ this.nestedResults.push(result);
517
+
518
+ // Add summary to main chat if available
519
+ if (result.summary) {
520
+ await this.addMessage({
521
+ role: 'system',
522
+ content: `[Nested Chat Summary]: ${result.summary}`,
523
+ name: 'system',
524
+ });
525
+ }
526
+ }
527
+
528
+ /**
529
+ * Add a message to the chat
530
+ * @param options - Message options
531
+ * @returns Created message
532
+ */
533
+ async addMessage(options: CreateMessageOptions): Promise<Message> {
534
+ const message: Message = {
535
+ id: uuidv4(),
536
+ role: options.role,
537
+ content: options.content,
538
+ name: options.name,
539
+ timestamp: new Date(),
540
+ contentType: options.contentType || 'text',
541
+ functionCall: options.functionCall,
542
+ metadata: {
543
+ ...options.metadata,
544
+ tokenCount: Math.ceil(options.content.length / 4),
545
+ },
546
+ status: 'delivered',
547
+ };
548
+
549
+ this.messages.push(message);
550
+ this.context.messageCount = this.messages.length;
551
+
552
+ this.emitEvent('message_received', { message });
553
+ this.emit('message:received', { chatId: this.chatId, message });
554
+
555
+ return message;
556
+ }
557
+
558
+ /**
559
+ * Add a participant to the chat
560
+ * @param options - Participant options
561
+ * @returns Created participant
562
+ */
563
+ addParticipant(options: AddParticipantOptions): ChatParticipant {
564
+ const participant: ChatParticipant = {
565
+ id: uuidv4(),
566
+ name: options.name,
567
+ type: options.type,
568
+ systemPrompt: options.systemPrompt,
569
+ status: 'active',
570
+ capabilities: options.capabilities || [],
571
+ modelConfig: options.modelConfig,
572
+ functions: options.functions,
573
+ maxConsecutiveReplies: options.maxConsecutiveReplies,
574
+ description: options.description,
575
+ };
576
+
577
+ this.participants.set(participant.name, participant);
578
+ this.context.activeParticipants.push(participant.name);
579
+
580
+ return participant;
581
+ }
582
+
583
+ /**
584
+ * Remove a participant from the chat
585
+ * @param name - Participant name
586
+ */
587
+ removeParticipant(name: string): void {
588
+ this.participants.delete(name);
589
+ this.context.activeParticipants = this.context.activeParticipants.filter(
590
+ n => n !== name,
591
+ );
592
+ }
593
+
594
+ /**
595
+ * Update a participant's status
596
+ * @param name - Participant name
597
+ * @param status - New status
598
+ */
599
+ updateParticipantStatus(name: string, status: ParticipantStatus): void {
600
+ const participant = this.participants.get(name);
601
+ if (participant) {
602
+ participant.status = status;
603
+ }
604
+ }
605
+
606
+ /**
607
+ * Add a termination condition
608
+ * @param condition - Condition to add
609
+ */
610
+ addTerminationCondition(condition: TerminationCondition): void {
611
+ this.terminationManager.addCondition(condition);
612
+ }
613
+
614
+ /**
615
+ * Add a nested chat configuration
616
+ * @param config - Nested chat config
617
+ */
618
+ addNestedChatConfig(config: NestedChatConfig): void {
619
+ this.nestedChatManager.addConfig(config);
620
+ }
621
+
622
+ /**
623
+ * Pause the chat
624
+ */
625
+ pause(): void {
626
+ if (this.status === 'active') {
627
+ this.status = 'paused';
628
+ }
629
+ }
630
+
631
+ /**
632
+ * Resume the chat
633
+ */
634
+ resume(): void {
635
+ if (this.status === 'paused') {
636
+ this.status = 'active';
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Stop the chat
642
+ * @param reason - Reason for stopping
643
+ * @returns Chat result
644
+ */
645
+ async stop(reason?: string): Promise<ChatResult> {
646
+ return this.endChat('terminated', reason || 'Manually stopped');
647
+ }
648
+
649
+ /**
650
+ * End the chat and produce a result
651
+ * @param status - Final status
652
+ * @param reason - Termination reason
653
+ * @returns Chat result
654
+ */
655
+ private endChat(status: ChatStatus, reason?: string): ChatResult {
656
+ this.status = status;
657
+ this.endTime = new Date();
658
+
659
+ const durationMs =
660
+ this.endTime.getTime() -
661
+ (this.startTime?.getTime() || this.endTime.getTime());
662
+
663
+ const result: ChatResult = {
664
+ chatId: this.chatId,
665
+ status,
666
+ messages: [...this.messages],
667
+ summary: this.generateSummary(),
668
+ terminationReason: reason,
669
+ totalRounds: this.context.currentRound,
670
+ totalMessages: this.messages.length,
671
+ participants: Array.from(this.participants.keys()),
672
+ durationMs,
673
+ metrics: { ...this.metrics },
674
+ nestedResults: [...this.nestedResults],
675
+ startedAt: this.startTime || new Date(),
676
+ endedAt: this.endTime,
677
+ };
678
+
679
+ if (reason) {
680
+ this.emitEvent('termination_triggered', { reason });
681
+ this.emit('termination:triggered', { chatId: this.chatId, reason });
682
+ }
683
+
684
+ this.emitEvent('chat_ended', { result });
685
+ this.emit('chat:ended', { chatId: this.chatId, result });
686
+
687
+ return result;
688
+ }
689
+
690
+ /**
691
+ * Handle an error during chat execution
692
+ * @param error - Error that occurred
693
+ * @returns Chat result with error
694
+ */
695
+ private handleError(error: unknown): ChatResult {
696
+ const chatError: ChatError = {
697
+ code: 'CHAT_ERROR',
698
+ message: error instanceof Error ? error.message : String(error),
699
+ stack: error instanceof Error ? error.stack : undefined,
700
+ context: {
701
+ round: this.context.currentRound,
702
+ messageCount: this.messages.length,
703
+ },
704
+ recoverable: false,
705
+ };
706
+
707
+ this.emit('chat:error', { chatId: this.chatId, error: chatError });
708
+
709
+ const result = this.endChat('error', chatError.message);
710
+ result.error = chatError;
711
+
712
+ return result;
713
+ }
714
+
715
+ /**
716
+ * Generate a summary of the conversation
717
+ * @returns Summary string
718
+ */
719
+ private generateSummary(): string {
720
+ const participantCounts = this.metrics.messagesPerParticipant;
721
+ const topContributors = Object.entries(participantCounts)
722
+ .sort(([, a], [, b]) => b - a)
723
+ .slice(0, 3)
724
+ .map(([name, count]) => `${name} (${count})`)
725
+ .join(', ');
726
+
727
+ return (
728
+ `Chat completed with ${this.messages.length} messages over ${this.context.currentRound} rounds. ` +
729
+ `Top contributors: ${topContributors || 'none'}. ` +
730
+ `Duration: ${Math.round((this.endTime?.getTime() || Date.now()) - (this.startTime?.getTime() || Date.now())) / 1000}s.`
731
+ );
732
+ }
733
+
734
+ /**
735
+ * Emit a chat event
736
+ * @param type - Event type
737
+ * @param data - Event data typed based on event type
738
+ */
739
+ private emitEvent<T extends ChatEventType>(
740
+ type: T,
741
+ data: T extends keyof ChatEventDataMap
742
+ ? ChatEventDataMap[T]
743
+ : Record<string, unknown>,
744
+ ): void {
745
+ const event: ChatEvent<T> = {
746
+ type,
747
+ timestamp: new Date(),
748
+ chatId: this.chatId,
749
+ data,
750
+ };
751
+
752
+ // Internal event tracking if needed
753
+ this.context.state['lastEvent'] = event;
754
+ }
755
+
756
+ /**
757
+ * Get the current chat status
758
+ * @returns Current status
759
+ */
760
+ getStatus(): ChatStatus {
761
+ return this.status;
762
+ }
763
+
764
+ /**
765
+ * Get the chat ID
766
+ * @returns Chat ID
767
+ */
768
+ getChatId(): string {
769
+ return this.chatId;
770
+ }
771
+
772
+ /**
773
+ * Get all messages
774
+ * @returns Message array
775
+ */
776
+ getMessages(): Message[] {
777
+ return [...this.messages];
778
+ }
779
+
780
+ /**
781
+ * Get all participants
782
+ * @returns Participant array
783
+ */
784
+ getParticipants(): ChatParticipant[] {
785
+ return Array.from(this.participants.values());
786
+ }
787
+
788
+ /**
789
+ * Get the current context
790
+ * @returns Chat context
791
+ */
792
+ getContext(): ChatContext {
793
+ return { ...this.context };
794
+ }
795
+
796
+ /**
797
+ * Get current metrics
798
+ * @returns Chat metrics
799
+ */
800
+ getMetrics(): ChatMetrics {
801
+ return { ...this.metrics };
802
+ }
803
+
804
+ /**
805
+ * Update context state
806
+ * @param key - State key
807
+ * @param value - State value (typed for common use cases)
808
+ */
809
+ updateState<T extends string | number | boolean | object | null>(
810
+ key: string,
811
+ value: T,
812
+ ): void {
813
+ this.context.state[key] = value;
814
+ }
815
+
816
+ /**
817
+ * Get context state value
818
+ * @param key - State key
819
+ * @returns State value
820
+ */
821
+ getState<T>(key: string): T | undefined {
822
+ return this.context.state[key] as T | undefined;
823
+ }
824
+ }
825
+
826
+ /**
827
+ * Builder for creating GroupChatManager instances
828
+ */
829
+ export class GroupChatBuilder {
830
+ private config: Partial<GroupChatConfig> = {
831
+ participants: [],
832
+ terminationConditions: [],
833
+ nestedChatConfigs: [],
834
+ };
835
+
836
+ /**
837
+ * Set the chat name
838
+ * @param name - Chat name
839
+ */
840
+ withName(name: string): this {
841
+ this.config.name = name;
842
+ return this;
843
+ }
844
+
845
+ /**
846
+ * Set the chat description
847
+ * @param description - Chat description
848
+ */
849
+ withDescription(description: string): this {
850
+ this.config.description = description;
851
+ return this;
852
+ }
853
+
854
+ /**
855
+ * Add a participant
856
+ * @param participant - Participant to add
857
+ */
858
+ withParticipant(participant: ChatParticipant): this {
859
+ this.config.participants = this.config.participants || [];
860
+ this.config.participants.push(participant);
861
+ return this;
862
+ }
863
+
864
+ /**
865
+ * Set the speaker selection method
866
+ * @param method - Selection method
867
+ */
868
+ withSpeakerSelection(
869
+ method: GroupChatConfig['speakerSelectionMethod'],
870
+ ): this {
871
+ this.config.speakerSelectionMethod = method;
872
+ return this;
873
+ }
874
+
875
+ /**
876
+ * Set maximum rounds
877
+ * @param maxRounds - Maximum rounds
878
+ */
879
+ withMaxRounds(maxRounds: number): this {
880
+ this.config.maxRounds = maxRounds;
881
+ return this;
882
+ }
883
+
884
+ /**
885
+ * Set maximum messages
886
+ * @param maxMessages - Maximum messages
887
+ */
888
+ withMaxMessages(maxMessages: number): this {
889
+ this.config.maxMessages = maxMessages;
890
+ return this;
891
+ }
892
+
893
+ /**
894
+ * Add a termination condition
895
+ * @param condition - Termination condition
896
+ */
897
+ withTerminationCondition(condition: TerminationCondition): this {
898
+ this.config.terminationConditions = this.config.terminationConditions || [];
899
+ this.config.terminationConditions.push(condition);
900
+ return this;
901
+ }
902
+
903
+ /**
904
+ * Enable nested chats
905
+ */
906
+ withNestedChats(): this {
907
+ this.config.allowNestedChats = true;
908
+ return this;
909
+ }
910
+
911
+ /**
912
+ * Add a nested chat configuration
913
+ * @param config - Nested chat config
914
+ */
915
+ withNestedChatConfig(config: NestedChatConfig): this {
916
+ this.config.nestedChatConfigs = this.config.nestedChatConfigs || [];
917
+ this.config.nestedChatConfigs.push(config);
918
+ return this;
919
+ }
920
+
921
+ /**
922
+ * Set the admin name
923
+ * @param name - Admin name
924
+ */
925
+ withAdmin(name: string): this {
926
+ this.config.adminName = name;
927
+ return this;
928
+ }
929
+
930
+ /**
931
+ * Set timeout
932
+ * @param timeoutMs - Timeout in milliseconds
933
+ */
934
+ withTimeout(timeoutMs: number): this {
935
+ this.config.timeoutMs = timeoutMs;
936
+ return this;
937
+ }
938
+
939
+ /**
940
+ * Build the GroupChatManager
941
+ * @returns Configured GroupChatManager
942
+ */
943
+ build(): GroupChatManager {
944
+ if (!this.config.name) {
945
+ this.config.name = `group-chat-${uuidv4().slice(0, 8)}`;
946
+ }
947
+
948
+ if (!this.config.speakerSelectionMethod) {
949
+ this.config.speakerSelectionMethod = 'round_robin';
950
+ }
951
+
952
+ if (!this.config.participants || this.config.participants.length < 2) {
953
+ throw new Error('GroupChat requires at least 2 participants');
954
+ }
955
+
956
+ return new GroupChatManager(this.config as GroupChatConfig);
957
+ }
958
+ }
959
+
960
+ /**
961
+ * Create a simple participant configuration
962
+ * @param name - Participant name
963
+ * @param systemPrompt - System prompt
964
+ * @param capabilities - Participant capabilities
965
+ * @returns Participant configuration
966
+ */
967
+ export function createParticipant(
968
+ name: string,
969
+ systemPrompt: string,
970
+ capabilities: string[] = [],
971
+ ): ChatParticipant {
972
+ return {
973
+ id: uuidv4(),
974
+ name,
975
+ type: 'agent',
976
+ systemPrompt,
977
+ status: 'active',
978
+ capabilities,
979
+ };
980
+ }