Spaces:
Runtime error
Runtime error
| title: "OmniRoute A2A Server Documentation" | |
| version: 3.8.2 | |
| lastUpdated: 2026-05-13 | |
| # OmniRoute A2A Server Documentation | |
| > Agent-to-Agent Protocol v0.3 β OmniRoute as an intelligent routing agent | |
| The A2A surface has two faces: | |
| - **JSON-RPC 2.0** at `POST /a2a` (canonical entry point, defined in `src/app/a2a/route.ts`). | |
| - **REST** under `/api/a2a/*` for dashboards and tooling (status, task list, cancel). | |
| Tasks are tracked by `A2ATaskManager` (`src/lib/a2a/taskManager.ts`, default 5-minute TTL). Skills are dispatched via `A2A_SKILL_HANDLERS` in `src/lib/a2a/taskExecution.ts`. | |
| ## Agent Discovery | |
| ```bash | |
| curl http://localhost:20128/.well-known/agent.json | |
| ``` | |
| Returns the Agent Card describing OmniRoute's capabilities, skills, and authentication requirements. | |
| The Agent Card's `version` field is sourced from `process.env.npm_package_version` (see `src/app/.well-known/agent.json/route.ts:13`), so it stays auto-synced with `package.json` on every release. | |
| --- | |
| ## Authentication | |
| All `/a2a` requests require an API key via the `Authorization` header: | |
| ``` | |
| Authorization: Bearer YOUR_OMNIROUTE_API_KEY | |
| ``` | |
| If no API key is configured on the server, authentication is bypassed. | |
| ## Enablement | |
| A2A is controlled by the **Endpoints β A2A** toggle and is disabled by default. When disabled, | |
| `GET /api/a2a/status` reports `status: "disabled"` and `online: false`; JSON-RPC calls to | |
| `POST /a2a` return HTTP 503 with JSON-RPC error code `-32000`. | |
| --- | |
| ## JSON-RPC 2.0 Methods | |
| ### `message/send` β Synchronous Execution | |
| Sends a message to a skill and waits for the complete response. | |
| ```bash | |
| curl -X POST http://localhost:20128/a2a \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer YOUR_KEY" \ | |
| -d '{ | |
| "jsonrpc": "2.0", | |
| "id": "1", | |
| "method": "message/send", | |
| "params": { | |
| "skill": "smart-routing", | |
| "messages": [{"role": "user", "content": "Write a hello world in Python"}], | |
| "metadata": {"model": "auto", "combo": "fast-coding"} | |
| } | |
| }' | |
| ``` | |
| **Response:** | |
| ```json | |
| { | |
| "jsonrpc": "2.0", | |
| "id": "1", | |
| "result": { | |
| "task": { "id": "uuid", "state": "completed" }, | |
| "artifacts": [{ "type": "text", "content": "..." }], | |
| "metadata": { | |
| "routing_explanation": "Selected claude-sonnet via provider \"anthropic\" (latency: 1200ms, cost: $0.003)", | |
| "cost_envelope": { "estimated": 0.005, "actual": 0.003, "currency": "USD" }, | |
| "resilience_trace": [ | |
| { "event": "primary_selected", "provider": "anthropic", "timestamp": "..." } | |
| ], | |
| "policy_verdict": { "allowed": true, "reason": "within budget and quota limits" } | |
| } | |
| } | |
| } | |
| ``` | |
| ### `message/stream` β SSE Streaming | |
| Same as `message/send` but returns Server-Sent Events for real-time streaming. | |
| ```bash | |
| curl -N -X POST http://localhost:20128/a2a \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer YOUR_KEY" \ | |
| -d '{ | |
| "jsonrpc": "2.0", | |
| "id": "1", | |
| "method": "message/stream", | |
| "params": { | |
| "skill": "smart-routing", | |
| "messages": [{"role": "user", "content": "Explain quantum computing"}] | |
| } | |
| }' | |
| ``` | |
| **SSE Events:** | |
| ``` | |
| data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"working"},"chunk":{"type":"text","content":"..."}}} | |
| : heartbeat 2026-03-03T17:00:00Z | |
| data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"completed"},"metadata":{...}}} | |
| ``` | |
| ### `tasks/get` β Query Task Status | |
| ```bash | |
| curl -X POST http://localhost:20128/a2a \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer YOUR_KEY" \ | |
| -d '{"jsonrpc":"2.0","id":"2","method":"tasks/get","params":{"taskId":"TASK_UUID"}}' | |
| ``` | |
| ### `tasks/cancel` β Cancel a Task | |
| ```bash | |
| curl -X POST http://localhost:20128/a2a \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer YOUR_KEY" \ | |
| -d '{"jsonrpc":"2.0","id":"3","method":"tasks/cancel","params":{"taskId":"TASK_UUID"}}' | |
| ``` | |
| --- | |
| ## Available Skills | |
| OmniRoute exposes 6 A2A skills wired in `src/lib/a2a/taskExecution.ts::A2A_SKILL_HANDLERS`. Each skill module lives in `src/lib/a2a/skills/`. | |
| | Skill | ID | Description | Tags | Examples | | |
| | :------------------ | :-------------------- | :------------------------------------------------------------------------------------------------------------ | :---------------------- | :-------------------------------------- | | |
| | Smart Routing | `smart-routing` | Routes a prompt through the optimal provider/combo using OmniRoute's combo engine + scoring | routing, providers | "Route this prompt via the best model" | | |
| | Quota Management | `quota-management` | Reports per-provider quota state, helps callers decide when to throttle/switch | quota, providers | "Check quota for anthropic" | | |
| | Provider Discovery | `provider-discovery` | Lists installed providers with capabilities, free-tier flags, OAuth status | providers, discovery | "What providers are available?" | | |
| | Cost Analysis | `cost-analysis` | Estimates cost of a request/conversation given the catalog + recent usage | cost, usage | "Estimate cost for this conversation" | | |
| | Health Report | `health-report` | Aggregates circuit breaker, cooldown, lockout state per provider | health, resilience | "Show health status of all providers" | | |
| | List Capabilities | `list-capabilities` | Returns the full 42-entry Agent Skills catalog as a markdown table with raw SKILL.md URLs for context injection | catalog, discovery, skills | "List all OmniRoute capabilities" | | |
| > Note: the Agent Card description currently advertises "36+ providers" (`src/app/.well-known/agent.json/route.ts:26` and `:55`). The actual catalog has grown to 180+ providers β the string should be updated in a follow-up change (tracked as a separate doc/code TODO; not modified here). | |
| ### `list-capabilities` Skill Detail | |
| The `list-capabilities` skill is particularly useful for external agents that need to discover what OmniRoute exposes before sending API calls. It returns a structured markdown table artifact: | |
| ``` | |
| | ID | Name | Category | Area | Endpoints/Commands | Raw URL | | |
| | --- | --- | --- | --- | --- | --- | | |
| | omni-auth | Auth & Sessions | api | auth | POST /api/auth/login, ... | https://raw.githubusercontent.com/... | | |
| ... | |
| ``` | |
| Each row includes the `rawUrl` column so agents can immediately fetch the full SKILL.md. The `metadata.totalSkills` field is always `42`. Implementation: `src/lib/a2a/skills/listCapabilities.ts`. See also [AGENT-SKILLS.md](./AGENT-SKILLS.md). | |
| --- | |
| ## REST API (auxiliary) | |
| The JSON-RPC endpoint `/a2a` is the canonical A2A entry point. The REST endpoints below provide auxiliary access for dashboards and external tooling: | |
| | Endpoint | Method | Description | Auth | | |
| | :--------------------------- | :----- | :------------------------------- | :--------------------- | | |
| | `/api/a2a/status` | GET | Server status, registered skills | (public) | | |
| | `/api/a2a/tasks` | GET | List tasks with filters | management | | |
| | `/api/a2a/tasks/[id]` | GET | Get task by ID | management | | |
| | `/api/a2a/tasks/[id]/cancel` | POST | Cancel running task | management | | |
| | `/.well-known/agent.json` | GET | Agent Card (A2A discovery) | (public, cached 3600s) | | |
| --- | |
| ## Adding a New Skill | |
| 1. **Create skill file:** `src/lib/a2a/skills/<your-skill>.ts` | |
| Export an async function `(task: A2ATask) => Promise<{ artifacts, metadata }>`. Follow the shape of existing skills such as `smartRouting.ts`. | |
| 2. **Register handler:** in `src/lib/a2a/taskExecution.ts`, add an entry to `A2A_SKILL_HANDLERS`: | |
| ```typescript | |
| export const A2A_SKILL_HANDLERS = { | |
| // ...existing skills | |
| "your-skill": async (task) => { | |
| const skillModule = await import("./skills/yourSkill"); | |
| return skillModule.executeYourSkill(task); | |
| }, | |
| }; | |
| ``` | |
| 3. **Expose in Agent Card:** in `src/app/.well-known/agent.json/route.ts`, append to the `skills` array: | |
| ```json | |
| { | |
| "id": "your-skill", | |
| "name": "Your Skill", | |
| "description": "Brief, intent-focused description", | |
| "tags": ["routing", "quota"], | |
| "examples": ["Sample natural-language invocation"] | |
| } | |
| ``` | |
| 4. **Write tests:** `tests/unit/a2a-<your-skill>.test.ts`. Cover happy path + error path. | |
| 5. **Document** the new skill in this file's `Available Skills` table. | |
| --- | |
| ## Task TTL | |
| Tasks expire after `ttlMinutes` (default 5 min) β configured in the `A2ATaskManager` constructor at `src/lib/a2a/taskManager.ts:82`. To customize, fork the `A2ATaskManager` instantiation and pass a different value (e.g., `new A2ATaskManager(15)` for 15-minute TTL). A background interval sweeps expired tasks every 60 seconds. | |
| --- | |
| ## Task Lifecycle | |
| ``` | |
| submitted β working β completed | |
| β failed | |
| β cancelled | |
| ``` | |
| - Tasks expire after 5 minutes by default (see [Task TTL](#task-ttl)) | |
| - Terminal states: `completed`, `failed`, `cancelled` | |
| - Event log tracks every state transition | |
| --- | |
| ## Error Codes | |
| | Code | Meaning | | |
| | :----- | :----------------------------- | | |
| | -32700 | Parse error (invalid JSON) | | |
| | -32600 | Invalid request / Unauthorized | | |
| | -32601 | Method or skill not found | | |
| | -32602 | Invalid params | | |
| | -32603 | Internal error | | |
| | -32000 | A2A endpoint is disabled | | |
| --- | |
| ## Integration Examples | |
| ### Python (requests) | |
| ```python | |
| import requests | |
| resp = requests.post("http://localhost:20128/a2a", json={ | |
| "jsonrpc": "2.0", "id": "1", | |
| "method": "message/send", | |
| "params": { | |
| "skill": "smart-routing", | |
| "messages": [{"role": "user", "content": "Hello"}] | |
| } | |
| }, headers={"Authorization": "Bearer YOUR_KEY"}) | |
| result = resp.json()["result"] | |
| print(result["artifacts"][0]["content"]) | |
| print(result["metadata"]["routing_explanation"]) | |
| ``` | |
| ### TypeScript (fetch) | |
| ```typescript | |
| const resp = await fetch("http://localhost:20128/a2a", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: "Bearer YOUR_KEY", | |
| }, | |
| body: JSON.stringify({ | |
| jsonrpc: "2.0", | |
| id: "1", | |
| method: "message/send", | |
| params: { | |
| skill: "smart-routing", | |
| messages: [{ role: "user", content: "Hello" }], | |
| }, | |
| }), | |
| }); | |
| const { result } = await resp.json(); | |
| console.log(result.metadata.routing_explanation); | |
| ``` | |