@hashgraphonline/conversational-agent 0.1.205 → 0.1.206
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/base-agent.d.ts +6 -0
- package/dist/cjs/context/ReferenceContextManager.d.ts +84 -0
- package/dist/cjs/context/ReferenceResponseProcessor.d.ts +76 -0
- package/dist/cjs/conversational-agent.d.ts +34 -0
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/langchain-agent.d.ts +1 -0
- package/dist/cjs/mcp/ContentProcessor.d.ts +37 -0
- package/dist/cjs/mcp/MCPClientManager.d.ts +19 -1
- package/dist/cjs/memory/ContentStorage.d.ts +205 -0
- package/dist/cjs/memory/MemoryWindow.d.ts +114 -0
- package/dist/cjs/memory/ReferenceIdGenerator.d.ts +45 -0
- package/dist/cjs/memory/SmartMemoryManager.d.ts +201 -0
- package/dist/cjs/memory/TokenCounter.d.ts +61 -0
- package/dist/cjs/memory/index.d.ts +7 -0
- package/dist/cjs/plugins/hbar-transfer/TransferHbarTool.d.ts +18 -0
- package/dist/cjs/services/ContentStoreManager.d.ts +54 -0
- package/dist/cjs/types/content-reference.d.ts +213 -0
- package/dist/cjs/types/index.d.ts +4 -0
- package/dist/esm/index12.js +15 -2
- package/dist/esm/index12.js.map +1 -1
- package/dist/esm/index14.js +119 -95
- package/dist/esm/index14.js.map +1 -1
- package/dist/esm/index15.js +159 -114
- package/dist/esm/index15.js.map +1 -1
- package/dist/esm/index16.js +122 -81
- package/dist/esm/index16.js.map +1 -1
- package/dist/esm/index17.js +236 -0
- package/dist/esm/index17.js.map +1 -0
- package/dist/esm/index18.js +95 -0
- package/dist/esm/index18.js.map +1 -0
- package/dist/esm/index19.js +663 -0
- package/dist/esm/index19.js.map +1 -0
- package/dist/esm/index2.js +3 -1
- package/dist/esm/index2.js.map +1 -1
- package/dist/esm/index20.js +233 -0
- package/dist/esm/index20.js.map +1 -0
- package/dist/esm/index21.js +182 -0
- package/dist/esm/index21.js.map +1 -0
- package/dist/esm/index22.js +126 -0
- package/dist/esm/index22.js.map +1 -0
- package/dist/esm/index23.js +68 -0
- package/dist/esm/index23.js.map +1 -0
- package/dist/esm/index24.js +38 -0
- package/dist/esm/index24.js.map +1 -0
- package/dist/esm/index6.js +143 -84
- package/dist/esm/index6.js.map +1 -1
- package/dist/esm/index7.js.map +1 -1
- package/dist/esm/index8.js +69 -5
- package/dist/esm/index8.js.map +1 -1
- package/dist/types/base-agent.d.ts +6 -0
- package/dist/types/context/ReferenceContextManager.d.ts +84 -0
- package/dist/types/context/ReferenceResponseProcessor.d.ts +76 -0
- package/dist/types/conversational-agent.d.ts +34 -0
- package/dist/types/langchain-agent.d.ts +1 -0
- package/dist/types/mcp/ContentProcessor.d.ts +37 -0
- package/dist/types/mcp/MCPClientManager.d.ts +19 -1
- package/dist/types/memory/ContentStorage.d.ts +205 -0
- package/dist/types/memory/MemoryWindow.d.ts +114 -0
- package/dist/types/memory/ReferenceIdGenerator.d.ts +45 -0
- package/dist/types/memory/SmartMemoryManager.d.ts +201 -0
- package/dist/types/memory/TokenCounter.d.ts +61 -0
- package/dist/types/memory/index.d.ts +7 -0
- package/dist/types/plugins/hbar-transfer/TransferHbarTool.d.ts +18 -0
- package/dist/types/services/ContentStoreManager.d.ts +54 -0
- package/dist/types/types/content-reference.d.ts +213 -0
- package/dist/types/types/index.d.ts +4 -0
- package/package.json +5 -3
- package/src/base-agent.ts +6 -0
- package/src/context/ReferenceContextManager.ts +345 -0
- package/src/context/ReferenceResponseProcessor.ts +296 -0
- package/src/conversational-agent.ts +166 -92
- package/src/langchain-agent.ts +89 -2
- package/src/mcp/ContentProcessor.ts +317 -0
- package/src/mcp/MCPClientManager.ts +61 -1
- package/src/mcp/adapters/langchain.ts +9 -4
- package/src/memory/ContentStorage.ts +954 -0
- package/src/memory/MemoryWindow.ts +247 -0
- package/src/memory/ReferenceIdGenerator.ts +84 -0
- package/src/memory/SmartMemoryManager.ts +323 -0
- package/src/memory/TokenCounter.ts +152 -0
- package/src/memory/index.ts +8 -0
- package/src/plugins/hbar-transfer/TransferHbarTool.ts +19 -1
- package/src/plugins/hcs-10/HCS10Plugin.ts +5 -4
- package/src/services/ContentStoreManager.ts +199 -0
- package/src/types/content-reference.ts +281 -0
- package/src/types/index.ts +6 -0
package/src/langchain-agent.ts
CHANGED
|
@@ -22,11 +22,13 @@ import {
|
|
|
22
22
|
} from './base-agent';
|
|
23
23
|
import { MCPClientManager } from './mcp/MCPClientManager';
|
|
24
24
|
import { convertMCPToolToLangChain } from './mcp/adapters/langchain';
|
|
25
|
+
import { SmartMemoryManager } from './memory/SmartMemoryManager';
|
|
25
26
|
|
|
26
27
|
export class LangChainAgent extends BaseAgent {
|
|
27
28
|
private executor: AgentExecutor | undefined;
|
|
28
29
|
private systemMessage = '';
|
|
29
30
|
private mcpManager?: MCPClientManager;
|
|
31
|
+
private smartMemory: SmartMemoryManager | undefined;
|
|
30
32
|
|
|
31
33
|
async boot(): Promise<void> {
|
|
32
34
|
if (this.initialized) {
|
|
@@ -51,7 +53,24 @@ export class LangChainAgent extends BaseAgent {
|
|
|
51
53
|
await this.initializeMCP();
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
this.smartMemory = new SmartMemoryManager({
|
|
57
|
+
modelName,
|
|
58
|
+
maxTokens: 90000,
|
|
59
|
+
reserveTokens: 10000,
|
|
60
|
+
storageLimit: 1000
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
this.logger.info('SmartMemoryManager initialized:', {
|
|
64
|
+
modelName,
|
|
65
|
+
toolsCount: this.tools.length,
|
|
66
|
+
maxTokens: 90000,
|
|
67
|
+
reserveTokens: 10000
|
|
68
|
+
});
|
|
69
|
+
|
|
54
70
|
this.systemMessage = this.buildSystemPrompt();
|
|
71
|
+
|
|
72
|
+
// Set the system prompt in smart memory
|
|
73
|
+
this.smartMemory.setSystemPrompt(this.systemMessage);
|
|
55
74
|
|
|
56
75
|
await this.createExecutor();
|
|
57
76
|
|
|
@@ -67,22 +86,64 @@ export class LangChainAgent extends BaseAgent {
|
|
|
67
86
|
message: string,
|
|
68
87
|
context?: ConversationContext
|
|
69
88
|
): Promise<ChatResponse> {
|
|
70
|
-
if (!this.initialized || !this.executor) {
|
|
89
|
+
if (!this.initialized || !this.executor || !this.smartMemory) {
|
|
71
90
|
throw new Error('Agent not initialized. Call boot() first.');
|
|
72
91
|
}
|
|
73
92
|
|
|
74
93
|
try {
|
|
94
|
+
this.logger.info('LangChainAgent.chat called with:', { message, contextLength: context?.messages?.length || 0 });
|
|
95
|
+
|
|
96
|
+
// If context is provided, populate smart memory with previous messages
|
|
97
|
+
if (context?.messages && context.messages.length > 0) {
|
|
98
|
+
// Clear existing memory and add messages from context
|
|
99
|
+
this.smartMemory.clear();
|
|
100
|
+
|
|
101
|
+
for (const msg of context.messages) {
|
|
102
|
+
this.smartMemory.addMessage(msg);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add the current user message to memory
|
|
107
|
+
const { HumanMessage } = await import('@langchain/core/messages');
|
|
108
|
+
this.smartMemory.addMessage(new HumanMessage(message));
|
|
109
|
+
|
|
110
|
+
const memoryStats = this.smartMemory.getMemoryStats();
|
|
111
|
+
this.logger.info('Memory stats before execution:', {
|
|
112
|
+
totalMessages: memoryStats.totalActiveMessages,
|
|
113
|
+
currentTokens: memoryStats.currentTokenCount,
|
|
114
|
+
maxTokens: memoryStats.maxTokens,
|
|
115
|
+
usagePercentage: memoryStats.usagePercentage,
|
|
116
|
+
toolsCount: this.tools.length
|
|
117
|
+
});
|
|
118
|
+
|
|
75
119
|
const result = await this.executor.invoke({
|
|
76
120
|
input: message,
|
|
77
|
-
chat_history:
|
|
121
|
+
chat_history: this.smartMemory.getMessages(),
|
|
78
122
|
});
|
|
79
123
|
|
|
124
|
+
this.logger.info('LangChainAgent executor result:', result);
|
|
125
|
+
|
|
80
126
|
let response: ChatResponse = {
|
|
81
127
|
output: result.output || '',
|
|
82
128
|
message: result.output || '',
|
|
83
129
|
notes: [],
|
|
130
|
+
intermediateSteps: result.intermediateSteps,
|
|
84
131
|
};
|
|
85
132
|
|
|
133
|
+
// Extract tool calls from intermediate steps
|
|
134
|
+
if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
|
|
135
|
+
const toolCalls = result.intermediateSteps.map((step: any, index: number) => ({
|
|
136
|
+
id: `call_${index}`,
|
|
137
|
+
name: step.action?.tool || 'unknown',
|
|
138
|
+
args: step.action?.toolInput || {},
|
|
139
|
+
output: typeof step.observation === 'string' ? step.observation : JSON.stringify(step.observation)
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
if (toolCalls.length > 0) {
|
|
143
|
+
response.tool_calls = toolCalls;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
86
147
|
const parsedSteps = result?.intermediateSteps?.[0]?.observation;
|
|
87
148
|
if (
|
|
88
149
|
parsedSteps &&
|
|
@@ -101,6 +162,12 @@ export class LangChainAgent extends BaseAgent {
|
|
|
101
162
|
response.output = 'Agent action complete.';
|
|
102
163
|
}
|
|
103
164
|
|
|
165
|
+
// Add the AI response to memory
|
|
166
|
+
if (response.output) {
|
|
167
|
+
const { AIMessage } = await import('@langchain/core/messages');
|
|
168
|
+
this.smartMemory.addMessage(new AIMessage(response.output));
|
|
169
|
+
}
|
|
170
|
+
|
|
104
171
|
if (this.tokenTracker) {
|
|
105
172
|
const tokenUsage = this.tokenTracker.getLatestTokenUsage();
|
|
106
173
|
if (tokenUsage) {
|
|
@@ -109,8 +176,21 @@ export class LangChainAgent extends BaseAgent {
|
|
|
109
176
|
}
|
|
110
177
|
}
|
|
111
178
|
|
|
179
|
+
const finalMemoryStats = this.smartMemory.getMemoryStats();
|
|
180
|
+
response.metadata = {
|
|
181
|
+
...response.metadata,
|
|
182
|
+
memoryStats: {
|
|
183
|
+
activeMessages: finalMemoryStats.totalActiveMessages,
|
|
184
|
+
tokenUsage: finalMemoryStats.currentTokenCount,
|
|
185
|
+
maxTokens: finalMemoryStats.maxTokens,
|
|
186
|
+
usagePercentage: finalMemoryStats.usagePercentage
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
this.logger.info('LangChainAgent.chat returning response:', response);
|
|
112
191
|
return response;
|
|
113
192
|
} catch (error) {
|
|
193
|
+
this.logger.error('LangChainAgent.chat error:', error);
|
|
114
194
|
return this.handleError(error);
|
|
115
195
|
}
|
|
116
196
|
}
|
|
@@ -120,6 +200,11 @@ export class LangChainAgent extends BaseAgent {
|
|
|
120
200
|
await this.mcpManager.disconnectAll();
|
|
121
201
|
}
|
|
122
202
|
|
|
203
|
+
if (this.smartMemory) {
|
|
204
|
+
this.smartMemory.dispose();
|
|
205
|
+
this.smartMemory = undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
123
208
|
this.executor = undefined;
|
|
124
209
|
this.agentKit = undefined;
|
|
125
210
|
this.tools = [];
|
|
@@ -175,6 +260,7 @@ export class LangChainAgent extends BaseAgent {
|
|
|
175
260
|
}
|
|
176
261
|
}
|
|
177
262
|
|
|
263
|
+
|
|
178
264
|
private async createAgentKit(): Promise<HederaAgentKit> {
|
|
179
265
|
const corePlugins = getAllHederaCorePlugins();
|
|
180
266
|
const extensionPlugins = this.config.extensions?.plugins || [];
|
|
@@ -232,6 +318,7 @@ export class LangChainAgent extends BaseAgent {
|
|
|
232
318
|
prompt,
|
|
233
319
|
});
|
|
234
320
|
|
|
321
|
+
// Create executor without memory - we handle memory manually with SmartMemoryManager
|
|
235
322
|
this.executor = new AgentExecutor({
|
|
236
323
|
agent,
|
|
237
324
|
tools: langchainTools,
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import type { ContentType, ContentSource } from '../types/content-reference';
|
|
2
|
+
import type { ContentStorage } from '../memory/ContentStorage';
|
|
3
|
+
import { Logger } from '@hashgraphonline/standards-sdk';
|
|
4
|
+
|
|
5
|
+
export interface MCPResponseContent {
|
|
6
|
+
content: unknown;
|
|
7
|
+
type: 'text' | 'image' | 'resource' | 'text[]' | 'image[]';
|
|
8
|
+
sizeBytes: number;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ProcessedResponse {
|
|
13
|
+
content: unknown;
|
|
14
|
+
wasProcessed: boolean;
|
|
15
|
+
referenceCreated?: boolean;
|
|
16
|
+
referenceId?: string;
|
|
17
|
+
originalSize?: number;
|
|
18
|
+
errors?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ContentAnalysis {
|
|
22
|
+
shouldProcess: boolean;
|
|
23
|
+
contents: MCPResponseContent[];
|
|
24
|
+
totalSize: number;
|
|
25
|
+
largestContentSize: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class MCPContentProcessor {
|
|
29
|
+
private contentStorage: ContentStorage;
|
|
30
|
+
private logger: Logger;
|
|
31
|
+
|
|
32
|
+
constructor(contentStorage: ContentStorage, logger: Logger) {
|
|
33
|
+
this.contentStorage = contentStorage;
|
|
34
|
+
this.logger = logger;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
analyzeResponse(response: unknown): ContentAnalysis {
|
|
38
|
+
const contents: MCPResponseContent[] = [];
|
|
39
|
+
let totalSize = 0;
|
|
40
|
+
|
|
41
|
+
this.extractContentFromResponse(response, contents);
|
|
42
|
+
|
|
43
|
+
totalSize = contents.reduce((sum, content) => sum + content.sizeBytes, 0);
|
|
44
|
+
const largestContentSize = contents.reduce((max, content) =>
|
|
45
|
+
Math.max(max, content.sizeBytes), 0);
|
|
46
|
+
|
|
47
|
+
const shouldProcess = contents.some(content =>
|
|
48
|
+
this.contentStorage.shouldUseReference(
|
|
49
|
+
typeof content.content === 'string'
|
|
50
|
+
? content.content
|
|
51
|
+
: JSON.stringify(content.content)
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
shouldProcess,
|
|
57
|
+
contents,
|
|
58
|
+
totalSize,
|
|
59
|
+
largestContentSize
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async processResponse(
|
|
64
|
+
response: unknown,
|
|
65
|
+
serverName: string,
|
|
66
|
+
toolName: string
|
|
67
|
+
): Promise<ProcessedResponse> {
|
|
68
|
+
try {
|
|
69
|
+
const analysis = this.analyzeResponse(response);
|
|
70
|
+
|
|
71
|
+
if (!analysis.shouldProcess) {
|
|
72
|
+
return {
|
|
73
|
+
content: response,
|
|
74
|
+
wasProcessed: false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const processedResponse = await this.createReferencedResponse(
|
|
79
|
+
response,
|
|
80
|
+
analysis,
|
|
81
|
+
serverName,
|
|
82
|
+
toolName
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return processedResponse;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.logger.error('Error processing MCP response:', error);
|
|
88
|
+
return {
|
|
89
|
+
content: response,
|
|
90
|
+
wasProcessed: false,
|
|
91
|
+
errors: [error instanceof Error ? error.message : 'Unknown processing error']
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private extractContentFromResponse(obj: unknown, contents: MCPResponseContent[]): void {
|
|
97
|
+
if (obj === null || obj === undefined) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (Array.isArray(obj)) {
|
|
102
|
+
obj.forEach(item => this.extractContentFromResponse(item, contents));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (typeof obj === 'object') {
|
|
107
|
+
const record = obj as Record<string, unknown>;
|
|
108
|
+
|
|
109
|
+
if (record.type === 'text' && typeof record.text === 'string') {
|
|
110
|
+
contents.push({
|
|
111
|
+
content: record.text,
|
|
112
|
+
type: 'text',
|
|
113
|
+
sizeBytes: Buffer.byteLength(record.text, 'utf8'),
|
|
114
|
+
mimeType: 'text/plain'
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (record.type === 'image' && typeof record.data === 'string') {
|
|
120
|
+
contents.push({
|
|
121
|
+
content: record.data,
|
|
122
|
+
type: 'image',
|
|
123
|
+
sizeBytes: Math.ceil(record.data.length * 0.75),
|
|
124
|
+
mimeType: record.mimeType as string || 'image/jpeg'
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (record.type === 'resource' && record.resource) {
|
|
130
|
+
const resourceStr = JSON.stringify(record.resource);
|
|
131
|
+
contents.push({
|
|
132
|
+
content: resourceStr,
|
|
133
|
+
type: 'resource',
|
|
134
|
+
sizeBytes: Buffer.byteLength(resourceStr, 'utf8'),
|
|
135
|
+
mimeType: 'application/json'
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Object.values(record).forEach(value =>
|
|
141
|
+
this.extractContentFromResponse(value, contents));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (typeof obj === 'string') {
|
|
146
|
+
if (obj.length > 1000) {
|
|
147
|
+
contents.push({
|
|
148
|
+
content: obj,
|
|
149
|
+
type: 'text',
|
|
150
|
+
sizeBytes: Buffer.byteLength(obj, 'utf8'),
|
|
151
|
+
mimeType: this.detectMimeType(obj)
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async createReferencedResponse(
|
|
158
|
+
originalResponse: unknown,
|
|
159
|
+
analysis: ContentAnalysis,
|
|
160
|
+
serverName: string,
|
|
161
|
+
toolName: string
|
|
162
|
+
): Promise<ProcessedResponse> {
|
|
163
|
+
const processedResponse = this.deepClone(originalResponse);
|
|
164
|
+
const errors: string[] = [];
|
|
165
|
+
let referenceCreated = false;
|
|
166
|
+
let totalReferenceSize = 0;
|
|
167
|
+
|
|
168
|
+
for (const contentInfo of analysis.contents) {
|
|
169
|
+
if (this.contentStorage.shouldUseReference(
|
|
170
|
+
typeof contentInfo.content === 'string'
|
|
171
|
+
? contentInfo.content
|
|
172
|
+
: JSON.stringify(contentInfo.content)
|
|
173
|
+
)) {
|
|
174
|
+
try {
|
|
175
|
+
const contentBuffer = Buffer.from(
|
|
176
|
+
typeof contentInfo.content === 'string'
|
|
177
|
+
? contentInfo.content
|
|
178
|
+
: JSON.stringify(contentInfo.content),
|
|
179
|
+
'utf8'
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const contentType = this.mapMimeTypeToContentType(contentInfo.mimeType);
|
|
183
|
+
|
|
184
|
+
const metadata: Parameters<typeof this.contentStorage.storeContentIfLarge>[1] = {
|
|
185
|
+
contentType,
|
|
186
|
+
source: 'mcp_tool' as ContentSource,
|
|
187
|
+
mcpToolName: `${serverName}::${toolName}`,
|
|
188
|
+
tags: ['mcp_response', serverName, toolName]
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if (contentInfo.mimeType !== undefined) {
|
|
192
|
+
metadata.mimeType = contentInfo.mimeType;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const reference = await this.contentStorage.storeContentIfLarge(
|
|
196
|
+
contentBuffer,
|
|
197
|
+
metadata
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (reference) {
|
|
201
|
+
this.replaceContentInResponse(
|
|
202
|
+
processedResponse,
|
|
203
|
+
contentInfo.content,
|
|
204
|
+
this.createLightweightReference(reference)
|
|
205
|
+
);
|
|
206
|
+
referenceCreated = true;
|
|
207
|
+
totalReferenceSize += contentBuffer.length;
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
errors.push(`Failed to create reference: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const result: ProcessedResponse = {
|
|
216
|
+
content: processedResponse,
|
|
217
|
+
wasProcessed: true,
|
|
218
|
+
referenceCreated,
|
|
219
|
+
originalSize: totalReferenceSize
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
if (errors.length > 0) {
|
|
223
|
+
result.errors = errors;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private createLightweightReference(reference: any): Record<string, unknown> {
|
|
230
|
+
return {
|
|
231
|
+
type: 'content_reference',
|
|
232
|
+
referenceId: reference.referenceId,
|
|
233
|
+
preview: reference.preview,
|
|
234
|
+
size: reference.metadata.sizeBytes,
|
|
235
|
+
contentType: reference.metadata.contentType,
|
|
236
|
+
format: 'ref://{id}',
|
|
237
|
+
_isReference: true
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private replaceContentInResponse(obj: unknown, oldContent: unknown, newContent: unknown): void {
|
|
242
|
+
if (obj === null || obj === undefined) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (Array.isArray(obj)) {
|
|
247
|
+
for (let i = 0; i < obj.length; i++) {
|
|
248
|
+
if (obj[i] === oldContent) {
|
|
249
|
+
obj[i] = newContent;
|
|
250
|
+
} else {
|
|
251
|
+
this.replaceContentInResponse(obj[i], oldContent, newContent);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (typeof obj === 'object') {
|
|
258
|
+
const record = obj as Record<string, unknown>;
|
|
259
|
+
|
|
260
|
+
for (const key in record) {
|
|
261
|
+
if (record[key] === oldContent) {
|
|
262
|
+
record[key] = newContent;
|
|
263
|
+
} else {
|
|
264
|
+
this.replaceContentInResponse(record[key], oldContent, newContent);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private detectMimeType(content: string): string {
|
|
271
|
+
if (content.trim().startsWith('{') || content.trim().startsWith('[')) {
|
|
272
|
+
return 'application/json';
|
|
273
|
+
}
|
|
274
|
+
if (content.includes('<html>') || content.includes('<!DOCTYPE')) {
|
|
275
|
+
return 'text/html';
|
|
276
|
+
}
|
|
277
|
+
if (content.includes('# ') || content.includes('## ')) {
|
|
278
|
+
return 'text/markdown';
|
|
279
|
+
}
|
|
280
|
+
return 'text/plain';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private mapMimeTypeToContentType(mimeType?: string): ContentType {
|
|
284
|
+
if (!mimeType) return 'text';
|
|
285
|
+
|
|
286
|
+
if (mimeType.startsWith('text/plain')) return 'text';
|
|
287
|
+
if (mimeType === 'application/json') return 'json';
|
|
288
|
+
if (mimeType === 'text/html') return 'html';
|
|
289
|
+
if (mimeType === 'text/markdown') return 'markdown';
|
|
290
|
+
if (mimeType.startsWith('text/')) return 'text';
|
|
291
|
+
|
|
292
|
+
return 'binary';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private deepClone<T>(obj: T): T {
|
|
296
|
+
if (obj === null || typeof obj !== 'object') {
|
|
297
|
+
return obj;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (obj instanceof Date) {
|
|
301
|
+
return new Date(obj.getTime()) as T;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (Array.isArray(obj)) {
|
|
305
|
+
return obj.map(item => this.deepClone(item)) as T;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const cloned = {} as T;
|
|
309
|
+
for (const key in obj) {
|
|
310
|
+
if (obj.hasOwnProperty(key)) {
|
|
311
|
+
cloned[key] = this.deepClone(obj[key]);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return cloned;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -2,6 +2,8 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
2
2
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
3
|
import type { MCPServerConfig, MCPToolInfo, MCPConnectionStatus } from './types';
|
|
4
4
|
import { Logger } from '@hashgraphonline/standards-sdk';
|
|
5
|
+
import type { ContentStorage } from '../memory/ContentStorage';
|
|
6
|
+
import { MCPContentProcessor, ProcessedResponse } from './ContentProcessor';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Manages connections to MCP servers and tool discovery
|
|
@@ -10,9 +12,13 @@ export class MCPClientManager {
|
|
|
10
12
|
private clients: Map<string, Client> = new Map();
|
|
11
13
|
private tools: Map<string, MCPToolInfo[]> = new Map();
|
|
12
14
|
private logger: Logger;
|
|
15
|
+
private contentProcessor?: MCPContentProcessor;
|
|
13
16
|
|
|
14
|
-
constructor(logger: Logger) {
|
|
17
|
+
constructor(logger: Logger, contentStorage?: ContentStorage) {
|
|
15
18
|
this.logger = logger;
|
|
19
|
+
if (contentStorage) {
|
|
20
|
+
this.contentProcessor = new MCPContentProcessor(contentStorage, logger);
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
/**
|
|
@@ -91,6 +97,27 @@ export class MCPClientManager {
|
|
|
91
97
|
arguments: args,
|
|
92
98
|
});
|
|
93
99
|
|
|
100
|
+
if (this.contentProcessor) {
|
|
101
|
+
const processed = await this.contentProcessor.processResponse(result, serverName, toolName);
|
|
102
|
+
|
|
103
|
+
if (processed.wasProcessed) {
|
|
104
|
+
this.logger.debug(
|
|
105
|
+
`Processed MCP response from ${serverName}::${toolName}`,
|
|
106
|
+
{
|
|
107
|
+
referenceCreated: processed.referenceCreated,
|
|
108
|
+
originalSize: processed.originalSize,
|
|
109
|
+
errors: processed.errors
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (processed.errors && processed.errors.length > 0) {
|
|
114
|
+
this.logger.warn(`Content processing warnings for ${serverName}::${toolName}:`, processed.errors);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return processed.content;
|
|
119
|
+
}
|
|
120
|
+
|
|
94
121
|
return result;
|
|
95
122
|
} catch (error) {
|
|
96
123
|
this.logger.error(`Error executing MCP tool ${toolName}:`, error);
|
|
@@ -145,4 +172,37 @@ export class MCPClientManager {
|
|
|
145
172
|
getConnectedServers(): string[] {
|
|
146
173
|
return Array.from(this.clients.keys());
|
|
147
174
|
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Enable content processing with content storage
|
|
178
|
+
*/
|
|
179
|
+
enableContentProcessing(contentStorage: ContentStorage): void {
|
|
180
|
+
this.contentProcessor = new MCPContentProcessor(contentStorage, this.logger);
|
|
181
|
+
this.logger.info('Content processing enabled for MCP responses');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Disable content processing
|
|
186
|
+
*/
|
|
187
|
+
disableContentProcessing(): void {
|
|
188
|
+
delete this.contentProcessor;
|
|
189
|
+
this.logger.info('Content processing disabled for MCP responses');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Check if content processing is enabled
|
|
194
|
+
*/
|
|
195
|
+
isContentProcessingEnabled(): boolean {
|
|
196
|
+
return this.contentProcessor !== undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Analyze a response without processing it (for testing/debugging)
|
|
201
|
+
*/
|
|
202
|
+
analyzeResponseContent(response: unknown): unknown {
|
|
203
|
+
if (!this.contentProcessor) {
|
|
204
|
+
throw new Error('Content processing is not enabled');
|
|
205
|
+
}
|
|
206
|
+
return this.contentProcessor.analyzeResponse(response);
|
|
207
|
+
}
|
|
148
208
|
}
|
|
@@ -40,8 +40,10 @@ export function convertMCPToolToLangChain(
|
|
|
40
40
|
input
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
+
let responseText = '';
|
|
44
|
+
|
|
43
45
|
if (typeof result === 'string') {
|
|
44
|
-
|
|
46
|
+
responseText = result;
|
|
45
47
|
} else if (
|
|
46
48
|
result &&
|
|
47
49
|
typeof result === 'object' &&
|
|
@@ -59,12 +61,15 @@ export function convertMCPToolToLangChain(
|
|
|
59
61
|
'text' in item
|
|
60
62
|
)
|
|
61
63
|
.map((item) => item.text);
|
|
62
|
-
|
|
64
|
+
responseText = textParts.join('\n');
|
|
65
|
+
} else {
|
|
66
|
+
responseText = JSON.stringify(content);
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
} else {
|
|
69
|
+
responseText = JSON.stringify(result);
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
return
|
|
72
|
+
return responseText;
|
|
68
73
|
} catch (error) {
|
|
69
74
|
const errorMessage =
|
|
70
75
|
error instanceof Error ? error.message : 'Unknown error';
|