mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-15 04:21:44 +00:00
lithos-llm now ships the built-in provider ids and constructors, so fabro-types drops its provider_ids module and every caller uses lithos_llm::catalog::builtin directly. The crates that name a provider now depend on lithos-llm themselves. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
115 lines
3.9 KiB
Rust
115 lines
3.9 KiB
Rust
use async_trait::async_trait;
|
|
use lithos_llm::catalog::{Catalog, ProviderId, builtin};
|
|
|
|
use crate::context::{AuthContextRequest, AuthContextResponse};
|
|
use crate::credential::{OAuthConfig, OAuthCredential};
|
|
use crate::strategies::api_key::ApiKeyStrategy;
|
|
use crate::strategies::codex_device::CodexDeviceStrategy;
|
|
|
|
pub const CODEX_CLIENT_ID: &str = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
pub const CODEX_AUTH_URL: &str = "https://auth.openai.com";
|
|
pub const CODEX_TOKEN_URL: &str = "https://auth.openai.com/oauth/token";
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum LoginResult {
|
|
ApiKey {
|
|
provider: ProviderId,
|
|
key: String,
|
|
},
|
|
OAuth {
|
|
provider: ProviderId,
|
|
credential: OAuthCredential,
|
|
},
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait AuthStrategy: Send {
|
|
async fn init(&mut self) -> anyhow::Result<AuthContextRequest>;
|
|
async fn complete(&mut self, response: AuthContextResponse) -> anyhow::Result<LoginResult>;
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum AuthMethod {
|
|
ApiKey,
|
|
CodexDevice(OAuthConfig),
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn codex_oauth_config() -> OAuthConfig {
|
|
OAuthConfig {
|
|
auth_url: CODEX_AUTH_URL.to_string(),
|
|
token_url: CODEX_TOKEN_URL.to_string(),
|
|
client_id: CODEX_CLIENT_ID.to_string(),
|
|
scopes: vec![
|
|
"openid".to_string(),
|
|
"profile".to_string(),
|
|
"email".to_string(),
|
|
"offline_access".to_string(),
|
|
],
|
|
redirect_uri: Some(format!("{CODEX_AUTH_URL}/deviceauth/callback")),
|
|
use_pkce: false,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn strategy_for(
|
|
provider_id: &ProviderId,
|
|
method: AuthMethod,
|
|
catalog: &Catalog,
|
|
) -> Box<dyn AuthStrategy> {
|
|
match method {
|
|
AuthMethod::ApiKey => {
|
|
let provider = catalog
|
|
.provider(provider_id.as_str())
|
|
.expect("API key auth requires a catalog provider");
|
|
Box::new(ApiKeyStrategy::new(provider))
|
|
}
|
|
AuthMethod::CodexDevice(config) => {
|
|
// Programming invariant: every call site that constructs
|
|
// `AuthMethod::CodexDevice` pairs it with `ProviderId::OPENAI`.
|
|
// `pick_auth_method` returns CodexDevice only when provider ==
|
|
// openai(), and the install flow hard-codes `ProviderId::openai()`.
|
|
// This check catches future regressions where a new call site
|
|
// forgets the constraint.
|
|
assert_eq!(
|
|
provider_id.as_str(),
|
|
builtin::ids::OPENAI,
|
|
"CodexDevice auth is only constructed by CLI code for the \
|
|
OpenAI provider; all existing call sites enforce this pairing: \
|
|
got provider_id={provider_id}"
|
|
);
|
|
Box::new(CodexDeviceStrategy::new(config))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::context::AuthContextRequest;
|
|
use crate::test_support::test_catalog;
|
|
|
|
#[test]
|
|
fn codex_oauth_config_has_expected_defaults() {
|
|
let config = codex_oauth_config();
|
|
assert_eq!(config.auth_url, CODEX_AUTH_URL);
|
|
assert_eq!(config.token_url, CODEX_TOKEN_URL);
|
|
assert_eq!(config.client_id, CODEX_CLIENT_ID);
|
|
assert!(!config.use_pkce);
|
|
assert!(config.scopes.contains(&"offline_access".to_string()));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn api_key_strategy_uses_provider_secret_names() {
|
|
let catalog = test_catalog();
|
|
let provider = catalog.provider("anthropic").unwrap();
|
|
let mut strategy = ApiKeyStrategy::new(provider);
|
|
let request = strategy.init().await.unwrap();
|
|
assert_eq!(request, AuthContextRequest::ApiKey {
|
|
provider_id: ProviderId::new("anthropic"),
|
|
display_name: "Anthropic".to_string(),
|
|
env_var_names: vec!["ANTHROPIC_API_KEY".to_string()],
|
|
api_key_url: Some("https://console.anthropic.com/settings/keys".to_string()),
|
|
});
|
|
}
|
|
}
|