ADR-0010: OpenRouter replaces Vercel AI Gateway for model routing
- Status: accepted
- Date: 2026-05-28
- Supersedes (in part): the gateway portion of ADR-0002. Vercel AI SDK is still the runtime SDK; only the routing layer changes.
Context
ADR-0002 picked Vercel AI Gateway for model routing because we needed:
- Provider-agnostic catalog so a Skill can swap
anthropic/...→openai/...with a string change. - Zero-markup pricing.
- BYOK support.
- Integrated observability in our existing Vercel dashboard.
That decision still holds on its merits — Gateway is genuinely good. But when we actually wired the real-model path into the simulator and the live runner, three issues surfaced that pushed us to OpenRouter:
-
Catalog scope. OpenRouter's catalog includes ~300+ models (including the smaller
gemini-3.1-flash-liteat $0.25/$1.50 per M tok that the sim cost model wants). Gateway has Vercel-blessed providers + models; community/long-tail models live elsewhere. -
Familiarity. The user already has an OpenRouter account, credit, and is comfortable with their pricing dashboard.
-
~latestaliases. OpenRouter exposes auto-updating per-family aliases (~anthropic/claude-sonnet-latest,~google/gemini-flash-latest). Trading skills that pick these get model upgrades automatically without re-saving the Skill payload — useful for our long-lived deployed agents.
The cost difference is small in either direction at our scale; this is a routing/catalog choice, not a cost choice.
Decision
All model calls from packages/agent-runtime route through OpenRouter via the @ai-sdk/openai-compatible provider (OpenRouter implements the OpenAI-compatible REST schema):
// packages/agent-runtime/src/model-provider.ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
const provider = createOpenAICompatible({
name: 'openrouter',
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
});The agent runtime's runSkill() resolves skill.model (a string like ~anthropic/claude-haiku-latest) into a LanguageModel via provider(modelId) before calling generateText.
AI_GATEWAY_API_KEY is removed from the env contract and Vercel project. OPENROUTER_API_KEY replaces it.
Alternatives considered
Alt A — Keep Vercel AI Gateway, just add gemini-flash-lite when Vercel adds it
- Works in steady state
- Means waiting for Vercel's catalog updates for any new model the user wants to try
- Not picked: we want a wider catalog now, not later.
Alt B — @openrouter/ai-sdk-provider (OpenRouter's official package)
- Vendor-specific features (fallback chains, BYOK passthrough)
- Tried first; v1.5.4's peer dep is pinned to AI SDK v5 and types fight v6 at the
LanguageModelboundary - Not picked: pure-AI-SDK
@ai-sdk/openai-compatibleis cleaner against v6 and the OpenAI-compat surface is sufficient for our flat tool-calling agent.
Alt C — Multiple providers with a router we write
- E.g. Claude direct + OpenAI direct + Gemini direct, each via their own AI SDK provider
- Maximum control
- More API keys, more catalog plumbing
- Not picked: OpenRouter does this for us with one key.
Consequences
Positive
- Bigger catalog. Skill authors can pick from ~300 models without us shipping new code.
- Auto-updating aliases.
~latestids mean a deployed Skill rides forward when providers release new tiers. - Lower friction for the user. One key, one dashboard, no new vendor onboarding.
- Cleaner provider type chain.
@ai-sdk/openai-compatibleis part of AI SDK proper — no peer-dep mismatches.
Negative / trade-offs
- No longer using Vercel AI Gateway. Our existing $5/mo free Gateway credits go unused (small loss).
- OpenRouter charges a markup over provider list prices (~5.5% in their default mode; BYOK eliminates it). Acceptable; Gateway's "zero markup" was a real cost win that we're giving back.
- Lock-in shift. Our single key now goes to OpenRouter. If they have an outage, all model calls fail. The contract surface is OpenAI-compatible so swapping in another OpenAI-compatible provider (Together, Fireworks, even direct OpenAI) is a baseURL change.
~latestaliases mean Skills aren't perfectly reproducible long-term — a sim run on Monday may differ from one on Wednesday if Sonnet 4.x ships a refresh. Pinned ids are still allowed for users who care.
Things we'll need to revisit
- If OpenRouter pricing drifts up materially, evaluate going direct (Claude / OpenAI / Gemini SDKs).
- If we want per-skill BYOK so users pay their own provider costs, OpenRouter supports it (forward
X-Provider-Keyheader). Surface in skill editor at that point. - The
resolveModelprovider is module-scoped lazy-init; if we ever want per-skill provider config (e.g. user BYOK), we'll pass a provider factory throughRunSkillInputinstead.
References
packages/agent-runtime/src/model-provider.ts— the integrationapps/web/components/skill-editor/model-catalog.ts— curated UI catalog- ADR-0002 — the original Gateway decision this refines
- OpenRouter API docs: https://openrouter.ai/docs/api-reference