@hashgraphonline/conversational-agent 0.1.215 → 0.1.217
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 +8 -0
- package/dist/cjs/core/ToolRegistry.d.ts +130 -0
- package/dist/cjs/execution/ExecutionPipeline.d.ts +81 -0
- package/dist/cjs/forms/FormEngine.d.ts +121 -0
- package/dist/cjs/forms/form-generator.d.ts +39 -2
- package/dist/cjs/forms/types.d.ts +21 -2
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +3 -4
- package/dist/cjs/langchain/FormAwareAgentExecutor.d.ts +53 -4
- package/dist/cjs/langchain/FormValidatingToolWrapper.d.ts +43 -6
- package/dist/cjs/langchain-agent.d.ts +49 -0
- package/dist/cjs/memory/ContentStorage.d.ts +7 -0
- package/dist/cjs/memory/SmartMemoryManager.d.ts +1 -0
- package/dist/cjs/services/ContentStoreManager.d.ts +11 -1
- package/dist/cjs/utils/ResponseFormatter.d.ts +26 -0
- package/dist/esm/index.js +2 -6
- package/dist/esm/index12.js +1 -1
- package/dist/esm/index12.js.map +1 -1
- package/dist/esm/index14.js +23 -5
- package/dist/esm/index14.js.map +1 -1
- package/dist/esm/index15.js +25 -4
- package/dist/esm/index15.js.map +1 -1
- package/dist/esm/index16.js +4 -2
- package/dist/esm/index16.js.map +1 -1
- package/dist/esm/index17.js +2 -7
- package/dist/esm/index17.js.map +1 -1
- package/dist/esm/index18.js +292 -150
- package/dist/esm/index18.js.map +1 -1
- package/dist/esm/index19.js +158 -65
- package/dist/esm/index19.js.map +1 -1
- package/dist/esm/index20.js +94 -270
- package/dist/esm/index20.js.map +1 -1
- package/dist/esm/index21.js +1 -1
- package/dist/esm/index23.js +14 -0
- package/dist/esm/index23.js.map +1 -1
- package/dist/esm/index24.js +508 -12
- package/dist/esm/index24.js.map +1 -1
- package/dist/esm/index25.js +1 -1
- package/dist/esm/index25.js.map +1 -1
- package/dist/esm/index26.js +1 -1
- package/dist/esm/index26.js.map +1 -1
- package/dist/esm/index27.js +189 -128
- package/dist/esm/index27.js.map +1 -1
- package/dist/esm/index28.js +164 -45
- package/dist/esm/index28.js.map +1 -1
- package/dist/esm/index29.js +302 -24
- package/dist/esm/index29.js.map +1 -1
- package/dist/esm/index30.js +144 -80
- package/dist/esm/index30.js.map +1 -1
- package/dist/esm/index31.js +63 -7
- package/dist/esm/index31.js.map +1 -1
- package/dist/esm/index32.js +24 -236
- package/dist/esm/index32.js.map +1 -1
- package/dist/esm/index33.js +95 -0
- package/dist/esm/index33.js.map +1 -0
- package/dist/esm/index34.js +245 -0
- package/dist/esm/index34.js.map +1 -0
- package/dist/esm/index5.js.map +1 -1
- package/dist/esm/index6.js +61 -22
- package/dist/esm/index6.js.map +1 -1
- package/dist/esm/index8.js +653 -131
- package/dist/esm/index8.js.map +1 -1
- package/dist/types/conversational-agent.d.ts +8 -0
- package/dist/types/core/ToolRegistry.d.ts +130 -0
- package/dist/types/execution/ExecutionPipeline.d.ts +81 -0
- package/dist/types/forms/FormEngine.d.ts +121 -0
- package/dist/types/forms/form-generator.d.ts +39 -2
- package/dist/types/forms/types.d.ts +21 -2
- package/dist/types/index.d.ts +3 -4
- package/dist/types/langchain/FormAwareAgentExecutor.d.ts +53 -4
- package/dist/types/langchain/FormValidatingToolWrapper.d.ts +43 -6
- package/dist/types/langchain-agent.d.ts +49 -0
- package/dist/types/memory/ContentStorage.d.ts +7 -0
- package/dist/types/memory/SmartMemoryManager.d.ts +1 -0
- package/dist/types/services/ContentStoreManager.d.ts +11 -1
- package/dist/types/utils/ResponseFormatter.d.ts +26 -0
- package/package.json +13 -10
- package/src/config/system-message.ts +14 -0
- package/src/context/ReferenceContextManager.ts +1 -1
- package/src/conversational-agent.ts +91 -36
- package/src/core/ToolRegistry.ts +358 -0
- package/src/execution/ExecutionPipeline.ts +301 -0
- package/src/forms/FormEngine.ts +443 -0
- package/src/forms/field-type-registry.ts +1 -13
- package/src/forms/form-generator.ts +394 -237
- package/src/forms/types.ts +20 -3
- package/src/index.ts +6 -10
- package/src/langchain/FormAwareAgentExecutor.ts +653 -22
- package/src/langchain/FormValidatingToolWrapper.ts +216 -93
- package/src/langchain-agent.ts +924 -185
- package/src/mcp/ContentProcessor.ts +20 -4
- package/src/mcp/MCPClientManager.ts +1 -1
- package/src/mcp/adapters/langchain.ts +1 -1
- package/src/memory/ContentStorage.ts +25 -5
- package/src/memory/SmartMemoryManager.ts +27 -4
- package/src/memory/TokenCounter.ts +1 -1
- package/src/plugins/hbar/HbarPlugin.ts +0 -1
- package/src/scripts/test-external-tool-wrapper.ts +3 -12
- package/src/scripts/test-hedera-kit-wrapper.ts +6 -22
- package/src/scripts/test-inscribe-form-generation.ts +24 -42
- package/src/scripts/test-inscribe-wrapper-verification.ts +1 -7
- package/src/services/ContentStoreManager.ts +23 -9
- package/src/services/EntityResolver.ts +2 -9
- package/src/tools/EntityResolverTool.ts +5 -8
- package/src/utils/ResponseFormatter.ts +146 -0
- package/dist/cjs/examples/external-tool-wrapper-example.d.ts +0 -131
- package/dist/cjs/langchain/ContentAwareAgentExecutor.d.ts +0 -14
- package/dist/cjs/langchain/external-tool-wrapper.d.ts +0 -179
- package/dist/cjs/scripts/test-external-tool-wrapper.d.ts +0 -5
- package/dist/cjs/scripts/test-hedera-kit-wrapper.d.ts +0 -36
- package/dist/cjs/scripts/test-inscribe-form-generation.d.ts +0 -15
- package/dist/cjs/scripts/test-inscribe-wrapper-verification.d.ts +0 -13
- package/dist/types/examples/external-tool-wrapper-example.d.ts +0 -131
- package/dist/types/langchain/ContentAwareAgentExecutor.d.ts +0 -14
- package/dist/types/langchain/external-tool-wrapper.d.ts +0 -179
- package/dist/types/scripts/test-external-tool-wrapper.d.ts +0 -5
- package/dist/types/scripts/test-hedera-kit-wrapper.d.ts +0 -36
- package/dist/types/scripts/test-inscribe-form-generation.d.ts +0 -15
- package/dist/types/scripts/test-inscribe-wrapper-verification.d.ts +0 -13
- package/src/examples/external-tool-wrapper-example.ts +0 -227
- package/src/langchain/ContentAwareAgentExecutor.ts +0 -19
- package/src/langchain/external-tool-wrapper.ts +0 -486
package/src/langchain-agent.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
2
2
|
import type { StructuredTool } from '@langchain/core/tools';
|
|
3
3
|
import { createOpenAIToolsAgent } from 'langchain/agents';
|
|
4
|
-
import { z } from 'zod';
|
|
5
4
|
import { FormAwareAgentExecutor } from './langchain/FormAwareAgentExecutor';
|
|
6
5
|
import {
|
|
7
6
|
ChatPromptTemplate,
|
|
@@ -25,10 +24,53 @@ import {
|
|
|
25
24
|
import { MCPClientManager } from './mcp/MCPClientManager';
|
|
26
25
|
import { convertMCPToolToLangChain } from './mcp/adapters/langchain';
|
|
27
26
|
import { SmartMemoryManager } from './memory/SmartMemoryManager';
|
|
28
|
-
import type { MCPConnectionStatus } from './mcp/types';
|
|
27
|
+
import type { MCPConnectionStatus, MCPServerConfig } from './mcp/types';
|
|
28
|
+
import { ResponseFormatter } from './utils/ResponseFormatter';
|
|
29
29
|
import type { FormSubmission } from './forms/types';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
30
|
+
import type { ToolRegistrationOptions } from './core/ToolRegistry';
|
|
31
|
+
import { HumanMessage, AIMessage } from '@langchain/core/messages';
|
|
32
|
+
import { ToolRegistry } from './core/ToolRegistry';
|
|
33
|
+
import {
|
|
34
|
+
ExecutionPipeline,
|
|
35
|
+
SessionContext,
|
|
36
|
+
} from './execution/ExecutionPipeline';
|
|
37
|
+
import { FormEngine } from './forms/FormEngine';
|
|
38
|
+
import type { ChainValues } from '@langchain/core/utils/types';
|
|
39
|
+
|
|
40
|
+
interface RenderConfigSchema {
|
|
41
|
+
_renderConfig?: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ToolExecutionData {
|
|
45
|
+
type: string;
|
|
46
|
+
formId?: string;
|
|
47
|
+
parameters?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface IntermediateStep {
|
|
51
|
+
action?: {
|
|
52
|
+
tool?: string;
|
|
53
|
+
toolInput?: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
observation?: unknown;
|
|
56
|
+
}
|
|
57
|
+
interface RenderConfigSchema {
|
|
58
|
+
_renderConfig?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ToolExecutionData {
|
|
62
|
+
type: string;
|
|
63
|
+
formId?: string;
|
|
64
|
+
parameters?: Record<string, unknown>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface IntermediateStep {
|
|
68
|
+
action?: {
|
|
69
|
+
tool?: string;
|
|
70
|
+
toolInput?: Record<string, unknown>;
|
|
71
|
+
};
|
|
72
|
+
observation?: unknown;
|
|
73
|
+
}
|
|
32
74
|
|
|
33
75
|
export class LangChainAgent extends BaseAgent {
|
|
34
76
|
private executor: FormAwareAgentExecutor | undefined;
|
|
@@ -36,11 +78,505 @@ export class LangChainAgent extends BaseAgent {
|
|
|
36
78
|
private mcpManager?: MCPClientManager;
|
|
37
79
|
private smartMemory: SmartMemoryManager | undefined;
|
|
38
80
|
private mcpConnectionStatus: Map<string, MCPConnectionStatus> = new Map();
|
|
81
|
+
private toolRegistry!: ToolRegistry;
|
|
82
|
+
private executionPipeline!: ExecutionPipeline;
|
|
83
|
+
private formEngine!: FormEngine;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get inscription tool by capability instead of hardcoded name
|
|
87
|
+
*/
|
|
88
|
+
private getInscriptionTool(): StructuredTool | null {
|
|
89
|
+
const criticalTools = this.toolRegistry.getToolsByCapability(
|
|
90
|
+
'priority',
|
|
91
|
+
'critical'
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
for (const entry of criticalTools) {
|
|
95
|
+
const tool = entry.tool;
|
|
96
|
+
const name = tool.name.toLowerCase();
|
|
97
|
+
const desc = tool.description?.toLowerCase() || '';
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
name.includes('inscribe') ||
|
|
101
|
+
name.includes('hashinal') ||
|
|
102
|
+
desc.includes('inscribe') ||
|
|
103
|
+
desc.includes('hashinal')
|
|
104
|
+
) {
|
|
105
|
+
return tool;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const allTools = this.toolRegistry.getAllRegistryEntries();
|
|
110
|
+
for (const entry of allTools) {
|
|
111
|
+
const tool = entry.tool;
|
|
112
|
+
const name = tool.name.toLowerCase();
|
|
113
|
+
const desc = tool.description?.toLowerCase() || '';
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
name.includes('inscribe') ||
|
|
117
|
+
name.includes('hashinal') ||
|
|
118
|
+
desc.includes('inscribe') ||
|
|
119
|
+
desc.includes('hashinal')
|
|
120
|
+
) {
|
|
121
|
+
return tool;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get a tool by name from the registry with type safety
|
|
130
|
+
*/
|
|
131
|
+
private getTool(name: string): StructuredTool | null {
|
|
132
|
+
const entry = this.toolRegistry.getTool(name);
|
|
133
|
+
return entry ? entry.tool : null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Execute a tool directly with parameters, optionally using ExecutionPipeline
|
|
138
|
+
*/
|
|
139
|
+
private async executeToolDirect(
|
|
140
|
+
toolName: string,
|
|
141
|
+
parameters: Record<string, unknown>,
|
|
142
|
+
useExecutionPipeline = false
|
|
143
|
+
): Promise<string> {
|
|
144
|
+
if (useExecutionPipeline && this.executionPipeline && this.smartMemory) {
|
|
145
|
+
const sessionContext: SessionContext = {
|
|
146
|
+
sessionId: `session-${Date.now()}`,
|
|
147
|
+
timestamp: Date.now(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const result = await this.executionPipeline.execute(
|
|
151
|
+
toolName,
|
|
152
|
+
parameters,
|
|
153
|
+
sessionContext
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!result.success) {
|
|
157
|
+
throw new Error(result.error || 'Pipeline execution failed');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result.output;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const entry = this.toolRegistry.getTool(toolName);
|
|
164
|
+
if (!entry) {
|
|
165
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const mergedArgs = { ...parameters, renderForm: false };
|
|
169
|
+
|
|
170
|
+
if (entry.wrapper) {
|
|
171
|
+
const maybeWrapper = entry.tool as unknown as {
|
|
172
|
+
originalTool?: {
|
|
173
|
+
call?: (a: Record<string, unknown>) => Promise<string>;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
if (maybeWrapper.originalTool?.call) {
|
|
177
|
+
return await maybeWrapper.originalTool.call(mergedArgs);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return await entry.tool.call(mergedArgs);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Create a standard ChatResponse from tool output
|
|
186
|
+
*/
|
|
187
|
+
private createToolResponse(toolOutput: string): ChatResponse {
|
|
188
|
+
return {
|
|
189
|
+
output: toolOutput,
|
|
190
|
+
message: toolOutput,
|
|
191
|
+
notes: [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handle TOOL_EXECUTION format messages
|
|
197
|
+
*/
|
|
198
|
+
private async handleToolExecution(
|
|
199
|
+
message: string,
|
|
200
|
+
context?: ConversationContext
|
|
201
|
+
): Promise<ChatResponse | null> {
|
|
202
|
+
let isToolExecution = false;
|
|
203
|
+
let toolExecutionData: ToolExecutionData | null = null;
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
if (message.includes('TOOL_EXECUTION')) {
|
|
207
|
+
const parsed = JSON.parse(message);
|
|
208
|
+
if (parsed.type === 'TOOL_EXECUTION') {
|
|
209
|
+
isToolExecution = true;
|
|
210
|
+
toolExecutionData = parsed;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch {}
|
|
214
|
+
|
|
215
|
+
if (!isToolExecution || !toolExecutionData?.formId) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const params = (toolExecutionData.parameters || {}) as Record<
|
|
221
|
+
string,
|
|
222
|
+
unknown
|
|
223
|
+
>;
|
|
224
|
+
const toolName = (toolExecutionData as unknown as { toolName?: string })
|
|
225
|
+
.toolName;
|
|
226
|
+
|
|
227
|
+
if (toolName) {
|
|
228
|
+
const toolOutput = await this.executeToolDirect(toolName, params);
|
|
229
|
+
return this.createToolResponse(toolOutput);
|
|
230
|
+
}
|
|
231
|
+
} catch {}
|
|
232
|
+
|
|
233
|
+
const formSubmission: FormSubmission = {
|
|
234
|
+
formId: toolExecutionData.formId,
|
|
235
|
+
toolName:
|
|
236
|
+
(toolExecutionData as unknown as { toolName?: string }).toolName || '',
|
|
237
|
+
parameters: toolExecutionData.parameters || {},
|
|
238
|
+
timestamp: Date.now(),
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
this.executor &&
|
|
243
|
+
'processFormSubmission' in this.executor &&
|
|
244
|
+
typeof this.executor.processFormSubmission === 'function'
|
|
245
|
+
) {
|
|
246
|
+
return this.processFormSubmission(formSubmission, context);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Handle direct tool execution commands
|
|
254
|
+
*/
|
|
255
|
+
private async handleDirectToolExecution(
|
|
256
|
+
message: string
|
|
257
|
+
): Promise<ChatResponse | null> {
|
|
258
|
+
if (
|
|
259
|
+
typeof message !== 'string' ||
|
|
260
|
+
!message.includes('Please execute the following tool:')
|
|
261
|
+
) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const toolLineMatch = message.match(/Tool:\s*(.+)/);
|
|
267
|
+
const argsLineIndex = message.indexOf('Arguments:');
|
|
268
|
+
|
|
269
|
+
if (toolLineMatch && argsLineIndex !== -1) {
|
|
270
|
+
const toolName = toolLineMatch[1].trim();
|
|
271
|
+
const argsText = message
|
|
272
|
+
.slice(argsLineIndex + 'Arguments:'.length)
|
|
273
|
+
.trim();
|
|
274
|
+
|
|
275
|
+
let args: Record<string, unknown> = {};
|
|
276
|
+
try {
|
|
277
|
+
args = JSON.parse(argsText);
|
|
278
|
+
} catch {}
|
|
279
|
+
|
|
280
|
+
const toolOutput = await this.executeToolDirect(toolName, args);
|
|
281
|
+
return this.createToolResponse(toolOutput);
|
|
282
|
+
}
|
|
283
|
+
} catch {}
|
|
284
|
+
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Handle JSON format tool calls and form submissions
|
|
290
|
+
*/
|
|
291
|
+
private async handleJsonToolCalls(
|
|
292
|
+
message: string,
|
|
293
|
+
context?: ConversationContext
|
|
294
|
+
): Promise<ChatResponse | null> {
|
|
295
|
+
if (typeof message !== 'string') {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const trimmed = message.trim();
|
|
301
|
+
if (
|
|
302
|
+
!(trimmed.startsWith('{') && trimmed.endsWith('}')) &&
|
|
303
|
+
!(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
304
|
+
) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const obj = JSON.parse(trimmed) as Record<string, unknown>;
|
|
309
|
+
const formId = obj['formId'] as string | undefined;
|
|
310
|
+
const toolName = (obj['toolName'] as string) || '';
|
|
311
|
+
const parameters = (obj['parameters'] as Record<string, unknown>) || {};
|
|
312
|
+
|
|
313
|
+
if (
|
|
314
|
+
formId &&
|
|
315
|
+
this.executor &&
|
|
316
|
+
'processFormSubmission' in this.executor &&
|
|
317
|
+
typeof this.executor.processFormSubmission === 'function'
|
|
318
|
+
) {
|
|
319
|
+
return this.processFormSubmission(
|
|
320
|
+
{ formId, toolName, parameters, timestamp: Date.now() },
|
|
321
|
+
context
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (toolName) {
|
|
326
|
+
const toolOutput = await this.executeToolDirect(toolName, parameters);
|
|
327
|
+
return this.createToolResponse(toolOutput);
|
|
328
|
+
}
|
|
329
|
+
} catch {}
|
|
330
|
+
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Handle content-ref messages for inscription tools
|
|
336
|
+
*/
|
|
337
|
+
private async handleContentRefMessages(
|
|
338
|
+
message: string
|
|
339
|
+
): Promise<ChatResponse | null> {
|
|
340
|
+
if (typeof message !== 'string' || !message.includes('content-ref:')) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const tool = this.getInscriptionTool();
|
|
346
|
+
if (!tool) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const idMatch =
|
|
351
|
+
message.match(/content-ref:([A-Za-z0-9_\-]+)/i) ||
|
|
352
|
+
message.match(/content-ref:([^\s)]+)/i);
|
|
353
|
+
const contentRef =
|
|
354
|
+
idMatch && idMatch[1]
|
|
355
|
+
? `content-ref:${idMatch[1]}`
|
|
356
|
+
: message.match(/content-ref:[^\s)]+/i)?.[0] || undefined;
|
|
357
|
+
|
|
358
|
+
const args = contentRef
|
|
359
|
+
? ({ contentRef, renderForm: true, withHashLinkBlocks: true } as Record<string, unknown>)
|
|
360
|
+
: ({ renderForm: true, withHashLinkBlocks: true } as Record<string, unknown>);
|
|
361
|
+
|
|
362
|
+
const toolOutput = await tool.call(args);
|
|
363
|
+
let parsed: Record<string, unknown> | undefined;
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
parsed =
|
|
367
|
+
typeof toolOutput === 'string'
|
|
368
|
+
? (JSON.parse(toolOutput) as Record<string, unknown>)
|
|
369
|
+
: (toolOutput as unknown as Record<string, unknown>);
|
|
370
|
+
} catch {}
|
|
371
|
+
|
|
372
|
+
if (parsed && parsed['requiresForm'] && parsed['formMessage']) {
|
|
373
|
+
const pending = new Map<
|
|
374
|
+
string,
|
|
375
|
+
{
|
|
376
|
+
toolName: string;
|
|
377
|
+
originalInput: Record<string, unknown>;
|
|
378
|
+
originalToolInput?: Record<string, unknown>;
|
|
379
|
+
schema: unknown;
|
|
380
|
+
}
|
|
381
|
+
>();
|
|
382
|
+
|
|
383
|
+
const originalInput = {
|
|
384
|
+
input: message,
|
|
385
|
+
chat_history: this.smartMemory!.getMessages(),
|
|
386
|
+
} as Record<string, unknown>;
|
|
387
|
+
|
|
388
|
+
const formMessage = parsed['formMessage'] as { id: string };
|
|
389
|
+
pending.set(formMessage.id, {
|
|
390
|
+
toolName: tool.name,
|
|
391
|
+
originalInput,
|
|
392
|
+
originalToolInput: args,
|
|
393
|
+
schema: null,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const maybeRestore = this.executor as unknown as {
|
|
397
|
+
restorePendingForms?: (p: Map<string, unknown>) => void;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
if (typeof maybeRestore.restorePendingForms === 'function') {
|
|
401
|
+
(
|
|
402
|
+
maybeRestore.restorePendingForms as unknown as (
|
|
403
|
+
p: Map<string, unknown>
|
|
404
|
+
) => void
|
|
405
|
+
)(pending as unknown as Map<string, unknown>);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const outputMsg =
|
|
409
|
+
(parsed['message'] as string) ||
|
|
410
|
+
'Please complete the form to continue.';
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
output: outputMsg,
|
|
414
|
+
message: outputMsg,
|
|
415
|
+
notes: [],
|
|
416
|
+
requiresForm: true,
|
|
417
|
+
formMessage: formMessage as unknown as ChatResponse['formMessage'],
|
|
418
|
+
} as ChatResponse;
|
|
419
|
+
}
|
|
420
|
+
} catch {}
|
|
421
|
+
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Process executor result and format response
|
|
427
|
+
*/
|
|
428
|
+
private async processExecutorResult(
|
|
429
|
+
result: ChainValues
|
|
430
|
+
): Promise<ChatResponse> {
|
|
431
|
+
let outputStr = '';
|
|
432
|
+
if (typeof result.output === 'string') {
|
|
433
|
+
outputStr = result.output;
|
|
434
|
+
} else if (result.output) {
|
|
435
|
+
try {
|
|
436
|
+
outputStr = JSON.stringify(result.output);
|
|
437
|
+
} catch {
|
|
438
|
+
outputStr = String(result.output);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
let response: ChatResponse = {
|
|
443
|
+
output: outputStr,
|
|
444
|
+
message: outputStr,
|
|
445
|
+
notes: [],
|
|
446
|
+
intermediateSteps: result.intermediateSteps,
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
if (result.requiresForm && result.formMessage) {
|
|
450
|
+
response.formMessage = result.formMessage;
|
|
451
|
+
response.requiresForm = true;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
|
|
455
|
+
const toolCalls = result.intermediateSteps.map(
|
|
456
|
+
(step: IntermediateStep, index: number) => ({
|
|
457
|
+
id: `call_${index}`,
|
|
458
|
+
name: step.action?.tool || 'unknown',
|
|
459
|
+
args: step.action?.toolInput || {},
|
|
460
|
+
output:
|
|
461
|
+
typeof step.observation === 'string'
|
|
462
|
+
? step.observation
|
|
463
|
+
: JSON.stringify(step.observation),
|
|
464
|
+
})
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
if (toolCalls.length > 0) {
|
|
468
|
+
response.tool_calls = toolCalls;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const parsedSteps = result?.intermediateSteps?.[0]?.observation;
|
|
473
|
+
if (
|
|
474
|
+
parsedSteps &&
|
|
475
|
+
typeof parsedSteps === 'string' &&
|
|
476
|
+
this.isJSON(parsedSteps)
|
|
477
|
+
) {
|
|
478
|
+
try {
|
|
479
|
+
const parsed = JSON.parse(parsedSteps);
|
|
480
|
+
|
|
481
|
+
if (ResponseFormatter.isInscriptionResponse(parsed)) {
|
|
482
|
+
const formattedMessage =
|
|
483
|
+
ResponseFormatter.formatInscriptionResponse(parsed);
|
|
484
|
+
response.output = formattedMessage;
|
|
485
|
+
response.message = formattedMessage;
|
|
486
|
+
|
|
487
|
+
if (parsed.inscription) {
|
|
488
|
+
response.inscription = parsed.inscription;
|
|
489
|
+
}
|
|
490
|
+
if (parsed.metadata) {
|
|
491
|
+
response.metadata = {
|
|
492
|
+
...response.metadata,
|
|
493
|
+
...parsed.metadata,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
response = { ...response, ...parsed };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const blockMetadata = this.processHashLinkBlocks(parsed);
|
|
501
|
+
if (blockMetadata.hashLinkBlock) {
|
|
502
|
+
response.metadata = {
|
|
503
|
+
...response.metadata,
|
|
504
|
+
...blockMetadata,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
this.logger.error('Error parsing intermediate steps:', error);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (!response.output || response.output.trim() === '') {
|
|
513
|
+
response.output = 'Agent action complete.';
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (response.output) {
|
|
517
|
+
this.smartMemory!.addMessage(new AIMessage(response.output));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (this.tokenTracker) {
|
|
521
|
+
const tokenUsage = this.tokenTracker.getLatestTokenUsage();
|
|
522
|
+
if (tokenUsage) {
|
|
523
|
+
response.tokenUsage = tokenUsage;
|
|
524
|
+
response.cost = calculateTokenCostSync(tokenUsage);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const finalMemoryStats = this.smartMemory!.getMemoryStats();
|
|
529
|
+
response.metadata = {
|
|
530
|
+
...response.metadata,
|
|
531
|
+
memoryStats: {
|
|
532
|
+
activeMessages: finalMemoryStats.totalActiveMessages,
|
|
533
|
+
tokenUsage: finalMemoryStats.currentTokenCount,
|
|
534
|
+
maxTokens: finalMemoryStats.maxTokens,
|
|
535
|
+
usagePercentage: finalMemoryStats.usagePercentage,
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
this.logger.info('LangChainAgent.chat returning response:', response);
|
|
540
|
+
return response;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Normalize context messages into LangChain message instances and load into memory
|
|
545
|
+
*/
|
|
546
|
+
private loadContextMessages(context?: ConversationContext): void {
|
|
547
|
+
if (!this.smartMemory) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (!context?.messages || context.messages.length === 0) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
this.smartMemory.clear();
|
|
554
|
+
const rawMessages = context.messages as unknown[];
|
|
555
|
+
for (const msg of rawMessages) {
|
|
556
|
+
if (msg instanceof HumanMessage || msg instanceof AIMessage) {
|
|
557
|
+
this.smartMemory.addMessage(msg);
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (
|
|
561
|
+
msg &&
|
|
562
|
+
typeof msg === 'object' &&
|
|
563
|
+
'content' in (msg as Record<string, unknown>) &&
|
|
564
|
+
'type' in (msg as Record<string, unknown>)
|
|
565
|
+
) {
|
|
566
|
+
const content = String((msg as { content: unknown }).content);
|
|
567
|
+
const type = String((msg as { type: unknown }).type);
|
|
568
|
+
if (type === 'human') {
|
|
569
|
+
this.smartMemory.addMessage(new HumanMessage(content));
|
|
570
|
+
} else {
|
|
571
|
+
this.smartMemory.addMessage(new AIMessage(content));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
39
576
|
|
|
40
577
|
async boot(): Promise<void> {
|
|
41
|
-
console.log('🚨🚨🚨 LANGCHAIN AGENT BOOT METHOD CALLED 🚨🚨🚨');
|
|
42
578
|
this.logger.info('🚨🚨🚨 LANGCHAIN AGENT BOOT METHOD CALLED 🚨🚨🚨');
|
|
43
|
-
|
|
579
|
+
|
|
44
580
|
if (this.initialized) {
|
|
45
581
|
this.logger.warn('Agent already initialized');
|
|
46
582
|
return;
|
|
@@ -56,77 +592,100 @@ export class LangChainAgent extends BaseAgent {
|
|
|
56
592
|
'gpt-4o-mini';
|
|
57
593
|
this.tokenTracker = new TokenUsageCallbackHandler(modelName);
|
|
58
594
|
|
|
595
|
+
this.toolRegistry = new ToolRegistry(this.logger);
|
|
596
|
+
|
|
59
597
|
const allTools = this.agentKit.getAggregatedLangChainTools();
|
|
60
|
-
this.logger.info('=== TOOL
|
|
61
|
-
this.logger.info(
|
|
62
|
-
|
|
598
|
+
this.logger.info('=== TOOL REGISTRATION START ===');
|
|
599
|
+
this.logger.info(
|
|
600
|
+
'All tools from agentKit:',
|
|
601
|
+
allTools.map((t) => t.name)
|
|
602
|
+
);
|
|
603
|
+
|
|
63
604
|
const filteredTools = this.filterTools(allTools);
|
|
64
|
-
this.logger.info(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
605
|
+
this.logger.info(
|
|
606
|
+
'Filtered tools for registration:',
|
|
607
|
+
filteredTools.map((t) => t.name)
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
for (const tool of filteredTools) {
|
|
611
|
+
this.logger.info(`🔧 Registering tool: ${tool.name}`);
|
|
612
|
+
|
|
613
|
+
const options: ToolRegistrationOptions = {};
|
|
614
|
+
|
|
615
|
+
const name = tool.name.toLowerCase();
|
|
616
|
+
const desc = tool.description?.toLowerCase() || '';
|
|
617
|
+
|
|
618
|
+
if (
|
|
619
|
+
name.includes('inscribe') ||
|
|
620
|
+
name.includes('hashinal') ||
|
|
621
|
+
desc.includes('inscribe') ||
|
|
622
|
+
desc.includes('hashinal')
|
|
623
|
+
) {
|
|
624
|
+
options.forceWrapper = true;
|
|
625
|
+
options.metadata = {
|
|
626
|
+
category: 'core' as const,
|
|
627
|
+
version: '1.0.0',
|
|
628
|
+
dependencies: [],
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
this.logger.info(`🎯 CRITICAL TOOL DEBUG - ${tool.name} schema:`, {
|
|
74
632
|
hasSchema: !!tool.schema,
|
|
75
633
|
schemaType: tool.schema?.constructor?.name,
|
|
76
|
-
hasRenderConfig: !!(tool.schema as
|
|
77
|
-
|
|
634
|
+
hasRenderConfig: !!(tool.schema as RenderConfigSchema)
|
|
635
|
+
?._renderConfig,
|
|
636
|
+
renderConfig: (tool.schema as RenderConfigSchema)?._renderConfig,
|
|
78
637
|
});
|
|
79
638
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
639
|
+
|
|
640
|
+
this.toolRegistry.registerTool(tool, options);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
this.tools = this.toolRegistry.getAllTools();
|
|
644
|
+
|
|
645
|
+
this.logger.info(`🚀 TOOLS REGISTERED: ${this.tools.length} tools`);
|
|
646
|
+
|
|
647
|
+
const stats = this.toolRegistry.getStatistics();
|
|
648
|
+
this.logger.info('📊 Tool Registry Statistics:', {
|
|
649
|
+
total: stats.totalTools,
|
|
650
|
+
wrapped: stats.wrappedTools,
|
|
651
|
+
unwrapped: stats.unwrappedTools,
|
|
652
|
+
categories: stats.categoryCounts,
|
|
653
|
+
priorities: stats.priorityCounts,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const inscriptionTool = this.getInscriptionTool();
|
|
657
|
+
if (inscriptionTool) {
|
|
658
|
+
const entry = this.toolRegistry.getTool(inscriptionTool.name);
|
|
659
|
+
if (entry) {
|
|
660
|
+
this.logger.info(
|
|
661
|
+
`🔒 FINAL SECURITY CHECK - ${inscriptionTool.name} tool type: ${entry.tool.constructor.name}`
|
|
662
|
+
);
|
|
663
|
+
if (entry.tool.constructor.name !== 'FormValidatingToolWrapper') {
|
|
664
|
+
this.logger.error(
|
|
665
|
+
`🚨 SECURITY BREACH: ${inscriptionTool.name} tool is NOT wrapped!`
|
|
666
|
+
);
|
|
667
|
+
throw new Error(
|
|
668
|
+
`Critical security failure: ${inscriptionTool.name} tool bypassed FormValidatingToolWrapper`
|
|
669
|
+
);
|
|
670
|
+
} else {
|
|
671
|
+
this.logger.info(
|
|
672
|
+
`✅ SECURITY VALIDATED: ${inscriptionTool.name} tool is properly wrapped`
|
|
673
|
+
);
|
|
103
674
|
}
|
|
104
675
|
}
|
|
105
|
-
|
|
106
|
-
// Check if schema is ZodObject or behaves like one (including ZodSchemaWithRender)
|
|
107
|
-
const isZodObjectLike = (schema: any): boolean => {
|
|
108
|
-
if (!schema || typeof schema !== 'object') {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
return (
|
|
112
|
-
schema instanceof z.ZodObject ||
|
|
113
|
-
schema._def?.typeName === 'ZodObject' ||
|
|
114
|
-
('shape' in schema && typeof schema.shape === 'object')
|
|
115
|
-
);
|
|
116
|
-
};
|
|
676
|
+
}
|
|
117
677
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.logger.info(`🚀 TOOLS ASSIGNED: ${this.tools.length} tools`);
|
|
678
|
+
const toolNames = this.toolRegistry.getToolNames();
|
|
679
|
+
const uniqueNames = new Set(toolNames);
|
|
680
|
+
if (toolNames.length !== uniqueNames.size) {
|
|
681
|
+
this.logger.error('DUPLICATE TOOL NAMES DETECTED in registry!');
|
|
682
|
+
const duplicates = toolNames.filter(
|
|
683
|
+
(name, index) => toolNames.indexOf(name) !== index
|
|
684
|
+
);
|
|
685
|
+
throw new Error(
|
|
686
|
+
`Duplicate tool names detected: ${duplicates.join(', ')}`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
130
689
|
|
|
131
690
|
if (this.config.mcp?.servers && this.config.mcp.servers.length > 0) {
|
|
132
691
|
if (this.config.mcp.autoConnect !== false) {
|
|
@@ -153,6 +712,15 @@ export class LangChainAgent extends BaseAgent {
|
|
|
153
712
|
reserveTokens: 10000,
|
|
154
713
|
});
|
|
155
714
|
|
|
715
|
+
this.formEngine = new FormEngine(this.logger);
|
|
716
|
+
|
|
717
|
+
this.executionPipeline = new ExecutionPipeline(
|
|
718
|
+
this.toolRegistry,
|
|
719
|
+
this.formEngine,
|
|
720
|
+
this.smartMemory,
|
|
721
|
+
this.logger
|
|
722
|
+
);
|
|
723
|
+
|
|
156
724
|
this.systemMessage = this.buildSystemPrompt();
|
|
157
725
|
|
|
158
726
|
this.smartMemory.setSystemPrompt(this.systemMessage);
|
|
@@ -160,7 +728,7 @@ export class LangChainAgent extends BaseAgent {
|
|
|
160
728
|
await this.createExecutor();
|
|
161
729
|
|
|
162
730
|
this.initialized = true;
|
|
163
|
-
this.logger.info('LangChain Hedera agent initialized');
|
|
731
|
+
this.logger.info('LangChain Hedera agent initialized with ToolRegistry');
|
|
164
732
|
} catch (error) {
|
|
165
733
|
this.logger.error('Failed to initialize agent:', error);
|
|
166
734
|
throw error;
|
|
@@ -176,20 +744,35 @@ export class LangChainAgent extends BaseAgent {
|
|
|
176
744
|
}
|
|
177
745
|
|
|
178
746
|
try {
|
|
179
|
-
this.
|
|
747
|
+
const toolExecutionResult = await this.handleToolExecution(
|
|
180
748
|
message,
|
|
181
|
-
|
|
182
|
-
|
|
749
|
+
context
|
|
750
|
+
);
|
|
751
|
+
if (toolExecutionResult) {
|
|
752
|
+
return toolExecutionResult;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const directToolResult = await this.handleDirectToolExecution(message);
|
|
756
|
+
if (directToolResult) {
|
|
757
|
+
return directToolResult;
|
|
758
|
+
}
|
|
183
759
|
|
|
184
|
-
|
|
185
|
-
|
|
760
|
+
const jsonToolResult = await this.handleJsonToolCalls(message, context);
|
|
761
|
+
if (jsonToolResult) {
|
|
762
|
+
return jsonToolResult;
|
|
763
|
+
}
|
|
186
764
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
765
|
+
const contentRefResult = await this.handleContentRefMessages(message);
|
|
766
|
+
if (contentRefResult) {
|
|
767
|
+
return contentRefResult;
|
|
190
768
|
}
|
|
191
769
|
|
|
192
|
-
|
|
770
|
+
this.logger.info('LangChainAgent.chat called with:', {
|
|
771
|
+
message,
|
|
772
|
+
contextLength: context?.messages?.length || 0,
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
this.loadContextMessages(context);
|
|
193
776
|
this.smartMemory.addMessage(new HumanMessage(message));
|
|
194
777
|
|
|
195
778
|
const memoryStats = this.smartMemory.getMemoryStats();
|
|
@@ -208,86 +791,7 @@ export class LangChainAgent extends BaseAgent {
|
|
|
208
791
|
|
|
209
792
|
this.logger.info('LangChainAgent executor result:', result);
|
|
210
793
|
|
|
211
|
-
|
|
212
|
-
? result.output
|
|
213
|
-
: result.output
|
|
214
|
-
? JSON.stringify(result.output)
|
|
215
|
-
: '';
|
|
216
|
-
|
|
217
|
-
let response: ChatResponse = {
|
|
218
|
-
output: outputStr,
|
|
219
|
-
message: outputStr,
|
|
220
|
-
notes: [],
|
|
221
|
-
intermediateSteps: result.intermediateSteps,
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
if (result.requiresForm && result.formMessage) {
|
|
225
|
-
response.formMessage = result.formMessage;
|
|
226
|
-
response.requiresForm = true;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
|
|
230
|
-
const toolCalls = result.intermediateSteps.map(
|
|
231
|
-
(step: any, index: number) => ({
|
|
232
|
-
id: `call_${index}`,
|
|
233
|
-
name: step.action?.tool || 'unknown',
|
|
234
|
-
args: step.action?.toolInput || {},
|
|
235
|
-
output:
|
|
236
|
-
typeof step.observation === 'string'
|
|
237
|
-
? step.observation
|
|
238
|
-
: JSON.stringify(step.observation),
|
|
239
|
-
})
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
if (toolCalls.length > 0) {
|
|
243
|
-
response.tool_calls = toolCalls;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const parsedSteps = result?.intermediateSteps?.[0]?.observation;
|
|
248
|
-
if (
|
|
249
|
-
parsedSteps &&
|
|
250
|
-
typeof parsedSteps === 'string' &&
|
|
251
|
-
this.isJSON(parsedSteps)
|
|
252
|
-
) {
|
|
253
|
-
try {
|
|
254
|
-
const parsed = JSON.parse(parsedSteps);
|
|
255
|
-
response = { ...response, ...parsed };
|
|
256
|
-
} catch (error) {
|
|
257
|
-
this.logger.error('Error parsing intermediate steps:', error);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (!response.output || response.output.trim() === '') {
|
|
262
|
-
response.output = 'Agent action complete.';
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (response.output) {
|
|
266
|
-
const { AIMessage } = await import('@langchain/core/messages');
|
|
267
|
-
this.smartMemory.addMessage(new AIMessage(response.output));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (this.tokenTracker) {
|
|
271
|
-
const tokenUsage = this.tokenTracker.getLatestTokenUsage();
|
|
272
|
-
if (tokenUsage) {
|
|
273
|
-
response.tokenUsage = tokenUsage;
|
|
274
|
-
response.cost = calculateTokenCostSync(tokenUsage);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const finalMemoryStats = this.smartMemory.getMemoryStats();
|
|
279
|
-
response.metadata = {
|
|
280
|
-
...response.metadata,
|
|
281
|
-
memoryStats: {
|
|
282
|
-
activeMessages: finalMemoryStats.totalActiveMessages,
|
|
283
|
-
tokenUsage: finalMemoryStats.currentTokenCount,
|
|
284
|
-
maxTokens: finalMemoryStats.maxTokens,
|
|
285
|
-
usagePercentage: finalMemoryStats.usagePercentage,
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
this.logger.info('LangChainAgent.chat returning response:', response);
|
|
290
|
-
return response;
|
|
794
|
+
return this.processExecutorResult(result);
|
|
291
795
|
} catch (error) {
|
|
292
796
|
this.logger.error('LangChainAgent.chat error:', error);
|
|
293
797
|
return this.handleError(error);
|
|
@@ -304,6 +808,10 @@ export class LangChainAgent extends BaseAgent {
|
|
|
304
808
|
this.smartMemory = undefined;
|
|
305
809
|
}
|
|
306
810
|
|
|
811
|
+
if (this.toolRegistry) {
|
|
812
|
+
this.toolRegistry.clear();
|
|
813
|
+
}
|
|
814
|
+
|
|
307
815
|
this.executor = undefined;
|
|
308
816
|
this.agentKit = undefined;
|
|
309
817
|
this.tools = [];
|
|
@@ -370,38 +878,118 @@ export class LangChainAgent extends BaseAgent {
|
|
|
370
878
|
submission: FormSubmission,
|
|
371
879
|
context?: ConversationContext
|
|
372
880
|
): Promise<ChatResponse> {
|
|
881
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission START');
|
|
882
|
+
|
|
373
883
|
if (!this.initialized || !this.executor || !this.smartMemory) {
|
|
884
|
+
this.logger.error('🔥 LangChainAgent.processFormSubmission - Agent not initialized');
|
|
374
885
|
throw new Error('Agent not initialized. Call boot() first.');
|
|
375
886
|
}
|
|
376
887
|
|
|
888
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - After initialization check');
|
|
889
|
+
|
|
377
890
|
try {
|
|
891
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - About to log submission info');
|
|
892
|
+
|
|
378
893
|
this.logger.info('Processing form submission:', {
|
|
379
894
|
formId: submission.formId,
|
|
380
|
-
|
|
895
|
+
toolName: submission.toolName,
|
|
896
|
+
parameterKeys: Object.keys(submission.parameters || {}),
|
|
897
|
+
hasParameters: !!submission.parameters,
|
|
898
|
+
parametersType: typeof submission.parameters,
|
|
899
|
+
parametersIsNull: submission.parameters === null,
|
|
900
|
+
parametersIsUndefined: submission.parameters === undefined,
|
|
901
|
+
hasContext: !!submission.context,
|
|
381
902
|
});
|
|
382
903
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
904
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - After submission info logged');
|
|
905
|
+
|
|
906
|
+
if (!submission.parameters || typeof submission.parameters !== 'object') {
|
|
907
|
+
this.logger.error('Invalid form submission parameters:', {
|
|
908
|
+
parameters: submission.parameters,
|
|
909
|
+
type: typeof submission.parameters,
|
|
910
|
+
});
|
|
911
|
+
const errorInfo = JSON.stringify(submission, null, 2);
|
|
912
|
+
return this.handleError(
|
|
913
|
+
new Error(`Invalid form submission parameters: ${errorInfo}`)
|
|
914
|
+
);
|
|
388
915
|
}
|
|
389
916
|
|
|
390
|
-
|
|
917
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - Parameters validated');
|
|
918
|
+
|
|
919
|
+
this.loadContextMessages(context);
|
|
391
920
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
921
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - Context loaded');
|
|
922
|
+
|
|
923
|
+
const safeSubmission = {
|
|
924
|
+
...submission,
|
|
925
|
+
parameters: submission.parameters || {},
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - Safe submission created');
|
|
929
|
+
|
|
930
|
+
this.logger.info('About to call executor.processFormSubmission with:', {
|
|
931
|
+
formId: safeSubmission.formId,
|
|
932
|
+
toolName: safeSubmission.toolName,
|
|
933
|
+
parameterKeys: Object.keys(safeSubmission.parameters),
|
|
934
|
+
parameterCount: Object.keys(safeSubmission.parameters).length,
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
const result = await this.executor.processFormSubmission(safeSubmission);
|
|
938
|
+
|
|
939
|
+
this.logger.info('🔍 DEBUG: Raw result from FormAwareAgentExecutor:', {
|
|
940
|
+
hasResult: !!result,
|
|
941
|
+
resultKeys: result ? Object.keys(result) : [],
|
|
942
|
+
hasMetadata: !!result?.metadata,
|
|
943
|
+
metadataKeys: result?.metadata ? Object.keys(result.metadata) : [],
|
|
944
|
+
hasHashLinkBlock: !!(result?.metadata as any)?.hashLinkBlock,
|
|
945
|
+
hashLinkBlockContent: (result?.metadata as any)?.hashLinkBlock
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
if (result?.metadata) {
|
|
949
|
+
this.logger.info('🔍 DEBUG: Full metadata from executor:', JSON.stringify(result.metadata));
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const preservedMetadata = result?.metadata ? { ...result.metadata } : {};
|
|
953
|
+
|
|
954
|
+
this.logger.info('Executor processFormSubmission result:', {
|
|
955
|
+
hasResult: !!result,
|
|
956
|
+
hasOutput: !!result.output,
|
|
957
|
+
hasError: !!result.error,
|
|
958
|
+
hasMetadata: !!result.metadata,
|
|
959
|
+
outputType: typeof result.output,
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
let outputMessage = 'Form processed successfully.';
|
|
963
|
+
if (typeof result.output === 'string') {
|
|
964
|
+
outputMessage = result.output;
|
|
965
|
+
} else if (result.output) {
|
|
966
|
+
try {
|
|
967
|
+
outputMessage = JSON.stringify(result.output);
|
|
968
|
+
} catch {
|
|
969
|
+
outputMessage = String(result.output);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
397
972
|
|
|
398
973
|
let response: ChatResponse = {
|
|
399
974
|
output: outputMessage,
|
|
400
975
|
message: outputMessage,
|
|
401
976
|
notes: [],
|
|
402
|
-
intermediateSteps: result.intermediateSteps as
|
|
977
|
+
intermediateSteps: result.intermediateSteps as IntermediateStep[],
|
|
403
978
|
};
|
|
404
979
|
|
|
980
|
+
if (result.metadata) {
|
|
981
|
+
response.metadata = {
|
|
982
|
+
...response.metadata,
|
|
983
|
+
...result.metadata
|
|
984
|
+
};
|
|
985
|
+
this.logger.info('🔍 DEBUG: Metadata after merge from result:', {
|
|
986
|
+
hasMetadata: !!response.metadata,
|
|
987
|
+
metadataKeys: response.metadata ? Object.keys(response.metadata) : [],
|
|
988
|
+
hasHashLinkBlock: !!(response.metadata as any)?.hashLinkBlock,
|
|
989
|
+
hashLinkBlockContent: (response.metadata as any)?.hashLinkBlock
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
|
|
405
993
|
if (result.requiresForm && result.formMessage) {
|
|
406
994
|
response.formMessage = result.formMessage;
|
|
407
995
|
response.requiresForm = true;
|
|
@@ -409,17 +997,25 @@ export class LangChainAgent extends BaseAgent {
|
|
|
409
997
|
|
|
410
998
|
if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
|
|
411
999
|
const toolCalls = result.intermediateSteps.map(
|
|
412
|
-
(step:
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
output
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
1000
|
+
(step: IntermediateStep, index: number) => {
|
|
1001
|
+
const name = step?.action?.tool || 'unknown';
|
|
1002
|
+
const args = step?.action?.toolInput || {};
|
|
1003
|
+
const obs = step?.observation;
|
|
1004
|
+
let output = '';
|
|
1005
|
+
if (typeof obs === 'string') {
|
|
1006
|
+
output = obs;
|
|
1007
|
+
} else if (obs && typeof obs === 'object') {
|
|
1008
|
+
try {
|
|
1009
|
+
output = JSON.stringify(obs);
|
|
1010
|
+
} catch {
|
|
1011
|
+
output = String(obs);
|
|
1012
|
+
}
|
|
1013
|
+
} else if (obs !== undefined) {
|
|
1014
|
+
output = String(obs);
|
|
1015
|
+
}
|
|
1016
|
+
return { id: `call_${index}`, name, args, output };
|
|
1017
|
+
}
|
|
421
1018
|
);
|
|
422
|
-
|
|
423
1019
|
if (toolCalls.length > 0) {
|
|
424
1020
|
response.tool_calls = toolCalls;
|
|
425
1021
|
}
|
|
@@ -434,13 +1030,20 @@ export class LangChainAgent extends BaseAgent {
|
|
|
434
1030
|
try {
|
|
435
1031
|
const parsed = JSON.parse(parsedSteps);
|
|
436
1032
|
response = { ...response, ...parsed };
|
|
1033
|
+
|
|
1034
|
+
const blockMetadata = this.processHashLinkBlocks(parsed);
|
|
1035
|
+
if (blockMetadata.hashLinkBlock) {
|
|
1036
|
+
response.metadata = {
|
|
1037
|
+
...response.metadata,
|
|
1038
|
+
...blockMetadata,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
437
1041
|
} catch (error) {
|
|
438
1042
|
this.logger.error('Error parsing intermediate steps:', error);
|
|
439
1043
|
}
|
|
440
1044
|
}
|
|
441
1045
|
|
|
442
1046
|
if (response.output) {
|
|
443
|
-
const { AIMessage } = await import('@langchain/core/messages');
|
|
444
1047
|
this.smartMemory.addMessage(new AIMessage(response.output));
|
|
445
1048
|
}
|
|
446
1049
|
|
|
@@ -453,7 +1056,14 @@ export class LangChainAgent extends BaseAgent {
|
|
|
453
1056
|
}
|
|
454
1057
|
|
|
455
1058
|
const finalMemoryStats = this.smartMemory.getMemoryStats();
|
|
1059
|
+
this.logger.info('🔍 DEBUG: Metadata before memoryStats merge:', {
|
|
1060
|
+
hasMetadata: !!response.metadata,
|
|
1061
|
+
metadataKeys: response.metadata ? Object.keys(response.metadata) : [],
|
|
1062
|
+
hasHashLinkBlock: !!(response.metadata as any)?.hashLinkBlock
|
|
1063
|
+
});
|
|
1064
|
+
|
|
456
1065
|
response.metadata = {
|
|
1066
|
+
...preservedMetadata,
|
|
457
1067
|
...response.metadata,
|
|
458
1068
|
memoryStats: {
|
|
459
1069
|
activeMessages: finalMemoryStats.totalActiveMessages,
|
|
@@ -462,6 +1072,19 @@ export class LangChainAgent extends BaseAgent {
|
|
|
462
1072
|
usagePercentage: finalMemoryStats.usagePercentage,
|
|
463
1073
|
},
|
|
464
1074
|
};
|
|
1075
|
+
|
|
1076
|
+
this.logger.info('🔍 DEBUG: Final response metadata before return:', {
|
|
1077
|
+
hasMetadata: !!response.metadata,
|
|
1078
|
+
metadataKeys: response.metadata ? Object.keys(response.metadata) : [],
|
|
1079
|
+
hasHashLinkBlock: !!(response.metadata as any)?.hashLinkBlock,
|
|
1080
|
+
fullMetadata: response.metadata
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
if ((preservedMetadata as any)?.hashLinkBlock && !(response.metadata as any)?.hashLinkBlock) {
|
|
1084
|
+
this.logger.error('❌ CRITICAL: HashLink metadata was lost during processing!');
|
|
1085
|
+
this.logger.error('Original metadata had hashLinkBlock:', (preservedMetadata as any).hashLinkBlock);
|
|
1086
|
+
this.logger.error('Final metadata missing hashLinkBlock');
|
|
1087
|
+
}
|
|
465
1088
|
|
|
466
1089
|
return response;
|
|
467
1090
|
} catch (error) {
|
|
@@ -507,6 +1130,8 @@ export class LangChainAgent extends BaseAgent {
|
|
|
507
1130
|
}
|
|
508
1131
|
|
|
509
1132
|
private async createExecutor(): Promise<void> {
|
|
1133
|
+
const existingPendingForms = this.executor?.getPendingForms() || new Map();
|
|
1134
|
+
|
|
510
1135
|
let llm: BaseChatModel;
|
|
511
1136
|
if (this.config.ai?.provider && this.config.ai.provider.getModel) {
|
|
512
1137
|
llm = this.config.ai.provider.getModel() as BaseChatModel;
|
|
@@ -540,6 +1165,41 @@ export class LangChainAgent extends BaseAgent {
|
|
|
540
1165
|
|
|
541
1166
|
const langchainTools = this.tools as unknown as StructuredTool[];
|
|
542
1167
|
|
|
1168
|
+
const inscriptionTool = this.getInscriptionTool();
|
|
1169
|
+
if (inscriptionTool) {
|
|
1170
|
+
const entry = this.toolRegistry.getTool(inscriptionTool.name);
|
|
1171
|
+
if (entry) {
|
|
1172
|
+
this.logger.info(
|
|
1173
|
+
`🔒 FINAL SECURITY CHECK - ${inscriptionTool.name} tool type: ${entry.tool.constructor.name}`
|
|
1174
|
+
);
|
|
1175
|
+
if (entry.tool.constructor.name !== 'FormValidatingToolWrapper') {
|
|
1176
|
+
this.logger.error(
|
|
1177
|
+
`🚨 SECURITY BREACH: ${inscriptionTool.name} tool is NOT wrapped!`
|
|
1178
|
+
);
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
`Critical security failure: ${inscriptionTool.name} tool bypassed FormValidatingToolWrapper`
|
|
1181
|
+
);
|
|
1182
|
+
} else {
|
|
1183
|
+
this.logger.info(
|
|
1184
|
+
`✅ SECURITY VALIDATED: ${inscriptionTool.name} tool is properly wrapped`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
const stats = this.toolRegistry.getStatistics();
|
|
1191
|
+
this.logger.info('🛡️ TOOL SECURITY REPORT:', {
|
|
1192
|
+
totalTools: stats.totalTools,
|
|
1193
|
+
wrappedTools: stats.wrappedTools,
|
|
1194
|
+
unwrappedTools: stats.unwrappedTools,
|
|
1195
|
+
categories: stats.categoryCounts,
|
|
1196
|
+
priorities: stats.priorityCounts,
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
this.logger.info(
|
|
1200
|
+
`📊 Tool Security Summary: ${stats.wrappedTools} wrapped, ${stats.unwrappedTools} unwrapped`
|
|
1201
|
+
);
|
|
1202
|
+
|
|
543
1203
|
const agent = await createOpenAIToolsAgent({
|
|
544
1204
|
llm,
|
|
545
1205
|
tools: langchainTools,
|
|
@@ -552,6 +1212,13 @@ export class LangChainAgent extends BaseAgent {
|
|
|
552
1212
|
verbose: this.config.debug?.verbose ?? false,
|
|
553
1213
|
returnIntermediateSteps: true,
|
|
554
1214
|
});
|
|
1215
|
+
|
|
1216
|
+
if (existingPendingForms.size > 0) {
|
|
1217
|
+
this.logger.info(
|
|
1218
|
+
`Restoring ${existingPendingForms.size} pending forms to new executor`
|
|
1219
|
+
);
|
|
1220
|
+
this.executor.restorePendingForms(existingPendingForms);
|
|
1221
|
+
}
|
|
555
1222
|
}
|
|
556
1223
|
|
|
557
1224
|
private handleError(error: unknown): ChatResponse {
|
|
@@ -651,8 +1318,17 @@ export class LangChainAgent extends BaseAgent {
|
|
|
651
1318
|
this.mcpManager,
|
|
652
1319
|
serverConfig
|
|
653
1320
|
);
|
|
654
|
-
|
|
1321
|
+
|
|
1322
|
+
this.toolRegistry.registerTool(langchainTool, {
|
|
1323
|
+
metadata: {
|
|
1324
|
+
category: 'mcp',
|
|
1325
|
+
version: '1.0.0',
|
|
1326
|
+
dependencies: [serverConfig.name],
|
|
1327
|
+
},
|
|
1328
|
+
});
|
|
655
1329
|
}
|
|
1330
|
+
|
|
1331
|
+
this.tools = this.toolRegistry.getAllTools();
|
|
656
1332
|
} else {
|
|
657
1333
|
this.logger.error(
|
|
658
1334
|
`Failed to connect to MCP server ${status.serverName}: ${status.error}`
|
|
@@ -687,7 +1363,7 @@ export class LangChainAgent extends BaseAgent {
|
|
|
687
1363
|
/**
|
|
688
1364
|
* Connect to a single MCP server in background with timeout
|
|
689
1365
|
*/
|
|
690
|
-
private connectServerInBackground(serverConfig:
|
|
1366
|
+
private connectServerInBackground(serverConfig: MCPServerConfig): void {
|
|
691
1367
|
const serverName = serverConfig.name;
|
|
692
1368
|
|
|
693
1369
|
setTimeout(async () => {
|
|
@@ -708,9 +1384,18 @@ export class LangChainAgent extends BaseAgent {
|
|
|
708
1384
|
this.mcpManager!,
|
|
709
1385
|
serverConfig
|
|
710
1386
|
);
|
|
711
|
-
|
|
1387
|
+
|
|
1388
|
+
this.toolRegistry.registerTool(langchainTool, {
|
|
1389
|
+
metadata: {
|
|
1390
|
+
category: 'mcp',
|
|
1391
|
+
version: '1.0.0',
|
|
1392
|
+
dependencies: [serverConfig.name],
|
|
1393
|
+
},
|
|
1394
|
+
});
|
|
712
1395
|
}
|
|
713
1396
|
|
|
1397
|
+
this.tools = this.toolRegistry.getAllTools();
|
|
1398
|
+
|
|
714
1399
|
if (this.initialized && this.executor) {
|
|
715
1400
|
this.logger.info(
|
|
716
1401
|
`Recreating executor with ${this.tools.length} total tools`
|
|
@@ -738,6 +1423,60 @@ export class LangChainAgent extends BaseAgent {
|
|
|
738
1423
|
}, 1000);
|
|
739
1424
|
}
|
|
740
1425
|
|
|
1426
|
+
/**
|
|
1427
|
+
* Detects and processes HashLink blocks from tool responses
|
|
1428
|
+
* @param parsedResponse - The parsed JSON response from a tool
|
|
1429
|
+
* @returns Metadata object containing hashLinkBlock if detected
|
|
1430
|
+
*/
|
|
1431
|
+
private processHashLinkBlocks(parsedResponse: unknown): {
|
|
1432
|
+
hashLinkBlock?: Record<string, unknown>;
|
|
1433
|
+
} {
|
|
1434
|
+
try {
|
|
1435
|
+
const responseRecord = parsedResponse as Record<string, unknown>;
|
|
1436
|
+
if (
|
|
1437
|
+
parsedResponse &&
|
|
1438
|
+
typeof parsedResponse === 'object' &&
|
|
1439
|
+
responseRecord.hashLinkBlock &&
|
|
1440
|
+
typeof responseRecord.hashLinkBlock === 'object'
|
|
1441
|
+
) {
|
|
1442
|
+
const block = responseRecord.hashLinkBlock as Record<string, unknown>;
|
|
1443
|
+
|
|
1444
|
+
if (
|
|
1445
|
+
block.blockId &&
|
|
1446
|
+
block.hashLink &&
|
|
1447
|
+
block.template &&
|
|
1448
|
+
block.attributes &&
|
|
1449
|
+
typeof block.blockId === 'string' &&
|
|
1450
|
+
typeof block.hashLink === 'string' &&
|
|
1451
|
+
typeof block.template === 'string' &&
|
|
1452
|
+
typeof block.attributes === 'object'
|
|
1453
|
+
) {
|
|
1454
|
+
this.logger.info('HashLink block detected:', {
|
|
1455
|
+
blockId: block.blockId,
|
|
1456
|
+
hashLink: block.hashLink,
|
|
1457
|
+
template: block.template,
|
|
1458
|
+
attributeKeys: Object.keys(block.attributes),
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
return {
|
|
1462
|
+
hashLinkBlock: {
|
|
1463
|
+
blockId: block.blockId,
|
|
1464
|
+
hashLink: block.hashLink,
|
|
1465
|
+
template: block.template,
|
|
1466
|
+
attributes: block.attributes,
|
|
1467
|
+
},
|
|
1468
|
+
};
|
|
1469
|
+
} else {
|
|
1470
|
+
this.logger.warn('Invalid HashLink block structure detected:', block);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
this.logger.error('Error processing HashLink blocks:', error);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
return {};
|
|
1478
|
+
}
|
|
1479
|
+
|
|
741
1480
|
/**
|
|
742
1481
|
* Check if a string is valid JSON
|
|
743
1482
|
*/
|