Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0598fc9
🤖 feat: add OpenRouter provider support
ammar-agent Nov 10, 2025
2894781
🤖 feat: add OpenRouter provider routing support
ammar-agent Nov 10, 2025
4720719
🤖 fix: pass OpenRouter provider config via extraBody
ammar-agent Nov 10, 2025
a2ef7fb
🤖 docs: fix OpenRouter provider routing field name
ammar-agent Nov 10, 2025
1fdefe8
🤖 feat: add OpenRouter reasoning support
ammar-agent Nov 10, 2025
51d19cd
🤖 docs: document OpenRouter reasoning support
ammar-agent Nov 10, 2025
7e210e0
🤖 feat: update model pricing database and add GLM-4.6
ammar-agent Nov 10, 2025
e09d1ef
🤖 refactor: flatten OpenRouter provider config (remove nested 'provid…
ammar-agent Nov 11, 2025
bd6723b
🤖 refactor: centralize provider registry to prevent desync bugs
ammar-agent Nov 11, 2025
f8ea9ba
🤖 docs: remove unnecessary benefits section from OpenRouter reasoning…
ammar-agent Nov 11, 2025
7c5dc71
🤖 refactor: map providers to SDK packages, improve tests
ammar-agent Nov 11, 2025
6ab2d20
🤖 refactor: remove unnecessary OpenRouter backwards compatibility
ammar-agent Nov 11, 2025
5917c43
🤖 test: remove nonsensical provider registry tests, add AGENTS.md gui…
ammar-agent Nov 11, 2025
6809267
🤖 refactor: deduplicate provider setup code
ammar-agent Nov 11, 2025
cfd1a7b
docs
Nov 11, 2025
d1973a0
🤖 refactor: add type-safe provider imports to eliminate eslint suppre…
ammar-agent Nov 11, 2025
8fad39c
🤖 refactor: use PROVIDER_REGISTRY in import functions to eliminate du…
ammar-agent Nov 11, 2025
09048bb
🤖 refactor: revert to hardcoded package names in import functions
ammar-agent Nov 11, 2025
9a7316c
🤖 refactor: map PROVIDER_REGISTRY to import functions, eliminate all …
ammar-agent Nov 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Required for integration tests when TEST_INTEGRATION=1
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-proj-...
OPENROUTER_API_KEY=sk-or-v1-...

# Optional: Set to 1 to run integration tests
# Integration tests require API keys to be set
Expand Down
3 changes: 3 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"@ai-sdk/anthropic": "^2.0.29",
"@ai-sdk/openai": "^2.0.52",
"@openrouter/ai-sdk-provider": "^1.2.1",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-scroll-area": "^1.2.10",
Expand Down Expand Up @@ -405,6 +406,8 @@

"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],

"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.2.1", "", { "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-sDc+/tlEM9VTsYlZ3YMwD9AHinSNusdLFGQhtb50eo5r68U/yBixEHRsKEevqSspiX3V6J06hU7C25t4KE9iag=="],

"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],

"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
Expand Down
34 changes: 34 additions & 0 deletions docs/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,36 @@ GPT-5 family of models:

TODO: add issue link here.

#### OpenRouter (Cloud)

Access 300+ models from multiple providers through a single API:

- `openrouter:anthropic/claude-3.5-sonnet`
- `openrouter:google/gemini-2.0-flash-thinking-exp`
- `openrouter:deepseek/deepseek-chat`
- `openrouter:openai/gpt-4o`
- Any model from [OpenRouter Models](https://openrouter.ai/models)

**Setup:**

1. Get your API key from [openrouter.ai](https://openrouter.ai/)
2. Add to `~/.cmux/providers.jsonc`:

```jsonc
{
"openrouter": {
"apiKey": "sk-or-v1-...",
},
}
```

**Benefits:**

- Single API key for hundreds of models
- Pay-as-you-go pricing with no monthly fees
- Transparent per-token costs
- Automatic failover for high availability

#### Ollama (Local)

Run models locally with Ollama. No API key required:
Expand Down Expand Up @@ -68,6 +98,10 @@ All providers are configured in `~/.cmux/providers.jsonc`. Example configuration
"openai": {
"apiKey": "sk-...",
},
// Required for OpenRouter models
"openrouter": {
"apiKey": "sk-or-v1-...",
},
// Optional for Ollama (only needed for custom URL)
"ollama": {
"baseUrl": "http://your-server:11434/api",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"dependencies": {
"@ai-sdk/anthropic": "^2.0.29",
"@ai-sdk/openai": "^2.0.52",
"@openrouter/ai-sdk-provider": "^1.2.1",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-scroll-area": "^1.2.10",
Expand Down
25 changes: 25 additions & 0 deletions src/services/aiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export async function preloadAISDKProviders(): Promise<void> {
import("@ai-sdk/anthropic"),
import("@ai-sdk/openai"),
import("ollama-ai-provider-v2"),
import("@openrouter/ai-sdk-provider"),
]);
}

Expand Down Expand Up @@ -416,6 +417,30 @@ export class AIService extends EventEmitter {
return Ok(provider(modelId));
}

// Handle OpenRouter provider
if (providerName === "openrouter") {
if (!providerConfig.apiKey) {
return Err({
type: "api_key_not_found",
provider: providerName,
});
}
// Use custom fetch if provided, otherwise default with unlimited timeout
const baseFetch =
typeof providerConfig.fetch === "function"
? (providerConfig.fetch as typeof fetch)
: defaultFetchWithUnlimitedTimeout;

// Lazy-load OpenRouter provider to reduce startup time
const { createOpenRouter } = await import("@openrouter/ai-sdk-provider");
const provider = createOpenRouter({
...providerConfig,
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
fetch: baseFetch as any,
});
return Ok(provider(modelId));
}

return Err({
type: "provider_not_supported",
provider: providerName,
Expand Down
9 changes: 9 additions & 0 deletions src/types/providerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export interface OpenAIProviderOptions {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface OllamaProviderOptions {}

/**
* OpenRouter-specific options
* Currently empty - OpenRouter handles provider-specific options via extraBody.
* This interface is provided for future extensibility.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface OpenRouterProviderOptions {}

/**
* Cmux provider options - used by both frontend and backend
*/
Expand All @@ -45,4 +53,5 @@ export interface CmuxProviderOptions {
anthropic?: AnthropicProviderOptions;
openai?: OpenAIProviderOptions;
ollama?: OllamaProviderOptions;
openrouter?: OpenRouterProviderOptions;
}
7 changes: 6 additions & 1 deletion src/utils/providers/ensureProvidersConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ const buildProvidersFromEnv = (env: NodeJS.ProcessEnv): ProvidersConfig => {
providers.openai = entry;
}

const openRouterKey = trim(env.OPENROUTER_API_KEY);
if (openRouterKey.length > 0) {
providers.openrouter = { apiKey: openRouterKey };
}

if (!providers.openai) {
const azureKey = trim(env.AZURE_OPENAI_API_KEY);
const azureEndpoint = trim(env.AZURE_OPENAI_ENDPOINT);
Expand Down Expand Up @@ -97,7 +102,7 @@ export const ensureProvidersConfig = (
const providersFromEnv = buildProvidersFromEnv(env);
if (!hasAnyConfiguredProvider(providersFromEnv)) {
throw new Error(
"No provider credentials found. Configure providers.jsonc or set ANTHROPIC_API_KEY / OPENAI_API_KEY."
"No provider credentials found. Configure providers.jsonc or set ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY."
);
}

Expand Down