From dca935e281ee1c121db7c7b7290bd1bfc5d4ce70 Mon Sep 17 00:00:00 2001 From: RCD <90105158+Finesssee@users.noreply.github.com> Date: Tue, 29 Sep 2026 20:41:37 +0700 Subject: [PATCH 1/2] Port upstream 0.67.0: clarify OpenRouter API key vs Management key guidance --- docs/PROVIDERS.md | 4 ++++ rust/src/locale/en-US.ftl | 2 +- rust/src/providers/openrouter/mod.rs | 17 +++++++---------- rust/src/providers/openrouter/tests.rs | 24 ++++++++++++++++++++++++ rust/src/settings/api_keys.rs | 4 +++- 5 files changed, 39 insertions(+), 12 deletions(-) diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 237f7dab3d..4b6cd395ea 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -87,6 +87,10 @@ Custom pricing overlays are exact-match overrides used only where the local spen OpenCode-held OpenAI/Codex OAuth can be reused for **remote Codex account quota** only when the Codex provider's `External OAuth sources` setting is explicitly enabled. Native Codex credentials still take precedence, an explicit `CODEX_HOME` stays isolated, and external credentials remain read-only. This does **not** import ordinary OpenCode sessions into Codex token or spend totals. OpenCode Go's local SQLite reader remains scoped to its own `opencode-go` assistant records; OpenAI API-platform usage is a separate provider. +### OpenRouter keys + +The **API key** field (or `OPENROUTER_API_KEY`) is required and accepts either a regular API key or a Management API key. A Management key entered there also enables account Activity on the official OpenRouter API. The separate **Management API key** field (or `OPENROUTER_MANAGEMENT_API_KEY`) is an optional additional key for account Activity, only needed to use a Management key different from the one in the required field. Filling only the Management field does not select an account for quota or balance; with no primary key, the provider reports the missing-key message instead. + ### z.ai Coding Plan quotas z.ai Coding Plans accept both `TOKENS_LIMIT` and `CREDIT_LIMIT` rows. The shortest known Coding Plan window becomes primary and the longest becomes secondary; `TIME_LIMIT` is the separate MCP lane. When absolute usage/remaining counts are available they determine the used percentage, otherwise the provider percentage is used, always clamped to 0–100%. This behavior is shared by the tray, provider detail, CLI, and other Windows surfaces. diff --git a/rust/src/locale/en-US.ftl b/rust/src/locale/en-US.ftl index fb96ecd4b8..94f3235e75 100644 --- a/rust/src/locale/en-US.ftl +++ b/rust/src/locale/en-US.ftl @@ -842,7 +842,7 @@ ProviderAutoResumeAfterQuotaResetHelper = Reopen the exact local Codex or Claude OpenRouterManagementKeyTitle = OpenRouter spend history OpenRouterManagementKeyLabel = Management API key -OpenRouterManagementKeyHelp = Optional. Used only for exact 30-day Activity spend from openrouter.ai. The primary OpenRouter API key remains separate. You can also set OPENROUTER_MANAGEMENT_API_KEY. +OpenRouterManagementKeyHelp = Optional additional key for account Activity. Only needed to use a separate Management API key from the one in the required API key field above. You can also set OPENROUTER_MANAGEMENT_API_KEY. OpenRouterManagementKeyConfigured = Management key configured ClaudeAccountsTitle = Claude Code accounts ClaudeAccountsHint = Close Claude Code before switching, then reopen it. Claude Desktop uses a separate login. diff --git a/rust/src/providers/openrouter/mod.rs b/rust/src/providers/openrouter/mod.rs index 1c15164c82..a9b3f0880f 100755 --- a/rust/src/providers/openrouter/mod.rs +++ b/rust/src/providers/openrouter/mod.rs @@ -33,6 +33,9 @@ const OPENROUTER_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3 const OPENROUTER_KEY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1); const OPENROUTER_ACTIVITY_URL: &str = "https://openrouter.ai/api/v1/activity"; const OPENROUTER_MANAGEMENT_ENV: &str = "OPENROUTER_MANAGEMENT_API_KEY"; +/// Shown when no primary key resolves. The optional Management key field never +/// substitutes for the primary key when selecting an account for quota/balance. +const MISSING_API_KEY_MESSAGE: &str = "Enter a regular API key or a Management API key in the API key field, or set OPENROUTER_API_KEY. In Settings, the optional Management API key field does not replace it."; /// Windows Credential Manager target for OpenRouter API token const OPENROUTER_CREDENTIAL_TARGET: &str = "codexbar-openrouter"; @@ -172,17 +175,11 @@ impl OpenRouterProvider { match keyring::Entry::new(OPENROUTER_CREDENTIAL_TARGET, "api_token") { Ok(entry) => match entry.get_password() { Ok(token) => Ok(token), - Err(_) => std::env::var("OPENROUTER_API_KEY").map_err(|_| { - ProviderError::NotInstalled( - "OpenRouter API key not found. Set in Preferences → Providers or OPENROUTER_API_KEY environment variable.".to_string(), - ) - }), + Err(_) => std::env::var("OPENROUTER_API_KEY") + .map_err(|_| ProviderError::NotInstalled(MISSING_API_KEY_MESSAGE.to_string())), }, - Err(_) => std::env::var("OPENROUTER_API_KEY").map_err(|_| { - ProviderError::NotInstalled( - "OpenRouter API key not found. Set in Preferences → Providers or OPENROUTER_API_KEY environment variable.".to_string(), - ) - }), + Err(_) => std::env::var("OPENROUTER_API_KEY") + .map_err(|_| ProviderError::NotInstalled(MISSING_API_KEY_MESSAGE.to_string())), } } diff --git a/rust/src/providers/openrouter/tests.rs b/rust/src/providers/openrouter/tests.rs index a7728bde20..3ebc28e53c 100644 --- a/rust/src/providers/openrouter/tests.rs +++ b/rust/src/providers/openrouter/tests.rs @@ -387,3 +387,27 @@ fn parsed_key_wire_fields_decode() { assert_eq!(parsed.data.limit_remaining, Some(12.5)); assert_eq!(parsed.data.limit_reset.as_deref(), Some("monthly")); } + +// Upstream 0.67.0 `OpenRouterSettingsError.missingToken` copy: a Management key +// is a valid primary key, and the optional Management field never replaces it. +#[test] +fn missing_key_message_explains_primary_and_management_fields() { + assert_eq!( + MISSING_API_KEY_MESSAGE, + "Enter a regular API key or a Management API key in the API key field, or set OPENROUTER_API_KEY. In Settings, the optional Management API key field does not replace it." + ); +} + +#[test] +fn api_key_field_help_says_management_keys_are_accepted() { + let info = crate::settings::get_api_key_providers() + .into_iter() + .find(|info| info.id == ProviderId::OpenRouter) + .expect("OpenRouter api key metadata"); + assert_eq!( + info.api_key_help, + Some( + "Required. Enter a regular API key or a Management API key here. Management keys also enable account Activity on the official OpenRouter API." + ) + ); +} diff --git a/rust/src/settings/api_keys.rs b/rust/src/settings/api_keys.rs index c2adf3565c..7ee7639935 100644 --- a/rust/src/settings/api_keys.rs +++ b/rust/src/settings/api_keys.rs @@ -264,7 +264,9 @@ pub fn get_api_key_providers() -> Vec { name: "OpenRouter", requires_api_key: true, api_key_env_var: Some("OPENROUTER_API_KEY"), - api_key_help: Some("Get your API key from openrouter.ai/settings/keys"), + api_key_help: Some( + "Required. Enter a regular API key or a Management API key here. Management keys also enable account Activity on the official OpenRouter API.", + ), config_file_path: None, dashboard_url: Some("https://openrouter.ai/settings/credits"), }, From 27ff74ec1343403c82c47c6b17eda491300e6233 Mon Sep 17 00:00:00 2001 From: RCD <90105158+Finesssee@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:48:14 +0700 Subject: [PATCH 2/2] Address thermo review --- rust/src/providers/openrouter/mod.rs | 14 +++++--------- rust/src/providers/openrouter/tests.rs | 14 -------------- rust/src/settings/tests.rs | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/rust/src/providers/openrouter/mod.rs b/rust/src/providers/openrouter/mod.rs index a9b3f0880f..b9e244db0e 100755 --- a/rust/src/providers/openrouter/mod.rs +++ b/rust/src/providers/openrouter/mod.rs @@ -172,15 +172,11 @@ impl OpenRouterProvider { return Ok(key.to_string()); } - match keyring::Entry::new(OPENROUTER_CREDENTIAL_TARGET, "api_token") { - Ok(entry) => match entry.get_password() { - Ok(token) => Ok(token), - Err(_) => std::env::var("OPENROUTER_API_KEY") - .map_err(|_| ProviderError::NotInstalled(MISSING_API_KEY_MESSAGE.to_string())), - }, - Err(_) => std::env::var("OPENROUTER_API_KEY") - .map_err(|_| ProviderError::NotInstalled(MISSING_API_KEY_MESSAGE.to_string())), - } + keyring::Entry::new(OPENROUTER_CREDENTIAL_TARGET, "api_token") + .ok() + .and_then(|entry| entry.get_password().ok()) + .or_else(|| std::env::var("OPENROUTER_API_KEY").ok()) + .ok_or_else(|| ProviderError::NotInstalled(MISSING_API_KEY_MESSAGE.to_string())) } fn configured_management_key() -> Option { diff --git a/rust/src/providers/openrouter/tests.rs b/rust/src/providers/openrouter/tests.rs index 3ebc28e53c..3a1db06431 100644 --- a/rust/src/providers/openrouter/tests.rs +++ b/rust/src/providers/openrouter/tests.rs @@ -397,17 +397,3 @@ fn missing_key_message_explains_primary_and_management_fields() { "Enter a regular API key or a Management API key in the API key field, or set OPENROUTER_API_KEY. In Settings, the optional Management API key field does not replace it." ); } - -#[test] -fn api_key_field_help_says_management_keys_are_accepted() { - let info = crate::settings::get_api_key_providers() - .into_iter() - .find(|info| info.id == ProviderId::OpenRouter) - .expect("OpenRouter api key metadata"); - assert_eq!( - info.api_key_help, - Some( - "Required. Enter a regular API key or a Management API key here. Management keys also enable account Activity on the official OpenRouter API." - ) - ); -} diff --git a/rust/src/settings/tests.rs b/rust/src/settings/tests.rs index a6db4aa05b..d78b52b36f 100644 --- a/rust/src/settings/tests.rs +++ b/rust/src/settings/tests.rs @@ -548,6 +548,20 @@ fn test_api_key_provider_catalog_includes_token_providers() { } } +#[test] +fn openrouter_api_key_help_explains_management_keys() { + let info = get_api_key_providers() + .into_iter() + .find(|info| info.id == ProviderId::OpenRouter) + .expect("OpenRouter api key metadata"); + assert_eq!( + info.api_key_help, + Some( + "Required. Enter a regular API key or a Management API key here. Management keys also enable account Activity on the official OpenRouter API." + ) + ); +} + #[test] fn test_t3_chat_is_cookie_configured_not_api_key_configured() { let providers = get_api_key_providers();