This commit is contained in:
celia-oai
2026-04-13 17:21:54 -07:00
parent 46aaf95a4f
commit 936f574dd5
6 changed files with 311 additions and 42 deletions

View File

@@ -105,7 +105,7 @@ use codex_feedback::emit_feedback_request_tags_with_auth_env;
use codex_login::api_bridge::auth_provider_from_auth;
use codex_login::api_bridge::auth_provider_from_runtime;
use codex_login::auth_env_telemetry::AuthEnvTelemetry;
use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
use codex_login::auth_env_telemetry::collect_auth_env_telemetry_for_runtime;
use codex_login::provider_auth::auth_manager_for_provider_runtime;
use codex_model_provider::ProviderRuntime;
use codex_model_provider::ResolvedModelProvider;
@@ -283,12 +283,11 @@ impl ModelClient {
let codex_api_key_env_enabled = auth_manager
.as_ref()
.is_some_and(|manager| manager.codex_api_key_env_enabled());
let auth_env_provider = match &provider_runtime {
ProviderRuntime::Legacy => &provider,
ProviderRuntime::Resolved(provider) => &provider.info,
};
let auth_env_telemetry =
collect_auth_env_telemetry(auth_env_provider, codex_api_key_env_enabled);
let auth_env_telemetry = collect_auth_env_telemetry_for_runtime(
&provider_runtime,
&provider,
codex_api_key_env_enabled,
);
Self {
state: Arc::new(ModelClientState {
auth_manager,

View File

@@ -120,11 +120,15 @@ async fn current_client_setup_uses_resolved_runtime_provider() {
let mut resolved_provider =
create_oss_provider_with_base_url("https://resolved.example.com/v1", WireApi::Responses);
resolved_provider.experimental_bearer_token = Some("resolved-token".to_string());
let runtime = resolve_model_provider(
let mut runtime = resolve_model_provider(
"custom",
&resolved_provider,
&ProviderResolutionPolicy::with_enabled_provider_ids([String::from("custom")]),
);
let ProviderRuntime::Resolved(provider) = &mut runtime else {
panic!("enabled provider should resolve through the provider framework");
};
provider.info.experimental_bearer_token = None;
let client = ModelClient::new(
/*auth_manager*/ None,
ThreadId::new(),

View File

@@ -1,6 +1,9 @@
use codex_api::CoreAuthProvider;
use codex_model_provider::ProviderAuthKind;
use codex_model_provider::ProviderRuntime;
use codex_model_provider_info::ModelProviderInfo;
use codex_protocol::error::CodexErr;
use codex_protocol::error::EnvVarError;
use crate::CodexAuth;
@@ -41,11 +44,60 @@ pub fn auth_provider_from_runtime(
provider_runtime: &ProviderRuntime,
legacy_provider: &ModelProviderInfo,
) -> codex_protocol::error::Result<CoreAuthProvider> {
let provider = match provider_runtime {
ProviderRuntime::Legacy => legacy_provider,
ProviderRuntime::Resolved(provider) => &provider.info,
};
auth_provider_from_auth(auth, provider)
match provider_runtime {
ProviderRuntime::Legacy => auth_provider_from_auth(auth, legacy_provider),
ProviderRuntime::Resolved(provider) => {
auth_provider_from_provider_auth(auth, &provider.auth)
}
}
}
pub fn auth_provider_from_provider_auth(
auth: Option<CodexAuth>,
provider_auth: &ProviderAuthKind,
) -> codex_protocol::error::Result<CoreAuthProvider> {
match provider_auth {
ProviderAuthKind::EnvBearer {
env_key,
instructions,
} => {
let token = std::env::var(env_key)
.ok()
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| {
CodexErr::EnvVar(EnvVarError {
var: env_key.clone(),
instructions: instructions.clone(),
})
})?;
Ok(CoreAuthProvider {
token: Some(token),
account_id: None,
})
}
ProviderAuthKind::StaticBearer { token } => Ok(CoreAuthProvider {
token: Some(token.clone()),
account_id: None,
}),
ProviderAuthKind::OpenAi | ProviderAuthKind::CommandBearer { .. } => {
if let Some(auth) = auth {
let token = auth.get_token()?;
Ok(CoreAuthProvider {
token: Some(token),
account_id: auth.get_account_id(),
})
} else {
Ok(CoreAuthProvider {
token: None,
account_id: None,
})
}
}
ProviderAuthKind::None => Ok(CoreAuthProvider {
token: None,
account_id: None,
}),
}
}
#[cfg(test)]
@@ -102,13 +154,17 @@ mod tests {
}
#[test]
fn runtime_auth_adapter_delegates_bearer_auth_to_legacy_provider() {
fn runtime_auth_adapter_uses_resolved_static_bearer_auth() {
let provider = bearer_provider();
let runtime = resolve_model_provider(
let mut runtime = resolve_model_provider(
"custom",
&provider,
&ProviderResolutionPolicy::with_enabled_provider_ids(["custom".to_string()]),
);
let ProviderRuntime::Resolved(resolved) = &mut runtime else {
panic!("enabled provider should resolve through the provider framework");
};
resolved.info.experimental_bearer_token = None;
let legacy = auth_provider_from_auth(None, &provider).expect("legacy auth");
let resolved = auth_provider_from_runtime(None, &runtime, &provider).expect("runtime auth");
@@ -118,13 +174,18 @@ mod tests {
}
#[test]
fn runtime_auth_adapter_delegates_env_key_errors_to_legacy_provider() {
fn runtime_auth_adapter_uses_resolved_env_key_errors() {
let provider = missing_env_key_provider();
let runtime = resolve_model_provider(
let mut runtime = resolve_model_provider(
"custom",
&provider,
&ProviderResolutionPolicy::with_enabled_provider_ids(["custom".to_string()]),
);
let ProviderRuntime::Resolved(resolved) = &mut runtime else {
panic!("enabled provider should resolve through the provider framework");
};
resolved.info.env_key = None;
resolved.info.env_key_instructions = None;
let legacy = match auth_provider_from_auth(None, &provider) {
Ok(_) => panic!("missing env key should fail"),

View File

@@ -1,3 +1,5 @@
use codex_model_provider::ProviderAuthKind;
use codex_model_provider::ProviderRuntime;
use codex_model_provider_info::ModelProviderInfo;
use codex_otel::AuthEnvTelemetryMetadata;
@@ -31,13 +33,50 @@ impl AuthEnvTelemetry {
pub fn collect_auth_env_telemetry(
provider: &ModelProviderInfo,
codex_api_key_env_enabled: bool,
) -> AuthEnvTelemetry {
let provider_env_key = provider.env_key.as_deref();
collect_auth_env_telemetry_for_env_key(provider_env_key, codex_api_key_env_enabled)
}
pub fn collect_auth_env_telemetry_for_runtime(
provider_runtime: &ProviderRuntime,
legacy_provider: &ModelProviderInfo,
codex_api_key_env_enabled: bool,
) -> AuthEnvTelemetry {
match provider_runtime {
ProviderRuntime::Legacy => {
collect_auth_env_telemetry(legacy_provider, codex_api_key_env_enabled)
}
ProviderRuntime::Resolved(provider) => {
collect_auth_env_telemetry_for_provider_auth(&provider.auth, codex_api_key_env_enabled)
}
}
}
pub fn collect_auth_env_telemetry_for_provider_auth(
provider_auth: &ProviderAuthKind,
codex_api_key_env_enabled: bool,
) -> AuthEnvTelemetry {
let provider_env_key = match provider_auth {
ProviderAuthKind::EnvBearer { env_key, .. } => Some(env_key.as_str()),
ProviderAuthKind::OpenAi
| ProviderAuthKind::StaticBearer { .. }
| ProviderAuthKind::CommandBearer { .. }
| ProviderAuthKind::None => None,
};
collect_auth_env_telemetry_for_env_key(provider_env_key, codex_api_key_env_enabled)
}
fn collect_auth_env_telemetry_for_env_key(
provider_env_key: Option<&str>,
codex_api_key_env_enabled: bool,
) -> AuthEnvTelemetry {
AuthEnvTelemetry {
openai_api_key_env_present: env_var_present(OPENAI_API_KEY_ENV_VAR),
codex_api_key_env_present: env_var_present(CODEX_API_KEY_ENV_VAR),
codex_api_key_env_enabled,
provider_env_key_name: provider.env_key.as_ref().map(|_| "configured".to_string()),
provider_env_key_present: provider.env_key.as_deref().map(env_var_present),
provider_env_key_name: provider_env_key.map(|_| "configured".to_string()),
provider_env_key_present: provider_env_key.map(env_var_present),
refresh_token_url_override_present: env_var_present(REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR),
}
}
@@ -53,6 +92,9 @@ fn env_var_present(name: &str) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use codex_model_provider::ProviderResolutionPolicy;
use codex_model_provider::ProviderRuntime;
use codex_model_provider::resolve_model_provider;
use codex_model_provider_info::WireApi;
use pretty_assertions::assert_eq;
@@ -85,4 +127,45 @@ mod tests {
Some("configured".to_string())
);
}
#[test]
fn runtime_auth_env_telemetry_uses_resolved_provider_auth() {
let provider = ModelProviderInfo {
name: "Custom".to_string(),
base_url: None,
env_key: Some("PATH".to_string()),
env_key_instructions: None,
experimental_bearer_token: None,
auth: None,
wire_api: WireApi::Responses,
query_params: None,
http_headers: None,
env_http_headers: None,
request_max_retries: None,
stream_max_retries: None,
stream_idle_timeout_ms: None,
websocket_connect_timeout_ms: None,
requires_openai_auth: false,
supports_websockets: false,
};
let mut runtime = resolve_model_provider(
"custom",
&provider,
&ProviderResolutionPolicy::with_enabled_provider_ids(["custom".to_string()]),
);
let ProviderRuntime::Resolved(resolved) = &mut runtime else {
panic!("enabled provider should resolve through the provider framework");
};
resolved.info.env_key = None;
let telemetry = collect_auth_env_telemetry_for_runtime(
&runtime, &provider, /*codex_api_key_env_enabled*/ false,
);
assert_eq!(
telemetry.provider_env_key_name,
Some("configured".to_string())
);
assert_eq!(telemetry.provider_env_key_present, Some(true));
}
}

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use codex_model_provider::ProviderAuthKind;
use codex_model_provider::ProviderRuntime;
use codex_model_provider_info::ModelProviderInfo;
@@ -23,11 +24,27 @@ pub fn auth_manager_for_provider_runtime(
provider_runtime: &ProviderRuntime,
legacy_provider: &ModelProviderInfo,
) -> Option<Arc<AuthManager>> {
let provider = match provider_runtime {
ProviderRuntime::Legacy => legacy_provider,
ProviderRuntime::Resolved(provider) => &provider.info,
};
auth_manager_for_provider(auth_manager, provider)
match provider_runtime {
ProviderRuntime::Legacy => auth_manager_for_provider(auth_manager, legacy_provider),
ProviderRuntime::Resolved(provider) => {
auth_manager_for_provider_auth(auth_manager, &provider.auth)
}
}
}
pub fn auth_manager_for_provider_auth(
auth_manager: Option<Arc<AuthManager>>,
provider_auth: &ProviderAuthKind,
) -> Option<Arc<AuthManager>> {
match provider_auth {
ProviderAuthKind::CommandBearer { config } => {
Some(AuthManager::external_bearer_only(config.clone()))
}
ProviderAuthKind::OpenAi
| ProviderAuthKind::EnvBearer { .. }
| ProviderAuthKind::StaticBearer { .. }
| ProviderAuthKind::None => auth_manager,
}
}
/// Returns an auth manager for request paths that always require authentication.
@@ -83,13 +100,17 @@ mod tests {
}
#[test]
fn runtime_auth_manager_adapter_delegates_command_auth_to_legacy_provider() {
fn runtime_auth_manager_adapter_uses_resolved_command_auth() {
let provider = provider_with_command_auth();
let runtime = resolve_model_provider(
let mut runtime = resolve_model_provider(
"custom",
&provider,
&ProviderResolutionPolicy::with_enabled_provider_ids(["custom".to_string()]),
);
let ProviderRuntime::Resolved(resolved) = &mut runtime else {
panic!("enabled provider should resolve through the provider framework");
};
resolved.info.auth = None;
assert!(auth_manager_for_provider(None, &provider).is_some());
assert!(auth_manager_for_provider_runtime(None, &runtime, &provider).is_some());

View File

@@ -6,6 +6,7 @@
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
use codex_protocol::config_types::ModelProviderAuthInfo;
use codex_protocol::error::Result as CodexResult;
use codex_protocol::openai_models::ModelsResponse;
use serde::Deserialize;
@@ -20,18 +21,13 @@ use std::time::Duration;
pub const PROVIDER_FRAMEWORK_ENABLED_PROVIDER_IDS: &[&str] = &[];
/// Runtime strategy selected for the active model provider.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Default)]
pub enum ProviderRuntime {
/// Preserve the existing provider implementation.
#[default]
Legacy,
/// Use provider-owned runtime strategies.
Resolved(ResolvedModelProvider),
}
impl Default for ProviderRuntime {
fn default() -> Self {
Self::Legacy
}
Resolved(Box<ResolvedModelProvider>),
}
/// Runtime provider object resolved from config-facing provider metadata.
@@ -57,17 +53,20 @@ impl ResolvedModelProvider {
}
/// Provider-owned authentication strategy.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ProviderAuthKind {
/// Preserve the existing provider-auth behavior for resolved test providers.
Legacy,
/// OpenAI-managed auth.
OpenAi,
/// Bearer token read from a configured environment variable.
EnvBearer { env_key: String },
EnvBearer {
env_key: String,
instructions: Option<String>,
},
/// Bearer token read from provider config.
StaticBearer { token: String },
/// Command-backed bearer token.
CommandBearer,
CommandBearer { config: ModelProviderAuthInfo },
/// No provider auth is required.
None,
}
@@ -169,7 +168,10 @@ pub fn resolve_model_provider(
return ProviderRuntime::Legacy;
}
ProviderRuntime::Resolved(resolve_generic_model_provider(provider_id, provider))
ProviderRuntime::Resolved(Box::new(resolve_generic_model_provider(
provider_id,
provider,
)))
}
fn resolve_generic_model_provider(
@@ -179,7 +181,7 @@ fn resolve_generic_model_provider(
ResolvedModelProvider {
id: provider_id.to_string(),
info: provider.clone(),
auth: ProviderAuthKind::Legacy,
auth: resolve_provider_auth(provider),
model_catalog: ProviderModelCatalog::Legacy,
transport: ProviderTransport {
base_url: provider.base_url.clone(),
@@ -191,6 +193,27 @@ fn resolve_generic_model_provider(
}
}
fn resolve_provider_auth(provider: &ModelProviderInfo) -> ProviderAuthKind {
if let Some(env_key) = &provider.env_key {
ProviderAuthKind::EnvBearer {
env_key: env_key.clone(),
instructions: provider.env_key_instructions.clone(),
}
} else if let Some(token) = &provider.experimental_bearer_token {
ProviderAuthKind::StaticBearer {
token: token.clone(),
}
} else if let Some(config) = &provider.auth {
ProviderAuthKind::CommandBearer {
config: config.clone(),
}
} else if provider.requires_openai_auth {
ProviderAuthKind::OpenAi
} else {
ProviderAuthKind::None
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -318,7 +341,7 @@ mod tests {
};
assert_eq!(resolved.id, OPENAI_PROVIDER_ID);
assert_eq!(resolved.info, *provider);
assert_eq!(resolved.auth, ProviderAuthKind::Legacy);
assert_eq!(resolved.auth, ProviderAuthKind::OpenAi);
assert_eq!(resolved.model_catalog, ProviderModelCatalog::Legacy);
assert_eq!(
resolved.capabilities,
@@ -326,6 +349,84 @@ mod tests {
);
}
#[test]
fn enabled_policy_resolves_env_bearer_auth() {
let provider = ModelProviderInfo {
name: "custom".to_string(),
base_url: Some("https://example.com/v1".to_string()),
env_key: Some("CUSTOM_API_KEY".to_string()),
env_key_instructions: Some("set CUSTOM_API_KEY".to_string()),
experimental_bearer_token: None,
auth: None,
wire_api: WireApi::Responses,
query_params: None,
http_headers: None,
env_http_headers: None,
request_max_retries: None,
stream_max_retries: None,
stream_idle_timeout_ms: None,
websocket_connect_timeout_ms: None,
requires_openai_auth: false,
supports_websockets: false,
};
let ProviderRuntime::Resolved(resolved) = resolve_model_provider(
"custom",
&provider,
&ProviderResolutionPolicy::with_enabled_provider_ids(["custom".to_string()]),
) else {
panic!("enabled provider should resolve through the provider framework");
};
assert_eq!(
resolved.auth,
ProviderAuthKind::EnvBearer {
env_key: "CUSTOM_API_KEY".to_string(),
instructions: Some("set CUSTOM_API_KEY".to_string()),
}
);
}
#[test]
fn enabled_policy_resolves_command_bearer_auth() {
let auth = ModelProviderAuthInfo {
command: "print-token".to_string(),
args: Vec::new(),
timeout_ms: NonZeroU64::MIN,
refresh_interval_ms: 0,
cwd: AbsolutePathBuf::resolve_path_against_base(".", "/tmp"),
};
let provider = ModelProviderInfo {
name: "custom".to_string(),
base_url: Some("https://example.com/v1".to_string()),
env_key: None,
env_key_instructions: None,
experimental_bearer_token: None,
auth: Some(auth.clone()),
wire_api: WireApi::Responses,
query_params: None,
http_headers: None,
env_http_headers: None,
request_max_retries: None,
stream_max_retries: None,
stream_idle_timeout_ms: None,
websocket_connect_timeout_ms: None,
requires_openai_auth: false,
supports_websockets: false,
};
let ProviderRuntime::Resolved(resolved) = resolve_model_provider(
"custom",
&provider,
&ProviderResolutionPolicy::with_enabled_provider_ids(["custom".to_string()]),
) else {
panic!("enabled provider should resolve through the provider framework");
};
assert_eq!(
resolved.auth,
ProviderAuthKind::CommandBearer { config: auth }
);
}
#[test]
fn generic_resolved_provider_uses_legacy_api_provider_adapter() {
let providers =