UNPKG

n8n-nodes-a2a-protocol

Version:

Agent2Agent (A2A) Protocol nodes for n8n - Enable agent interoperability, communication, and MCP integration

857 lines (856 loc) 45.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.A2ARemoteAgent = void 0; const n8n_workflow_1 = require("n8n-workflow"); const express_1 = __importDefault(require("express")); const urlUtils_1 = require("../../utils/urlUtils"); class A2ARemoteAgent { constructor() { this.description = { displayName: 'A2A Remote Agent Server', name: 'a2aRemoteAgent', icon: 'file:a2a-server.svg', group: ['trigger'], version: 1, subtitle: '={{$parameter["agentId"]}}', description: 'Start A2A (Agent-to-Agent) Server to receive tasks from clients', defaults: { name: 'A2A Agent Server', }, inputs: [], outputs: ["main" /* NodeConnectionType.Main */], credentials: [], properties: [ { displayName: 'Agent ID', name: 'agentId', type: 'string', default: 'n8n-a2a-agent', description: 'Unique identifier for this A2A agent', required: true, }, { displayName: 'Port', name: 'port', type: 'number', default: urlUtils_1.DEFAULT_A2A_PORTS.AGENTS.TRANSLATOR, description: '⚠️ Port to listen on for A2A requests. NOTE: Port conflicts will be detected during workflow activation, not save. Run "node validate-a2a-ports.js" to check for conflicts before saving.', required: true, }, { displayName: 'Processing Mode', name: 'processingMode', type: 'options', options: [ { name: 'Synchronous', value: 'sync', description: 'Process tasks synchronously and wait for workflow results', }, { name: 'Asynchronous', value: 'async', description: 'Accept tasks immediately and process asynchronously', }, { name: 'Mixed', value: 'mixed', description: 'Support both sync and async based on client request', }, ], default: 'mixed', description: 'How to process incoming A2A tasks', }, { displayName: 'Enable Workflow Integration', name: 'enableWorkflowIntegration', type: 'boolean', default: true, description: 'Forward tasks to connected workflow nodes for processing', }, { displayName: 'Authentication Mode', name: 'authMode', type: 'options', options: [ { name: 'None', value: 'none', description: 'No authentication required', }, { name: 'Bearer Token', value: 'bearer_token', description: 'Require Bearer token authentication', }, { name: 'API Key', value: 'api_key', description: 'Require API key authentication', }, ], default: 'none', description: 'Authentication method for A2A requests', }, { displayName: 'Auto-Register with Registry', name: 'autoRegister', type: 'boolean', default: true, description: 'Automatically register this agent with the A2A Registry', }, { displayName: 'Registry URL', name: 'registryUrl', type: 'string', default: (0, urlUtils_1.getDefaultRegistryUrl)(), displayOptions: { show: { autoRegister: [true], }, }, description: 'URL of the A2A Registry Server', placeholder: (0, urlUtils_1.getDefaultRegistryUrl)(), required: true, }, { displayName: 'Technical Capabilities', name: 'technicalCapabilities', type: 'options', placeholder: 'Add Technical Capability', typeOptions: { multipleValues: true, }, options: [ { name: 'Streaming Support (SSE)', value: 'streaming', description: 'Support Server-Sent Events for real-time updates', }, { name: 'Push Notifications', value: 'pushNotifications', description: 'Support webhook-based push notifications', }, { name: 'State Transition History', value: 'stateTransitionHistory', description: 'Provide detailed task state transition history', }, ], default: ['streaming'], description: 'Technical protocol features this agent supports (A2A spec compliance)', }, { displayName: 'Agent Skills', name: 'skills', type: 'json', default: JSON.stringify([ { id: 'general_processing', name: 'General Processing', description: 'General task processing with N8N workflows', inputModes: ['text', 'json'], outputModes: ['json', 'text'], category: 'processing', enabled: true, tags: ['processing', 'workflow'], examples: 'Process data through N8N workflows' }, { id: 'workflow_orchestration', name: 'Workflow Orchestration', description: 'N8N workflow execution and management', inputModes: ['json'], outputModes: ['json'], category: 'orchestration', enabled: true, tags: ['orchestration', 'automation'], examples: 'Execute and manage complex N8N workflows' }, { id: 'data_transformation', name: 'Data Transformation', description: 'Transform and manipulate data formats', inputModes: ['json', 'xml', 'csv'], outputModes: ['json', 'xml', 'csv'], category: 'data', enabled: false, tags: ['data', 'transformation'], examples: 'Convert JSON to CSV, transform data structures' }, { id: 'ai_assistance', name: 'AI Assistance', description: 'AI-powered text processing and generation', inputModes: ['text', 'json'], outputModes: ['text', 'json'], category: 'ai', enabled: false, tags: ['ai', 'nlp', 'generation'], examples: 'Generate text, analyze content, summarize documents' }, { id: 'api_integration', name: 'API Integration', description: 'Connect and integrate with external APIs', inputModes: ['json'], outputModes: ['json'], category: 'integration', enabled: false, tags: ['api', 'integration', 'external'], examples: 'Call REST APIs, integrate with third-party services' }, { id: 'file_processing', name: 'File Processing', description: 'Process and manipulate files', inputModes: ['file', 'binary'], outputModes: ['file', 'binary', 'json'], category: 'files', enabled: false, tags: ['files', 'processing', 'binary'], examples: 'Process images, parse documents, convert file formats' }, { id: 'notification_services', name: 'Notification Services', description: 'Send notifications via email, SMS, chat', inputModes: ['json', 'text'], outputModes: ['json'], category: 'communication', enabled: false, tags: ['notifications', 'email', 'sms'], examples: 'Send email alerts, SMS notifications, chat messages' }, { id: 'database_operations', name: 'Database Operations', description: 'Perform database queries and operations', inputModes: ['json', 'sql'], outputModes: ['json'], category: 'database', enabled: false, tags: ['database', 'sql', 'queries'], examples: 'Execute SQL queries, manage database records' } ], null, 2), description: 'Configure agent skills/functions (A2A spec compliant). Set enabled: true to activate a skill.', typeOptions: { rows: 15, }, }, ], }; } async trigger() { const port = this.getNodeParameter('port'); const agentId = this.getNodeParameter('agentId'); const processingMode = this.getNodeParameter('processingMode'); const enableWorkflowIntegration = this.getNodeParameter('enableWorkflowIntegration'); const authMode = this.getNodeParameter('authMode'); const autoRegister = this.getNodeParameter('autoRegister'); const registryUrl = this.getNodeParameter('registryUrl'); const technicalCapabilities = this.getNodeParameter('technicalCapabilities'); const skillsJson = this.getNodeParameter('skills'); // Generate stable node ID for this configuration (without timestamp) const nodeId = `agent_${agentId}_${port}`; // ✅ VALIDATE PORT BEFORE STARTING - This will show popup errors that prevent workflow activation const portValidation = await (0, urlUtils_1.validateNodePort)(port, 'agent', `A2A Agent (${agentId})`, nodeId); if (!portValidation.isValid && portValidation.errorMessage) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `❌ Port ${port} is already in use. Please choose a different port for A2A Agent (${agentId}).`); } // Stop previous server instance if it exists (safe - only closes Express servers) await (0, urlUtils_1.stopPreviousServerInstance)(nodeId); // Parse technical capabilities (A2A protocol features) const agentCapabilities = {}; technicalCapabilities.forEach((cap) => { agentCapabilities[cap] = true; }); // Parse and filter skills - only include enabled ones let agentSkills = []; try { const allSkills = JSON.parse(skillsJson); agentSkills = allSkills.filter((skill) => skill.enabled === true); } catch (error) { // Fallback to default skills if JSON parsing fails agentSkills = [ { id: 'general_processing', name: 'General Processing', description: 'General task processing with N8N workflows', inputModes: ['text', 'json'], outputModes: ['json', 'text'], category: 'processing', tags: ['processing', 'workflow'], examples: 'Process data through N8N workflows' }, { id: 'workflow_orchestration', name: 'Workflow Orchestration', description: 'N8N workflow execution and management', inputModes: ['json'], outputModes: ['json'], category: 'orchestration', tags: ['orchestration', 'automation'], examples: 'Execute and manage complex N8N workflows' } ]; } const taskStore = new Map(); const taskQueue = []; // Queue for tasks to emit to workflow const app = (0, express_1.default)(); app.use(express_1.default.json()); // Process task queue and emit to workflow (this happens in proper trigger context) const processTaskQueue = () => { if (enableWorkflowIntegration && taskQueue.length > 0) { const tasksToEmit = taskQueue.splice(0); // Get all tasks and clear queue tasksToEmit.forEach(taskData => { this.emit([this.helpers.returnJsonArray([taskData])]); }); } }; // Check for new tasks every 100ms const taskProcessor = setInterval(processTaskQueue, 100); app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-A2A-*'); if (req.method === 'OPTIONS') return res.sendStatus(200); next(); }); // Authentication middleware if (authMode !== 'none') { app.use((req, res, next) => { if (req.path === '/health' || req.path === '/capabilities') return next(); const authHeader = req.headers.authorization; if (!authHeader) { return res.status(401).json({ error: 'Authentication required', auth_mode: authMode, a2a_protocol_version: '1.0' }); } if (authMode === 'bearer_token' && !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Bearer token required', a2a_protocol_version: '1.0' }); } if (authMode === 'api_key' && !authHeader.startsWith('ApiKey ')) { return res.status(401).json({ error: 'API key required', a2a_protocol_version: '1.0' }); } next(); }); } // Get dynamic endpoints const endpoints = (0, urlUtils_1.createAgentEndpoints)(port); const urlConfig = (0, urlUtils_1.detectInstanceUrl)(); const callbackUrlBase = (0, urlUtils_1.buildUrl)(urlConfig, port, '/tasks'); // Registry registration function const registerWithRegistry = async () => { if (!autoRegister) { return; } try { const agentCard = { agent_id: agentId, name: `N8N A2A Agent ${agentId}`, description: 'N8N-powered A2A agent with workflow integration', version: '1.0.0', a2a_protocol_version: '1.0', capabilities: agentCapabilities, skills: agentSkills.map((skill) => ({ id: skill.id, name: skill.name, description: skill.description, inputModes: skill.inputModes, outputModes: skill.outputModes, category: skill.category, tags: skill.tags, examples: skill.examples })), supported_protocols: ['a2a-v1'], supported_modalities: ['text', 'json'], processing_modes: [processingMode], workflow_integration: { enabled: enableWorkflowIntegration, }, auth_mode: authMode, endpoint: endpoints.endpoint, tasks: endpoints.tasks, health: endpoints.health, timestamp: new Date().toISOString(), }; // Ensure proper URL formatting (remove trailing slash if present) const cleanRegistryUrl = registryUrl.replace(/\/+$/, ''); const registrationUrl = `${cleanRegistryUrl}/v1/agents`; const response = await fetch(registrationUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(agentCard), }); let result = null; // Check if response is JSON const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { result = await response.json(); } else { // Not JSON response, get as text const textResult = await response.text(); result = { error: 'Non-JSON response', content_type: contentType, response_text: textResult }; } // Add registration event to task queue for workflow if (enableWorkflowIntegration) { taskQueue.push({ event_type: 'a2a_server_registered', agent_id: agentId, registry_url: registryUrl, registration_successful: response.ok, agent_card: agentCard, registry_response: result, timestamp: new Date().toISOString(), }); } } catch (error) { // Registry registration failed - continue without registry } }; app.get('/health', (req, res) => res.json({ status: 'healthy', agent_id: agentId, agent_type: 'n8n_a2a_agent', a2a_protocol_version: '1.0', processing_mode: processingMode, workflow_integration: enableWorkflowIntegration, auth_mode: authMode, active_tasks: taskStore.size, uptime_seconds: Math.floor(process.uptime()), timestamp: new Date().toISOString(), })); app.get('/capabilities', (req, res) => res.json({ agent_id: agentId, name: `N8N A2A Agent ${agentId}`, description: 'N8N-powered A2A agent with workflow integration', version: '1.0.0', a2a_protocol_version: '1.0', capabilities: agentCapabilities, skills: agentSkills.map((skill) => ({ id: skill.id, name: skill.name, description: skill.description, inputModes: skill.inputModes, outputModes: skill.outputModes, category: skill.category, tags: skill.tags, examples: skill.examples })), supported_protocols: ['a2a-v1'], supported_modalities: ['text', 'json'], processing_modes: [processingMode], workflow_integration: { enabled: enableWorkflowIntegration, }, auth_mode: authMode, endpoint: endpoints.endpoint, tasks: endpoints.tasks, health: endpoints.health, timestamp: new Date().toISOString(), })); app.post('/tasks', async (req, res) => { try { // Validate JSON-RPC 2.0 request format if (!req.body.jsonrpc || req.body.jsonrpc !== "2.0") { return res.status(400).json({ jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request", data: "Missing or invalid jsonrpc field. Expected '2.0'." }, id: req.body.id || null }); } const supportedMethods = ["submitTask", "getTaskStatus", "getCapabilities"]; if (!req.body.method || !supportedMethods.includes(req.body.method)) { return res.status(400).json({ jsonrpc: "2.0", error: { code: -32601, message: "Method not found", data: `Unsupported method: ${req.body.method}. Supported methods: ${supportedMethods.join(', ')}.` }, id: req.body.id || null }); } if (!req.body.params) { return res.status(400).json({ jsonrpc: "2.0", error: { code: -32602, message: "Invalid params", data: "Missing params object in JSON-RPC request." }, id: req.body.id || null }); } const params = req.body.params; const rpcId = req.body.id; const method = req.body.method; // Handle different JSON-RPC 2.0 methods if (method === "getTaskStatus") { // Handle task status request const taskId = params.task_id; const task = taskStore.get(taskId); if (!task) { return res.status(404).json({ jsonrpc: "2.0", error: { code: -32602, message: "Task not found", data: `No task found with ID: ${taskId}` }, id: rpcId }); } return res.json({ jsonrpc: "2.0", result: Object.assign(Object.assign(Object.assign(Object.assign({ task_id: taskId, session_id: task.session_id, status: task.status, agent_id: agentId, created_at: task.created_at, processing_mode: task.processing_mode, task_type: task.task_type, a2a_protocol_version: '1.0' }, (task.workflow_result && { workflow_result: task.workflow_result })), (task.completed_at && { completed_at: task.completed_at })), (task.client_info && { client_info: task.client_info })), { timestamp: new Date().toISOString() }), id: rpcId }); } else if (method === "getCapabilities") { // Handle capabilities request return res.json({ jsonrpc: "2.0", result: { agent_id: agentId, name: `N8N A2A Agent ${agentId}`, description: 'N8N-powered A2A agent with workflow integration', version: '1.0.0', a2a_protocol_version: '1.0', capabilities: agentCapabilities, skills: agentSkills.map((skill) => ({ id: skill.id, name: skill.name, description: skill.description, inputModes: skill.inputModes, outputModes: skill.outputModes, category: skill.category, tags: skill.tags, examples: skill.examples })), supported_protocols: ['a2a-v1'], supported_modalities: ['text', 'json'], processing_modes: [processingMode], workflow_integration: { enabled: enableWorkflowIntegration, }, auth_mode: authMode, endpoint: endpoints.endpoint, tasks: endpoints.tasks, health: endpoints.health, timestamp: new Date().toISOString(), }, id: rpcId }); } else if (method === "submitTask") { // Handle task submission (existing logic) const taskId = `task_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`; const sessionId = req.headers['x-a2a-session-id'] || `session_${Date.now()}`; const correlationId = req.headers['x-a2a-correlation-id'] || taskId; const requestedMode = params.processing_mode || req.headers['x-a2a-processing-mode'] || processingMode; // Determine if we should process synchronously const shouldProcessSync = requestedMode === 'sync' || (processingMode === 'sync') || (processingMode === 'mixed' && requestedMode === 'sync'); const a2aTask = { task_id: taskId, session_id: sessionId, correlation_id: correlationId, agent_id: agentId, created_at: new Date().toISOString(), processing_mode: requestedMode, task_type: params.type || 'general', description: params.description || 'A2A task processing', input_data: params, context: params.context || {}, required_capabilities: params.required_capabilities || [], client_info: { user_agent: req.headers['user-agent'], ip_address: req.ip, a2a_client_id: req.headers['x-a2a-client-id'], }, status: 'received', a2a_protocol_version: '1.0', jsonrpc_id: rpcId, }; taskStore.set(taskId, a2aTask); // FORWARD CLIENT'S TASK DATA to next workflow node for processing if (enableWorkflowIntegration) { if (shouldProcessSync) { // SYNC MODE: Emit to workflow and wait for results // Update task status to processing a2aTask.status = 'processing'; taskStore.set(taskId, a2aTask); // Create workflow data with CLIENT DATA AT ROOT LEVEL const workflowData = Object.assign(Object.assign({ // ESSENTIAL FIELDS - Always available task_id: taskId, session_id: sessionId }, a2aTask.input_data), { // CORE A2A FIELDS (without a2a_ prefix for common fields) task_type: a2aTask.task_type, description: a2aTask.description, context: a2aTask.context, processing_mode: requestedMode, created_at: a2aTask.created_at, // A2A PROTOCOL METADATA (with a2a_ prefix) a2a_correlation_id: correlationId, a2a_agent_id: agentId, a2a_session_id: sessionId, a2a_required_capabilities: a2aTask.required_capabilities, a2a_status: 'processing', a2a_callback_url: `${callbackUrlBase}/${taskId}/status`, a2a_protocol_version: '1.0', a2a_sync_mode: true, // WORKFLOW METADATA workflow_integration: true, event_type: 'task_received', timestamp: new Date().toISOString(), client_info: a2aTask.client_info, // SYNC PROCESSING GUIDANCE _workflow_instructions: { sync_mode: true, callback_required: true, callback_url: `${callbackUrlBase}/${taskId}/status`, note: 'Sync mode - please callback with results, client is waiting' } }); // Add to queue for emission in proper trigger context taskQueue.push(workflowData); // WAIT FOR WORKFLOW RESULTS (polling approach) const maxWaitTime = 30000; // 30 seconds max const pollInterval = 200; // Check every 200ms const startTime = Date.now(); // Wait for workflow to complete and callback with results const waitForResults = new Promise((resolve, reject) => { const checkForResults = () => { const updatedTask = taskStore.get(taskId); const elapsedTime = Date.now() - startTime; if (updatedTask && updatedTask.status === 'completed' && updatedTask.result) { resolve(updatedTask.result); } else if (elapsedTime > maxWaitTime) { reject(new Error(`Workflow processing timeout after ${maxWaitTime}ms`)); } else { // Continue polling setTimeout(checkForResults, pollInterval); } }; // Start polling setTimeout(checkForResults, pollInterval); }); try { // Wait for workflow results const workflowResult = await waitForResults; // Update final task status const finalTask = taskStore.get(taskId); if (finalTask) { finalTask.status = 'completed'; finalTask.completed_at = new Date().toISOString(); taskStore.set(taskId, finalTask); } // Return JSON-RPC 2.0 workflow results in sync response res.status(200).json({ jsonrpc: "2.0", result: { task_id: taskId, session_id: sessionId, correlation_id: correlationId, status: 'completed', processing_mode: 'synchronous_workflow', processing_location: 'n8n_workflow', processing_time_ms: Date.now() - new Date(a2aTask.created_at).getTime(), workflow_result: workflowResult, // This is the workflow output! completed_at: new Date().toISOString(), a2a_protocol_version: '1.0', agent_id: agentId, workflow_processing: { workflow_completed: true, workflow_result_available: true, processing_method: 'workflow_callback' } }, id: rpcId }); } catch (error) { // JSON-RPC 2.0 sync processing timeout or error res.status(408).json({ jsonrpc: "2.0", error: { code: -32603, message: "Workflow processing timeout", data: { task_id: taskId, session_id: sessionId, correlation_id: correlationId, status: 'timeout', processing_mode: 'synchronous_workflow_timeout', processing_time_ms: Date.now() - new Date(a2aTask.created_at).getTime(), a2a_protocol_version: '1.0', agent_id: agentId, workflow_processing: { workflow_timeout: true, max_wait_time_ms: maxWaitTime, suggestion: 'Try async mode for long-running workflows' }, original_error: error.message } }, id: rpcId }); } } else { // ASYNC MODE: Emit to workflow for processing // Update task status to processing (will be processed by workflow) a2aTask.status = 'processing'; taskStore.set(taskId, a2aTask); // Create workflow data with CLIENT DATA AT ROOT LEVEL const workflowData = Object.assign(Object.assign({ // ESSENTIAL FIELDS - Always available task_id: taskId, session_id: sessionId }, a2aTask.input_data), { // CORE A2A FIELDS (without a2a_ prefix for common fields) task_type: a2aTask.task_type, description: a2aTask.description, context: a2aTask.context, processing_mode: requestedMode, created_at: a2aTask.created_at, // A2A PROTOCOL METADATA (with a2a_ prefix) a2a_correlation_id: correlationId, a2a_agent_id: agentId, a2a_session_id: sessionId, a2a_required_capabilities: a2aTask.required_capabilities, a2a_status: 'processing', a2a_callback_url: `${callbackUrlBase}/${taskId}/status`, a2a_protocol_version: '1.0', a2a_sync_mode: false, // WORKFLOW METADATA workflow_integration: true, event_type: 'task_received', timestamp: new Date().toISOString(), client_info: a2aTask.client_info, // ASYNC PROCESSING GUIDANCE _workflow_instructions: { sync_mode: false, callback_optional: true, callback_url: `${callbackUrlBase}/${taskId}/status`, note: 'Async mode - callback optional but recommended for status updates' } }); // Add to queue for emission in proper trigger context taskQueue.push(workflowData); // Return JSON-RPC 2.0 immediate accepted response for async mode res.status(202).json({ jsonrpc: "2.0", result: { task_id: taskId, session_id: sessionId, correlation_id: correlationId, status: 'accepted', message: 'Task accepted and forwarded to N8N workflow for asynchronous processing', processing_mode: 'asynchronous_workflow', processing_location: 'n8n_workflow', status_url: `${endpoints.endpoint}/tasks/${taskId}`, a2a_protocol_version: '1.0', agent_id: agentId, estimated_completion: new Date(Date.now() + 60000).toISOString(), workflow_callback_info: { callback_url: `${callbackUrlBase}/${taskId}/status`, callback_optional: true, note: 'Workflow can optionally call back to update status/results' } }, id: rpcId }); } } } // Close submitTask method } catch (error) { res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Task processing failed", data: { original_error: error.message, a2a_protocol_version: '1.0', troubleshooting: { common_causes: [ 'Invalid task data format', 'Server configuration error', 'Network connectivity issue' ], documentation: 'See WORKFLOW-CONFIGURATION-GUIDE.md' } } }, id: req.body.id || null }); } }); app.get('/tasks/:taskId', (req, res) => { const task = taskStore.get(req.params.taskId); if (!task) return res.status(404).json({ error: 'Task not found', task_id: req.params.taskId, a2a_protocol_version: '1.0' }); if (enableWorkflowIntegration) { // Add status request to task queue for workflow taskQueue.push({ event_type: 'a2a_task_status_requested', task_id: req.params.taskId, task_details: task, workflow_integration: true, timestamp: new Date().toISOString(), }); } res.json(Object.assign(Object.assign(Object.assign(Object.assign({ task_id: req.params.taskId, session_id: task.session_id, status: task.status, agent_id: agentId, created_at: task.created_at, processing_mode: task.processing_mode, task_type: task.task_type, a2a_protocol_version: '1.0' }, (task.result && { result: task.result })), (task.completed_at && { completed_at: task.completed_at })), (task.client_info && { client_info: task.client_info })), { timestamp: new Date().toISOString() })); }); app.post('/tasks/:taskId/status', (req, res) => { const task = taskStore.get(req.params.taskId); if (!task) return res.status(404).json({ error: 'Task not found', task_id: req.params.taskId, a2a_protocol_version: '1.0' }); // Update task with callback results task.status = req.body.status || 'completed'; task.result = req.body.result; task.completed_at = req.body.completed_at || new Date().toISOString(); taskStore.set(req.params.taskId, task); res.json({ task_id: req.params.taskId, status: 'callback_received', message: 'Task status updated successfully', a2a_protocol_version: '1.0' }); }); const server = app.listen(port, async () => { // Store the server reference for this node instance (0, urlUtils_1.storeActiveServer)(nodeId, server, port, 'agent'); // Wait a moment for registry to be fully ready, then register setTimeout(async () => { await registerWithRegistry(); }, 2000); // 2 second delay }); // Handle server startup errors server.on('error', async (error) => { if (error.code === 'EADDRINUSE') { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `❌ Port ${port} is already in use. Please choose a different port for A2A Agent (${agentId}).`); } else { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `❌ A2A Agent server error: ${error.message}`); } }); return { closeFunction: async () => { server.close(); taskStore.clear(); clearInterval(taskProcessor); taskQueue.length = 0; // Clear task queue (0, urlUtils_1.cleanupActiveServer)(nodeId); // Clean up stored server reference }, manualTriggerFunction: async () => { this.emit([this.helpers.returnJsonArray([Object.assign(Object.assign({ event_type: 'a2a_server_ready', message: 'A2A Server is ready to receive tasks from clients', agent_id: agentId, a2a_protocol_version: '1.0' }, endpoints), { processing_mode: processingMode, workflow_integration: enableWorkflowIntegration, auth_mode: authMode, instructions: 'Send tasks to the /tasks endpoint. Client data will be forwarded to next workflow nodes.', timestamp: new Date().toISOString() })])]); }, }; } } exports.A2ARemoteAgent = A2ARemoteAgent;