Agentic Trading
Decisions

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:

  1. Catalog scope. OpenRouter's catalog includes ~300+ models (including the smaller gemini-3.1-flash-lite at $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.

  2. Familiarity. The user already has an OpenRouter account, credit, and is comfortable with their pricing dashboard.

  3. ~latest aliases. 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 LanguageModel boundary
  • Not picked: pure-AI-SDK @ai-sdk/openai-compatible is 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. ~latest ids 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-compatible is 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.
  • ~latest aliases 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-Key header). Surface in skill editor at that point.
  • The resolveModel provider is module-scoped lazy-init; if we ever want per-skill provider config (e.g. user BYOK), we'll pass a provider factory through RunSkillInput instead.

References

On this page