Files
codex/codex-rs/model-provider/src/provider.rs
T
Celia Chen a803790a10 feat: add opt-in provider runtime abstraction (#17713)
## Summary

- Add `codex-model-provider` as the runtime home for model-provider
behavior that does not belong in `codex-core`, `codex-login`, or
`codex-api`.
- The new crate wraps configured `ModelProviderInfo` in a
`ModelProvider` trait object that can resolve the API provider config,
provider-scoped auth manager, and request auth provider for each call.
- This centralizes provider auth behavior in one place today, and gives
us an extension point for future provider-specific auth, model listing,
request setup, and related runtime behavior.

## Tests
Ran tests manually to make sure that provider auth under different
configs still work as expected.

---------

Co-authored-by: pakrym-oai <pakrym@openai.com>
2026-04-17 02:27:45 +00:00

127 lines
4.2 KiB
Rust

use std::fmt;
use std::sync::Arc;
use codex_api::Provider;
use codex_api::SharedAuthProvider;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_model_provider_info::ModelProviderInfo;
use crate::auth::auth_manager_for_provider;
use crate::auth::resolve_provider_auth;
/// Runtime provider abstraction used by model execution.
///
/// Implementations own provider-specific behavior for a model backend. The
/// `ModelProviderInfo` returned by `info` is the serialized/configured provider
/// metadata used by the default OpenAI-compatible implementation.
#[async_trait::async_trait]
pub trait ModelProvider: fmt::Debug + Send + Sync {
/// Returns the configured provider metadata.
fn info(&self) -> &ModelProviderInfo;
/// Returns the provider-scoped auth manager, when this provider uses one.
///
/// TODO(celia-oai): Make auth manager access internal to this crate so callers
/// resolve provider-specific auth only through `ModelProvider`. We first need
/// to think through whether Codex should have a unified provider-specific auth
/// manager throughout the codebase; that is a larger refactor than this change.
fn auth_manager(&self) -> Option<Arc<AuthManager>>;
/// Returns the current provider-scoped auth value, if one is configured.
async fn auth(&self) -> Option<CodexAuth>;
/// Returns provider configuration adapted for the API client.
async fn api_provider(&self) -> codex_protocol::error::Result<Provider> {
let auth = self.auth().await;
self.info()
.to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))
}
/// Returns the auth provider used to attach request credentials.
async fn api_auth(&self) -> codex_protocol::error::Result<SharedAuthProvider> {
let auth = self.auth().await;
resolve_provider_auth(auth.as_ref(), self.info())
}
}
/// Shared runtime model provider handle.
pub type SharedModelProvider = Arc<dyn ModelProvider>;
/// Creates the default runtime model provider for configured provider metadata.
pub fn create_model_provider(
provider_info: ModelProviderInfo,
auth_manager: Option<Arc<AuthManager>>,
) -> SharedModelProvider {
let auth_manager = auth_manager_for_provider(auth_manager, &provider_info);
Arc::new(ConfiguredModelProvider {
info: provider_info,
auth_manager,
})
}
/// Runtime model provider backed by configured `ModelProviderInfo`.
#[derive(Clone, Debug)]
struct ConfiguredModelProvider {
info: ModelProviderInfo,
auth_manager: Option<Arc<AuthManager>>,
}
#[async_trait::async_trait]
impl ModelProvider for ConfiguredModelProvider {
fn info(&self) -> &ModelProviderInfo {
&self.info
}
fn auth_manager(&self) -> Option<Arc<AuthManager>> {
self.auth_manager.clone()
}
async fn auth(&self) -> Option<CodexAuth> {
match self.auth_manager.as_ref() {
Some(auth_manager) => auth_manager.auth().await,
None => None,
}
}
}
#[cfg(test)]
mod tests {
use std::num::NonZeroU64;
use codex_protocol::config_types::ModelProviderAuthInfo;
use super::*;
fn provider_info_with_command_auth() -> ModelProviderInfo {
ModelProviderInfo {
auth: Some(ModelProviderAuthInfo {
command: "print-token".to_string(),
args: Vec::new(),
timeout_ms: NonZeroU64::new(5_000).expect("timeout should be non-zero"),
refresh_interval_ms: 300_000,
cwd: std::env::current_dir()
.expect("current dir should be available")
.try_into()
.expect("current dir should be absolute"),
}),
requires_openai_auth: false,
..ModelProviderInfo::create_openai_provider(/*base_url*/ None)
}
}
#[test]
fn create_model_provider_builds_command_auth_manager_without_base_manager() {
let provider = create_model_provider(
provider_info_with_command_auth(),
/*auth_manager*/ None,
);
let auth_manager = provider
.auth_manager()
.expect("command auth provider should have an auth manager");
assert!(auth_manager.has_external_auth());
}
}