@hashgraphonline/conversational-agent 0.1.207 → 0.1.209
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/conversational-agent.d.ts +67 -8
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/langchain/ContentAwareAgentExecutor.d.ts +4 -6
- package/dist/cjs/langchain-agent.d.ts +8 -0
- package/dist/cjs/memory/SmartMemoryManager.d.ts +58 -21
- package/dist/cjs/memory/index.d.ts +1 -1
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index12.js +124 -46
- package/dist/esm/index12.js.map +1 -1
- package/dist/esm/index13.js +178 -13
- package/dist/esm/index13.js.map +1 -1
- package/dist/esm/index14.js +604 -100
- package/dist/esm/index14.js.map +1 -1
- package/dist/esm/index15.js +453 -59
- package/dist/esm/index15.js.map +1 -1
- package/dist/esm/index16.js +44 -172
- package/dist/esm/index16.js.map +1 -1
- package/dist/esm/index17.js +11 -156
- package/dist/esm/index17.js.map +1 -1
- package/dist/esm/index18.js +106 -191
- package/dist/esm/index18.js.map +1 -1
- package/dist/esm/index19.js +7 -90
- package/dist/esm/index19.js.map +1 -1
- package/dist/esm/index2.js +22 -13
- package/dist/esm/index2.js.map +1 -1
- package/dist/esm/index20.js +130 -616
- package/dist/esm/index20.js.map +1 -1
- package/dist/esm/index21.js +138 -215
- package/dist/esm/index21.js.map +1 -1
- package/dist/esm/index22.js +45 -159
- package/dist/esm/index22.js.map +1 -1
- package/dist/esm/index23.js +25 -121
- package/dist/esm/index23.js.map +1 -1
- package/dist/esm/index24.js +83 -56
- package/dist/esm/index24.js.map +1 -1
- package/dist/esm/index25.js +236 -32
- package/dist/esm/index25.js.map +1 -1
- package/dist/esm/index5.js +1 -1
- package/dist/esm/index6.js +295 -17
- package/dist/esm/index6.js.map +1 -1
- package/dist/esm/index8.js +82 -8
- package/dist/esm/index8.js.map +1 -1
- package/dist/types/conversational-agent.d.ts +67 -8
- package/dist/types/index.d.ts +1 -0
- package/dist/types/langchain/ContentAwareAgentExecutor.d.ts +4 -6
- package/dist/types/langchain-agent.d.ts +8 -0
- package/dist/types/memory/SmartMemoryManager.d.ts +58 -21
- package/dist/types/memory/index.d.ts +1 -1
- package/package.json +3 -3
- package/src/context/ReferenceContextManager.ts +9 -4
- package/src/context/ReferenceResponseProcessor.ts +3 -4
- package/src/conversational-agent.ts +379 -31
- package/src/index.ts +2 -0
- package/src/langchain/ContentAwareAgentExecutor.ts +4 -97
- package/src/langchain-agent.ts +94 -11
- package/src/mcp/ContentProcessor.ts +13 -3
- package/src/mcp/adapters/langchain.ts +1 -9
- package/src/memory/ContentStorage.ts +3 -51
- package/src/memory/MemoryWindow.ts +4 -16
- package/src/memory/ReferenceIdGenerator.ts +0 -4
- package/src/memory/SmartMemoryManager.ts +400 -33
- package/src/memory/TokenCounter.ts +12 -16
- package/src/memory/index.ts +1 -1
- package/src/plugins/hcs-10/HCS10Plugin.ts +44 -14
- package/src/services/ContentStoreManager.ts +0 -3
- package/src/types/content-reference.ts +8 -8
- package/src/types/index.ts +0 -1
|
@@ -22,8 +22,23 @@ import { OpenConvaiState } from '@hashgraphonline/standards-agent-kit';
|
|
|
22
22
|
import type { IStateManager } from '@hashgraphonline/standards-agent-kit';
|
|
23
23
|
import { PrivateKey } from '@hashgraph/sdk';
|
|
24
24
|
import { getSystemMessage } from './config/system-message';
|
|
25
|
-
import type { MCPServerConfig } from './mcp/types';
|
|
25
|
+
import type { MCPServerConfig, MCPConnectionStatus } from './mcp/types';
|
|
26
26
|
import { ContentStoreManager } from './services/ContentStoreManager';
|
|
27
|
+
import { SmartMemoryManager, type SmartMemoryConfig } from './memory';
|
|
28
|
+
|
|
29
|
+
export type ToolDescriptor = {
|
|
30
|
+
name: string;
|
|
31
|
+
namespace?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ChatHistoryItem = {
|
|
35
|
+
type: 'human' | 'ai';
|
|
36
|
+
content: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type AgentInstance = ReturnType<typeof createAgent>;
|
|
40
|
+
|
|
41
|
+
export type MirrorNetwork = 'testnet' | 'mainnet' | 'previewnet';
|
|
27
42
|
|
|
28
43
|
const DEFAULT_MODEL_NAME = 'gpt-4o';
|
|
29
44
|
const DEFAULT_TEMPERATURE = 0.1;
|
|
@@ -49,6 +64,12 @@ export interface ConversationalAgentOptions {
|
|
|
49
64
|
enabledPlugins?: string[];
|
|
50
65
|
toolFilter?: (tool: { name: string; namespace?: string }) => boolean;
|
|
51
66
|
mcpServers?: MCPServerConfig[];
|
|
67
|
+
|
|
68
|
+
/** Enable automatic entity memory functionality (default: true) */
|
|
69
|
+
entityMemoryEnabled?: boolean;
|
|
70
|
+
|
|
71
|
+
/** Configuration for entity memory system */
|
|
72
|
+
entityMemoryConfig?: SmartMemoryConfig;
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
/**
|
|
@@ -61,15 +82,17 @@ export interface ConversationalAgentOptions {
|
|
|
61
82
|
* @returns A new instance of the ConversationalAgent class.
|
|
62
83
|
*/
|
|
63
84
|
export class ConversationalAgent {
|
|
64
|
-
|
|
85
|
+
protected agent?: AgentInstance;
|
|
65
86
|
public hcs10Plugin: HCS10Plugin;
|
|
66
87
|
public hcs2Plugin: HCS2Plugin;
|
|
67
88
|
public inscribePlugin: InscribePlugin;
|
|
68
89
|
public hbarTransferPlugin: HbarTransferPlugin;
|
|
69
90
|
public stateManager: IStateManager;
|
|
70
91
|
private options: ConversationalAgentOptions;
|
|
71
|
-
|
|
92
|
+
protected logger: Logger;
|
|
72
93
|
private contentStoreManager?: ContentStoreManager;
|
|
94
|
+
private memoryManager?: SmartMemoryManager | undefined;
|
|
95
|
+
private mcpConnectionStatus: Map<string, MCPConnectionStatus> = new Map();
|
|
73
96
|
|
|
74
97
|
constructor(options: ConversationalAgentOptions) {
|
|
75
98
|
this.options = options;
|
|
@@ -82,6 +105,11 @@ export class ConversationalAgent {
|
|
|
82
105
|
module: 'ConversationalAgent',
|
|
83
106
|
silent: options.disableLogging || false,
|
|
84
107
|
});
|
|
108
|
+
|
|
109
|
+
if (this.options.entityMemoryEnabled !== false) {
|
|
110
|
+
this.memoryManager = new SmartMemoryManager(this.options.entityMemoryConfig);
|
|
111
|
+
this.logger.info('Entity memory initialized');
|
|
112
|
+
}
|
|
85
113
|
}
|
|
86
114
|
|
|
87
115
|
/**
|
|
@@ -110,7 +138,7 @@ export class ConversationalAgent {
|
|
|
110
138
|
const serverSigner = new ServerSigner(
|
|
111
139
|
accountId!,
|
|
112
140
|
privateKeyInstance,
|
|
113
|
-
network as
|
|
141
|
+
network as MirrorNetwork
|
|
114
142
|
);
|
|
115
143
|
|
|
116
144
|
const allPlugins = this.preparePlugins();
|
|
@@ -126,7 +154,6 @@ export class ConversationalAgent {
|
|
|
126
154
|
|
|
127
155
|
this.configureHCS10Plugin(allPlugins);
|
|
128
156
|
|
|
129
|
-
// Initialize ContentStoreManager if MCP servers are configured
|
|
130
157
|
if (this.options.mcpServers && this.options.mcpServers.length > 0) {
|
|
131
158
|
this.contentStoreManager = new ContentStoreManager();
|
|
132
159
|
await this.contentStoreManager.initialize();
|
|
@@ -134,6 +161,11 @@ export class ConversationalAgent {
|
|
|
134
161
|
}
|
|
135
162
|
|
|
136
163
|
await this.agent.boot();
|
|
164
|
+
|
|
165
|
+
// Start MCP connections asynchronously after agent is booted
|
|
166
|
+
if (this.options.mcpServers && this.options.mcpServers.length > 0) {
|
|
167
|
+
this.connectMCP();
|
|
168
|
+
}
|
|
137
169
|
} catch (error) {
|
|
138
170
|
this.logger.error('Failed to initialize ConversationalAgent:', error);
|
|
139
171
|
throw error;
|
|
@@ -186,28 +218,42 @@ export class ConversationalAgent {
|
|
|
186
218
|
*/
|
|
187
219
|
async processMessage(
|
|
188
220
|
message: string,
|
|
189
|
-
chatHistory:
|
|
190
|
-
type: 'human' | 'ai';
|
|
191
|
-
content: string;
|
|
192
|
-
}[] = []
|
|
221
|
+
chatHistory: ChatHistoryItem[] = []
|
|
193
222
|
): Promise<ChatResponse> {
|
|
194
223
|
if (!this.agent) {
|
|
195
224
|
throw new Error('Agent not initialized. Call initialize() first.');
|
|
196
225
|
}
|
|
197
226
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
227
|
+
try {
|
|
228
|
+
const resolvedMessage = this.memoryManager
|
|
229
|
+
? await this.resolveEntitiesInMessage(message)
|
|
230
|
+
: message;
|
|
231
|
+
|
|
232
|
+
const messages = chatHistory.map((msg) => {
|
|
233
|
+
if (msg.type === 'human') {
|
|
234
|
+
return new HumanMessage(msg.content);
|
|
235
|
+
} else {
|
|
236
|
+
return new AIMessage(msg.content);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
205
239
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
240
|
+
const context: ConversationContext = {
|
|
241
|
+
messages,
|
|
242
|
+
};
|
|
209
243
|
|
|
210
|
-
|
|
244
|
+
const response = await this.agent.chat(resolvedMessage, context);
|
|
245
|
+
|
|
246
|
+
if (this.memoryManager) {
|
|
247
|
+
await this.extractAndStoreEntities(response, message);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this.logger.info('Message processed successfully');
|
|
251
|
+
|
|
252
|
+
return response;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
this.logger.error('Error processing message:', error);
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
211
257
|
}
|
|
212
258
|
|
|
213
259
|
/**
|
|
@@ -238,7 +284,7 @@ export class ConversationalAgent {
|
|
|
238
284
|
this.hbarTransferPlugin,
|
|
239
285
|
];
|
|
240
286
|
|
|
241
|
-
const corePlugins = getAllHederaCorePlugins();
|
|
287
|
+
const corePlugins: BasePlugin[] = getAllHederaCorePlugins();
|
|
242
288
|
|
|
243
289
|
if (enabledPlugins) {
|
|
244
290
|
const enabledSet = new Set(enabledPlugins);
|
|
@@ -284,6 +330,7 @@ export class ConversationalAgent {
|
|
|
284
330
|
operationalMode: operationalMode,
|
|
285
331
|
...(userAccountId && { userAccountId }),
|
|
286
332
|
...(scheduleUserTransactionsInBytesMode !== undefined && {
|
|
333
|
+
scheduleUserTransactionsInBytesMode: scheduleUserTransactionsInBytesMode,
|
|
287
334
|
scheduleUserTransactions: scheduleUserTransactionsInBytesMode,
|
|
288
335
|
}),
|
|
289
336
|
},
|
|
@@ -292,7 +339,7 @@ export class ConversationalAgent {
|
|
|
292
339
|
temperature: DEFAULT_TEMPERATURE,
|
|
293
340
|
},
|
|
294
341
|
filtering: {
|
|
295
|
-
toolPredicate: (tool) => {
|
|
342
|
+
toolPredicate: (tool: ToolDescriptor): boolean => {
|
|
296
343
|
if (tool.name === 'hedera-account-transfer-hbar') return false;
|
|
297
344
|
if (this.options.toolFilter && !this.options.toolFilter(tool)) {
|
|
298
345
|
return false;
|
|
@@ -315,7 +362,7 @@ export class ConversationalAgent {
|
|
|
315
362
|
...(this.options.mcpServers && {
|
|
316
363
|
mcp: {
|
|
317
364
|
servers: this.options.mcpServers,
|
|
318
|
-
autoConnect:
|
|
365
|
+
autoConnect: false,
|
|
319
366
|
},
|
|
320
367
|
}),
|
|
321
368
|
debug: {
|
|
@@ -333,7 +380,7 @@ export class ConversationalAgent {
|
|
|
333
380
|
private configureHCS10Plugin(allPlugins: BasePlugin[]): void {
|
|
334
381
|
const hcs10 = allPlugins.find((p) => p.id === 'hcs-10');
|
|
335
382
|
if (hcs10) {
|
|
336
|
-
(hcs10 as { appConfig?: Record<string, unknown> }).appConfig = {
|
|
383
|
+
(hcs10 as BasePlugin & { appConfig?: Record<string, unknown> }).appConfig = {
|
|
337
384
|
stateManager: this.stateManager,
|
|
338
385
|
};
|
|
339
386
|
}
|
|
@@ -469,17 +516,318 @@ export class ConversationalAgent {
|
|
|
469
516
|
}
|
|
470
517
|
}
|
|
471
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Resolve entity references in the message content
|
|
521
|
+
* @param content - Message content to resolve
|
|
522
|
+
* @returns Resolved message content with entity IDs replaced
|
|
523
|
+
*/
|
|
524
|
+
private async resolveEntitiesInMessage(content: string): Promise<string> {
|
|
525
|
+
if (!this.memoryManager) {
|
|
526
|
+
return content;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
this.logger.info(`Starting entity resolution for message: "${content.substring(0, 100)}..."`);
|
|
531
|
+
|
|
532
|
+
if (!content || typeof content !== 'string') {
|
|
533
|
+
this.logger.warn('Invalid content provided for entity resolution');
|
|
534
|
+
return content || '';
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (content.length > 5000) {
|
|
538
|
+
this.logger.warn('Content too long for entity resolution, truncating');
|
|
539
|
+
content = content.substring(0, 5000);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
let resolvedContent = content;
|
|
543
|
+
|
|
544
|
+
const patterns = [
|
|
545
|
+
/\b(my|the|our)\s+(token|account|topic|schedule)\b/gi,
|
|
546
|
+
/'([^']+)'/g,
|
|
547
|
+
/"([^"]+)"/g,
|
|
548
|
+
/\b([A-Z][A-Za-z0-9_-]{2,})\b/g
|
|
549
|
+
];
|
|
550
|
+
|
|
551
|
+
for (const pattern of patterns) {
|
|
552
|
+
try {
|
|
553
|
+
let match;
|
|
554
|
+
const matches: any[] = [];
|
|
555
|
+
while ((match = pattern.exec(resolvedContent)) !== null) {
|
|
556
|
+
matches.push(match);
|
|
557
|
+
if (!pattern.global) break;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
for (const match of matches) {
|
|
561
|
+
try {
|
|
562
|
+
const originalRef = match[0];
|
|
563
|
+
const entityName = match[1] || match[0];
|
|
564
|
+
|
|
565
|
+
if (entityName.length > 50) {
|
|
566
|
+
this.logger.debug(`Skipping overly long entity name: ${entityName.substring(0, 20)}...`);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const commonWords = ['the', 'my', 'our', 'this', 'that', 'it', 'is', 'are', 'was', 'will'];
|
|
571
|
+
if (commonWords.includes(entityName.toLowerCase())) {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let entityAssociations: any[] = [];
|
|
576
|
+
|
|
577
|
+
if (match[1] && ['token', 'account', 'topic', 'schedule'].includes(match[1].toLowerCase())) {
|
|
578
|
+
const entityType = match[1].toLowerCase();
|
|
579
|
+
entityAssociations = this.memoryManager.resolveEntityReference(
|
|
580
|
+
entityName,
|
|
581
|
+
{ entityType, limit: 1, fuzzyMatch: true }
|
|
582
|
+
);
|
|
583
|
+
} else {
|
|
584
|
+
entityAssociations = this.memoryManager.resolveEntityReference(
|
|
585
|
+
entityName,
|
|
586
|
+
{ limit: 1, fuzzyMatch: false }
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (entityAssociations.length > 0) {
|
|
591
|
+
const entity = entityAssociations[0];
|
|
592
|
+
if (entity.entityId && entity.entityId.trim().length > 0) {
|
|
593
|
+
if (entityAssociations.length > 1) {
|
|
594
|
+
this.logger.info(`Multiple entities found for "${originalRef}", using most recent: ${entity.entityName}`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
resolvedContent = resolvedContent.replace(originalRef, entity.entityId);
|
|
598
|
+
this.logger.info(`Resolved entity reference: "${originalRef}" -> ${entity.entityId}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
} catch (matchError) {
|
|
602
|
+
this.logger.debug('Error processing entity match:', matchError);
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} catch (patternError) {
|
|
607
|
+
this.logger.debug('Error processing pattern:', patternError);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (resolvedContent !== content) {
|
|
613
|
+
this.logger.info(`Entity resolution completed. Original: "${content}" -> Resolved: "${resolvedContent}"`);
|
|
614
|
+
} else {
|
|
615
|
+
this.logger.info('No entity references resolved in message');
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return resolvedContent;
|
|
619
|
+
} catch (error) {
|
|
620
|
+
this.logger.warn('Entity resolution failed, using original message:', error);
|
|
621
|
+
return content;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Extract and store entity associations from transaction responses
|
|
627
|
+
* @param response - Agent response containing potential entity information
|
|
628
|
+
* @param originalMessage - Original user message for context
|
|
629
|
+
*/
|
|
630
|
+
private async extractAndStoreEntities(response: any, originalMessage: string): Promise<void> {
|
|
631
|
+
if (!this.memoryManager) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
this.logger.info('Starting entity extraction from response');
|
|
637
|
+
|
|
638
|
+
const entityPatterns = {
|
|
639
|
+
token: /(?:token|Token)\s*(?:ID\s*[:"*\s]*)?([0-9]+\.[0-9]+\.[0-9]+)/g,
|
|
640
|
+
account: /(?:account|Account)\s*(?:ID\s*[:"*\s]*)?([0-9]+\.[0-9]+\.[0-9]+)/g,
|
|
641
|
+
topic: /(?:topic|Topic)\s*(?:ID\s*[:"*\s]*)?([0-9]+\.[0-9]+\.[0-9]+)/g,
|
|
642
|
+
schedule: /(?:schedule|Schedule)\s*(?:ID\s*[:"*\s]*)?([0-9]+\.[0-9]+\.[0-9]+)/g
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const responseText = typeof response === 'string' ? response : JSON.stringify(response);
|
|
646
|
+
this.logger.info(`Searching response text: ${responseText.substring(0, 200)}...`);
|
|
647
|
+
|
|
648
|
+
for (const [entityType, pattern] of Object.entries(entityPatterns)) {
|
|
649
|
+
let match;
|
|
650
|
+
while ((match = pattern.exec(responseText)) !== null) {
|
|
651
|
+
const entityId = match[1];
|
|
652
|
+
|
|
653
|
+
let entityName = `${entityType}-${entityId}`;
|
|
654
|
+
|
|
655
|
+
const namePatterns = [
|
|
656
|
+
new RegExp(`(?:called|named)\\s+([\\w\\d_-]+)`, 'i'),
|
|
657
|
+
new RegExp(`(?:token|account|topic|schedule)\\s+([\\w\\d_-]+)`, 'i'),
|
|
658
|
+
new RegExp(`([\\w\\d_-]+)\\s+${entityType}`, 'i')
|
|
659
|
+
];
|
|
660
|
+
|
|
661
|
+
for (const namePattern of namePatterns) {
|
|
662
|
+
const nameMatch = originalMessage.match(namePattern);
|
|
663
|
+
if (nameMatch && nameMatch[1]) {
|
|
664
|
+
entityName = nameMatch[1].trim();
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
this.logger.info(`Extracting entity: ${entityName} (${entityType}) -> ${entityId}`);
|
|
670
|
+
|
|
671
|
+
this.memoryManager.storeEntityAssociation(
|
|
672
|
+
entityId,
|
|
673
|
+
entityName,
|
|
674
|
+
entityType,
|
|
675
|
+
this.extractTransactionId(response)
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
this.logger.info(`Stored entity association: ${entityName} (${entityId})`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
this.logger.info('Entity extraction completed');
|
|
683
|
+
} catch (error) {
|
|
684
|
+
this.logger.warn('Entity extraction failed:', error);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Extract transaction ID from response if available
|
|
690
|
+
* @param response - Transaction response
|
|
691
|
+
* @returns Transaction ID or undefined
|
|
692
|
+
*/
|
|
693
|
+
private extractTransactionId(response: any): string | undefined {
|
|
694
|
+
try {
|
|
695
|
+
if (typeof response === 'object' && response?.transactionId) {
|
|
696
|
+
return response.transactionId;
|
|
697
|
+
}
|
|
698
|
+
if (typeof response === 'string') {
|
|
699
|
+
const match = response.match(/transaction[\s\w]*ID[\s:"]*([0-9a-fA-F@\.\-]+)/i);
|
|
700
|
+
return match ? match[1] : undefined;
|
|
701
|
+
}
|
|
702
|
+
return undefined;
|
|
703
|
+
} catch {
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Connect to MCP servers asynchronously
|
|
710
|
+
* @private
|
|
711
|
+
*/
|
|
712
|
+
private connectMCP(): void {
|
|
713
|
+
if (!this.agent || !this.options.mcpServers) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Initialize connection status for all servers
|
|
718
|
+
this.options.mcpServers.forEach(server => {
|
|
719
|
+
this.mcpConnectionStatus.set(server.name, {
|
|
720
|
+
serverName: server.name,
|
|
721
|
+
connected: false,
|
|
722
|
+
tools: []
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// Call the agent's MCP connection method if available
|
|
727
|
+
if (typeof (this.agent as any).connectMCPServers === 'function') {
|
|
728
|
+
(this.agent as any).connectMCPServers().catch((error: any) => {
|
|
729
|
+
this.logger.error('Failed to connect MCP servers:', error);
|
|
730
|
+
});
|
|
731
|
+
} else {
|
|
732
|
+
// Fallback for agents that don't support async MCP connections
|
|
733
|
+
this.startConnections();
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Start MCP connections without blocking initialization
|
|
739
|
+
* @private
|
|
740
|
+
*/
|
|
741
|
+
private async startConnections(): Promise<void> {
|
|
742
|
+
if (!this.agent || !this.options.mcpServers) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
try {
|
|
747
|
+
this.logger.info('Starting MCP server connections asynchronously...');
|
|
748
|
+
|
|
749
|
+
for (const server of this.options.mcpServers) {
|
|
750
|
+
this.connectServer(server);
|
|
751
|
+
}
|
|
752
|
+
} catch (error) {
|
|
753
|
+
this.logger.error('Error starting MCP connections:', error);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Connect to a single MCP server
|
|
759
|
+
* @param {MCPServerConfig} server - Server configuration
|
|
760
|
+
* @private
|
|
761
|
+
*/
|
|
762
|
+
private async connectServer(server: MCPServerConfig): Promise<void> {
|
|
763
|
+
try {
|
|
764
|
+
this.logger.info(`Connecting to MCP server: ${server.name}`);
|
|
765
|
+
|
|
766
|
+
// TODO: Implement actual MCP connection logic
|
|
767
|
+
// For now, we'll simulate the connection process
|
|
768
|
+
const status = this.mcpConnectionStatus.get(server.name);
|
|
769
|
+
if (status) {
|
|
770
|
+
// Simulate connection success after a delay
|
|
771
|
+
setTimeout(() => {
|
|
772
|
+
status.connected = true;
|
|
773
|
+
this.logger.info(`MCP server ${server.name} connected successfully`);
|
|
774
|
+
}, Math.random() * 2000 + 1000); // 1-3 second delay
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
} catch (error) {
|
|
778
|
+
this.logger.error(`Failed to connect to MCP server ${server.name}:`, error);
|
|
779
|
+
|
|
780
|
+
const status = this.mcpConnectionStatus.get(server.name);
|
|
781
|
+
if (status) {
|
|
782
|
+
status.connected = false;
|
|
783
|
+
status.error = error instanceof Error ? error.message : 'Unknown error';
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Get MCP connection status for all servers
|
|
790
|
+
* @returns {Map<string, MCPConnectionStatus>} Connection status map
|
|
791
|
+
*/
|
|
792
|
+
getMCPConnectionStatus(): Map<string, MCPConnectionStatus> {
|
|
793
|
+
return new Map(this.mcpConnectionStatus);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Check if a specific MCP server is connected
|
|
798
|
+
* @param {string} serverName - Name of the server to check
|
|
799
|
+
* @returns {boolean} True if connected, false otherwise
|
|
800
|
+
*/
|
|
801
|
+
isMCPServerConnected(serverName: string): boolean {
|
|
802
|
+
const status = this.mcpConnectionStatus.get(serverName);
|
|
803
|
+
return status?.connected ?? false;
|
|
804
|
+
}
|
|
805
|
+
|
|
472
806
|
/**
|
|
473
807
|
* Clean up resources
|
|
474
808
|
*/
|
|
475
809
|
async cleanup(): Promise<void> {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
810
|
+
try {
|
|
811
|
+
this.logger.info('Cleaning up ConversationalAgent...');
|
|
812
|
+
|
|
813
|
+
if (this.memoryManager) {
|
|
814
|
+
try {
|
|
815
|
+
this.memoryManager.dispose();
|
|
816
|
+
this.logger.info('Memory manager cleaned up successfully');
|
|
817
|
+
} catch (error) {
|
|
818
|
+
this.logger.warn('Error cleaning up memory manager:', error);
|
|
819
|
+
}
|
|
820
|
+
this.memoryManager = undefined as any;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (this.contentStoreManager) {
|
|
824
|
+
await this.contentStoreManager.dispose();
|
|
825
|
+
this.logger.info('ContentStoreManager cleaned up');
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
this.logger.info('ConversationalAgent cleanup completed');
|
|
829
|
+
} catch (error) {
|
|
830
|
+
this.logger.error('Error during cleanup:', error);
|
|
483
831
|
}
|
|
484
832
|
}
|
|
485
833
|
}
|
package/src/index.ts
CHANGED
|
@@ -31,3 +31,5 @@ export type { IStateManager } from '@hashgraphonline/standards-agent-kit';
|
|
|
31
31
|
|
|
32
32
|
export type { MCPServerConfig, MCPConnectionStatus, MCPToolInfo } from './mcp/types';
|
|
33
33
|
export { MCPServers, createMCPConfig, validateServerConfig } from './mcp/helpers';
|
|
34
|
+
|
|
35
|
+
export * from './memory';
|
|
@@ -1,112 +1,19 @@
|
|
|
1
1
|
import { AgentExecutor } from 'langchain/agents';
|
|
2
|
-
import type { AgentAction, AgentFinish, AgentStep } from '@langchain/core/agents';
|
|
3
|
-
import type { ChainValues } from '@langchain/core/utils/types';
|
|
4
|
-
import type { CallbackManagerForChainRun } from '@langchain/core/callbacks/manager';
|
|
5
|
-
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
6
|
-
import type { ToolInterface } from '@langchain/core/tools';
|
|
7
|
-
import { ContentStoreService, shouldUseReference } from '@hashgraphonline/standards-sdk';
|
|
8
2
|
import { Logger } from '@hashgraphonline/standards-sdk';
|
|
9
3
|
|
|
10
4
|
/**
|
|
11
5
|
* Custom AgentExecutor that intercepts large tool outputs and converts them to content references
|
|
12
6
|
* before they are sent to the LLM to avoid token limit issues.
|
|
7
|
+
*
|
|
8
|
+
* Note: The content reference conversion is already handled in the MCP adapter,
|
|
9
|
+
* so this class currently just extends AgentExecutor without modifications.
|
|
10
|
+
* We keep it as a placeholder for future enhancements.
|
|
13
11
|
*/
|
|
14
12
|
export class ContentAwareAgentExecutor extends AgentExecutor {
|
|
15
13
|
private logger: Logger;
|
|
16
|
-
private readonly MCP_REFERENCE_THRESHOLD = 10 * 1024; // 10KB for MCP tools
|
|
17
14
|
|
|
18
15
|
constructor(config: any) {
|
|
19
16
|
super(config);
|
|
20
17
|
this.logger = new Logger({ module: 'ContentAwareAgentExecutor' });
|
|
21
18
|
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Override the method that processes tool outputs to intercept large content
|
|
25
|
-
*/
|
|
26
|
-
override async _call(inputs: ChainValues): Promise<ChainValues> {
|
|
27
|
-
try {
|
|
28
|
-
// Store the original _takeNextStep method
|
|
29
|
-
const originalTakeNextStep = this._takeNextStep.bind(this);
|
|
30
|
-
|
|
31
|
-
// Override _takeNextStep to intercept tool outputs
|
|
32
|
-
this._takeNextStep = async (
|
|
33
|
-
nameToolMap: Record<string, ToolInterface>,
|
|
34
|
-
inputs: ChainValues,
|
|
35
|
-
intermediateSteps: AgentStep[],
|
|
36
|
-
runManager?: CallbackManagerForChainRun,
|
|
37
|
-
config?: RunnableConfig
|
|
38
|
-
): Promise<AgentFinish | AgentStep[]> => {
|
|
39
|
-
// Call the original method
|
|
40
|
-
const result = await originalTakeNextStep(nameToolMap, inputs, intermediateSteps, runManager, config);
|
|
41
|
-
|
|
42
|
-
// If result is an array of intermediate steps, process tool outputs
|
|
43
|
-
if (Array.isArray(result)) {
|
|
44
|
-
const processedSteps = await Promise.all(
|
|
45
|
-
result.map(async (step) => {
|
|
46
|
-
const { action, observation } = step;
|
|
47
|
-
|
|
48
|
-
// Check if observation is large and should be stored as reference
|
|
49
|
-
if (typeof observation === 'string') {
|
|
50
|
-
const observationBuffer = Buffer.from(observation, 'utf8');
|
|
51
|
-
const shouldStore = observationBuffer.length > this.MCP_REFERENCE_THRESHOLD ||
|
|
52
|
-
shouldUseReference(observationBuffer);
|
|
53
|
-
|
|
54
|
-
if (shouldStore) {
|
|
55
|
-
this.logger.info(
|
|
56
|
-
`[ContentAwareAgentExecutor] Large tool output detected: ${observationBuffer.length} bytes from ${action.tool}`
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const contentStore = ContentStoreService.getInstance();
|
|
60
|
-
if (contentStore) {
|
|
61
|
-
try {
|
|
62
|
-
const referenceId = await contentStore.storeContent(observationBuffer, {
|
|
63
|
-
contentType: 'text',
|
|
64
|
-
source: 'tool',
|
|
65
|
-
toolName: action.tool,
|
|
66
|
-
originalSize: observationBuffer.length
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const referenceMessage = `content-ref:${referenceId}`;
|
|
70
|
-
this.logger.info(
|
|
71
|
-
`[ContentAwareAgentExecutor] Stored large output as reference: ${referenceMessage}`
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
// Return the step with the reference instead of the full content
|
|
75
|
-
return { action, observation: referenceMessage };
|
|
76
|
-
} catch (error) {
|
|
77
|
-
this.logger.warn(
|
|
78
|
-
'[ContentAwareAgentExecutor] Failed to store content as reference:',
|
|
79
|
-
error
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
this.logger.warn('[ContentAwareAgentExecutor] ContentStoreService not available');
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Return unchanged if not large or storage failed
|
|
89
|
-
return step;
|
|
90
|
-
})
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
return processedSteps;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Return as-is if not an array (AgentFinish)
|
|
97
|
-
return result;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// Call the parent _call method with our overridden _takeNextStep
|
|
101
|
-
const result = await super._call(inputs);
|
|
102
|
-
|
|
103
|
-
// Restore the original method
|
|
104
|
-
this._takeNextStep = originalTakeNextStep;
|
|
105
|
-
|
|
106
|
-
return result;
|
|
107
|
-
} catch (error) {
|
|
108
|
-
this.logger.error('[ContentAwareAgentExecutor] Error in _call:', error);
|
|
109
|
-
throw error;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
19
|
}
|