// =============================================================================
// A2A CLIENT
// =============================================================================
// Discovers and calls ANY agent via A2A protocol (JSON-RPC 2.0 over HTTP).
// Used by tools.ts (food) and ap2-tools.ts (payments).
// =============================================================================
import type {
AgentCard,
JSONRPCResponse,
SearchResponse,
MenuResponse,
DeliveryResponse,
OrderResponse,
OrderStatusResponse,
OrderItem,
Restaurant,
Menu,
SearchParams,
MenuParams,
DeliveryParams,
PlaceOrderParams,
OrderStatusParams,
} from './interfaces.js';
const REGISTRY_URL = `http://${process.env.REGISTRY_HOST || 'localhost'}:8004`;
// =============================================================================
// CORE A2A FUNCTIONS
// =============================================================================
export async function discoverAgents(capability: string): Promise<string[]> {
const res = await fetch(`${REGISTRY_URL}/agents?capability=${capability}`);
const agents = await res.json() as AgentCard[];
return agents.map(agent => agent.agentId);
}
export async function callAgent<TParams, TResult>(
agentId: string,
method: string,
params: TParams
): Promise<TResult> {
const res = await fetch(`${REGISTRY_URL}/agents`);
const agents = await res.json() as AgentCard[];
const agent = agents.find(a => a.agentId === agentId);
if (!agent) {
throw new Error(`Agent not found: ${agentId}`);
}
const response = await fetch(agent.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: Date.now(),
method,
params,
}),
});
const data = await response.json() as JSONRPCResponse;
if (data.error) {
throw new Error(data.error.message);
}
return data.result as TResult;
}
// =============================================================================
// FOOD AGENT HELPERS
// =============================================================================
function getFoodAgentId(id: string): string {
if (id.startsWith('dd-') || id.startsWith('doordash')) return 'doordash-agent-001';
if (id.startsWith('ue-') || id.startsWith('ubereats')) return 'ubereats-agent-001';
return 'grubhub-agent-001';
}
// =============================================================================
// FOOD FUNCTIONS
// =============================================================================
export async function searchRestaurants(query: string, location: string): Promise<Restaurant[]> {
const agents = await discoverAgents('search_restaurants');
const results = await Promise.all(
agents.map(agentId =>
callAgent<SearchParams, SearchResponse>(agentId, 'agent/search_restaurants', { query, location })
)
);
return results.flatMap(r => r.restaurants);
}
export async function getMenu(restaurantId: string): Promise<Menu> {
const result = await callAgent<MenuParams, MenuResponse>(
getFoodAgentId(restaurantId),
'agent/get_menu',
{ restaurantId }
);
return result.menu;
}
export async function getDeliveryEstimate(restaurantId: string, location: string): Promise<DeliveryResponse> {
return callAgent<DeliveryParams, DeliveryResponse>(
getFoodAgentId(restaurantId),
'agent/get_delivery_estimate',
{ restaurantId, location }
);
}
export async function placeOrder(restaurantId: string, items: OrderItem[], deliveryAddress: string): Promise<OrderResponse> {
return callAgent<PlaceOrderParams, OrderResponse>(
getFoodAgentId(restaurantId),
'agent/place_order',
{ restaurantId, items, deliveryAddress }
);
}
export async function checkOrderStatus(orderId: string): Promise<OrderStatusResponse> {
return callAgent<OrderStatusParams, OrderStatusResponse>(
getFoodAgentId(orderId),
'agent/check_order_status',
{ orderId }
);
}