UNPKG

@hashgraphonline/conversational-agent

Version:

Hashgraph Online conversational AI agent implementing HCS-10 communication, HCS-2 registries, and content inscription on Hedera. https://hol.org

967 lines (961 loc) 33.2 kB
import { AgentExecutor } from "langchain/agents"; import { z, ZodError } from "zod"; import { FormGenerator } from "./index11.js"; import { FormEngine } from "./index12.js"; import { Logger } from "@hashgraphonline/standards-sdk"; import { isFormValidatable } from "@hashgraphonline/standards-agent-kit"; import { ResponseFormatter } from "./index36.js"; const globalPendingForms = /* @__PURE__ */ new Map(); class FormAwareAgentExecutor extends AgentExecutor { constructor(...args) { super(...args); this.pendingForms = /* @__PURE__ */ new Map(); this.formGenerator = new FormGenerator(); this.formEngine = new FormEngine( new Logger({ module: "FormAwareAgentExecutor.FormEngine" }) ); this.formLogger = new Logger({ module: "FormAwareAgentExecutor" }); this.parameterPreprocessingCallback = void 0; } /** * Type guard to check if a Zod type is a ZodObject */ isZodObject(schema) { return schema instanceof z.ZodObject; } /** * Type guard to check if metadata has hashLinkBlock */ hasHashLinkBlock(metadata) { return typeof metadata === "object" && metadata !== null && "hashLinkBlock" in metadata && typeof metadata.hashLinkBlock === "object" && metadata.hashLinkBlock !== null; } /** * Set parameter preprocessing callback */ setParameterPreprocessingCallback(callback) { this.parameterPreprocessingCallback = callback; } /** * BULLETPROOF TOOL INTERCEPTION * Override the single-step execution to intercept tool calls BEFORE LangChain processes them */ async _takeNextStep(nameToolMap, inputs, intermediateSteps, runManager, config) { this.formLogger.info("🛡️ BULLETPROOF INTERCEPTION: _takeNextStep called", { availableTools: Object.keys(nameToolMap), inputKeys: Object.keys(inputs) }); const result = await this.agent.plan( intermediateSteps, inputs, runManager?.getChild() ); if ("returnValues" in result) { this.formLogger.info("Agent returned finish action, passing through"); return result; } const action = result; const toolName = action.tool; const toolInput = action.toolInput; this.formLogger.info(`🎯 INTERCEPTING TOOL CALL: ${toolName}`, { toolInput, hasInNameToolMap: toolName in nameToolMap, toolInputKeys: Object.keys(toolInput || {}) }); const tool = nameToolMap[toolName] || this.tools.find((t) => t.name === toolName); if (!tool) { this.formLogger.error(`Tool ${toolName} not found in registry`); throw new Error(`Tool "${toolName}" not found`); } let shouldGenerateForm = false; if (isFormValidatable(tool)) { this.formLogger.info( `🔍 Tool ${toolName} implements FormValidatable, checking shouldGenerateForm()`, { toolInput } ); try { shouldGenerateForm = tool.shouldGenerateForm(toolInput); this.formLogger.info( `FormValidatable.shouldGenerateForm() result: ${shouldGenerateForm}`, { toolName, toolInput } ); } catch (error) { this.formLogger.error( `Error calling shouldGenerateForm() on ${toolName}:`, error ); shouldGenerateForm = false; } } if (shouldGenerateForm) { this.formLogger.info(`🚨 FORM GENERATION TRIGGERED for ${toolName}`); try { let schemaToUse; let isFocusedSchema = false; if (isFormValidatable(tool)) { this.formLogger.info( `🎯 Tool ${toolName} is FormValidatable, attempting to get focused schema` ); try { const focusedSchema = tool.getFormSchema(); if (focusedSchema) { schemaToUse = focusedSchema; isFocusedSchema = true; this.formLogger.info( `✅ Successfully obtained focused schema for ${toolName}` ); } else { this.formLogger.warn( `getFormSchema() returned null/undefined for ${toolName}, using default schema` ); schemaToUse = tool.schema; isFocusedSchema = false; } } catch (error) { this.formLogger.error( `Failed to get focused schema from ${toolName}:`, error ); this.formLogger.info( `Falling back to default schema for ${toolName}` ); schemaToUse = tool.schema; isFocusedSchema = false; } } else { this.formLogger.info( `Tool ${toolName} is not FormValidatable, using default schema` ); schemaToUse = tool.schema; isFocusedSchema = false; } let schemaFieldCount = "unknown"; try { if (this.isZodObject(schemaToUse)) { const zodObject = schemaToUse; const shape = zodObject.shape; if (shape && typeof shape === "object") { schemaFieldCount = Object.keys(shape).length.toString(); } } } catch { } this.formLogger.info( `📋 Generating form with ${isFocusedSchema ? "FOCUSED" : "DEFAULT"} schema`, { toolName, schemaType: schemaToUse?.constructor?.name, estimatedFieldCount: schemaFieldCount, isFocusedSchema } ); let missingFields; if (isFocusedSchema) { this.formLogger.info( `⭐ Using focused schema - letting FormGenerator determine fields from schema` ); missingFields = void 0; } else { missingFields = /* @__PURE__ */ new Set(); if (this.isZodObject(schemaToUse)) { const zodObject = schemaToUse; const shape = zodObject.shape || {}; for (const fieldName of Object.keys(shape)) { const value = (toolInput || {})[fieldName]; const isEmpty = isFormValidatable(tool) && tool.isFieldEmpty ? tool.isFieldEmpty(fieldName, value) : value === void 0 || value === "" || value === null || Array.isArray(value) && value.length === 0; const isRequired = this.isFieldRequired(schemaToUse, fieldName); const isEssential = isFormValidatable(tool) && tool.getEssentialFields ? tool.getEssentialFields().includes(fieldName) : false; this.formLogger.info(`🔍 Field analysis: ${fieldName}`, { value, isEmpty, isRequired, isEssential, willAddToMissingFields: isEmpty && (isRequired || isEssential) }); if (isEmpty && (isRequired || isEssential)) { missingFields.add(fieldName); } } } this.formLogger.info(`📋 Missing fields analysis complete`, { totalFields: this.isZodObject(schemaToUse) ? Object.keys(schemaToUse.shape).length : 0, missingFieldsCount: missingFields.size, missingFields: Array.from(missingFields) }); } const formMessage = await this.formGenerator.generateFormFromSchema( schemaToUse, toolInput, { toolName, toolDescription: tool.description }, missingFields ); if (this.isZodObject(schemaToUse)) { try { const { jsonSchema, uiSchema } = this.formGenerator.generateJsonSchemaForm( schemaToUse, toolInput, missingFields ); formMessage.jsonSchema = jsonSchema; formMessage.uiSchema = uiSchema; } catch (error) { this.formLogger.warn( "Failed to generate JSON Schema for RJSF:", error ); } } formMessage.partialInput = toolInput; const formData = { toolName, originalInput: inputs, originalToolInput: toolInput, schema: schemaToUse, toolRef: tool, originalToolRef: tool.originalTool }; this.pendingForms.set(formMessage.id, formData); globalPendingForms.set(formMessage.id, formData); this.formLogger.info(`✅ FORM INTERCEPT SUCCESS for ${toolName}`); const formResult = { requiresForm: true, formMessage }; return [ { action, observation: JSON.stringify(formResult) } ]; } catch (error) { this.formLogger.error(`Form generation failed for ${toolName}:`, error); } } this.formLogger.info( `⚪ Passing through to normal tool execution for ${toolName}` ); if (this.parameterPreprocessingCallback && toolInput) { this.formLogger.info( `🔄 Applying parameter preprocessing for ${toolName}` ); try { const preprocessedInput = await this.parameterPreprocessingCallback( toolName, toolInput ); if (preprocessedInput && typeof preprocessedInput === "object" && "__requestForm" in preprocessedInput) { const rf = preprocessedInput.__requestForm; const formId = rf.id || `form_${Date.now()}_${Math.random().toString(36).slice(2)}`; const formMessage = { type: "form", id: formId, originalPrompt: "Parameter validation required", toolName, formConfig: { title: rf.title || "Complete required parameters", description: rf.description || "One or more parameters require confirmation. Please review and submit.", submitLabel: rf.submitLabel || "Continue", fields: (rf.fields || []).map((f) => { const allowedTypes = [ "text", "number", "select", "checkbox", "textarea" ]; const resolvedType = allowedTypes.includes( f.type ) ? f.type : "text"; return { name: f.name, label: f.label, type: resolvedType, required: f.required ?? true, options: f.options }; }) } }; const resolvedSchema = isFormValidatable(tool) ? (() => { try { const s = tool.getFormSchema(); return s || tool.schema; } catch { return tool.schema; } })() : tool.schema; this.pendingForms.set(formId, { toolName, originalInput: inputs, originalToolInput: toolInput, schema: resolvedSchema, toolRef: tool, originalToolRef: tool.originalTool }); globalPendingForms.set(formId, { toolName, originalInput: inputs, originalToolInput: toolInput, schema: resolvedSchema }); return [ { action, observation: JSON.stringify({ requiresForm: true, formMessage }) } ]; } if (JSON.stringify(preprocessedInput) !== JSON.stringify(toolInput)) { this.formLogger.info(`📝 Parameters preprocessed for ${toolName}:`, { original: Object.keys(toolInput), preprocessed: Object.keys(preprocessedInput), hasChanges: true }); try { action.toolInput = preprocessedInput; } catch { } } else { this.formLogger.debug(`No parameter changes needed for ${toolName}`); } } catch (preprocessError) { this.formLogger.warn( `Parameter preprocessing failed for ${toolName}, using original parameters:`, preprocessError ); } } return super._takeNextStep( nameToolMap, inputs, intermediateSteps, runManager, config ); } /** * Helper to determine if a field is required in the schema */ isFieldRequired(schema, fieldPath) { if (!schema || !fieldPath) { return false; } try { const obj = schema; const def = obj._def; if (!def || def.typeName !== "ZodObject") { return false; } const rawShape = typeof def.shape === "function" ? def.shape() : def.shape; if (!rawShape || typeof rawShape !== "object") { return false; } const shape = rawShape; const fieldSchema = shape[fieldPath]; if (!fieldSchema) { return false; } const unwrapOptional = (s) => { const inner = s._def; if (inner && inner.typeName === "ZodOptional" && inner.innerType) { return inner.innerType; } return s; }; const unwrapped = unwrapOptional(fieldSchema); const fdef = unwrapped._def; if (!fdef) { return true; } if (fdef.typeName === "ZodOptional" || fdef.typeName === "ZodDefault") { return false; } if (fdef.defaultValue !== void 0) { return false; } return true; } catch (error) { this.formLogger.debug( `Could not determine if field ${fieldPath} is required:`, error ); } return false; } /** * Override _call to intercept Zod validation errors at the execution level */ async _call(inputs) { try { const result = await super._call(inputs); if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) { for (const step of result.intermediateSteps) { if (step.observation) { try { const parsed = typeof step.observation === "string" ? JSON.parse(step.observation) : step.observation; if (parsed.requiresForm && parsed.formMessage) { this.formLogger.info("Tool requested form generation", { toolName: step.action?.tool, hasForm: true }); const actionToolName = step.action?.tool || "unknown"; const toolInstance = this.tools.find( (t) => t.name === actionToolName ); const originalToolCandidate = toolInstance || {}; const pf = { toolName: actionToolName, originalInput: inputs, originalToolInput: step.action?.toolInput, schema: null, toolRef: toolInstance, originalToolRef: originalToolCandidate?.originalTool }; this.pendingForms.set(parsed.formMessage.id, pf); globalPendingForms.set(parsed.formMessage.id, pf); return { ...result, requiresForm: true, formMessage: parsed.formMessage, output: parsed.message || "Please complete the form to continue." }; } if (parsed.hashLinkBlock || parsed.success && parsed.inscription && parsed.hashLinkBlock) { this.formLogger.info("Tool returned HashLink blocks", { toolName: step.action?.tool, hasHashLink: true, blockId: parsed.hashLinkBlock?.blockId }); const hashLinkResponse = this.processHashLinkResponse(parsed); return { ...result, hasHashLinkBlocks: true, hashLinkBlock: hashLinkResponse.hashLinkBlock, output: hashLinkResponse.message }; } } catch { } } } } return result; } catch (error) { if (error instanceof ZodError) { this.formLogger.info("Intercepted ZodError during agent execution"); return this.handleValidationError(error, inputs, []); } throw error; } } /** * Handles Zod validation errors by generating forms */ async handleValidationError(error, inputs, intermediateSteps) { this.formLogger.info("Zod validation error detected, generating form", { errorIssues: error.issues.length, inputKeys: Object.keys(inputs) }); let toolInfo = this.extractToolInfoFromError( error, inputs, intermediateSteps ); if (!toolInfo) { this.formLogger.warn( "Could not extract tool info from validation error, trying fallback detection" ); const fallbackTool = this.detectToolFromErrorContext(error); if (!fallbackTool) { this.formLogger.error( "No tool detected for form generation, rethrowing error" ); throw error; } toolInfo = fallbackTool; } this.formLogger.info("Generating form for tool:", { toolName: toolInfo.toolName, hasSchema: !!toolInfo.schema }); const formMessage = this.formGenerator.generateFormFromError( error, toolInfo.schema, toolInfo.toolName, inputs.input || "" ); this.pendingForms.set(formMessage.id, { toolName: toolInfo.toolName, originalInput: inputs, schema: toolInfo.schema }); globalPendingForms.set(formMessage.id, { toolName: toolInfo.toolName, originalInput: inputs, schema: toolInfo.schema }); return { output: this.formatFormResponse(formMessage), formMessage, requiresForm: true, intermediateSteps: intermediateSteps || [] }; } /** * Get a copy of pending forms for preservation during executor recreation */ getPendingForms() { return new Map(this.pendingForms); } /** * Restore pending forms from a previous executor instance */ restorePendingForms(forms) { for (const [formId, formData] of forms) { this.pendingForms.set(formId, formData); } } /** * Processes form submission and continues tool execution */ async processFormSubmission(submission) { this.formLogger.info( "🚀 FormAwareAgentExecutor.processFormSubmission called!", { submissionFormId: submission.formId, submissionToolName: submission.toolName } ); if (!submission) { throw new Error("Form submission is null or undefined"); } if (!submission.formId) { throw new Error("Form submission missing formId"); } if (!submission.parameters || submission.parameters === null || typeof submission.parameters !== "object" || Array.isArray(submission.parameters)) { throw new Error( `Form submission parameters are invalid: ${typeof submission.parameters}, isNull: ${submission.parameters === null}, isArray: ${Array.isArray( submission.parameters )}, parameters: ${JSON.stringify(submission.parameters)}` ); } this.formLogger.info("Processing form submission:", { formId: submission.formId, toolName: submission.toolName, parameterKeys: Object.keys(submission.parameters), parametersType: typeof submission.parameters, parametersIsNull: submission.parameters === null, parametersIsUndefined: submission.parameters === void 0, hasContext: !!submission.context }); let pendingForm = this.pendingForms.get(submission.formId); if (!pendingForm) { pendingForm = globalPendingForms.get(submission.formId); if (!pendingForm) { throw new Error(`No pending form found for ID: ${submission.formId}`); } } this.pendingForms.delete(submission.formId); globalPendingForms.delete(submission.formId); const tool = pendingForm.toolRef || this.tools.find((t) => t.name === pendingForm.toolName); if (!tool) { throw new Error( `Tool not found for form submission: ${pendingForm.toolName}` ); } let baseToolInput = {}; try { if (pendingForm.originalToolInput && typeof pendingForm.originalToolInput === "object") { baseToolInput = { ...pendingForm.originalToolInput }; } } catch (error) { this.formLogger.warn( "Failed to extract base tool input, using empty object:", error ); baseToolInput = {}; } let submissionData = {}; try { if (submission.parameters && typeof submission.parameters === "object") { submissionData = { ...submission.parameters }; } } catch (error) { this.formLogger.warn( "Failed to extract submission parameters, using empty object:", error ); submissionData = {}; } const mergedToolInput = {}; try { Object.keys(baseToolInput).forEach((key) => { const value = baseToolInput[key]; if (value !== void 0 && value !== null) { mergedToolInput[key] = value; } }); Object.keys(submissionData).forEach((key) => { const value = submissionData[key]; if (value !== void 0 && value !== null) { mergedToolInput[key] = value; } }); mergedToolInput.renderForm = false; mergedToolInput.__fromForm = true; this.formLogger.info("Successfully merged tool input:", { baseKeys: Object.keys(baseToolInput), submissionKeys: Object.keys(submissionData), mergedKeys: Object.keys(mergedToolInput) }); } catch (error) { this.formLogger.error("Failed to merge tool input data:", error); throw new Error( `Failed to merge tool input data: ${error instanceof Error ? error.message : "Unknown error"}` ); } try { const maybeWrapper = tool; let toolOutput; if (typeof maybeWrapper.executeOriginal === "function") { toolOutput = await maybeWrapper.executeOriginal(mergedToolInput); } else if (typeof maybeWrapper.getOriginalTool === "function") { const ot = maybeWrapper.getOriginalTool(); const otCall = ot; if (ot && typeof otCall._call === "function") { toolOutput = await otCall._call(mergedToolInput); } else if (ot && typeof otCall.call === "function") { toolOutput = await otCall.call(mergedToolInput); } else { const tcall = tool; if (typeof tcall.call === "function") { toolOutput = await tcall.call(mergedToolInput); } else { throw new Error( "No callable tool implementation found for form submission" ); } } } else if (maybeWrapper.originalTool && typeof maybeWrapper.originalTool._call === "function") { toolOutput = await maybeWrapper.originalTool._call(mergedToolInput); } else if (maybeWrapper.originalTool && typeof maybeWrapper.originalTool.call === "function") { toolOutput = await maybeWrapper.originalTool.call(mergedToolInput); } else if (typeof tool.call === "function") { toolOutput = await tool.call(mergedToolInput); } else { throw new Error( "No callable tool implementation found for form submission" ); } let responseMetadata = {}; let formattedOutput; try { const parsed = JSON.parse(toolOutput); this.formLogger.info( "✅ METADATA EXTRACTION: Successfully parsed JSON", { jsonKeys: Object.keys(parsed), hasHashLinkBlock: !!parsed.hashLinkBlock } ); if (parsed && typeof parsed === "object") { if (ResponseFormatter.isHashLinkResponse(parsed)) { this.formLogger.info( "🔗 HASHLINK DETECTED: Processing HashLink response separately to preserve metadata" ); const parsedRecord = parsed; responseMetadata = { ...responseMetadata, hashLinkBlock: parsedRecord.hashLinkBlock, ...parsedRecord }; formattedOutput = ResponseFormatter.formatHashLinkResponse(parsed); this.formLogger.info( "🔗 METADATA PRESERVED: HashLink metadata extracted for component rendering", { blockId: this.hasHashLinkBlock(responseMetadata) ? responseMetadata.hashLinkBlock.blockId : void 0, hasTemplate: this.hasHashLinkBlock(responseMetadata) ? !!responseMetadata.hashLinkBlock.template : false } ); } else { formattedOutput = ResponseFormatter.formatResponse(toolOutput); responseMetadata = { ...responseMetadata, hashLinkBlock: parsed.hashLinkBlock, ...parsed }; } } else { formattedOutput = ResponseFormatter.formatResponse(toolOutput); } } catch (error) { this.formLogger.warn( "❌ METADATA EXTRACTION: Tool output is not JSON", { error: error instanceof Error ? error.message : "unknown error", outputSample: typeof toolOutput === "string" ? toolOutput.substring(0, 200) : "not-string" } ); formattedOutput = ResponseFormatter.formatResponse(toolOutput); } return { output: formattedOutput, formCompleted: true, originalFormId: submission.formId, intermediateSteps: [], metadata: responseMetadata, rawToolOutput: toolOutput, toolName: pendingForm.toolName }; } catch (error) { if (error instanceof ZodError) { return this.handleValidationError(error, mergedToolInput, []); } throw error; } } /** * Extracts tool information from the execution context */ extractToolInfoFromError(error, inputs, intermediateSteps) { try { if (intermediateSteps.length > 0) { const lastStep = intermediateSteps[intermediateSteps.length - 1]; if (lastStep.action && lastStep.action.tool) { const tool = this.tools.find((t) => t.name === lastStep.action.tool); if (tool && "schema" in tool) { this.formLogger.info( "Found tool from intermediate steps:", lastStep.action.tool ); return { toolName: lastStep.action.tool, schema: tool.schema }; } } } const inputSteps = inputs.intermediateSteps || []; if (inputSteps.length > 0) { const lastStep = inputSteps[inputSteps.length - 1]; let action; if (Array.isArray(lastStep) && lastStep.length > 0) { action = lastStep[0]; } else if (lastStep.action) { action = lastStep.action; } else { action = lastStep; } if (action && action.tool) { const tool = this.tools.find((t) => t.name === action.tool); if (tool && "schema" in tool) { this.formLogger.info("Found tool from input steps:", action.tool); return { toolName: action.tool, schema: tool.schema }; } } } const toolFromContext = this.findToolFromContext(inputs); if (toolFromContext) { this.formLogger.info( "Found tool from context:", toolFromContext.toolName ); return toolFromContext; } return null; } catch (err) { this.formLogger.error("Error extracting tool info:", err); return null; } } /** * Attempts to find tool from execution context */ findToolFromContext(inputs) { const inputText = inputs.input || ""; for (const tool of this.tools) { const keywords = this.extractToolKeywords(tool.name); if (keywords.some( (keyword) => inputText.toLowerCase().includes(keyword.toLowerCase()) )) { if ("schema" in tool) { return { toolName: tool.name, schema: tool.schema }; } } } return null; } /** * Additional fallback to detect tool from error context */ detectToolFromErrorContext(error) { const errorPaths = error.issues.map((issue) => issue.path.join(".")); for (const tool of this.tools) { if ("schema" in tool) { const toolSchema = tool.schema; if (this.schemaMatchesErrorPaths(toolSchema, errorPaths)) { this.formLogger.info( "Detected tool from error path analysis:", tool.name ); return { toolName: tool.name, schema: toolSchema }; } } } return null; } /** * Checks if a schema structure matches error paths */ schemaMatchesErrorPaths(schema, errorPaths) { const schemaRecord = schema; if (!schemaRecord || !schemaRecord._def) return false; try { const def = schemaRecord._def; if (def.typeName === "ZodObject") { const shape = def.shape; const schemaKeys = Object.keys(shape || {}); return errorPaths.some((path) => { const topLevelKey = path.split(".")[0]; return schemaKeys.includes(topLevelKey); }); } } catch (err) { this.formLogger.debug("Error analyzing schema structure:", err); } return false; } /** * Extracts keywords from tool name for matching */ extractToolKeywords(toolName) { const words = toolName.replace(/([A-Z])/g, " $1").toLowerCase().split(/[\s_-]+/).filter((w) => w.length > 2); return [...words, toolName.toLowerCase()]; } /** * Formats the form message for display */ formatFormResponse(formMessage) { const fieldCount = formMessage.formConfig.fields.length; const fieldList = formMessage.formConfig.fields.slice(0, 3).map((f) => `• ${f.label}`).join("\n"); return `I need some additional information to complete your request. ${formMessage.formConfig.description} Required fields: ${fieldList}${fieldCount > 3 ? ` ... and ${fieldCount - 3} more` : ""} Please fill out the form below to continue.`; } /** * Check if there are pending forms */ hasPendingForms() { return this.pendingForms.size > 0; } /** * Get information about pending forms */ getPendingFormsInfo() { return Array.from(this.pendingForms.entries()).map(([formId, info]) => ({ formId, toolName: info.toolName })); } /** * Processes HashLink block responses from tools */ processHashLinkResponse(toolResponse) { try { let hashLinkBlock; if (toolResponse.hashLinkBlock) { hashLinkBlock = toolResponse.hashLinkBlock; } else if (toolResponse.success && toolResponse.hashLinkBlock) { hashLinkBlock = toolResponse.hashLinkBlock; } if (!hashLinkBlock) { throw new Error("HashLink block data not found in response"); } if (!hashLinkBlock.blockId || !hashLinkBlock.hashLink || !hashLinkBlock.attributes) { throw new Error( "Invalid HashLink block structure - missing required fields" ); } let message = "Content processed successfully!"; if (toolResponse.success && toolResponse.inscription) { const inscription = toolResponse.inscription; const metadata = toolResponse.metadata || {}; message = `✅ ${inscription.standard} Hashinal inscription completed! `; if (metadata.name) { message += `**${metadata.name}** `; } if (metadata.description) { message += `${metadata.description} `; } message += `📍 **Topic ID:** ${inscription.topicId} `; message += `🔗 **HRL:** ${inscription.hrl} `; if (inscription.cdnUrl) { message += `🌐 **CDN URL:** ${inscription.cdnUrl} `; } if (metadata.creator) { message += `👤 **Creator:** ${metadata.creator} `; } } this.formLogger.info("Processed HashLink response", { blockId: hashLinkBlock.blockId, hashLink: hashLinkBlock.hashLink, hasTemplate: !!hashLinkBlock.template, attributeCount: Object.keys(hashLinkBlock.attributes || {}).length }); return { hasHashLinkBlocks: true, hashLinkBlock, message }; } catch (error) { this.formLogger.error("Error processing HashLink response:", error); return { hasHashLinkBlocks: false, message: "Content processed, but interactive display is not available." }; } } /** * Get FormEngine statistics for debugging */ getFormEngineStatistics() { return { strategies: this.formEngine.getRegisteredStrategies(), middleware: this.formEngine.getRegisteredMiddleware() }; } } export { FormAwareAgentExecutor }; //# sourceMappingURL=index32.js.map