feat: add OpenRouter preset to setup wizard (#270)

* feat: add OpenRouter preset to setup wizard

Add OpenRouter as a top-level provider option in the onboarding wizard
(Step 3). Selecting it pre-fills the base URL (https://openrouter.ai/api/v1)
and prompts for an API key, avoiding manual URL entry. Under the hood it
uses the existing openai_compatible backend.

Inlines the key collection flow (rather than delegating to
setup_api_key_provider) so success messages consistently say "OpenRouter"
instead of "openai_compatible", including the early-return env-key path.

Closes #178

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address serrrfirat review comments on OpenRouter wizard preset

- Re-run path now recognizes OpenRouter: display shows "OpenRouter"
  and keep-current routes to setup_openrouter() when base URL contains
  openrouter.ai
- Refactor setup_openrouter() to delegate to setup_api_key_provider()
  with a display_name override, eliminating ~40 lines of duplication
- Update README: remove false claim about model fetching from
  OpenRouter API, add footnote explaining shared secret/env var
  between OpenRouter and OpenAI-compatible
- Fix pre-existing clippy warning in settings.rs (field_reassign_with_default)

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
alexthebuildr
2026-02-24 22:23:39 +04:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 4d27079cc3
commit 62dc5d046e
2 changed files with 61 additions and 13 deletions
+12 -1
View File
@@ -172,7 +172,18 @@ env-var mode or skipped secrets.
| Anthropic | API key | `anthropic_api_key` | `ANTHROPIC_API_KEY` |
| OpenAI | API key | `openai_api_key` | `OPENAI_API_KEY` |
| Ollama | None | - | - |
| OpenAI-compatible | Optional API key | `llm_compatible_api_key` | `LLM_API_KEY` |
| OpenRouter¹ | API key | `llm_compatible_api_key` | `LLM_API_KEY` |
| OpenAI-compatible¹ | Optional API key | `llm_compatible_api_key` | `LLM_API_KEY` |
¹ OpenRouter and OpenAI-compatible share the same secret name and env var because
OpenRouter is stored as `llm_backend = "openai_compatible"` under the hood.
Switching between them overwrites the same credential slot.
**OpenRouter** (`setup_openrouter`):
- Pre-configured OpenAI-compatible preset with base URL `https://openrouter.ai/api/v1`
- Delegates to `setup_api_key_provider()` with a display name override ("OpenRouter")
- Sets `llm_backend = "openai_compatible"` and `openai_compatible_base_url` automatically
- Clears `selected_model` so Step 4 prompts for a model name (manual text input, no API-based model fetching)
**API-key providers** (`setup_api_key_provider`):
1. Check env var → if set, ask to reuse, persist to secrets store
+49 -12
View File
@@ -735,13 +735,24 @@ impl SetupWizard {
async fn step_inference_provider(&mut self) -> Result<(), SetupError> {
// Show current provider if already configured
if let Some(ref current) = self.settings.llm_backend {
let display = match current.as_str() {
"nearai" => "NEAR AI",
"anthropic" => "Anthropic (Claude)",
"openai" => "OpenAI",
"ollama" => "Ollama (local)",
"openai_compatible" => "OpenAI-compatible endpoint",
other => other,
let is_openrouter = current == "openai_compatible"
&& self
.settings
.openai_compatible_base_url
.as_deref()
.is_some_and(|u| u.contains("openrouter.ai"));
let display = if is_openrouter {
"OpenRouter"
} else {
match current.as_str() {
"nearai" => "NEAR AI",
"anthropic" => "Anthropic (Claude)",
"openai" => "OpenAI",
"ollama" => "Ollama (local)",
"openai_compatible" => "OpenAI-compatible endpoint",
other => other,
}
};
print_info(&format!("Current provider: {}", display));
println!();
@@ -753,6 +764,9 @@ impl SetupWizard {
if is_known && confirm("Keep current provider?", true).map_err(SetupError::Io)? {
// Still run the auth sub-flow in case they need to update keys
if is_openrouter {
return self.setup_openrouter().await;
}
match current.as_str() {
"nearai" => return self.setup_nearai().await,
"anthropic" => return self.setup_anthropic().await,
@@ -784,7 +798,8 @@ impl SetupWizard {
"Anthropic - Claude models (direct API key)",
"OpenAI - GPT models (direct API key)",
"Ollama - local models, no API key needed",
"OpenAI-compatible - custom endpoint (vLLM, LiteLLM, Together, etc.)",
"OpenRouter - 200+ models via single API key",
"OpenAI-compatible - custom endpoint (vLLM, LiteLLM, etc.)",
];
let choice = select_one("Provider:", options).map_err(SetupError::Io)?;
@@ -794,7 +809,8 @@ impl SetupWizard {
1 => self.setup_anthropic().await?,
2 => self.setup_openai().await?,
3 => self.setup_ollama()?,
4 => self.setup_openai_compatible().await?,
4 => self.setup_openrouter().await?,
5 => self.setup_openai_compatible().await?,
_ => return Err(SetupError::Config("Invalid provider selection".to_string())),
}
@@ -868,6 +884,7 @@ impl SetupWizard {
"llm_anthropic_api_key",
"Anthropic API key",
"https://console.anthropic.com/settings/keys",
None,
)
.await
}
@@ -880,11 +897,12 @@ impl SetupWizard {
"llm_openai_api_key",
"OpenAI API key",
"https://platform.openai.com/api-keys",
None,
)
.await
}
/// Shared setup flow for API-key-based providers (Anthropic, OpenAI).
/// Shared setup flow for API-key-based providers (Anthropic, OpenAI, OpenRouter).
async fn setup_api_key_provider(
&mut self,
backend: &str,
@@ -892,12 +910,13 @@ impl SetupWizard {
secret_name: &str,
prompt_label: &str,
hint_url: &str,
override_display_name: Option<&str>,
) -> Result<(), SetupError> {
let display_name = match backend {
let display_name = override_display_name.unwrap_or(match backend {
"anthropic" => "Anthropic",
"openai" => "OpenAI",
other => other,
};
});
self.settings.llm_backend = Some(backend.to_string());
if self.settings.selected_model.is_some() {
@@ -977,6 +996,24 @@ impl SetupWizard {
Ok(())
}
/// OpenRouter provider setup: pre-configured OpenAI-compatible endpoint.
///
/// Sets the base URL to `https://openrouter.ai/api/v1` and delegates
/// API key collection to `setup_api_key_provider` with a display name
/// override so messages say "OpenRouter" instead of "openai_compatible".
async fn setup_openrouter(&mut self) -> Result<(), SetupError> {
self.settings.openai_compatible_base_url = Some("https://openrouter.ai/api/v1".to_string());
self.setup_api_key_provider(
"openai_compatible",
"LLM_API_KEY",
"llm_compatible_api_key",
"OpenRouter API key",
"https://openrouter.ai/settings/keys",
Some("OpenRouter"),
)
.await
}
/// OpenAI-compatible provider setup: base URL + optional API key.
async fn setup_openai_compatible(&mut self) -> Result<(), SetupError> {
self.settings.llm_backend = Some("openai_compatible".to_string());