@xynehq/jaf
Version:
Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools
211 lines • 9.54 kB
JavaScript
/**
* 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