fix(providers): use Bearer auth for Gemini CLI OAuth tokens

* fix(providers): use Bearer auth for Gemini CLI OAuth tokens

When credentials come from ~/.gemini/oauth_creds.json (Gemini CLI),
send them as Authorization: Bearer header instead of ?key= query
parameter. API keys from env vars or config continue using ?key=.

Fixes #194

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(gemini): harden OAuth bearer auth flow and tests

* fix(gemini): granular auth source tracking and review fixes

Build on chumyin's auth model refactor with:
- Expand GeminiAuth to 4 variants (ExplicitKey/EnvGeminiKey/EnvGoogleKey/
  OAuthToken) so auth_source() uses stored discriminant without re-reading
  env vars at call time
- Add is_api_key()/credential() helpers on the enum
- Upgrade expired OAuth token log from debug to warn
- Add tests: provider_rejects_empty_key, auth_source_explicit_key,
  auth_source_none_without_credentials

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: apply rustfmt to fix CI lint failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: root <root@instance-20220913-1738.vcn09131738.oraclevcn.com>
Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
This commit is contained in:
Edvard Schøyen 2026-02-15 14:32:33 -05:00 committed by GitHub
parent e057bf4128
commit 49bb20f961
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 358 additions and 148 deletions

View file

@ -181,7 +181,10 @@ mod tests {
.iter()
.zip(mocks.iter())
.map(|((name, _), mock)| {
(name.to_string(), Box::new(Arc::clone(mock)) as Box<dyn Provider>)
(
name.to_string(),
Box::new(Arc::clone(mock)) as Box<dyn Provider>,
)
})
.collect();
@ -198,11 +201,7 @@ mod tests {
})
.collect();
let router = RouterProvider::new(
provider_list,
route_list,
"default-model".to_string(),
);
let router = RouterProvider::new(provider_list, route_list, "default-model".to_string());
(router, mocks)
}
@ -270,7 +269,10 @@ mod tests {
#[tokio::test]
async fn non_hint_model_uses_default_provider() {
let (router, mocks) = make_router(
vec![("primary", "primary-response"), ("secondary", "secondary-response")],
vec![
("primary", "primary-response"),
("secondary", "secondary-response"),
],
vec![("code", "secondary", "codellama")],
);
@ -285,10 +287,7 @@ mod tests {
#[test]
fn resolve_preserves_model_for_non_hints() {
let (router, _) = make_router(
vec![("default", "ok")],
vec![],
);
let (router, _) = make_router(vec![("default", "ok")], vec![]);
let (idx, model) = router.resolve("gpt-4o");
assert_eq!(idx, 0);
@ -320,10 +319,7 @@ mod tests {
#[tokio::test]
async fn warmup_calls_all_providers() {
let (router, _) = make_router(
vec![("a", "ok"), ("b", "ok")],
vec![],
);
let (router, _) = make_router(vec![("a", "ok"), ("b", "ok")], vec![]);
// Warmup should not error
assert!(router.warmup().await.is_ok());
@ -333,7 +329,10 @@ mod tests {
async fn chat_with_system_passes_system_prompt() {
let mock = Arc::new(MockProvider::new("response"));
let router = RouterProvider::new(
vec![("default".into(), Box::new(Arc::clone(&mock)) as Box<dyn Provider>)],
vec![(
"default".into(),
Box::new(Arc::clone(&mock)) as Box<dyn Provider>,
)],
vec![],
"model".into(),
);