fix: usage script provider credential resolution (#1479)

The JS-script usage path resolved {{apiKey}}/{{baseUrl}} with env-only
field guessing, so apps that store credentials elsewhere (Codex:
auth.OPENAI_API_KEY + config.toml base_url) always got empty values and
custom-template queries failed despite a fully configured provider.

- query_usage / test_usage_script now delegate to
  Provider::resolve_usage_credentials, the same per-app resolver used by
  the native balance/coding-plan path and mirrored by the frontend
  getProviderCredentials; explicit non-empty script values still win
- test_usage_script loads the provider and applies the same fallback,
  so testing matches what a saved script does
- the custom-template variable preview shows the effective values
  (script overrides first, then provider config) instead of always
  showing provider credentials
- extract_codex_base_url documents and test-locks the frontend-mirror
  invariant: non-active [model_providers.*] sections are never read

Reworked from the original patch to reuse the existing resolver instead
of duplicating per-app extraction.

Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
pa001024
2026-06-10 22:57:27 +08:00
committed by GitHub
parent 4f911727d2
commit 9ea303b224
4 changed files with 189 additions and 60 deletions
+48 -1
View File
@@ -208,7 +208,10 @@ pub fn extract_codex_api_key(auth: Option<&Value>, config_text: Option<&str>) ->
/// Extract the upstream base URL from a Codex `config.toml` string.
///
/// Prefers the active `[model_providers.<model_provider>].base_url`, falling
/// back to a top-level `base_url` when no model provider is selected.
/// back to a top-level `base_url`. Deliberately never reads a non-active
/// `[model_providers.*]` section — the frontend `extractCodexBaseUrl`
/// (`getRecoverableBaseUrlAssignments`) excludes those too, and a leftover
/// section unrelated to the active provider must not leak into `{{baseUrl}}`.
pub fn extract_codex_base_url(config_text: &str) -> Option<String> {
let doc = config_text.parse::<toml::Value>().ok()?;
@@ -1254,6 +1257,50 @@ mod tests {
use super::*;
use serde_json::json;
#[test]
fn extract_base_url_prefers_active_provider_section() {
let input = r#"model_provider = "azure"
[model_providers.azure]
base_url = "https://azure.example.com/v1"
[model_providers.other]
base_url = "https://other.example.com/v1"
"#;
assert_eq!(
extract_codex_base_url(input).as_deref(),
Some("https://azure.example.com/v1")
);
}
#[test]
fn extract_base_url_falls_back_to_top_level_only() {
let top_level = r#"base_url = "https://top-level.example.com/v1""#;
assert_eq!(
extract_codex_base_url(top_level).as_deref(),
Some("https://top-level.example.com/v1")
);
}
// Mirrors the frontend extractCodexBaseUrl: a non-active provider section
// is never a credential source, whether the active provider points
// elsewhere (e.g. the built-in "openai") or none is selected at all.
#[test]
fn extract_base_url_ignores_non_active_provider_sections() {
let mismatched = r#"model_provider = "openai"
[model_providers.custom]
base_url = "https://leftover.example.com/v1"
"#;
assert_eq!(extract_codex_base_url(mismatched), None);
let no_active = r#"[model_providers.any]
base_url = "https://single.example.com/v1"
"#;
assert_eq!(extract_codex_base_url(no_active), None);
}
#[test]
fn prepare_provider_live_config_rejects_key_without_config() {
let err = prepare_codex_provider_live_config(&json!({"OPENAI_API_KEY": "sk-test"}), "")