// =============================================================================
// FOOD DELIVERY AGENTS (A2A Protocol)
// =============================================================================
// Mock agents for DoorDash, UberEats, and Grubhub.
//
// These demonstrate A2A (Agent-to-Agent) protocol:
// - Each agent registers with the registry
// - Other agents discover them via registry
// - Communication via JSON-RPC 2.0 over HTTP
//
// KEY DIFFERENCE FROM AP2:
// - A2A: No mandates needed, agents freely communicate
// - AP2: Requires user-signed mandates for payments (see stripe-agent.ts)
// =============================================================================
import { createServer } from 'http';
import type { AgentCard, JSONRPCRequest, JSONRPCResponse, ServiceType } from '../../src/types.js';
import { getMockRestaurants, getMockMenu } from '../../mock-data/restaurants.js';
// =============================================================================
// CONFIG
// =============================================================================
const REGISTRY_URL = `http://${process.env.REGISTRY_HOST || 'localhost'}:8004`;
interface AgentConfig {
agentId: string;
name: string;
port: number;
service: ServiceType;
capabilities: string[];
}
// =============================================================================
// FOOD DELIVERY AGENT CLASS
// =============================================================================
class FoodDeliveryAgent {
private agentId: string;
private name: string;
private port: number;
private service: ServiceType;
private capabilities: string[];
private httpServer: ReturnType<typeof createServer> | null = null;
constructor(config: AgentConfig) {
this.agentId = config.agentId;
this.name = config.name;
this.port = config.port;
this.service = config.service;
this.capabilities = config.capabilities;
}
// ---------------------------------------------------------------------------
// START SERVER & REGISTER WITH REGISTRY
// ---------------------------------------------------------------------------
async start(): Promise<void> {
// Create HTTP server for JSON-RPC requests
this.httpServer = createServer(async (req, res) => {
if (req.method === 'POST') {
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(chunk);
const body = JSON.parse(Buffer.concat(chunks).toString());
const response = await this.handleRequest(body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(response));
} else {
res.writeHead(404);
res.end();
}
});
this.httpServer.listen(this.port, '0.0.0.0', () => {
console.log(`🍔 ${this.name} running on port ${this.port}`);
});
// Register with A2A registry so other agents can discover us
const card: AgentCard = {
agentId: this.agentId,
name: this.name,
version: '1.0.0',
endpoint: `http://${this.agentId.replace('-agent-001', '-agent')}:${this.port}`,
capabilities: this.capabilities,
};
await fetch(`${REGISTRY_URL}/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(card),
});
console.log(`🍔 ${this.name} registered with registry`);
}
// ---------------------------------------------------------------------------
// HANDLE JSON-RPC REQUEST
// ---------------------------------------------------------------------------
private async handleRequest(request: JSONRPCRequest): Promise<JSONRPCResponse> {
const { method, params, id } = request;
const p = params as Record<string, unknown>;
let result: unknown;
switch (method) {
case 'agent/search_restaurants':
result = await this.searchRestaurants(p.query as string);
break;
case 'agent/get_menu':
result = await this.getMenu(p.restaurantId as string);
break;
case 'agent/get_delivery_estimate':
result = await this.getDeliveryEstimate();
break;
case 'agent/place_order':
result = await this.placeOrder();
break;
case 'agent/check_order_status':
result = await this.checkOrderStatus(p.orderId as string);
break;
default:
return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown method: ${method}` } };
}
return { jsonrpc: '2.0', id, result };
}
// ---------------------------------------------------------------------------
// AGENT METHODS (returns mock data)
// ---------------------------------------------------------------------------
private async searchRestaurants(query: string) {
return { restaurants: getMockRestaurants(query, this.service), service: this.service };
}
private async getMenu(restaurantId: string) {
return { menu: getMockMenu(restaurantId), service: this.service };
}
private async getDeliveryEstimate() {
return {
estimatedTime: 25 + Math.floor(Math.random() * 20),
deliveryFee: 2.99 + Math.random() * 4,
minimumOrder: 15,
service: this.service,
};
}
private async placeOrder() {
return {
orderId: `${this.service}-${Date.now()}`,
status: 'confirmed',
service: this.service,
};
}
private async checkOrderStatus(orderId: string) {
return {
orderId,
status: 'preparing',
currentStage: 'Restaurant is preparing your order',
service: this.service,
};
}
stop(): void {
this.httpServer?.close();
}
}
// =============================================================================
// AGENT INSTANCES
// =============================================================================
const doorDashAgent = new FoodDeliveryAgent({
agentId: 'doordash-agent-001',
name: 'DoorDash Agent',
service: 'doordash',
port: 8001,
capabilities: ['search_restaurants', 'get_menu', 'get_delivery_estimate', 'place_order', 'check_order_status'],
});
const uberEatsAgent = new FoodDeliveryAgent({
agentId: 'ubereats-agent-001',
name: 'UberEats Agent',
service: 'ubereats',
port: 8002,
capabilities: ['search_restaurants', 'get_menu', 'get_delivery_estimate', 'place_order', 'check_order_status'],
});
const grubhubAgent = new FoodDeliveryAgent({
agentId: 'grubhub-agent-001',
name: 'Grubhub Agent',
service: 'grubhub',
port: 8003,
capabilities: ['search_restaurants', 'get_menu', 'get_delivery_estimate', 'place_order', 'check_order_status'],
});
// =============================================================================
// START AGENT (based on env var)
// =============================================================================
const agentName = process.env.AGENT_NAME || 'doordash';
const agents: Record<string, FoodDeliveryAgent> = {
doordash: doorDashAgent,
ubereats: uberEatsAgent,
grubhub: grubhubAgent,
};
const agent = agents[agentName.toLowerCase()];
if (!agent) {
console.error(`Unknown agent: ${agentName}`);
process.exit(1);
}
agent.start().catch((error) => {
console.error(`Failed to start ${agentName} agent:`, error);
process.exit(1);
});