diff --git a/lib/crates/fabro-auth/src/env_source.rs b/lib/crates/fabro-auth/src/env_source.rs index d55a8fa5f..c387a1e84 100644 --- a/lib/crates/fabro-auth/src/env_source.rs +++ b/lib/crates/fabro-auth/src/env_source.rs @@ -1,11 +1,10 @@ -use std::collections::HashMap; use std::sync::Arc; use async_trait::async_trait; use fabro_model::Provider; use crate::credential_source::{CredentialSource, ResolvedCredentials}; -use crate::{ApiCredential, ApiKeyHeader, EnvLookup}; +use crate::{ApiCredential, EnvLookup}; #[derive(Clone)] pub struct EnvCredentialSource { @@ -33,66 +32,32 @@ impl EnvCredentialSource { .iter() .find_map(|var| self.lookup(var))?; - Some(match provider { - Provider::Anthropic => ApiCredential { - provider, - auth_header: ApiKeyHeader::Custom { - name: "x-api-key".to_string(), - value: key, - }, - extra_headers: HashMap::new(), - base_url: self.lookup("ANTHROPIC_BASE_URL"), - codex_mode: false, - org_id: None, - project_id: None, - }, + let mut cred = ApiCredential::from_api_key(provider, key); + match provider { + Provider::Anthropic => { + cred.base_url = self.lookup("ANTHROPIC_BASE_URL"); + } Provider::OpenAi => { - let mut extra_headers = HashMap::new(); - let mut base_url = self.lookup("OPENAI_BASE_URL"); - let mut codex_mode = false; + cred.base_url = self.lookup("OPENAI_BASE_URL"); + cred.org_id = self.lookup("OPENAI_ORG_ID"); + cred.project_id = self.lookup("OPENAI_PROJECT_ID"); if let Some(account_id) = self.lookup("CHATGPT_ACCOUNT_ID") { - base_url = Some("https://chatgpt.com/backend-api/codex".to_string()); - codex_mode = true; - extra_headers.insert("ChatGPT-Account-Id".to_string(), account_id); - extra_headers.insert("originator".to_string(), "fabro".to_string()); - } - ApiCredential { - provider, - auth_header: ApiKeyHeader::Bearer(key), - extra_headers, - base_url, - codex_mode, - org_id: self.lookup("OPENAI_ORG_ID"), - project_id: self.lookup("OPENAI_PROJECT_ID"), + cred.base_url = Some("https://chatgpt.com/backend-api/codex".to_string()); + cred.codex_mode = true; + cred.extra_headers + .insert("ChatGPT-Account-Id".to_string(), account_id); + cred.extra_headers + .insert("originator".to_string(), "fabro".to_string()); } } - Provider::Gemini => ApiCredential { - provider, - auth_header: ApiKeyHeader::Bearer(key), - extra_headers: HashMap::new(), - base_url: self.lookup("GEMINI_BASE_URL"), - codex_mode: false, - org_id: None, - project_id: None, - }, - Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => { - bearer_credential(provider, key) + Provider::Gemini => { + cred.base_url = self.lookup("GEMINI_BASE_URL"); } + Provider::Kimi | Provider::Zai | Provider::Minimax | Provider::Inception => {} // OpenAiCompatible has no api_key_env_vars, so find_map returned None above. Provider::OpenAiCompatible => unreachable!(), - }) - } -} - -fn bearer_credential(provider: Provider, key: String) -> ApiCredential { - ApiCredential { - provider, - auth_header: ApiKeyHeader::Bearer(key), - extra_headers: HashMap::new(), - base_url: None, - codex_mode: false, - org_id: None, - project_id: None, + } + Some(cred) } } diff --git a/lib/crates/fabro-auth/src/resolve.rs b/lib/crates/fabro-auth/src/resolve.rs index 84bcf9fe7..03643d6c8 100644 --- a/lib/crates/fabro-auth/src/resolve.rs +++ b/lib/crates/fabro-auth/src/resolve.rs @@ -37,6 +37,32 @@ pub struct ApiCredential { pub project_id: Option, } +impl ApiCredential { + /// Build an `ApiCredential` from just an API key. Picks the right + /// auth header kind for the provider (Anthropic uses `x-api-key`; + /// everyone else uses `Authorization: Bearer`). All other fields + /// default to empty. + #[must_use] + pub fn from_api_key(provider: Provider, key: String) -> Self { + let auth_header = match provider { + Provider::Anthropic => ApiKeyHeader::Custom { + name: "x-api-key".to_string(), + value: key, + }, + _ => ApiKeyHeader::Bearer(key), + }; + Self { + provider, + auth_header, + extra_headers: HashMap::new(), + base_url: None, + codex_mode: false, + org_id: None, + project_id: None, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct CliCredential { pub env_vars: HashMap, @@ -202,7 +228,6 @@ impl CredentialResolver { } fn to_api_credential(&self, vault: &Vault, credential: &AuthCredential) -> ApiCredential { - let mut extra_headers = HashMap::new(); let base_url = match credential.provider { Provider::Anthropic => self.lookup_env_or_vault(vault, "ANTHROPIC_BASE_URL"), Provider::OpenAi => self.lookup_env_or_vault(vault, "OPENAI_BASE_URL"), @@ -213,32 +238,19 @@ impl CredentialResolver { } }; match &credential.details { - AuthDetails::ApiKey { key } => ApiCredential { - provider: credential.provider, - auth_header: match credential.provider { - Provider::Anthropic => ApiKeyHeader::Custom { - name: "x-api-key".to_string(), - value: key.clone(), - }, - _ => ApiKeyHeader::Bearer(key.clone()), - }, - extra_headers, - base_url, - codex_mode: false, - org_id: if credential.provider == Provider::OpenAi { - self.lookup_env_or_vault(vault, "OPENAI_ORG_ID") - } else { - None - }, - project_id: if credential.provider == Provider::OpenAi { - self.lookup_env_or_vault(vault, "OPENAI_PROJECT_ID") - } else { - None - }, - }, + AuthDetails::ApiKey { key } => { + let mut cred = ApiCredential::from_api_key(credential.provider, key.clone()); + cred.base_url = base_url; + if credential.provider == Provider::OpenAi { + cred.org_id = self.lookup_env_or_vault(vault, "OPENAI_ORG_ID"); + cred.project_id = self.lookup_env_or_vault(vault, "OPENAI_PROJECT_ID"); + } + cred + } AuthDetails::CodexOAuth { tokens, account_id, .. } => { + let mut extra_headers = HashMap::new(); if let Some(account_id) = account_id { extra_headers.insert("ChatGPT-Account-Id".to_string(), account_id.clone()); extra_headers.insert("originator".to_string(), "fabro".to_string()); diff --git a/lib/crates/fabro-cli/src/shared/provider_auth.rs b/lib/crates/fabro-cli/src/shared/provider_auth.rs index 68b59ff04..a3ed147ac 100644 --- a/lib/crates/fabro-cli/src/shared/provider_auth.rs +++ b/lib/crates/fabro-cli/src/shared/provider_auth.rs @@ -15,8 +15,8 @@ use dialoguer::console::Term; use dialoguer::theme::ColorfulTheme; use dialoguer::{Confirm, Password}; use fabro_auth::{ - ApiCredential, ApiKeyHeader, AuthContextRequest, AuthContextResponse, AuthCredential, - AuthMethod, codex_oauth_config, strategy_for, + ApiCredential, AuthContextRequest, AuthContextResponse, AuthCredential, AuthMethod, + codex_oauth_config, strategy_for, }; use fabro_llm::client::Client as LlmClient; use fabro_llm::generate::{GenerateParams, generate}; @@ -78,23 +78,10 @@ pub(crate) enum ApiKeySource { // --------------------------------------------------------------------------- pub(crate) async fn validate_api_key(provider: Provider, api_key: &str) -> Result<(), String> { - let auth_header = if provider == Provider::Anthropic { - ApiKeyHeader::Custom { - name: "x-api-key".to_string(), - value: api_key.to_string(), - } - } else { - ApiKeyHeader::Bearer(api_key.to_string()) - }; - let client = LlmClient::from_credentials(vec![ApiCredential { + let client = LlmClient::from_credentials(vec![ApiCredential::from_api_key( provider, - auth_header, - extra_headers: std::collections::HashMap::new(), - base_url: None, - codex_mode: false, - org_id: None, - project_id: None, - }]) + api_key.to_string(), + )]) .await .map_err(|e| e.to_string())?;