-
Notifications
You must be signed in to change notification settings - Fork 13
🤖 feat: add OpenRouter provider support #550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add official OpenRouter provider integration for access to 300+ models through a single API. Fixes errors that occurred when using baseURL override approach. Changes: - Install @openrouter/ai-sdk-provider package - Add OpenRouter to aiService.ts createModel method - Add OpenRouterProviderOptions to provider types - Add OPENROUTER_API_KEY environment variable support - Update docs/models.md with OpenRouter setup guide OpenRouter provides: - Universal model access (Anthropic, OpenAI, Google, Cerebras, etc.) - Pay-as-you-go pricing with transparent per-token costs - High availability with automatic failover - Immediate access to new models Usage: openrouter:anthropic/claude-3.5-sonnet openrouter:google/gemini-2.0-flash-thinking-exp openrouter:cerebras/glm-4.6 openrouter:deepseek/deepseek-chat _Generated with `cmux`_
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Lines 1121 to 1125 in 0598fc9
| ipcMain.handle(IPC_CHANNELS.PROVIDERS_LIST, () => { | |
| try { | |
| // Return all supported providers, not just configured ones | |
| // This matches the providers defined in the registry | |
| return ["anthropic", "openai"]; |
The new OpenRouter integration never shows up in the providers UI because PROVIDERS_LIST still returns only "anthropic" and "openai". The renderer calls this handler to know which providers can be configured (src/browser/api.ts and src/preload.ts), so users will not see or be able to configure OpenRouter credentials even though the backend now supports it. Please include "openrouter" (and any other supported providers) in this list.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Enable transparent pass-through of OpenRouter provider routing options
from providers.jsonc to control which infrastructure providers serve
requests (Cerebras, Fireworks, Together, etc.).
Changes:
- Update OpenRouterProviderOptions documentation
- Add OpenRouter case to buildProviderOptions
- Document provider routing in docs/models.md with examples
- Add GLM-4.6 example (z-ai/glm-4.6, not cerebras/glm-4.6)
Usage:
```jsonc
{
"openrouter": {
"apiKey": "sk-or-v1-...",
"provider": {
"order": ["Cerebras", "Fireworks"],
"allow_fallbacks": true
}
}
}
```
The ProviderConfig already supports arbitrary properties via
`[key: string]: unknown`, so OpenRouter options pass through
transparently to the SDK's extraBody parameter.
_Generated with `cmux`_
Map provider routing options from providers.jsonc to OpenRouter's
extraBody parameter. The SDK expects standard options (apiKey, baseURL,
headers, fetch) at the top level and everything else in extraBody.
Before: Spread entire providerConfig (provider routing ignored)
After: Extract standard fields, pass rest via extraBody
This enables provider routing to actually work:
```jsonc
{
"openrouter": {
"apiKey": "sk-or-v1-...",
"provider": {
"require": "Cerebras"
}
}
}
```
_Generated with `cmux`_
The documentation incorrectly showed:
"provider": { "require": "Cerebras" }
OpenRouter's API doesn't have a 'require' field. The correct format is:
"provider": { "order": ["Cerebras"], "allow_fallbacks": false }
Changes:
- Fixed example to use correct 'order' + 'allow_fallbacks' fields
- Added comprehensive list of all provider routing options
- Added link to official OpenRouter provider routing docs
_Generated with `cmux`_
Enable thinking levels for OpenRouter reasoning models (Claude Sonnet Thinking, etc.) by passing reasoning.effort through providerOptions. OpenRouter supports two reasoning control methods: 1. reasoning.effort: 'low'|'medium'|'high' (maps to our thinking levels) 2. reasoning.max_tokens: number (token budget) We use effort-based control which maps cleanly to our existing thinking level UI (off/low/medium/high). Changes: - Added OPENROUTER_REASONING_EFFORT mapping in thinking.ts - Updated buildProviderOptions to pass reasoning config when thinking > off - Added OpenRouterReasoningOptions type for type safety - Set exclude: false to show reasoning traces in UI This complements factory-level provider routing (configured in providers.jsonc) with per-request reasoning control (based on thinking slider). _Generated with `cmux`_
Added section explaining how to use the thinking slider with OpenRouter reasoning models. The thinking level controls reasoning.effort (low, medium, high) which works with Claude Sonnet Thinking and other reasoning-capable models via OpenRouter. _Generated with `cmux`_
Ran scripts/update_models.ts to pull latest model data from LiteLLM. Added Z.AI GLM-4.6 to models-extra.ts with OpenRouter pricing: - 200K context window (202,752 tokens) - $0.40/M input, $1.75/M output - Supports tool use, reasoning, and structured outputs This fixes model stat lookups for: - openrouter:z-ai/glm-4.6 - openrouter:anthropic/claude-3.7-sonnet:thinking (already in models.json) Changes: - Updated src/utils/tokens/models.json (3,379 additions from LiteLLM) - Added openrouter/z-ai/glm-4.6 to models-extra.ts _Generated with `cmux`_
…er' key)
- Support both flat and nested config formats for backwards compatibility
- New flat format: { order: [...], allow_fallbacks: false }
- Old nested format: { provider: { order: [...], ... } } still works
- Update docs to show simpler flat format
- Routing options automatically wrapped under 'provider' for API
Resolves unnecessary nesting in user configuration.
Problem: When adding OpenRouter, had to manually update PROVIDERS_LIST in ipcMain.ts. This created a class of bugs where a provider could be implemented in aiService but forgotten in the UI providers list. Solution: - Created src/constants/providers.ts with SUPPORTED_PROVIDERS constant - Single source of truth for all provider names - ipcMain.ts now uses [...SUPPORTED_PROVIDERS] instead of hardcoded list - Added runtime check in aiService.ts to validate provider is supported - Added unit tests for provider registry validation Benefits: - Adding a new provider only requires updating SUPPORTED_PROVIDERS - Runtime check prevents silent failures if handler not implemented - Type-safe with ProviderName type and isValidProvider() guard - Impossible to have provider in list but not in implementation Addresses Codex P1 comment about exposing OpenRouter in providers list.
c359ab0 to
bd6723b
Compare
Per review feedback:
- Changed SUPPORTED_PROVIDERS from simple array to PROVIDER_REGISTRY object
- Maps each provider name to its Vercel AI SDK package name
- More useful: documents which npm package each provider uses
- Better tests: verify registry structure, package name format, consistency
Benefits:
- Self-documenting: can see at a glance which package is needed
- Type-safe: ProviderName derived from registry keys
- Better error messages: can reference package name in errors
- Validates package names follow npm conventions
Example:
PROVIDER_REGISTRY = {
anthropic: '@ai-sdk/anthropic',
openrouter: '@openrouter/ai-sdk-provider',
...
}
Tests now verify:
- Package names match npm format (@scope/pkg or pkg)
- Registry has expected providers with correct packages
- SUPPORTED_PROVIDERS array stays in sync with registry keys
- Type narrowing works correctly
Since OpenRouter was never in main, no need for backwards compatibility:
- Removed nested format support from aiService.ts
- Removed compatibility note from docs
- Simplified code to only support flat format
Before (unnecessary):
if ('provider' in extraOptions) {
// Old nested format
extraBody = extraOptions;
} else {
// New flat format
// ... complex restructuring
}
After (clean):
// Build extraBody: routing options go under 'provider'
for (const [key, value] of Object.entries(extraOptions)) {
if (OPENROUTER_ROUTING_OPTIONS.includes(key)) {
routingOptions[key] = value;
} else {
otherOptions[key] = value;
}
}
Result: -9 lines of unnecessary code, clearer logic
…dance
Per review feedback, the tests were insane - they just duplicated the implementation:
- ❌ Removed: expect(PROVIDER_REGISTRY.anthropic).toBe('@ai-sdk/anthropic')
- ❌ Removed: expect(isValidProvider('anthropic')).toBe(true) for each provider
- ❌ Removed: Type narrowing 'test' that can't actually test types
Kept only tests that verify actual invariants:
- ✅ Registry is not empty
- ✅ Package names follow npm conventions
- ✅ SUPPORTED_PROVIDERS stays in sync with registry keys
- ✅ isValidProvider rejects invalid input
Added to AGENTS.md:
- Examples of bad vs good tests
- Rule of thumb: If changing implementation requires changing test the same way, test is useless
Before: 6 tests, 31 expect() calls (mostly duplicating implementation)
After: 4 tests, 9 expect() calls (all testing actual behavior/invariants)
Per review feedback, fixed two duplications:
1. Custom fetch logic repeated 3 times
Before: Each provider had identical baseFetch setup (4 lines x 3 = 12 lines)
After: Extracted to getProviderFetch() helper (4 lines total)
Savings: -8 lines
2. Import paths duplicated between registry and code
Before: Package names in PROVIDER_REGISTRY + hardcoded in dynamic imports
After: All imports use PROVIDER_REGISTRY as source of truth
Example:
- import("@openrouter/ai-sdk-provider") → import(PROVIDER_REGISTRY.openrouter)
- preloadAISDKProviders also uses Object.values(PROVIDER_REGISTRY)
Benefits:
- Single source of truth for package names
- Adding new provider: update registry, get imports for free
- Impossible for registry and imports to get out of sync
- Less code to maintain (-14 lines net)
…ssions - Replace all dynamic imports with typed helper functions - Change type to interface for OpenRouterReasoningOptions - Remove 20+ eslint-disable comments across provider initialization - Add importAnthropic/OpenAI/Ollama/OpenRouter helpers to providers.ts - Single type cast for fetch compatibility (documented why it's safe) Improves code quality without sacrificing type safety.
…plication - Replace hardcoded package names with PROVIDER_REGISTRY values - Centralizes package names to single source of truth - Localize 4 eslint-disable comments to wrapper functions (vs 20+ throughout codebase) - Add documentation explaining why eslint-disable is necessary Net: -4 duplicate string literals
While using PROVIDER_REGISTRY values would eliminate duplication, it creates worse type safety issues: 1. TypeScript can't infer return types from dynamic imports with variables 2. This forces 'any' casts which propagate to all call sites 3. Net result: 4 package name duplicates vs 20+ eslint-disable comments Accepting the duplication as the lesser evil. Package names change rarely and are validated by import() at runtime.
…duplication Changed PROVIDER_REGISTRY from mapping names to package strings to mapping names to import functions. This makes the registry the true single source of truth while maintaining perfect type safety. Changes: - providers.ts: Reorganize to define import functions first, then registry - aiService.ts: Call registry functions (e.g., PROVIDER_REGISTRY.anthropic()) - preloadAISDKProviders: Changed .map(pkg => import(pkg)) to .map(fn => fn()) - providers.test.ts: Updated test to verify registry values are functions Benefits: - Zero duplication (package names only in import functions) - Perfect type inference (TypeScript infers from literal imports) - Single source of truth (registry is the canonical provider list) - Better semantics (registry maps to 'how to import' not 'what package') No breaking changes - all existing usages (Object.keys, 'in' checks) work identically.
Add official OpenRouter provider integration for access to 300+ models through a single API. Fixes errors that occurred when using baseURL override approach.
Changes
@openrouter/ai-sdk-providerpackageaiService.tscreateModel methodOpenRouterProviderOptionsto provider typesOPENROUTER_API_KEYenvironment variable supportdocs/models.mdwith OpenRouter setup guideBenefits
OpenRouter provides:
Usage
Model format:
openrouter:anthropic/claude-3.5-sonnetopenrouter:google/gemini-2.0-flash-thinking-expopenrouter:cerebras/glm-4.6openrouter:deepseek/deepseek-chatTesting
Why Not BaseURL Override?
The baseURL override approach has several issues:
The official provider handles all edge cases automatically.
Generated with
cmux