mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
fix(usage): resolve per-app credentials for native balance/coding-plan queries (#3355)
* fix(usage): resolve per-app credentials for native balance/coding-plan queries
The native usage-query paths (balance + coding_plan) in
`query_provider_usage_inner` read credentials only from `env.ANTHROPIC_*`.
That matches Claude providers, but Codex stores its key in
`auth.OPENAI_API_KEY` with the base URL inside a TOML `config` string,
Hermes/OpenClaw flatten them at the top level, and OpenCode nests them under
`options`. So the card "refresh usage" / auto-query returned empty
credentials and failed ("查询失败") for those apps, even though the
config-page "Test" button worked (the frontend extracts per-app correctly).
Introduce a single per-app resolver `Provider::resolve_usage_credentials`
that mirrors the frontend `getProviderCredentials`, and route both native
branches through it. Add `extract_codex_base_url` to `codex_config` as the
canonical Codex TOML base-URL parser and make the proxy adapter delegate to
it (removing a duplicate copy). Align the frontend `getProviderCredentials`
to cover OpenCode and Claude Desktop and to use the same OpenRouter/Google
key fallbacks as the backend.
Fixes #3158
Fixes #3100
Fixes #2625
* refactor(usage): explicit AppType arms + frontend trailing-slash trim
Address two review nits on the per-app credential resolver:
- provider.rs: replace the catch-all `_` arm in resolve_usage_credentials with an explicit `AppType::Claude | AppType::ClaudeDesktop` arm, so a new AppType variant fails to compile here instead of silently defaulting to the Anthropic env shape.
- UsageScriptModal.tsx: normalize getProviderCredentials baseUrl through a single trailing-slash trim so the frontend 'Test' path matches the backend resolver (trim_end_matches), keeping front/back truly in lockstep.
No behavior change for well-formed configs; tests/typecheck/fmt/clippy clean.
* fix(usage): skip empty primary credential fields in fallback chain
`obj.get(key)` returns `Some` for a present-but-empty field, so the
`.or_else()` fallback chains for the Claude/ClaudeDesktop and Gemini api-key
lookups only skipped *absent* keys, not empty ones. Presets seed fields like
`ANTHROPIC_AUTH_TOKEN` as present-but-empty placeholders, so a provider whose
real key lives in a fallback field (`ANTHROPIC_API_KEY` / `OPENROUTER_API_KEY`
/ `GOOGLE_API_KEY`, or `GOOGLE_API_KEY` for Gemini) resolved to an empty key on
the native balance/coding-plan path — while the frontend `a || b` (which skips
empty strings) still found it, reproducing the same Test-works / refresh-fails
divergence this PR removes.
Add a `first_non_empty` helper that skips present-but-empty values, matching
the frontend `||` semantics, and use it for both fallback chains. Tests cover
empty primary + populated fallback for Claude and Gemini.
This commit is contained in:
@@ -392,22 +392,9 @@ fn extract_codex_model_from_toml(config_text: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
fn extract_codex_base_url_from_toml(config_text: &str) -> Option<String> {
|
||||
let doc = config_text.parse::<TomlValue>().ok()?;
|
||||
|
||||
if let Some(active_provider) = doc.get("model_provider").and_then(|v| v.as_str()) {
|
||||
if let Some(base_url) = doc
|
||||
.get("model_providers")
|
||||
.and_then(|providers| providers.get(active_provider))
|
||||
.and_then(|provider| provider.get("base_url"))
|
||||
.and_then(|v| v.as_str())
|
||||
{
|
||||
return Some(base_url.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
doc.get("base_url")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(ToString::to_string)
|
||||
// Canonical parser lives in codex_config; keep this thin alias so the
|
||||
// proxy hot path and the usage-credential resolver share one implementation.
|
||||
crate::codex_config::extract_codex_base_url(config_text)
|
||||
}
|
||||
|
||||
impl CodexAdapter {
|
||||
|
||||
Reference in New Issue
Block a user