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

302 lines (266 loc) 7.4 kB
import { ZodError } from 'zod'; import { Logger } from '@hashgraphonline/standards-sdk'; import { SmartMemoryManager } from '../memory/smart-memory-manager'; import { FormEngine, ToolExecutionResult } from '../forms/form-engine'; import type { FormMessage, FormSubmission } from '../forms/types'; import type { ToolRegistry, ToolRegistryEntry } from '../core/tool-registry'; /** * Session context for tool execution */ export interface SessionContext { sessionId: string; userId?: string; timestamp: number; conversationId?: string; } /** * Context passed through execution pipeline */ export interface ExecutionContext { toolName: string; input: unknown; session: SessionContext; memory: SmartMemoryManager; traceId: string; toolEntry: ToolRegistryEntry; } /** * Result of tool execution with metadata */ export interface ExecutionResult extends ToolExecutionResult { traceId: string; executionTime: number; } /** * ExecutionPipeline handles tool execution coordination */ export class ExecutionPipeline { private logger: Logger; private toolRegistry: ToolRegistry; private formEngine: FormEngine; private memory: SmartMemoryManager; constructor( toolRegistry: ToolRegistry, formEngine: FormEngine, memory: SmartMemoryManager, logger?: Logger ) { this.toolRegistry = toolRegistry; this.formEngine = formEngine; this.memory = memory; this.logger = logger || new Logger({ module: 'ExecutionPipeline' }); } /** * Execute a tool through the pipeline */ async execute( toolName: string, input: unknown, sessionContext?: SessionContext ): Promise<ExecutionResult> { const traceId = `trace-${Date.now()}-${Math.random() .toString(36) .substr(2, 9)}`; const startTime = Date.now(); const toolEntry = this.toolRegistry.getTool(toolName); if (!toolEntry) { throw new Error(`Tool not found in registry: ${toolName}`); } const context: ExecutionContext = { toolName, input, session: sessionContext || this.buildDefaultSession(), memory: this.memory, traceId, toolEntry, }; try { const shouldGenerateForm = await this.checkFormGeneration(context); if (shouldGenerateForm.requiresForm && shouldGenerateForm.formMessage) { return { success: false, output: 'Form generation required', requiresForm: true, formMessage: shouldGenerateForm.formMessage, traceId, executionTime: Date.now() - startTime, }; } const result = await this.executeToolDirect(context); return { success: true, output: result, traceId, executionTime: Date.now() - startTime, }; } catch (error) { return this.handleExecutionError( error, context, traceId, Date.now() - startTime ); } } /** * Execute tool with validation */ async executeWithValidation( toolName: string, input: unknown, sessionContext?: SessionContext ): Promise<ExecutionResult> { return this.execute(toolName, input, sessionContext); } /** * Process form submission */ async processFormSubmission( toolName: string, formId: string, parameters: Record<string, unknown>, sessionContext?: SessionContext ): Promise<ExecutionResult> { const traceId = `form-${Date.now()}-${Math.random() .toString(36) .substr(2, 9)}`; const startTime = Date.now(); try { const formSubmission: FormSubmission = { formId, toolName, parameters, timestamp: Date.now(), }; const processedInput = await this.formEngine.processSubmission( formSubmission ); return this.execute(toolName, processedInput, sessionContext); } catch (error) { return { success: false, output: 'Form submission processing failed', error: error instanceof Error ? error.message : String(error), traceId, executionTime: Date.now() - startTime, }; } } /** * Check if form generation is required */ private async checkFormGeneration(context: ExecutionContext): Promise<{ requiresForm: boolean; formMessage?: FormMessage; }> { const inputRecord = context.input as Record<string, unknown>; if (inputRecord?.__fromForm === true || inputRecord?.renderForm === false) { return { requiresForm: false }; } if ( !this.formEngine.shouldGenerateForm(context.toolEntry.tool, context.input) ) { return { requiresForm: false }; } const formMessage = await this.formEngine.generateForm( context.toolName, context.toolEntry.tool, context.input ); if (formMessage) { return { requiresForm: true, formMessage }; } return { requiresForm: false }; } /** * Execute tool directly */ private async executeToolDirect(context: ExecutionContext): Promise<string> { const { toolEntry, input } = context; const parameters = (input as Record<string, unknown>) || {}; const mergedArgs = { ...parameters, renderForm: false }; if (toolEntry.wrapper) { return this.executeWrappedTool(toolEntry, mergedArgs); } return await toolEntry.tool.call(mergedArgs); } /** * Execute wrapped tool */ private async executeWrappedTool( toolEntry: ToolRegistryEntry, mergedArgs: Record<string, unknown> ): Promise<string> { const wrapper = toolEntry.wrapper; if (!wrapper) { throw new Error('Tool wrapper not found'); } const wrapperAsAny = wrapper as unknown as { executeOriginal?: (args: Record<string, unknown>) => Promise<string>; originalTool?: { call?: (args: Record<string, unknown>) => Promise<string>; }; }; if (wrapperAsAny.executeOriginal) { return await wrapperAsAny.executeOriginal(mergedArgs); } if (wrapperAsAny.originalTool?.call) { return await wrapperAsAny.originalTool.call(mergedArgs); } return await toolEntry.originalTool.call(mergedArgs); } /** * Handle execution error */ private handleExecutionError( error: unknown, context: ExecutionContext, traceId: string, executionTime: number ): ExecutionResult { const errorMessage = error instanceof Error ? error.message : String(error); if (error instanceof ZodError) { return { success: false, output: 'Validation error occurred', error: errorMessage, traceId, executionTime, }; } this.logger.error(`Tool execution failed: ${context.toolName}`, { traceId, error: errorMessage, }); return { success: false, output: 'Tool execution failed', error: errorMessage, traceId, executionTime, }; } /** * Build default session context */ private buildDefaultSession(): SessionContext { return { sessionId: `session-${Date.now()}-${Math.random() .toString(36) .substr(2, 9)}`, timestamp: Date.now(), }; } /** * Get statistics about the pipeline */ getStatistics(): { totalMiddleware: number; registeredMiddleware: string[]; } { return { totalMiddleware: 0, registeredMiddleware: [], }; } }