@hashgraphonline/conversational-agent 0.1.214 → 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/cli/dist/CLIApp.d.ts +9 -0
- package/cli/dist/CLIApp.js +127 -0
- package/cli/dist/LocalConversationalAgent.d.ts +37 -0
- package/cli/dist/LocalConversationalAgent.js +58 -0
- package/cli/dist/app.d.ts +16 -0
- package/cli/dist/app.js +13 -0
- package/cli/dist/cli.d.ts +2 -0
- package/cli/dist/cli.js +51 -0
- package/cli/dist/components/AppContainer.d.ts +16 -0
- package/cli/dist/components/AppContainer.js +24 -0
- package/cli/dist/components/AppScreens.d.ts +2 -0
- package/cli/dist/components/AppScreens.js +259 -0
- package/cli/dist/components/ChatScreen.d.ts +15 -0
- package/cli/dist/components/ChatScreen.js +39 -0
- package/cli/dist/components/DebugLoadingScreen.d.ts +5 -0
- package/cli/dist/components/DebugLoadingScreen.js +31 -0
- package/cli/dist/components/LoadingScreen.d.ts +2 -0
- package/cli/dist/components/LoadingScreen.js +16 -0
- package/cli/dist/components/LoadingScreenDebug.d.ts +5 -0
- package/cli/dist/components/LoadingScreenDebug.js +27 -0
- package/cli/dist/components/MCPConfigScreen.d.ts +28 -0
- package/cli/dist/components/MCPConfigScreen.js +168 -0
- package/cli/dist/components/ScreenRouter.d.ts +12 -0
- package/cli/dist/components/ScreenRouter.js +22 -0
- package/cli/dist/components/SetupScreen.d.ts +15 -0
- package/cli/dist/components/SetupScreen.js +65 -0
- package/cli/dist/components/SingleLoadingScreen.d.ts +5 -0
- package/cli/dist/components/SingleLoadingScreen.js +27 -0
- package/cli/dist/components/StatusBadge.d.ts +7 -0
- package/cli/dist/components/StatusBadge.js +28 -0
- package/cli/dist/components/TerminalWindow.d.ts +8 -0
- package/cli/dist/components/TerminalWindow.js +24 -0
- package/cli/dist/components/WelcomeScreen.d.ts +11 -0
- package/cli/dist/components/WelcomeScreen.js +47 -0
- package/cli/dist/context/AppContext.d.ts +68 -0
- package/cli/dist/context/AppContext.js +363 -0
- package/cli/dist/hooks/useInitializeAgent.d.ts +19 -0
- package/cli/dist/hooks/useInitializeAgent.js +28 -0
- package/cli/dist/hooks/useStableState.d.ts +38 -0
- package/cli/dist/hooks/useStableState.js +68 -0
- package/cli/dist/managers/AgentManager.d.ts +57 -0
- package/cli/dist/managers/AgentManager.js +119 -0
- package/cli/dist/managers/ConfigManager.d.ts +53 -0
- package/cli/dist/managers/ConfigManager.js +173 -0
- package/cli/dist/types.d.ts +31 -0
- package/cli/dist/types.js +19 -0
- package/dist/cjs/base-agent.d.ts +2 -0
- 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/field-type-registry.d.ts +51 -0
- package/dist/cjs/forms/form-generator.d.ts +123 -0
- package/dist/cjs/forms/index.d.ts +2 -0
- package/dist/cjs/forms/types.d.ts +108 -0
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/langchain/FormAwareAgentExecutor.d.ts +108 -0
- package/dist/cjs/langchain/FormValidatingToolWrapper.d.ts +81 -0
- package/dist/cjs/langchain-agent.d.ts +65 -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 +8 -1
- package/dist/esm/index.js.map +1 -1
- 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 +609 -36
- package/dist/esm/index18.js.map +1 -1
- package/dist/esm/index19.js +229 -84
- package/dist/esm/index19.js.map +1 -1
- package/dist/esm/index20.js +111 -17
- package/dist/esm/index20.js.map +1 -1
- package/dist/esm/index21.js +44 -7
- package/dist/esm/index21.js.map +1 -1
- package/dist/esm/index22.js +86 -157
- package/dist/esm/index22.js.map +1 -1
- package/dist/esm/index23.js +32 -150
- package/dist/esm/index23.js.map +1 -1
- package/dist/esm/index24.js +746 -80
- package/dist/esm/index24.js.map +1 -1
- package/dist/esm/index25.js +154 -45
- package/dist/esm/index25.js.map +1 -1
- package/dist/esm/index26.js +149 -24
- package/dist/esm/index26.js.map +1 -1
- package/dist/esm/index27.js +196 -217
- package/dist/esm/index27.js.map +1 -1
- package/dist/esm/index28.js +187 -0
- package/dist/esm/index28.js.map +1 -0
- package/dist/esm/index29.js +308 -0
- package/dist/esm/index29.js.map +1 -0
- package/dist/esm/index30.js +159 -0
- package/dist/esm/index30.js.map +1 -0
- package/dist/esm/index31.js +68 -0
- package/dist/esm/index31.js.map +1 -0
- package/dist/esm/index32.js +30 -0
- package/dist/esm/index32.js.map +1 -0
- 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 +2 -2
- package/dist/esm/index5.js.map +1 -1
- package/dist/esm/index6.js +68 -25
- package/dist/esm/index6.js.map +1 -1
- package/dist/esm/index7.js.map +1 -1
- package/dist/esm/index8.js +744 -70
- package/dist/esm/index8.js.map +1 -1
- package/dist/types/base-agent.d.ts +2 -0
- 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/field-type-registry.d.ts +51 -0
- package/dist/types/forms/form-generator.d.ts +123 -0
- package/dist/types/forms/index.d.ts +2 -0
- package/dist/types/forms/types.d.ts +108 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/langchain/FormAwareAgentExecutor.d.ts +108 -0
- package/dist/types/langchain/FormValidatingToolWrapper.d.ts +81 -0
- package/dist/types/langchain-agent.d.ts +65 -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 +35 -34
- package/src/base-agent.ts +2 -0
- package/src/config/system-message.ts +14 -0
- package/src/context/ReferenceContextManager.ts +1 -1
- package/src/conversational-agent.ts +95 -38
- 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 +203 -0
- package/src/forms/form-generator.ts +841 -0
- package/src/forms/index.ts +2 -0
- package/src/forms/types.ts +125 -0
- package/src/index.ts +9 -0
- package/src/langchain/FormAwareAgentExecutor.ts +971 -0
- package/src/langchain/FormValidatingToolWrapper.ts +355 -0
- package/src/langchain-agent.ts +1034 -87
- 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 +103 -0
- package/src/scripts/test-hedera-kit-wrapper.ts +265 -0
- package/src/scripts/test-inscribe-form-generation.ts +494 -0
- package/src/scripts/test-inscribe-wrapper-verification.ts +220 -0
- 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/cli/readme.md +0 -181
- package/dist/cjs/langchain/ContentAwareAgentExecutor.d.ts +0 -14
- package/dist/types/langchain/ContentAwareAgentExecutor.d.ts +0 -14
- package/src/langchain/ContentAwareAgentExecutor.ts +0 -19
package/src/langchain-agent.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 {
|
|
4
|
+
import { FormAwareAgentExecutor } from './langchain/FormAwareAgentExecutor';
|
|
5
5
|
import {
|
|
6
6
|
ChatPromptTemplate,
|
|
7
7
|
MessagesPlaceholder,
|
|
@@ -24,16 +24,559 @@ import {
|
|
|
24
24
|
import { MCPClientManager } from './mcp/MCPClientManager';
|
|
25
25
|
import { convertMCPToolToLangChain } from './mcp/adapters/langchain';
|
|
26
26
|
import { SmartMemoryManager } from './memory/SmartMemoryManager';
|
|
27
|
-
import type { MCPConnectionStatus } from './mcp/types';
|
|
27
|
+
import type { MCPConnectionStatus, MCPServerConfig } from './mcp/types';
|
|
28
|
+
import { ResponseFormatter } from './utils/ResponseFormatter';
|
|
29
|
+
import type { FormSubmission } from './forms/types';
|
|
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
|
+
}
|
|
28
74
|
|
|
29
75
|
export class LangChainAgent extends BaseAgent {
|
|
30
|
-
private executor:
|
|
76
|
+
private executor: FormAwareAgentExecutor | undefined;
|
|
31
77
|
private systemMessage = '';
|
|
32
78
|
private mcpManager?: MCPClientManager;
|
|
33
79
|
private smartMemory: SmartMemoryManager | undefined;
|
|
34
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
|
+
}
|
|
35
576
|
|
|
36
577
|
async boot(): Promise<void> {
|
|
578
|
+
this.logger.info('🚨🚨🚨 LANGCHAIN AGENT BOOT METHOD CALLED 🚨🚨🚨');
|
|
579
|
+
|
|
37
580
|
if (this.initialized) {
|
|
38
581
|
this.logger.warn('Agent already initialized');
|
|
39
582
|
return;
|
|
@@ -49,8 +592,100 @@ export class LangChainAgent extends BaseAgent {
|
|
|
49
592
|
'gpt-4o-mini';
|
|
50
593
|
this.tokenTracker = new TokenUsageCallbackHandler(modelName);
|
|
51
594
|
|
|
595
|
+
this.toolRegistry = new ToolRegistry(this.logger);
|
|
596
|
+
|
|
52
597
|
const allTools = this.agentKit.getAggregatedLangChainTools();
|
|
53
|
-
this.
|
|
598
|
+
this.logger.info('=== TOOL REGISTRATION START ===');
|
|
599
|
+
this.logger.info(
|
|
600
|
+
'All tools from agentKit:',
|
|
601
|
+
allTools.map((t) => t.name)
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
const filteredTools = this.filterTools(allTools);
|
|
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:`, {
|
|
632
|
+
hasSchema: !!tool.schema,
|
|
633
|
+
schemaType: tool.schema?.constructor?.name,
|
|
634
|
+
hasRenderConfig: !!(tool.schema as RenderConfigSchema)
|
|
635
|
+
?._renderConfig,
|
|
636
|
+
renderConfig: (tool.schema as RenderConfigSchema)?._renderConfig,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
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
|
+
);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
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
|
+
}
|
|
54
689
|
|
|
55
690
|
if (this.config.mcp?.servers && this.config.mcp.servers.length > 0) {
|
|
56
691
|
if (this.config.mcp.autoConnect !== false) {
|
|
@@ -77,6 +712,15 @@ export class LangChainAgent extends BaseAgent {
|
|
|
77
712
|
reserveTokens: 10000,
|
|
78
713
|
});
|
|
79
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
|
+
|
|
80
724
|
this.systemMessage = this.buildSystemPrompt();
|
|
81
725
|
|
|
82
726
|
this.smartMemory.setSystemPrompt(this.systemMessage);
|
|
@@ -84,7 +728,7 @@ export class LangChainAgent extends BaseAgent {
|
|
|
84
728
|
await this.createExecutor();
|
|
85
729
|
|
|
86
730
|
this.initialized = true;
|
|
87
|
-
this.logger.info('LangChain Hedera agent initialized');
|
|
731
|
+
this.logger.info('LangChain Hedera agent initialized with ToolRegistry');
|
|
88
732
|
} catch (error) {
|
|
89
733
|
this.logger.error('Failed to initialize agent:', error);
|
|
90
734
|
throw error;
|
|
@@ -100,20 +744,35 @@ export class LangChainAgent extends BaseAgent {
|
|
|
100
744
|
}
|
|
101
745
|
|
|
102
746
|
try {
|
|
103
|
-
this.
|
|
747
|
+
const toolExecutionResult = await this.handleToolExecution(
|
|
104
748
|
message,
|
|
105
|
-
|
|
106
|
-
|
|
749
|
+
context
|
|
750
|
+
);
|
|
751
|
+
if (toolExecutionResult) {
|
|
752
|
+
return toolExecutionResult;
|
|
753
|
+
}
|
|
107
754
|
|
|
108
|
-
|
|
109
|
-
|
|
755
|
+
const directToolResult = await this.handleDirectToolExecution(message);
|
|
756
|
+
if (directToolResult) {
|
|
757
|
+
return directToolResult;
|
|
758
|
+
}
|
|
110
759
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
760
|
+
const jsonToolResult = await this.handleJsonToolCalls(message, context);
|
|
761
|
+
if (jsonToolResult) {
|
|
762
|
+
return jsonToolResult;
|
|
114
763
|
}
|
|
115
764
|
|
|
116
|
-
const
|
|
765
|
+
const contentRefResult = await this.handleContentRefMessages(message);
|
|
766
|
+
if (contentRefResult) {
|
|
767
|
+
return contentRefResult;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
this.logger.info('LangChainAgent.chat called with:', {
|
|
771
|
+
message,
|
|
772
|
+
contextLength: context?.messages?.length || 0,
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
this.loadContextMessages(context);
|
|
117
776
|
this.smartMemory.addMessage(new HumanMessage(message));
|
|
118
777
|
|
|
119
778
|
const memoryStats = this.smartMemory.getMemoryStats();
|
|
@@ -132,75 +791,7 @@ export class LangChainAgent extends BaseAgent {
|
|
|
132
791
|
|
|
133
792
|
this.logger.info('LangChainAgent executor result:', result);
|
|
134
793
|
|
|
135
|
-
|
|
136
|
-
output: result.output || '',
|
|
137
|
-
message: result.output || '',
|
|
138
|
-
notes: [],
|
|
139
|
-
intermediateSteps: result.intermediateSteps,
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
|
|
143
|
-
const toolCalls = result.intermediateSteps.map(
|
|
144
|
-
(step: any, index: number) => ({
|
|
145
|
-
id: `call_${index}`,
|
|
146
|
-
name: step.action?.tool || 'unknown',
|
|
147
|
-
args: step.action?.toolInput || {},
|
|
148
|
-
output:
|
|
149
|
-
typeof step.observation === 'string'
|
|
150
|
-
? step.observation
|
|
151
|
-
: JSON.stringify(step.observation),
|
|
152
|
-
})
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
if (toolCalls.length > 0) {
|
|
156
|
-
response.tool_calls = toolCalls;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const parsedSteps = result?.intermediateSteps?.[0]?.observation;
|
|
161
|
-
if (
|
|
162
|
-
parsedSteps &&
|
|
163
|
-
typeof parsedSteps === 'string' &&
|
|
164
|
-
this.isJSON(parsedSteps)
|
|
165
|
-
) {
|
|
166
|
-
try {
|
|
167
|
-
const parsed = JSON.parse(parsedSteps);
|
|
168
|
-
response = { ...response, ...parsed };
|
|
169
|
-
} catch (error) {
|
|
170
|
-
this.logger.error('Error parsing intermediate steps:', error);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (!response.output || response.output.trim() === '') {
|
|
175
|
-
response.output = 'Agent action complete.';
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (response.output) {
|
|
179
|
-
const { AIMessage } = await import('@langchain/core/messages');
|
|
180
|
-
this.smartMemory.addMessage(new AIMessage(response.output));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (this.tokenTracker) {
|
|
184
|
-
const tokenUsage = this.tokenTracker.getLatestTokenUsage();
|
|
185
|
-
if (tokenUsage) {
|
|
186
|
-
response.tokenUsage = tokenUsage;
|
|
187
|
-
response.cost = calculateTokenCostSync(tokenUsage);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const finalMemoryStats = this.smartMemory.getMemoryStats();
|
|
192
|
-
response.metadata = {
|
|
193
|
-
...response.metadata,
|
|
194
|
-
memoryStats: {
|
|
195
|
-
activeMessages: finalMemoryStats.totalActiveMessages,
|
|
196
|
-
tokenUsage: finalMemoryStats.currentTokenCount,
|
|
197
|
-
maxTokens: finalMemoryStats.maxTokens,
|
|
198
|
-
usagePercentage: finalMemoryStats.usagePercentage,
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
this.logger.info('LangChainAgent.chat returning response:', response);
|
|
203
|
-
return response;
|
|
794
|
+
return this.processExecutorResult(result);
|
|
204
795
|
} catch (error) {
|
|
205
796
|
this.logger.error('LangChainAgent.chat error:', error);
|
|
206
797
|
return this.handleError(error);
|
|
@@ -217,6 +808,10 @@ export class LangChainAgent extends BaseAgent {
|
|
|
217
808
|
this.smartMemory = undefined;
|
|
218
809
|
}
|
|
219
810
|
|
|
811
|
+
if (this.toolRegistry) {
|
|
812
|
+
this.toolRegistry.clear();
|
|
813
|
+
}
|
|
814
|
+
|
|
220
815
|
this.executor = undefined;
|
|
221
816
|
this.agentKit = undefined;
|
|
222
817
|
this.tools = [];
|
|
@@ -276,6 +871,242 @@ export class LangChainAgent extends BaseAgent {
|
|
|
276
871
|
return new Map(this.mcpConnectionStatus);
|
|
277
872
|
}
|
|
278
873
|
|
|
874
|
+
/**
|
|
875
|
+
* Processes form submission and continues with tool execution
|
|
876
|
+
*/
|
|
877
|
+
async processFormSubmission(
|
|
878
|
+
submission: FormSubmission,
|
|
879
|
+
context?: ConversationContext
|
|
880
|
+
): Promise<ChatResponse> {
|
|
881
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission START');
|
|
882
|
+
|
|
883
|
+
if (!this.initialized || !this.executor || !this.smartMemory) {
|
|
884
|
+
this.logger.error('🔥 LangChainAgent.processFormSubmission - Agent not initialized');
|
|
885
|
+
throw new Error('Agent not initialized. Call boot() first.');
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - After initialization check');
|
|
889
|
+
|
|
890
|
+
try {
|
|
891
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - About to log submission info');
|
|
892
|
+
|
|
893
|
+
this.logger.info('Processing form submission:', {
|
|
894
|
+
formId: submission.formId,
|
|
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,
|
|
902
|
+
});
|
|
903
|
+
|
|
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
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
this.logger.info('🔥 LangChainAgent.processFormSubmission - Parameters validated');
|
|
918
|
+
|
|
919
|
+
this.loadContextMessages(context);
|
|
920
|
+
|
|
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
|
+
}
|
|
972
|
+
|
|
973
|
+
let response: ChatResponse = {
|
|
974
|
+
output: outputMessage,
|
|
975
|
+
message: outputMessage,
|
|
976
|
+
notes: [],
|
|
977
|
+
intermediateSteps: result.intermediateSteps as IntermediateStep[],
|
|
978
|
+
};
|
|
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
|
+
|
|
993
|
+
if (result.requiresForm && result.formMessage) {
|
|
994
|
+
response.formMessage = result.formMessage;
|
|
995
|
+
response.requiresForm = true;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
|
|
999
|
+
const toolCalls = result.intermediateSteps.map(
|
|
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
|
+
}
|
|
1018
|
+
);
|
|
1019
|
+
if (toolCalls.length > 0) {
|
|
1020
|
+
response.tool_calls = toolCalls;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const parsedSteps = result?.intermediateSteps?.[0]?.observation;
|
|
1025
|
+
if (
|
|
1026
|
+
parsedSteps &&
|
|
1027
|
+
typeof parsedSteps === 'string' &&
|
|
1028
|
+
this.isJSON(parsedSteps)
|
|
1029
|
+
) {
|
|
1030
|
+
try {
|
|
1031
|
+
const parsed = JSON.parse(parsedSteps);
|
|
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
|
+
}
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
this.logger.error('Error parsing intermediate steps:', error);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (response.output) {
|
|
1047
|
+
this.smartMemory.addMessage(new AIMessage(response.output));
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (this.tokenTracker) {
|
|
1051
|
+
const tokenUsage = this.tokenTracker.getLatestTokenUsage();
|
|
1052
|
+
if (tokenUsage) {
|
|
1053
|
+
response.tokenUsage = tokenUsage;
|
|
1054
|
+
response.cost = calculateTokenCostSync(tokenUsage);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
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
|
+
|
|
1065
|
+
response.metadata = {
|
|
1066
|
+
...preservedMetadata,
|
|
1067
|
+
...response.metadata,
|
|
1068
|
+
memoryStats: {
|
|
1069
|
+
activeMessages: finalMemoryStats.totalActiveMessages,
|
|
1070
|
+
tokenUsage: finalMemoryStats.currentTokenCount,
|
|
1071
|
+
maxTokens: finalMemoryStats.maxTokens,
|
|
1072
|
+
usagePercentage: finalMemoryStats.usagePercentage,
|
|
1073
|
+
},
|
|
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
|
+
}
|
|
1088
|
+
|
|
1089
|
+
return response;
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
this.logger.error('Form submission processing error:', error);
|
|
1092
|
+
return this.handleError(error);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Check if the agent has pending forms that need to be completed
|
|
1098
|
+
*/
|
|
1099
|
+
hasPendingForms(): boolean {
|
|
1100
|
+
return this.executor ? this.executor.hasPendingForms() : false;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Get information about pending forms
|
|
1105
|
+
*/
|
|
1106
|
+
getPendingFormsInfo(): Array<{ formId: string; toolName: string }> {
|
|
1107
|
+
return this.executor ? this.executor.getPendingFormsInfo() : [];
|
|
1108
|
+
}
|
|
1109
|
+
|
|
279
1110
|
private async createAgentKit(): Promise<HederaAgentKit> {
|
|
280
1111
|
const corePlugins = getAllHederaCorePlugins();
|
|
281
1112
|
const extensionPlugins = this.config.extensions?.plugins || [];
|
|
@@ -299,6 +1130,8 @@ export class LangChainAgent extends BaseAgent {
|
|
|
299
1130
|
}
|
|
300
1131
|
|
|
301
1132
|
private async createExecutor(): Promise<void> {
|
|
1133
|
+
const existingPendingForms = this.executor?.getPendingForms() || new Map();
|
|
1134
|
+
|
|
302
1135
|
let llm: BaseChatModel;
|
|
303
1136
|
if (this.config.ai?.provider && this.config.ai.provider.getModel) {
|
|
304
1137
|
llm = this.config.ai.provider.getModel() as BaseChatModel;
|
|
@@ -332,18 +1165,60 @@ export class LangChainAgent extends BaseAgent {
|
|
|
332
1165
|
|
|
333
1166
|
const langchainTools = this.tools as unknown as StructuredTool[];
|
|
334
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
|
+
|
|
335
1203
|
const agent = await createOpenAIToolsAgent({
|
|
336
1204
|
llm,
|
|
337
1205
|
tools: langchainTools,
|
|
338
1206
|
prompt,
|
|
339
1207
|
});
|
|
340
1208
|
|
|
341
|
-
this.executor = new
|
|
1209
|
+
this.executor = new FormAwareAgentExecutor({
|
|
342
1210
|
agent,
|
|
343
1211
|
tools: langchainTools,
|
|
344
1212
|
verbose: this.config.debug?.verbose ?? false,
|
|
345
1213
|
returnIntermediateSteps: true,
|
|
346
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
|
+
}
|
|
347
1222
|
}
|
|
348
1223
|
|
|
349
1224
|
private handleError(error: unknown): ChatResponse {
|
|
@@ -443,8 +1318,17 @@ export class LangChainAgent extends BaseAgent {
|
|
|
443
1318
|
this.mcpManager,
|
|
444
1319
|
serverConfig
|
|
445
1320
|
);
|
|
446
|
-
|
|
1321
|
+
|
|
1322
|
+
this.toolRegistry.registerTool(langchainTool, {
|
|
1323
|
+
metadata: {
|
|
1324
|
+
category: 'mcp',
|
|
1325
|
+
version: '1.0.0',
|
|
1326
|
+
dependencies: [serverConfig.name],
|
|
1327
|
+
},
|
|
1328
|
+
});
|
|
447
1329
|
}
|
|
1330
|
+
|
|
1331
|
+
this.tools = this.toolRegistry.getAllTools();
|
|
448
1332
|
} else {
|
|
449
1333
|
this.logger.error(
|
|
450
1334
|
`Failed to connect to MCP server ${status.serverName}: ${status.error}`
|
|
@@ -479,7 +1363,7 @@ export class LangChainAgent extends BaseAgent {
|
|
|
479
1363
|
/**
|
|
480
1364
|
* Connect to a single MCP server in background with timeout
|
|
481
1365
|
*/
|
|
482
|
-
private connectServerInBackground(serverConfig:
|
|
1366
|
+
private connectServerInBackground(serverConfig: MCPServerConfig): void {
|
|
483
1367
|
const serverName = serverConfig.name;
|
|
484
1368
|
|
|
485
1369
|
setTimeout(async () => {
|
|
@@ -500,9 +1384,18 @@ export class LangChainAgent extends BaseAgent {
|
|
|
500
1384
|
this.mcpManager!,
|
|
501
1385
|
serverConfig
|
|
502
1386
|
);
|
|
503
|
-
|
|
1387
|
+
|
|
1388
|
+
this.toolRegistry.registerTool(langchainTool, {
|
|
1389
|
+
metadata: {
|
|
1390
|
+
category: 'mcp',
|
|
1391
|
+
version: '1.0.0',
|
|
1392
|
+
dependencies: [serverConfig.name],
|
|
1393
|
+
},
|
|
1394
|
+
});
|
|
504
1395
|
}
|
|
505
1396
|
|
|
1397
|
+
this.tools = this.toolRegistry.getAllTools();
|
|
1398
|
+
|
|
506
1399
|
if (this.initialized && this.executor) {
|
|
507
1400
|
this.logger.info(
|
|
508
1401
|
`Recreating executor with ${this.tools.length} total tools`
|
|
@@ -530,6 +1423,60 @@ export class LangChainAgent extends BaseAgent {
|
|
|
530
1423
|
}, 1000);
|
|
531
1424
|
}
|
|
532
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
|
+
|
|
533
1480
|
/**
|
|
534
1481
|
* Check if a string is valid JSON
|
|
535
1482
|
*/
|