feat: OpenRouter provider adapter - #226
Merged
Merged
Conversation
gadievron
requested review from
dgeyshis,
shahar-davidson and
sounil
as code owners
August 11, 2026 12:11
Adds `openrouter` as a first-class provider type. OpenRouter fronts many model providers behind one OpenAI-compatible endpoint, one API key, and one prepaid balance, so the adapter is a thin delegation layer over the OpenAI adapter's translation helpers. What it adds is exactly the OpenRouter-specific surface: - default base_url https://openrouter.ai/api/v1 (still overridable); api_key falls back to OPENROUTER_API_KEY and fails loud when unresolvable, never falling through to the SDK's OPENAI_API_KEY - HTTP-Referer / X-Title attribution headers - error-mapping deltas, live-verified where possible: 400 "not a valid model ID" -> LLMNotFoundError (OpenRouter does not 404 for unknown models), 402 credits-exhausted -> LLMAuthError with a top-up hint, 403 moderation-flag -> LLMRefusalError, and finish_reason == "error" (mid-generation provider failure) -> LLMResponseError instead of warn-and-normalise - 15 pricing records in config/models.json for the current Claude/GPT/Gemini models under their OpenRouter vendor/model slugs, rates pulled from the live OpenRouter catalogue (2026-08-01) - openai.py's shared warnings/errors now take an adapter= name so reused code attributes messages to OpenRouterAdapter - contract-harness row (reusing the OpenAI scenario scripts) plus 16 OpenRouter-specific tests; README shipped-adapters entry Live-validated against OpenRouter: validate(), a text round, a full tool round-trip, and the unknown-model mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds utilities/llm/providers/OPENROUTER.md — prerequisites, full config.json example, key resolution order, model-slug conventions, cost accounting, an error/troubleshooting table, and current limitations — and links it from the README's shipped-adapters table. Placed next to the adapter code (the repo convention, cf. the parser PARSER_PIPELINE.md files) because docs/ is gitignored — which is also why the README's HOW_TO_ADD_AN_ADAPTER.md link resolves to nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ent refusal
The 403 classifier matched only ("moderation","flagged"); an abuse-disabled
key ("...flagged for abuse and disabled") contains "flagged" and was mislabeled
LLMRefusalError, hiding the real remedy (rotate the key). And a moderation 403
worded "violates the content policy" (no "moderation"/"flagged") wrongly fell
through to auth. Check key/account markers (abuse/disabled/suspended/…) first ->
auth; broaden moderation markers to include content-policy/violation wording.
Adds two regression tests (abuse-key -> auth; content-policy -> refusal).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…I path The Python registry accepted `type: openrouter`, but the Go setup wizard's hardcoded supportedProviderTypes rejected it, so guided `openant setup llm` couldn't add it (config-file editing worked, the wizard didn't). Add openrouter to the list, probe it through probeOpenAI against its base URL (OpenAI-compatible), and add an api-key hint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The openrouter probe reused probeOpenAI, whose endpoint is api.openai.com when base_url is blank (the leave-blank path the api-key hint steers users to) and <base>/v1/chat/completions otherwise — so an OpenRouter key either 401'd against OpenAI or hit a double-/v1 404 (openrouter.ai/api/v1/v1/...). Setup could not complete for any non-blank-key OpenRouter config. Add probeOpenRouter: default a blank base_url to https://openrouter.ai/api/v1 and append /chat/completions (single /v1, matching the Python adapter's _DEFAULT_BASE_URL). Extract the shared HTTP body into probeChatCompletionsAt. Adds Go tests (blank base_url -> openrouter single-/v1 path; explicit base_url -> no double /v1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…banned word
The prior fix listed "abuse"/"banned" as bare key-disabled markers, so a
moderation 403 about the INPUT ("input was banned by the content filter")
misrouted to LLMAuthError — which llm_reachability treats as fatal, aborting
the whole scan on a one-off moderated batch (LLMRefusalError only skips the
batch). Require BOTH an account noun (key/account/credential/organization) AND
a disable-state before classifying auth; broaden moderation markers to include
"content filter". Adds a regression test (moderation-with-'banned' -> refusal).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The wizard wiring landed on this branch, so the "not offered by the setup wizard yet / configure by hand" claims in README + OPENROUTER.md were false. Update them; note the remaining gap (no per-phase model pre-fill / tierModel). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gadievron
force-pushed
the
feat/openrouter-adapter
branch
from
August 11, 2026 12:21
ab9c7d9 to
1f43aa2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Add OpenRouter provider support — an OpenAI-compatible gateway fronting many providers — rebased onto current master.
Supersedes #204 (@NahumKorda's original OpenRouter adapter).
Contents
openrouter.ai/api/v1, notapi.openai.com); a sharper 403 moderation-vs-auth heuristic (a disabled/abuse key stays auth, a moderated input stays a refusal); doc updates.Tests
Full openant-core suite green; Go
build/testgreen (incl. new probe tests); OpenRouter adapter + contract tests pass.Acknowledgements
The OpenRouter adapter originates with @NahumKorda's work in #204 — this PR carries their commits (rebased) plus the review follow-ups above. The squash-merge will carry a
Co-authored-by: NahumKordatrailer so the contribution is credited on their graph. Thank you, @NahumKorda.