@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.
- package/README.md +1088 -0
- package/dist/group-chat.d.ts +327 -0
- package/dist/group-chat.d.ts.map +1 -0
- package/dist/group-chat.js +724 -0
- package/dist/group-chat.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/nested-chat.d.ts +296 -0
- package/dist/nested-chat.d.ts.map +1 -0
- package/dist/nested-chat.js +600 -0
- package/dist/nested-chat.js.map +1 -0
- package/dist/speaker-selection.d.ts +195 -0
- package/dist/speaker-selection.d.ts.map +1 -0
- package/dist/speaker-selection.js +569 -0
- package/dist/speaker-selection.js.map +1 -0
- package/dist/termination.d.ts +237 -0
- package/dist/termination.d.ts.map +1 -0
- package/dist/termination.js +566 -0
- package/dist/termination.js.map +1 -0
- package/dist/types.d.ts +1248 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +201 -0
- package/dist/types.js.map +1 -0
- package/package.json +59 -0
- package/src/group-chat.ts +980 -0
- package/src/index.ts +145 -0
- package/src/nested-chat.ts +795 -0
- package/src/speaker-selection.ts +794 -0
- package/src/termination.ts +704 -0
- package/src/types.ts +876 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nested Chat Handling for AutoGen-style Group Chat
|
|
3
|
+
*
|
|
4
|
+
* Implements support for sub-discussions within a main conversation,
|
|
5
|
+
* allowing focused interactions between subsets of participants.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitter } from 'eventemitter3';
|
|
9
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
Message,
|
|
13
|
+
ChatParticipant,
|
|
14
|
+
ChatContext,
|
|
15
|
+
ChatResult,
|
|
16
|
+
ChatStatus,
|
|
17
|
+
NestedChatConfig,
|
|
18
|
+
NestedChatTrigger,
|
|
19
|
+
NestedChatTriggerValue,
|
|
20
|
+
NestedChatConditionFn,
|
|
21
|
+
NestedChatResult,
|
|
22
|
+
SummaryMethod,
|
|
23
|
+
CreateMessageOptions,
|
|
24
|
+
} from './types';
|
|
25
|
+
|
|
26
|
+
// Re-export NestedChatResult from types
|
|
27
|
+
export type { NestedChatResult } from './types';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Events emitted by the nested chat manager
|
|
31
|
+
*/
|
|
32
|
+
export interface NestedChatEvents {
|
|
33
|
+
'nested:started': (data: {
|
|
34
|
+
nestedChatId: string;
|
|
35
|
+
config: NestedChatConfig;
|
|
36
|
+
parentMessageId: string;
|
|
37
|
+
}) => void;
|
|
38
|
+
'nested:message': (data: { nestedChatId: string; message: Message }) => void;
|
|
39
|
+
'nested:completed': (data: {
|
|
40
|
+
nestedChatId: string;
|
|
41
|
+
result: ChatResult;
|
|
42
|
+
}) => void;
|
|
43
|
+
'nested:error': (data: { nestedChatId: string; error: Error }) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* State of a nested chat session
|
|
48
|
+
*/
|
|
49
|
+
interface NestedChatState {
|
|
50
|
+
id: string;
|
|
51
|
+
config: NestedChatConfig;
|
|
52
|
+
parentChatId: string;
|
|
53
|
+
parentMessageId: string;
|
|
54
|
+
participants: ChatParticipant[];
|
|
55
|
+
messages: Message[];
|
|
56
|
+
status: ChatStatus;
|
|
57
|
+
startedAt: Date;
|
|
58
|
+
context: ChatContext;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Manager for nested chat sessions within a group chat
|
|
63
|
+
*/
|
|
64
|
+
export class NestedChatManager extends EventEmitter<NestedChatEvents> {
|
|
65
|
+
private configs: Map<string, NestedChatConfig> = new Map();
|
|
66
|
+
private activeChats: Map<string, NestedChatState> = new Map();
|
|
67
|
+
private completedChats: Map<string, NestedChatResult> = new Map();
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a nested chat manager
|
|
71
|
+
* @param configs - Initial nested chat configurations
|
|
72
|
+
*/
|
|
73
|
+
constructor(configs: NestedChatConfig[] = []) {
|
|
74
|
+
super();
|
|
75
|
+
for (const config of configs) {
|
|
76
|
+
this.addConfig(config);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Add a nested chat configuration
|
|
82
|
+
* @param config - Configuration to add
|
|
83
|
+
*/
|
|
84
|
+
addConfig(config: NestedChatConfig): void {
|
|
85
|
+
this.configs.set(config.id, config);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Remove a nested chat configuration
|
|
90
|
+
* @param configId - Configuration ID to remove
|
|
91
|
+
*/
|
|
92
|
+
removeConfig(configId: string): void {
|
|
93
|
+
this.configs.delete(configId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get all configurations
|
|
98
|
+
* @returns Array of nested chat configurations
|
|
99
|
+
*/
|
|
100
|
+
getConfigs(): NestedChatConfig[] {
|
|
101
|
+
return Array.from(this.configs.values());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if a message should trigger a nested chat
|
|
106
|
+
* @param message - Message to check
|
|
107
|
+
* @param participants - Current participants
|
|
108
|
+
* @param context - Current chat context
|
|
109
|
+
* @returns Triggered configuration or null
|
|
110
|
+
*/
|
|
111
|
+
checkTrigger(
|
|
112
|
+
message: Message,
|
|
113
|
+
participants: ChatParticipant[],
|
|
114
|
+
context: ChatContext,
|
|
115
|
+
): NestedChatConfig | null {
|
|
116
|
+
for (const config of this.configs.values()) {
|
|
117
|
+
if (
|
|
118
|
+
this.evaluateTrigger(config.trigger, message, participants, context)
|
|
119
|
+
) {
|
|
120
|
+
return config;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Evaluate if a trigger condition is met
|
|
128
|
+
* @param trigger - Trigger configuration
|
|
129
|
+
* @param message - Current message
|
|
130
|
+
* @param participants - Available participants
|
|
131
|
+
* @param context - Chat context
|
|
132
|
+
* @returns Whether the trigger is activated
|
|
133
|
+
*/
|
|
134
|
+
private evaluateTrigger(
|
|
135
|
+
trigger: NestedChatTrigger,
|
|
136
|
+
message: Message,
|
|
137
|
+
participants: ChatParticipant[],
|
|
138
|
+
context: ChatContext,
|
|
139
|
+
): boolean {
|
|
140
|
+
switch (trigger.type) {
|
|
141
|
+
case 'keyword':
|
|
142
|
+
return this.evaluateKeywordTrigger(trigger.value, message);
|
|
143
|
+
|
|
144
|
+
case 'participant':
|
|
145
|
+
return this.evaluateParticipantTrigger(
|
|
146
|
+
trigger.value,
|
|
147
|
+
message,
|
|
148
|
+
participants,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
case 'condition':
|
|
152
|
+
return this.evaluateConditionTrigger(trigger.value, message, context);
|
|
153
|
+
|
|
154
|
+
case 'manual':
|
|
155
|
+
return this.evaluateManualTrigger(trigger.value, context);
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Evaluate keyword-based trigger
|
|
164
|
+
* @param value - Keyword(s) to check
|
|
165
|
+
* @param message - Message to check
|
|
166
|
+
* @returns Whether keyword is found
|
|
167
|
+
*/
|
|
168
|
+
private evaluateKeywordTrigger(
|
|
169
|
+
value: NestedChatTriggerValue,
|
|
170
|
+
message: Message,
|
|
171
|
+
): boolean {
|
|
172
|
+
const keywords: string[] = Array.isArray(value)
|
|
173
|
+
? value
|
|
174
|
+
: typeof value === 'string'
|
|
175
|
+
? [value]
|
|
176
|
+
: [];
|
|
177
|
+
const contentLower = message.content.toLowerCase();
|
|
178
|
+
|
|
179
|
+
return keywords.some(keyword =>
|
|
180
|
+
contentLower.includes(keyword.toLowerCase()),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Evaluate participant-based trigger
|
|
186
|
+
* @param value - Participant name(s)
|
|
187
|
+
* @param message - Current message
|
|
188
|
+
* @param participants - Available participants
|
|
189
|
+
* @returns Whether participant condition is met
|
|
190
|
+
*/
|
|
191
|
+
private evaluateParticipantTrigger(
|
|
192
|
+
value: NestedChatTriggerValue,
|
|
193
|
+
message: Message,
|
|
194
|
+
participants: ChatParticipant[],
|
|
195
|
+
): boolean {
|
|
196
|
+
const targetParticipants: string[] = Array.isArray(value)
|
|
197
|
+
? value
|
|
198
|
+
: typeof value === 'string'
|
|
199
|
+
? [value]
|
|
200
|
+
: [];
|
|
201
|
+
|
|
202
|
+
// Check if message is from one of the target participants
|
|
203
|
+
if (targetParticipants.includes(message.name)) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check if message mentions target participants
|
|
208
|
+
for (const targetName of targetParticipants) {
|
|
209
|
+
const participant = participants.find(p => p.name === targetName);
|
|
210
|
+
if (participant && message.content.includes(`@${targetName}`)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Evaluate condition-based trigger
|
|
220
|
+
* @param value - Condition expression or function
|
|
221
|
+
* @param message - Current message
|
|
222
|
+
* @param context - Chat context
|
|
223
|
+
* @returns Whether condition is met
|
|
224
|
+
*/
|
|
225
|
+
private evaluateConditionTrigger(
|
|
226
|
+
value: NestedChatTriggerValue,
|
|
227
|
+
message: Message,
|
|
228
|
+
context: ChatContext,
|
|
229
|
+
): boolean {
|
|
230
|
+
// Check if value is a function (NestedChatConditionFn)
|
|
231
|
+
if (typeof value === 'function') {
|
|
232
|
+
return (value as NestedChatConditionFn)(message, context);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (typeof value === 'string') {
|
|
236
|
+
// Simple condition parsing
|
|
237
|
+
const condition = value.toLowerCase();
|
|
238
|
+
|
|
239
|
+
if (condition.includes('round >')) {
|
|
240
|
+
const parts = condition.split('>');
|
|
241
|
+
const threshold = parseInt(parts[1]?.trim() || '0', 10);
|
|
242
|
+
return context.currentRound > threshold;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (condition.includes('messages >')) {
|
|
246
|
+
const parts = condition.split('>');
|
|
247
|
+
const threshold = parseInt(parts[1]?.trim() || '0', 10);
|
|
248
|
+
return context.messageCount > threshold;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Evaluate manual trigger
|
|
257
|
+
* @param value - Manual trigger value (state key to check)
|
|
258
|
+
* @param context - Chat context
|
|
259
|
+
* @returns Whether manual trigger is set
|
|
260
|
+
*/
|
|
261
|
+
private evaluateManualTrigger(
|
|
262
|
+
value: NestedChatTriggerValue,
|
|
263
|
+
context: ChatContext,
|
|
264
|
+
): boolean {
|
|
265
|
+
// For manual triggers, value should be a string representing the state key
|
|
266
|
+
const triggerKey = typeof value === 'string' ? value : String(value);
|
|
267
|
+
return context.state[triggerKey] === true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Start a nested chat session
|
|
272
|
+
* @param config - Nested chat configuration
|
|
273
|
+
* @param parentChatId - Parent chat ID
|
|
274
|
+
* @param parentMessageId - Message that triggered the nested chat
|
|
275
|
+
* @param allParticipants - All available participants
|
|
276
|
+
* @param parentContext - Parent chat context
|
|
277
|
+
* @returns Nested chat ID
|
|
278
|
+
*/
|
|
279
|
+
startNestedChat(
|
|
280
|
+
config: NestedChatConfig,
|
|
281
|
+
parentChatId: string,
|
|
282
|
+
parentMessageId: string,
|
|
283
|
+
allParticipants: ChatParticipant[],
|
|
284
|
+
parentContext: ChatContext,
|
|
285
|
+
): string {
|
|
286
|
+
const nestedChatId = uuidv4();
|
|
287
|
+
|
|
288
|
+
// Select participants for nested chat
|
|
289
|
+
const nestedParticipants = allParticipants.filter(p =>
|
|
290
|
+
config.participants.includes(p.name),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (nestedParticipants.length < 2) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Nested chat requires at least 2 participants, found ${nestedParticipants.length}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Create nested context
|
|
300
|
+
const nestedContext: ChatContext = {
|
|
301
|
+
chatId: nestedChatId,
|
|
302
|
+
currentRound: 0,
|
|
303
|
+
messageCount: 0,
|
|
304
|
+
activeParticipants: nestedParticipants.map(p => p.name),
|
|
305
|
+
startTime: new Date(),
|
|
306
|
+
state: config.shareContext ? { ...parentContext.state } : {},
|
|
307
|
+
parentContext: parentContext,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// Create nested chat state
|
|
311
|
+
const state: NestedChatState = {
|
|
312
|
+
id: nestedChatId,
|
|
313
|
+
config,
|
|
314
|
+
parentChatId,
|
|
315
|
+
parentMessageId,
|
|
316
|
+
participants: nestedParticipants,
|
|
317
|
+
messages: [],
|
|
318
|
+
status: 'active',
|
|
319
|
+
startedAt: new Date(),
|
|
320
|
+
context: nestedContext,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
this.activeChats.set(nestedChatId, state);
|
|
324
|
+
|
|
325
|
+
// Add initial prompt message if configured
|
|
326
|
+
if (config.prompt) {
|
|
327
|
+
const systemMessage = this.createMessage({
|
|
328
|
+
role: 'system',
|
|
329
|
+
content: config.prompt,
|
|
330
|
+
name: 'system',
|
|
331
|
+
});
|
|
332
|
+
state.messages.push(systemMessage);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.emit('nested:started', {
|
|
336
|
+
nestedChatId,
|
|
337
|
+
config,
|
|
338
|
+
parentMessageId,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return nestedChatId;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Add a message to a nested chat
|
|
346
|
+
* @param nestedChatId - Nested chat ID
|
|
347
|
+
* @param message - Message to add
|
|
348
|
+
*/
|
|
349
|
+
addMessage(nestedChatId: string, message: Message): void {
|
|
350
|
+
const state = this.activeChats.get(nestedChatId);
|
|
351
|
+
if (!state) {
|
|
352
|
+
throw new Error(`Nested chat not found: ${nestedChatId}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (state.status !== 'active') {
|
|
356
|
+
throw new Error(`Nested chat is not active: ${state.status}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
state.messages.push(message);
|
|
360
|
+
state.context.messageCount = state.messages.length;
|
|
361
|
+
|
|
362
|
+
this.emit('nested:message', { nestedChatId, message });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* End a nested chat session
|
|
367
|
+
* @param nestedChatId - Nested chat ID
|
|
368
|
+
* @param status - Final status
|
|
369
|
+
* @param terminationReason - Reason for ending
|
|
370
|
+
* @returns Nested chat result
|
|
371
|
+
*/
|
|
372
|
+
async endNestedChat(
|
|
373
|
+
nestedChatId: string,
|
|
374
|
+
status: ChatStatus = 'completed',
|
|
375
|
+
terminationReason?: string,
|
|
376
|
+
): Promise<NestedChatResult> {
|
|
377
|
+
const state = this.activeChats.get(nestedChatId);
|
|
378
|
+
if (!state) {
|
|
379
|
+
throw new Error(`Nested chat not found: ${nestedChatId}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
state.status = status;
|
|
383
|
+
|
|
384
|
+
// Generate summary
|
|
385
|
+
const summary = await this.generateSummary(
|
|
386
|
+
state.messages,
|
|
387
|
+
state.config.summaryMethod || 'last',
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// Create chat result
|
|
391
|
+
const endedAt = new Date();
|
|
392
|
+
const chatResult: ChatResult = {
|
|
393
|
+
chatId: nestedChatId,
|
|
394
|
+
status,
|
|
395
|
+
messages: state.messages,
|
|
396
|
+
summary,
|
|
397
|
+
terminationReason,
|
|
398
|
+
totalRounds: state.context.currentRound,
|
|
399
|
+
totalMessages: state.messages.length,
|
|
400
|
+
participants: state.participants.map(p => p.name),
|
|
401
|
+
durationMs: endedAt.getTime() - state.startedAt.getTime(),
|
|
402
|
+
startedAt: state.startedAt,
|
|
403
|
+
endedAt,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const result: NestedChatResult = {
|
|
407
|
+
nestedChatId,
|
|
408
|
+
configId: state.config.id,
|
|
409
|
+
result: chatResult,
|
|
410
|
+
summary,
|
|
411
|
+
parentMessageId: state.parentMessageId,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Move from active to completed
|
|
415
|
+
this.activeChats.delete(nestedChatId);
|
|
416
|
+
this.completedChats.set(nestedChatId, result);
|
|
417
|
+
|
|
418
|
+
this.emit('nested:completed', { nestedChatId, result: chatResult });
|
|
419
|
+
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Generate a summary of the nested chat
|
|
425
|
+
* @param messages - Chat messages
|
|
426
|
+
* @param method - Summary method
|
|
427
|
+
* @returns Generated summary
|
|
428
|
+
*/
|
|
429
|
+
private async generateSummary(
|
|
430
|
+
messages: Message[],
|
|
431
|
+
method: SummaryMethod,
|
|
432
|
+
): Promise<string> {
|
|
433
|
+
switch (method) {
|
|
434
|
+
case 'last':
|
|
435
|
+
return this.generateLastMessageSummary(messages);
|
|
436
|
+
|
|
437
|
+
case 'llm':
|
|
438
|
+
return this.generateLLMSummary(messages);
|
|
439
|
+
|
|
440
|
+
case 'reflection':
|
|
441
|
+
return this.generateReflectionSummary(messages);
|
|
442
|
+
|
|
443
|
+
case 'custom':
|
|
444
|
+
return this.generateCustomSummary(messages);
|
|
445
|
+
|
|
446
|
+
default:
|
|
447
|
+
return this.generateLastMessageSummary(messages);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Generate summary from the last message
|
|
453
|
+
* @param messages - Chat messages
|
|
454
|
+
* @returns Last message content as summary
|
|
455
|
+
*/
|
|
456
|
+
private generateLastMessageSummary(messages: Message[]): string {
|
|
457
|
+
const lastNonSystemMessage = [...messages]
|
|
458
|
+
.reverse()
|
|
459
|
+
.find(m => m.role !== 'system');
|
|
460
|
+
|
|
461
|
+
if (!lastNonSystemMessage) {
|
|
462
|
+
return 'No substantive messages in nested chat.';
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return `${lastNonSystemMessage.name}: ${lastNonSystemMessage.content}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Generate summary using LLM (placeholder)
|
|
470
|
+
* @param messages - Chat messages
|
|
471
|
+
* @returns LLM-generated summary
|
|
472
|
+
*/
|
|
473
|
+
private async generateLLMSummary(messages: Message[]): Promise<string> {
|
|
474
|
+
// In a real implementation, this would call an LLM
|
|
475
|
+
// For now, generate a structured summary
|
|
476
|
+
|
|
477
|
+
const participantMessages = new Map<string, number>();
|
|
478
|
+
const topics: string[] = [];
|
|
479
|
+
|
|
480
|
+
for (const message of messages) {
|
|
481
|
+
if (message.role !== 'system') {
|
|
482
|
+
participantMessages.set(
|
|
483
|
+
message.name,
|
|
484
|
+
(participantMessages.get(message.name) || 0) + 1,
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// Extract potential topics (simplified)
|
|
488
|
+
const words = message.content.split(/\s+/).filter(w => w.length > 5);
|
|
489
|
+
topics.push(...words.slice(0, 3));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const participantSummary = Array.from(participantMessages.entries())
|
|
494
|
+
.map(([name, count]) => `${name} (${count} messages)`)
|
|
495
|
+
.join(', ');
|
|
496
|
+
|
|
497
|
+
const uniqueTopics = [...new Set(topics)].slice(0, 5).join(', ');
|
|
498
|
+
|
|
499
|
+
return `Nested discussion with ${participantMessages.size} participants (${participantSummary}). Key topics: ${uniqueTopics || 'general discussion'}. Total ${messages.length} messages exchanged.`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Generate a reflection-style summary
|
|
504
|
+
* @param messages - Chat messages
|
|
505
|
+
* @returns Reflection summary
|
|
506
|
+
*/
|
|
507
|
+
private generateReflectionSummary(messages: Message[]): string {
|
|
508
|
+
const nonSystemMessages = messages.filter(m => m.role !== 'system');
|
|
509
|
+
|
|
510
|
+
if (nonSystemMessages.length === 0) {
|
|
511
|
+
return 'No discussion occurred.';
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const firstMessage = nonSystemMessages[0]!;
|
|
515
|
+
const lastMessage = nonSystemMessages[nonSystemMessages.length - 1]!;
|
|
516
|
+
|
|
517
|
+
// Check for resolution indicators
|
|
518
|
+
const resolutionKeywords = [
|
|
519
|
+
'agree',
|
|
520
|
+
'resolved',
|
|
521
|
+
'decided',
|
|
522
|
+
'conclusion',
|
|
523
|
+
'done',
|
|
524
|
+
];
|
|
525
|
+
const hasResolution = nonSystemMessages.some(m =>
|
|
526
|
+
resolutionKeywords.some(k => m.content.toLowerCase().includes(k)),
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
const participants = [...new Set(nonSystemMessages.map(m => m.name))];
|
|
530
|
+
|
|
531
|
+
return (
|
|
532
|
+
`Discussion started with ${firstMessage.name}: "${firstMessage.content.slice(0, 50)}...". ` +
|
|
533
|
+
`${participants.length} participants contributed ${nonSystemMessages.length} messages. ` +
|
|
534
|
+
`${hasResolution ? 'A resolution was reached.' : 'Discussion ongoing.'} ` +
|
|
535
|
+
`Final message from ${lastMessage.name}: "${lastMessage.content.slice(0, 50)}..."`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Generate a custom summary (placeholder)
|
|
541
|
+
* @param messages - Chat messages
|
|
542
|
+
* @returns Custom summary
|
|
543
|
+
*/
|
|
544
|
+
private generateCustomSummary(messages: Message[]): string {
|
|
545
|
+
// Default to last message summary
|
|
546
|
+
return this.generateLastMessageSummary(messages);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Get the state of an active nested chat
|
|
551
|
+
* @param nestedChatId - Nested chat ID
|
|
552
|
+
* @returns Nested chat state or undefined
|
|
553
|
+
*/
|
|
554
|
+
getActiveChat(nestedChatId: string): NestedChatState | undefined {
|
|
555
|
+
return this.activeChats.get(nestedChatId);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Get a completed nested chat result
|
|
560
|
+
* @param nestedChatId - Nested chat ID
|
|
561
|
+
* @returns Nested chat result or undefined
|
|
562
|
+
*/
|
|
563
|
+
getCompletedChat(nestedChatId: string): NestedChatResult | undefined {
|
|
564
|
+
return this.completedChats.get(nestedChatId);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Get all active nested chat IDs
|
|
569
|
+
* @returns Array of active nested chat IDs
|
|
570
|
+
*/
|
|
571
|
+
getActiveChats(): string[] {
|
|
572
|
+
return Array.from(this.activeChats.keys());
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Get all completed nested chat results
|
|
577
|
+
* @returns Array of nested chat results
|
|
578
|
+
*/
|
|
579
|
+
getCompletedChats(): NestedChatResult[] {
|
|
580
|
+
return Array.from(this.completedChats.values());
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Check if there are any active nested chats
|
|
585
|
+
* @returns Whether there are active nested chats
|
|
586
|
+
*/
|
|
587
|
+
hasActiveChats(): boolean {
|
|
588
|
+
return this.activeChats.size > 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Increment the round counter for a nested chat
|
|
593
|
+
* @param nestedChatId - Nested chat ID
|
|
594
|
+
*/
|
|
595
|
+
incrementRound(nestedChatId: string): void {
|
|
596
|
+
const state = this.activeChats.get(nestedChatId);
|
|
597
|
+
if (state) {
|
|
598
|
+
state.context.currentRound++;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Get the current context for a nested chat
|
|
604
|
+
* @param nestedChatId - Nested chat ID
|
|
605
|
+
* @returns Chat context or undefined
|
|
606
|
+
*/
|
|
607
|
+
getContext(nestedChatId: string): ChatContext | undefined {
|
|
608
|
+
return this.activeChats.get(nestedChatId)?.context;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Update state in a nested chat context
|
|
613
|
+
* @param nestedChatId - Nested chat ID
|
|
614
|
+
* @param key - State key
|
|
615
|
+
* @param value - State value
|
|
616
|
+
*/
|
|
617
|
+
updateState(nestedChatId: string, key: string, value: unknown): void {
|
|
618
|
+
const state = this.activeChats.get(nestedChatId);
|
|
619
|
+
if (state) {
|
|
620
|
+
state.context.state[key] = value;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Create a message object
|
|
626
|
+
* @param options - Message creation options
|
|
627
|
+
* @returns Created message
|
|
628
|
+
*/
|
|
629
|
+
private createMessage(options: CreateMessageOptions): Message {
|
|
630
|
+
return {
|
|
631
|
+
id: uuidv4(),
|
|
632
|
+
role: options.role,
|
|
633
|
+
content: options.content,
|
|
634
|
+
name: options.name,
|
|
635
|
+
timestamp: new Date(),
|
|
636
|
+
contentType: options.contentType || 'text',
|
|
637
|
+
functionCall: options.functionCall,
|
|
638
|
+
metadata: options.metadata,
|
|
639
|
+
status: 'delivered',
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Clear all nested chat data
|
|
645
|
+
*/
|
|
646
|
+
clear(): void {
|
|
647
|
+
this.configs.clear();
|
|
648
|
+
this.activeChats.clear();
|
|
649
|
+
this.completedChats.clear();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Builder for creating nested chat configurations
|
|
655
|
+
*/
|
|
656
|
+
export class NestedChatConfigBuilder {
|
|
657
|
+
private config: Partial<NestedChatConfig> = {};
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Set the config ID
|
|
661
|
+
* @param id - Configuration ID
|
|
662
|
+
*/
|
|
663
|
+
withId(id: string): this {
|
|
664
|
+
this.config.id = id;
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Set the config name
|
|
670
|
+
* @param name - Configuration name
|
|
671
|
+
*/
|
|
672
|
+
withName(name: string): this {
|
|
673
|
+
this.config.name = name;
|
|
674
|
+
return this;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Set a keyword trigger
|
|
679
|
+
* @param keywords - Keywords to trigger the nested chat
|
|
680
|
+
*/
|
|
681
|
+
withKeywordTrigger(keywords: string | string[]): this {
|
|
682
|
+
this.config.trigger = {
|
|
683
|
+
type: 'keyword',
|
|
684
|
+
value: keywords,
|
|
685
|
+
description: `Triggered by keyword(s): ${Array.isArray(keywords) ? keywords.join(', ') : keywords}`,
|
|
686
|
+
};
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Set a participant trigger
|
|
692
|
+
* @param participants - Participants that trigger the nested chat
|
|
693
|
+
*/
|
|
694
|
+
withParticipantTrigger(participants: string | string[]): this {
|
|
695
|
+
this.config.trigger = {
|
|
696
|
+
type: 'participant',
|
|
697
|
+
value: participants,
|
|
698
|
+
description: `Triggered by participant(s): ${Array.isArray(participants) ? participants.join(', ') : participants}`,
|
|
699
|
+
};
|
|
700
|
+
return this;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Set a condition trigger
|
|
705
|
+
* @param condition - Condition expression or function
|
|
706
|
+
*/
|
|
707
|
+
withConditionTrigger(
|
|
708
|
+
condition: string | ((message: Message, context: ChatContext) => boolean),
|
|
709
|
+
): this {
|
|
710
|
+
this.config.trigger = {
|
|
711
|
+
type: 'condition',
|
|
712
|
+
value: condition,
|
|
713
|
+
description: 'Triggered by condition',
|
|
714
|
+
};
|
|
715
|
+
return this;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Set a manual trigger
|
|
720
|
+
* @param stateKey - State key to check for manual trigger
|
|
721
|
+
*/
|
|
722
|
+
withManualTrigger(stateKey: string): this {
|
|
723
|
+
this.config.trigger = {
|
|
724
|
+
type: 'manual',
|
|
725
|
+
value: stateKey,
|
|
726
|
+
description: `Manually triggered via state key: ${stateKey}`,
|
|
727
|
+
};
|
|
728
|
+
return this;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Set the participants for the nested chat
|
|
733
|
+
* @param participants - Participant names
|
|
734
|
+
*/
|
|
735
|
+
withParticipants(participants: string[]): this {
|
|
736
|
+
this.config.participants = participants;
|
|
737
|
+
return this;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Set the maximum rounds
|
|
742
|
+
* @param maxRounds - Maximum number of rounds
|
|
743
|
+
*/
|
|
744
|
+
withMaxRounds(maxRounds: number): this {
|
|
745
|
+
this.config.maxRounds = maxRounds;
|
|
746
|
+
return this;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Set the summary method
|
|
751
|
+
* @param method - Summary method
|
|
752
|
+
*/
|
|
753
|
+
withSummaryMethod(method: SummaryMethod): this {
|
|
754
|
+
this.config.summaryMethod = method;
|
|
755
|
+
return this;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Set the initial prompt
|
|
760
|
+
* @param prompt - Prompt for the nested chat
|
|
761
|
+
*/
|
|
762
|
+
withPrompt(prompt: string): this {
|
|
763
|
+
this.config.prompt = prompt;
|
|
764
|
+
return this;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Enable context sharing with parent
|
|
769
|
+
*/
|
|
770
|
+
withSharedContext(): this {
|
|
771
|
+
this.config.shareContext = true;
|
|
772
|
+
return this;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Build the configuration
|
|
777
|
+
* @returns Built nested chat configuration
|
|
778
|
+
*/
|
|
779
|
+
build(): NestedChatConfig {
|
|
780
|
+
if (!this.config.id) {
|
|
781
|
+
this.config.id = uuidv4();
|
|
782
|
+
}
|
|
783
|
+
if (!this.config.name) {
|
|
784
|
+
this.config.name = `nested-chat-${this.config.id}`;
|
|
785
|
+
}
|
|
786
|
+
if (!this.config.trigger) {
|
|
787
|
+
throw new Error('Nested chat config requires a trigger');
|
|
788
|
+
}
|
|
789
|
+
if (!this.config.participants || this.config.participants.length < 2) {
|
|
790
|
+
throw new Error('Nested chat requires at least 2 participants');
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return this.config as NestedChatConfig;
|
|
794
|
+
}
|
|
795
|
+
}
|