An OpenAI/Anthropic-compatible proxy that sits between your coding agent
(Claude Code, OpenCode, Cursor, plain SDKs) and agentrouter.org. It repairs
the quirks that make agents fail against that upstream: null-valued fields,
broken SSE frames, and false-positive content blocks.
Rendering mermaid diagram...
| File | Role |
|---|---|
serve.ts | Local Deno server (run this for real traffic) |
main.ts | Val Town HTTP entrypoint — same logic, hosted |
lib/proxy.ts | Core handler: headers, retry, stats, response finalize |
lib/clients.ts | Agent detection (Claude Code / OpenCode / Cursor / SDK / curl) |
lib/body.ts | Null stripping, preamble injection, context trimming |
lib/sse.ts | SSE stream repair |
- Agent + dialect detection — inspects
User-Agent,anthropic-version,x-api-key,x-appand the request path to classify each request asclaude-code,opencode,cursor,anthropic-sdk,openai-sdk,curl, orbrowser, and whether it speaks the OpenAI or Anthropic dialect. The detected identity is forwarded asX-Client-Kind/X-Client-Versionand counted in/_stats. - Dialect-aware preamble injection — the educational preamble goes into
messages[].role=systemfor OpenAI-style requests and into the top-levelsystemfield for Anthropic-style ones (string or text-block form). - Null stripping — recursively removes
nullfields (response_format: nulletc.) that AgentRouter rejects with400 expected object, received null. - SSE repair — drops
data: nullandbilling.summaryframes that crash strict AI-SDK parsers, while preserving event framing. - Content-block auto-retry — on a block-looking 400/403/451, retries once with context trimmed to system + last user message, and logs the offending message.
- Stats endpoint —
GET /_statsreports requests, blocks, retry outcomes, filtered SSE lines, and per-client / per-path counters. - UA modes —
opencode(default, the known-accepted UA),auto(per-client UA), orpassthrough(forward the client's own).
agentrouter.org is behind an Alibaba Cloud WAF that challenges datacenter
IPs with a JS captcha, so hosted requests come back as an HTML captcha page.
From your own machine it works normally.
# no download needed — Deno runs it straight from Val Town deno run --allow-net --allow-env https://esm.town/v/allanhorca07/agentr/serve.ts # or clone and run vt clone allanhorca07/agentr && cd agentr deno run --allow-net --allow-env serve.ts
Listens on http://localhost:4182 (pass a port as the first arg to change it).
| Var | Default | Purpose |
|---|---|---|
AGENTROUTER_API_KEY | — | Fallback key when the client sends none |
AGENTROUTER_UPSTREAM | https://agentrouter.org | Upstream base URL |
AGENTROUTER_DEBUG | off | 1 = log each request, drops, retries |
AGENTROUTER_UA_MODE | opencode | opencode | auto | passthrough |
curl http://localhost:4182/_stats curl http://localhost:4182/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $AGENTROUTER_API_KEY" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Say hello"}],"max_tokens":50}'
# OpenCode / OpenAI-compatible clients export OPENAI_BASE_URL=http://localhost:4182/v1 export OPENAI_API_KEY=sk-... # Claude Code export ANTHROPIC_BASE_URL=http://localhost:4182 export ANTHROPIC_API_KEY=sk-...
https://agentr.val.run runs the same code. /_stats works; proxied calls to
the upstream return the WAF captcha page because of the datacenter-IP block.
Use it to verify proxy behavior, not for real traffic.