UNPKG

@memberjunction/ai-agent-manager-actions

Version:

Agent Management actions for creating and managing AI agents in MemberJunction

342 lines 13.3 kB
import { BaseAction } from "@memberjunction/actions"; import { BaseEntity, Metadata, LogError, RunView } from "@memberjunction/core"; /** * Abstract base class for agent management actions. * Provides common functionality for validating permissions, loading entities, * and managing agent metadata. Only the Agent Manager agent should be able * to execute these actions. */ export class BaseAgentManagementAction extends BaseAction { /** * Validates that the action is being called by the Agent Manager agent */ async validateAgentManagerPermission(params) { // In a production system, this would check the calling agent's identity // For now, we'll add a comment about the security check // TODO: Implement proper agent identity verification // Check if the action is being called with proper context if (!params.ContextUser) { return { Success: false, ResultCode: 'PERMISSION_DENIED', Message: 'User context is required for agent management actions' }; } return null; // Permission granted } /** * Extract and validate a string parameter */ getStringParam(params, paramName, required = true) { const param = params.Params.find(p => p.Name.trim().toLowerCase() === paramName.trim().toLowerCase()); if (!param || !param.Value) { if (required) { return { error: { Success: false, ResultCode: 'VALIDATION_ERROR', Message: `${paramName} parameter is required` } }; } return { value: undefined }; } return { value: param.Value }; } /** * Extract and validate an object parameter */ getObjectParam(params, paramName, required = true) { const param = params.Params.find(p => p.Name.trim().toLowerCase() === paramName.toLowerCase()); if (!param || !param.Value) { if (required) { return { error: { Success: false, ResultCode: 'VALIDATION_ERROR', Message: `${paramName} parameter is required` } }; } return { value: undefined }; } return { value: param.Value }; } /** * Resolve the metadata provider for the running action. Pass `params` from * `InternalRunAction` so every entity load/save binds to the per-request provider * (`params.Provider`) rather than the process-global default. Falls back to a default * Metadata helper instance when no provider was passed (e.g. legacy callers). */ getMetadata(params) { return (params?.Provider ?? new Metadata()); } /** * Validates if a string is a valid UUID format */ isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(uuid); } /** * Extract and validate a UUID parameter */ getUuidParam(params, paramName, required = true) { const param = params.Params.find(p => p.Name.trim().toLowerCase() === paramName.trim().toLowerCase()); if (!param || !param.Value) { if (required) { return { error: { Success: false, ResultCode: 'VALIDATION_ERROR', Message: `${paramName} parameter is required` } }; } return { value: undefined }; } const value = param.Value; // Validate UUID format (including NULL check) if (!value || value.trim().toUpperCase() === 'NULL' || value.trim() === '' || !this.isValidUUID(value.trim())) { return { error: { Success: false, ResultCode: 'VALIDATION_ERROR', Message: `Invalid ${paramName} format: '${value}'. Must be a valid UUID.` } }; } return { value: value.trim() }; } /** * Get database provider with transaction support validation * * @example * ```typescript * // Example usage for implementing transactions in actions: * const providerResult = this.getTransactionProvider(); * if (providerResult.error) return providerResult.error; * const provider = providerResult.provider!; * * // Begin transaction for multi-record operations * await provider.BeginTransaction(); * * try { * // Perform multiple database operations * const agent = await md.GetEntityObject<MJAIAgentEntity>('MJ: AI Agents', contextUser); * await agent.Save(); * * const prompt = await md.GetEntityObject<MJAIPromptEntity>('MJ: AI Prompts', contextUser); * await prompt.Save(); * * // Commit transaction - all operations succeeded * await provider.CommitTransaction(); * * return { Success: true, ResultCode: 'SUCCESS', Message: 'All operations completed successfully' }; * * } catch (transactionError) { * // Rollback transaction on any error * await provider.RollbackTransaction(); * throw transactionError; // Re-throw to be caught by outer try-catch * } * ``` */ getTransactionProvider() { // Get the database provider for transaction management const provider = BaseEntity.Provider; if (!provider) { return { error: { Success: false, ResultCode: 'NO_PROVIDER', Message: 'No database provider available for transaction management' } }; } // Verify the provider supports transaction management if (typeof provider.BeginTransaction !== 'function' || typeof provider.CommitTransaction !== 'function' || typeof provider.RollbackTransaction !== 'function') { return { error: { Success: false, ResultCode: 'NO_TRANSACTION_SUPPORT', Message: 'Database provider does not support transaction management' } }; } return { provider }; } /** * Load an AI Agent entity by ID. Pass the running action's `params` so the underlying * entity load uses the per-request provider (multi-tenant correctness). */ async loadAgent(agentID, contextUser, params) { try { const md = this.getMetadata(params); const agent = await md.GetEntityObject('MJ: AI Agents', contextUser); if (!agent) { return { error: { Success: false, ResultCode: 'FAILED', Message: 'Failed to create AI Agent entity object' } }; } const loaded = await agent.Load(agentID); if (!loaded) { return { error: { Success: false, ResultCode: 'NOT_FOUND', Message: `Agent with ID '${agentID}' not found` } }; } return { agent }; } catch (e) { return { error: { Success: false, ResultCode: 'ERROR', Message: `Error loading agent: ${e instanceof Error ? e.message : 'Unknown error'}` } }; } } /** * Validate agent type exists. Pass the running action's `params` so the lookup uses * the per-request provider (multi-tenant correctness). */ async validateAgentType(typeID, contextUser, params) { try { const md = this.getMetadata(params); const agentType = await md.GetEntityObject('MJ: AI Agent Types', contextUser); if (!agentType) { return { error: { Success: false, ResultCode: 'FAILED', Message: 'Failed to create AI Agent Type entity object' } }; } const loaded = await agentType.Load(typeID); if (!loaded) { return { error: { Success: false, ResultCode: 'INVALID_TYPE', Message: `Agent Type with ID '${typeID}' not found` } }; } return { type: agentType }; } catch (e) { return { error: { Success: false, ResultCode: 'ERROR', Message: `Error validating agent type: ${e instanceof Error ? e.message : 'Unknown error'}` } }; } } /** * Common error handler */ handleError(error, operation) { const message = error instanceof Error ? error.message : 'Unknown error'; LogError(`Agent Management Action error during ${operation}: ${message}`); return { Success: false, ResultCode: 'ERROR', Message: `Failed to ${operation}: ${message}` }; } /** * Creates a prompt and associates it with an agent. Pass the running action's `params` * so the underlying entity creates bind to the per-request provider (multi-tenant * correctness). */ async createAndAssociatePrompt(agent, promptText, contextUser, params) { try { const md = this.getMetadata(params); // Create the prompt const prompt = await md.GetEntityObject('MJ: AI Prompts', contextUser); if (!prompt) { return { success: false, error: 'Failed to create AI Prompt entity object' }; } prompt.NewRecord(); prompt.Name = `${agent.Name} System Prompt`; prompt.Description = `System prompt for ${agent.Name} agent`; prompt.TypeID = await this.getPromptTypeID('Chat', contextUser, params); prompt.Status = 'Active'; prompt.ResponseFormat = 'JSON'; prompt.PromptRole = 'System'; prompt.PromptPosition = 'First'; prompt.TemplateText = promptText; // This uses the extended property const promptSaved = await prompt.Save(); if (!promptSaved) { return { success: false, error: prompt.LatestResult?.Message || 'Failed to save prompt' }; } // Create the agent-prompt association const agentPrompt = await md.GetEntityObject('MJ: AI Agent Prompts', contextUser); if (!agentPrompt) { return { success: false, error: 'Failed to create AI Agent Prompt entity object' }; } agentPrompt.NewRecord(); agentPrompt.AgentID = agent.ID; agentPrompt.PromptID = prompt.ID; agentPrompt.ExecutionOrder = 1; agentPrompt.Status = 'Active'; const associationSaved = await agentPrompt.Save(); if (!associationSaved) { // Try to clean up the prompt since association failed await prompt.Delete(); return { success: false, error: agentPrompt.LatestResult?.Message || 'Failed to associate prompt with agent' }; } return { success: true, promptId: prompt.ID }; } catch (e) { return { success: false, error: e instanceof Error ? e.message : 'Unknown error creating prompt' }; } } /** * Gets the prompt type ID for a given type name. Pass the running action's `params` * so the RunView binds to the per-request provider. */ async getPromptTypeID(typeName, contextUser, params) { const provider = (params?.Provider ?? new Metadata()); const rv = RunView.FromMetadataProvider(provider); const result = await rv.RunView({ EntityName: 'MJ: AI Prompt Types', ExtraFilter: `Name = '${typeName}'`, MaxRows: 1, ResultType: 'simple' }, contextUser); if (!result.Success) { throw new Error(`Failed to query prompt types: ${result.ErrorMessage || 'Unknown error'}`); } if (result.Results && result.Results.length > 0) { return result.Results[0].ID; } throw new Error(`Prompt type '${typeName}' not found`); } } //# sourceMappingURL=base-agent-management.action.js.map