UNPKG

@dexwox-labs/a2a-server

Version:

TypeScript server implementation for Google's Agent-to-Agent (A2A) protocol - includes Express/WebSocket handlers, request validation and queue management

205 lines (190 loc) 5.85 kB
/** * @module A2AServer * @description Server implementation for hosting A2A protocol agents */ import express from 'express'; import { json } from 'body-parser'; import cors from 'cors'; import { WebSocketServer } from 'ws'; import type { Express, Request, Response, NextFunction, ErrorRequestHandler } from 'express'; import { A2AError, AgentCard } from '@dexwox-labs/a2a-core'; import { RequestHandler } from './request-handler'; /** * Server implementation for hosting A2A protocol agents * * The A2AServer class provides a complete HTTP and WebSocket server implementation * for hosting agents that implement the A2A protocol. It handles request routing, * error handling, and WebSocket connections for streaming. * * @example * ```typescript * import { A2AServer, DefaultRequestHandler } from '@dexwox-labs/a2a-node'; * * // Define an agent * const agent = { * id: 'weather-agent', * name: 'Weather Agent', * description: 'Provides weather information', * capabilities: ['weather-forecasting'], * endpoint: 'http://localhost:3000' * }; * * // Create a request handler * const requestHandler = new DefaultRequestHandler([agent]); * * // Create and start the server * const server = new A2AServer(agent, requestHandler); * server.start(3000); * ``` */ export class A2AServer { /** Express application instance */ private readonly app: ReturnType<typeof express>; /** WebSocket server instance */ private wss: WebSocketServer | null = null; /** Agent card for this server */ private readonly agentCard: AgentCard; /** Handler for processing incoming requests */ private readonly requestHandler: RequestHandler; /** * Creates a new A2AServer instance * * @param agentCard - The agent card describing this server's capabilities * @param requestHandler - Handler for processing incoming requests * @param contextMiddleware - Optional custom middleware for request context */ constructor( agentCard: AgentCard, requestHandler: RequestHandler, private readonly contextMiddleware?: (req: Request, res: Response, next: NextFunction) => void ) { this.app = express(); this.agentCard = agentCard; this.requestHandler = requestHandler; this.configureMiddleware(); this.configureRoutes(); this.configureErrorHandling(); } /** * Configures Express middleware for the server * * Sets up JSON parsing, CORS, and request context middleware. * @private */ private configureMiddleware(): void { this.app.use(json() as express.RequestHandler); this.app.use(cors({ origin: process.env.CORS_ORIGIN || '*', methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] }) as express.RequestHandler); // Add context middleware if provided, otherwise use default if (this.contextMiddleware) { this.app.use(this.contextMiddleware); } else { this.app.use(require('./agent-execution/context-middleware').contextMiddleware(this.agentCard.id)); } } /** * Configures HTTP routes for the server * * Sets up the agent card endpoint and API routes. * @private */ private configureRoutes(): void { // Agent card endpoint this.app.get('/.well-known/agent.json', (_: Request, res: Response) => { res.json(this.agentCard); }); // API routes this.app.use('/api/v1', this.requestHandler.router); } /** * Configures global error handling for the server * * Sets up middleware to catch and format errors according to the A2A protocol. * @private */ private configureErrorHandling(): void { const errorHandler: ErrorRequestHandler = ( err: unknown, req: Request, res: Response, next: NextFunction ) => { if (err instanceof Error && 'code' in err && 'message' in err) { const a2aError = err as A2AError; res.status(400).json({ jsonrpc: '2.0', error: { code: a2aError.code, message: a2aError.message, ...(a2aError.data && { data: a2aError.data }) } }); } console.error(err); res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' } }); }; this.app.use(errorHandler); } /** * Starts the A2A server on the specified port * * This method starts both the HTTP server and WebSocket server for handling * A2A protocol requests. The WebSocket server is used for streaming messages. * * @param port - The port to listen on (default: 3000) * * @example * ```typescript * // Start on the default port (3000) * server.start(); * * // Start on a specific port * server.start(8080); * ``` */ public start(port: number = 3000): void { const server = this.app.listen(port, () => { console.log(`Server running on port ${port}`); }); // Create WebSocket server this.wss = new WebSocketServer({ server }); this.wss.on('connection', (ws) => { console.log('New WebSocket connection'); ws.on('message', async (data) => { try { const message = JSON.parse(data.toString()); if (message.method === 'streamMessage') { for await (const part of this.requestHandler.handleStreamMessage( message.params.parts, message.params.agentId )) { ws.send(JSON.stringify(part)); } } } catch (err) { ws.send(JSON.stringify( this.requestHandler.normalizeError(err) )); } }); ws.on('close', () => { console.log('WebSocket connection closed'); }); }); } }