UNPKG

@xynehq/jaf

Version:

Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools

211 lines 9.54 kB
/** * Pure functional JSON-RPC protocol handlers for A2A * All handlers are pure functions with no side effects */ import { A2AErrorCodes } from './types.js'; import { executeA2AAgent, executeA2AAgentWithStreaming } from './executor.js'; import { sendMessageRequestSchema } from './types.js'; // Pure function to validate JSON-RPC request export const validateJSONRPCRequest = (request) => { return (typeof request === 'object' && request !== null && request.jsonrpc === '2.0' && (typeof request.id === 'string' || typeof request.id === 'number') && typeof request.method === 'string'); }; // Pure function to create JSON-RPC success response export const createJSONRPCSuccessResponse = (id, result) => ({ jsonrpc: '2.0', id, result }); // Pure function to create JSON-RPC error response export const createJSONRPCErrorResponse = (id, error) => ({ jsonrpc: '2.0', id, error }); // Pure function to create A2A error export const createA2AError = (code, message, data) => ({ code, message, data }); // Pure function to map JavaScript errors to A2A errors export const mapErrorToA2AError = (error) => { if (error instanceof Error) { return createA2AError(A2AErrorCodes.INTERNAL_ERROR, error.message, { stack: error.stack }); } return createA2AError(A2AErrorCodes.INTERNAL_ERROR, typeof error === 'string' ? error : 'Unknown error occurred'); }; // Pure function to validate send message request export const validateSendMessageRequest = (request) => { try { const validatedRequest = sendMessageRequestSchema.parse(request); return { isValid: true, data: validatedRequest }; } catch (error) { return { isValid: false, error: createA2AError(A2AErrorCodes.INVALID_PARAMS, 'Invalid send message request parameters', error) }; } }; // Pure function to handle message/send method export const handleMessageSend = async (request, agent, modelProvider) => { try { const context = { message: request.params.message, sessionId: request.params.message.contextId || `session_${Date.now()}`, metadata: request.params.metadata }; const result = await executeA2AAgent(context, agent, modelProvider); if (result.error) { return createJSONRPCErrorResponse(request.id, createA2AError(A2AErrorCodes.INTERNAL_ERROR, result.error)); } return createJSONRPCSuccessResponse(request.id, result.finalTask || { message: 'No result available' }); } catch (error) { return createJSONRPCErrorResponse(request.id, mapErrorToA2AError(error)); } }; // Pure function to handle message/stream method export const handleMessageStream = async function* (request, agent, modelProvider) { try { const context = { message: request.params.message, sessionId: request.params.message.contextId || `session_${Date.now()}`, metadata: request.params.metadata }; for await (const event of executeA2AAgentWithStreaming(context, agent, modelProvider)) { yield createJSONRPCSuccessResponse(request.id, event); } } catch (error) { yield createJSONRPCErrorResponse(request.id, mapErrorToA2AError(error)); } }; // Pure function to handle tasks/get method export const handleTasksGet = async (request, taskProvider) => { try { const taskResult = await taskProvider.getTask(request.params.id); if (!taskResult.success) { return createJSONRPCErrorResponse(request.id, createA2AError(A2AErrorCodes.INTERNAL_ERROR, `Failed to get task: ${taskResult.error.message}`)); } const task = taskResult.data; if (!task) { return createJSONRPCErrorResponse(request.id, createA2AError(A2AErrorCodes.TASK_NOT_FOUND, `Task with id ${request.params.id} not found`)); } // Apply history length limit if specified let resultTask = task; if (request.params.historyLength && task.history) { const limitedHistory = task.history.slice(-request.params.historyLength); resultTask = { ...task, history: limitedHistory }; } return createJSONRPCSuccessResponse(request.id, resultTask); } catch (error) { return createJSONRPCErrorResponse(request.id, mapErrorToA2AError(error)); } }; // Pure function to handle tasks/cancel method export const handleTasksCancel = async (request, taskProvider) => { try { const taskResult = await taskProvider.getTask(request.params.id); if (!taskResult.success) { return createJSONRPCErrorResponse(request.id, createA2AError(A2AErrorCodes.INTERNAL_ERROR, `Failed to get task: ${taskResult.error.message}`)); } const task = taskResult.data; if (!task) { return createJSONRPCErrorResponse(request.id, createA2AError(A2AErrorCodes.TASK_NOT_FOUND, `Task with id ${request.params.id} not found`)); } // Check if task can be canceled if (task.status.state === 'completed' || task.status.state === 'failed' || task.status.state === 'canceled') { return createJSONRPCErrorResponse(request.id, createA2AError(A2AErrorCodes.TASK_NOT_CANCELABLE, `Task ${request.params.id} cannot be canceled in state ${task.status.state}`)); } // Update task status to canceled const updateResult = await taskProvider.updateTaskStatus(request.params.id, 'canceled', undefined, new Date().toISOString()); if (!updateResult.success) { return createJSONRPCErrorResponse(request.id, createA2AError(A2AErrorCodes.INTERNAL_ERROR, `Failed to cancel task: ${updateResult.error.message}`)); } // Get the updated task to return const updatedTaskResult = await taskProvider.getTask(request.params.id); const canceledTask = updatedTaskResult.success ? updatedTaskResult.data : task; return createJSONRPCSuccessResponse(request.id, canceledTask); } catch (error) { return createJSONRPCErrorResponse(request.id, mapErrorToA2AError(error)); } }; // Pure function to handle agent/getAuthenticatedExtendedCard method export const handleGetAuthenticatedExtendedCard = async (request, agentCard) => { try { // In a real implementation, this would check authentication // For now, return the standard agent card return createJSONRPCSuccessResponse(request.id, agentCard); } catch (error) { return createJSONRPCErrorResponse(request.id, mapErrorToA2AError(error)); } }; // Pure function to route A2A requests export const routeA2ARequest = (request, agent, modelProvider, taskProvider, agentCard) => { if (!validateJSONRPCRequest(request)) { return Promise.resolve(createJSONRPCErrorResponse(request.id || null, createA2AError(A2AErrorCodes.INVALID_REQUEST, 'Invalid JSON-RPC request'))); } // request is now confirmed to be JSONRPCRequest const validRequest = request; switch (validRequest.method) { case 'message/send': { const validation = validateSendMessageRequest(validRequest); if (!validation.isValid) { return Promise.resolve(createJSONRPCErrorResponse(validRequest.id, validation.error)); } return handleMessageSend(validation.data, agent, modelProvider); } case 'message/stream': { const validation = validateSendMessageRequest(validRequest); if (!validation.isValid) { return (async function* () { yield createJSONRPCErrorResponse(validRequest.id, validation.error); })(); } // Convert to streaming request const streamingRequest = { ...validation.data, method: 'message/stream' }; return handleMessageStream(streamingRequest, agent, modelProvider); } case 'tasks/get': return handleTasksGet(validRequest, taskProvider); case 'tasks/cancel': return handleTasksCancel(validRequest, taskProvider); case 'agent/getAuthenticatedExtendedCard': return handleGetAuthenticatedExtendedCard(validRequest, agentCard); default: return Promise.resolve(createJSONRPCErrorResponse(validRequest.id, createA2AError(A2AErrorCodes.METHOD_NOT_FOUND, `Method ${validRequest.method} not found`))); } }; // Pure function to create protocol handler configuration export const createProtocolHandlerConfig = (agents, modelProvider, agentCard, taskProvider) => ({ agents, modelProvider, agentCard, taskProvider, // Pure function to handle any A2A request handleRequest: (request, agentName) => { const agent = agentName ? agents.get(agentName) : agents.values().next().value; if (!agent) { return Promise.resolve(createJSONRPCErrorResponse(request.id || null, createA2AError(A2AErrorCodes.INVALID_PARAMS, `Agent ${agentName} not found`))); } if (!taskProvider) { return Promise.resolve(createJSONRPCErrorResponse(request.id || null, createA2AError(A2AErrorCodes.INTERNAL_ERROR, 'No task provider configured'))); } return routeA2ARequest(request, agent, modelProvider, taskProvider, agentCard); } }); //# sourceMappingURL=protocol.js.map