auth: move domain mode below app wire types (#29721)

## Why

Authentication mode is a domain concept used by login, model selection,
telemetry, and transports. Keeping the canonical type in app-server
protocol forces those lower-level crates to depend on an unrelated wire
API.

## What changed

- Added canonical `codex_protocol::auth::AuthMode` domain values.
- Kept the app-server wire DTO unchanged and added an explicit app-side
conversion.
- Removed production app-server-protocol dependencies from login,
model-provider-info, models-manager, and otel call paths.

## Stack

This is PR 2 of 6, stacked on [PR
#29714](https://github.com/openai/codex/pull/29714). Review only the
delta from `codex/split-json-rpc-protocols`. Next: [PR
#29722](https://github.com/openai/codex/pull/29722).

## Validation

- Auth and login coverage passed in the focused protocol/domain test
run.
- App-server account and auth conversion coverage passed.
This commit is contained in:
Adam Perry @ OpenAI authored and GitHub committed 2026-06-24 03:10:20 +00:00
1 parent 806a4b66c9
commit 31372078d1
47 files changed
+224 -146

No files matched your search

-1
View File
@@ -11,7 +11,6 @@ workspace = true
base64 = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
codex-agent-identity = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-client = { workspace = true }
codex-config = { workspace = true }
codex-keyring-store = { workspace = true }
+5 -5
View File
@@ -2,8 +2,8 @@ use super::*;
use crate::auth::storage::FileAuthStorage;
use crate::auth::storage::get_auth_file;
use crate::token_data::IdTokenInfo;
use codex_app_server_protocol::AuthMode;
use codex_protocol::account::PlanType as AccountPlanType;
use codex_protocol::auth::AuthMode;
use codex_protocol::auth::KnownPlan as InternalKnownPlan;
use codex_protocol::auth::PlanType as InternalPlanType;
use codex_protocol::protocol::SessionSource;
@@ -162,7 +162,7 @@ async fn stored_agent_identity_jwt_keeps_auth_json_unchanged() -> anyhow::Result
save_auth(
codex_home.path(),
&AuthDotJson {
auth_mode: Some(ApiAuthMode::AgentIdentity),
auth_mode: Some(AuthMode::AgentIdentity),
openai_api_key: None,
tokens: None,
last_refresh: None,
@@ -897,7 +897,7 @@ async fn loads_api_key_from_auth_json() {
fn logout_removes_auth_file() -> Result<(), std::io::Error> {
let dir = tempdir()?;
let auth_dot_json = AuthDotJson {
auth_mode: Some(ApiAuthMode::ApiKey),
auth_mode: Some(AuthMode::ApiKey),
openai_api_key: Some("sk-test-key".to_string()),
tokens: None,
last_refresh: None,
@@ -1045,7 +1045,7 @@ async fn external_bearer_only_auth_manager_uses_cached_provider_token() {
assert_eq!(first.as_deref(), Some("provider-token"));
assert_eq!(second.as_deref(), Some("provider-token"));
assert_eq!(manager.auth_mode(), Some(AuthMode::ApiKey));
assert_eq!(manager.get_api_auth_mode(), Some(ApiAuthMode::ApiKey));
assert_eq!(manager.get_api_auth_mode(), Some(AuthMode::ApiKey));
}
#[tokio::test]
@@ -1822,7 +1822,7 @@ async fn enforce_login_restrictions_logs_out_for_agent_identity_workspace_mismat
save_auth(
codex_home.path(),
&AuthDotJson {
auth_mode: Some(ApiAuthMode::AgentIdentity),
auth_mode: Some(AuthMode::AgentIdentity),
openai_api_key: None,
tokens: None,
last_refresh: None,
+1 -1
View File
@@ -7,7 +7,7 @@ use serde::Serialize;
use super::manager::save_auth;
use super::storage::AuthDotJson;
use super::storage::AuthKeyringBackendKind;
use codex_app_server_protocol::AuthMode;
use codex_protocol::auth::AuthMode;
/// Managed Amazon Bedrock API key persisted in `auth.json`.
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
@@ -1,5 +1,5 @@
use codex_app_server_protocol::AuthMode;
use codex_config::types::AuthCredentialsStoreMode;
use codex_protocol::auth::AuthMode;
use pretty_assertions::assert_eq;
use serial_test::serial;
use tempfile::tempdir;
+1 -1
View File
@@ -2,7 +2,7 @@ use super::manager::ExternalAuth;
use super::manager::ExternalAuthFuture;
use super::manager::ExternalAuthRefreshContext;
use super::manager::ExternalAuthTokens;
use codex_app_server_protocol::AuthMode;
use codex_protocol::auth::AuthMode;
use codex_protocol::config_types::ModelProviderAuthInfo;
use std::fmt;
use std::io;
+42 -39
View File
@@ -20,8 +20,7 @@ use tokio::sync::watch;
use tracing::instrument;
use codex_agent_identity::ChatGptEnvironment;
use codex_app_server_protocol::AuthMode;
use codex_app_server_protocol::AuthMode as ApiAuthMode;
use codex_protocol::auth::AuthMode;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::ModelProviderAuthInfo;
@@ -242,13 +241,13 @@ impl CodexAuth {
auth_route_config: Option<&AuthRouteConfig>,
) -> std::io::Result<Self> {
let auth_mode = auth_dot_json.resolved_mode();
if auth_mode == ApiAuthMode::ApiKey {
if auth_mode == AuthMode::ApiKey {
let Some(api_key) = auth_dot_json.openai_api_key.as_deref() else {
return Err(std::io::Error::other("API key auth is missing a key."));
};
return Ok(Self::from_api_key(api_key));
}
if auth_mode == ApiAuthMode::AgentIdentity {
if auth_mode == AuthMode::AgentIdentity {
let Some(agent_identity) = auth_dot_json.agent_identity.clone() else {
return Err(std::io::Error::other(
"agent identity auth is missing agent identity auth material.",
@@ -282,7 +281,7 @@ impl CodexAuth {
}
}
}
if auth_mode == ApiAuthMode::PersonalAccessToken {
if auth_mode == AuthMode::PersonalAccessToken {
let Some(personal_access_token) = auth_dot_json.personal_access_token.as_deref() else {
return Err(std::io::Error::other(
"personal access token auth is missing a personal access token.",
@@ -291,7 +290,7 @@ impl CodexAuth {
return Self::from_personal_access_token(personal_access_token, auth_route_config)
.await;
}
if auth_mode == ApiAuthMode::BedrockApiKey {
if auth_mode == AuthMode::BedrockApiKey {
let Some(auth) = auth_dot_json.bedrock_api_key else {
return Err(std::io::Error::other(
"Bedrock API key auth is missing a Bedrock API key.",
@@ -308,7 +307,7 @@ impl CodexAuth {
};
match auth_mode {
ApiAuthMode::Chatgpt => {
AuthMode::Chatgpt => {
let storage = create_auth_storage(
codex_home.to_path_buf(),
storage_mode,
@@ -316,15 +315,13 @@ impl CodexAuth {
);
Ok(Self::Chatgpt(ChatgptAuth { state, storage }))
}
ApiAuthMode::ChatgptAuthTokens => {
Ok(Self::ChatgptAuthTokens(ChatgptAuthTokens { state }))
}
ApiAuthMode::ApiKey => unreachable!("api key mode is handled above"),
ApiAuthMode::AgentIdentity => unreachable!("agent identity mode is handled above"),
ApiAuthMode::PersonalAccessToken => {
AuthMode::ChatgptAuthTokens => Ok(Self::ChatgptAuthTokens(ChatgptAuthTokens { state })),
AuthMode::ApiKey => unreachable!("api key mode is handled above"),
AuthMode::AgentIdentity => unreachable!("agent identity mode is handled above"),
AuthMode::PersonalAccessToken => {
unreachable!("personal access token mode is handled above")
}
ApiAuthMode::BedrockApiKey => unreachable!("bedrock api key mode is handled above"),
AuthMode::BedrockApiKey => unreachable!("bedrock api key mode is handled above"),
}
}
@@ -395,6 +392,9 @@ impl CodexAuth {
))
}
/// Returns the effective backend auth mode.
///
/// Externally managed ChatGPT tokens are normalized to [`AuthMode::Chatgpt`].
pub fn auth_mode(&self) -> AuthMode {
match self {
Self::ApiKey(_) => AuthMode::ApiKey,
@@ -405,14 +405,15 @@ impl CodexAuth {
}
}
pub fn api_auth_mode(&self) -> ApiAuthMode {
/// Returns the precise kind of credentials backing this authentication.
pub fn api_auth_mode(&self) -> AuthMode {
match self {
Self::ApiKey(_) => ApiAuthMode::ApiKey,
Self::Chatgpt(_) => ApiAuthMode::Chatgpt,
Self::ChatgptAuthTokens(_) => ApiAuthMode::ChatgptAuthTokens,
Self::AgentIdentity(_) => ApiAuthMode::AgentIdentity,
Self::PersonalAccessToken(_) => ApiAuthMode::PersonalAccessToken,
Self::BedrockApiKey(_) => ApiAuthMode::BedrockApiKey,
Self::ApiKey(_) => AuthMode::ApiKey,
Self::Chatgpt(_) => AuthMode::Chatgpt,
Self::ChatgptAuthTokens(_) => AuthMode::ChatgptAuthTokens,
Self::AgentIdentity(_) => AuthMode::AgentIdentity,
Self::PersonalAccessToken(_) => AuthMode::PersonalAccessToken,
Self::BedrockApiKey(_) => AuthMode::BedrockApiKey,
}
}
@@ -660,7 +661,7 @@ impl CodexAuth {
/// Consider this private to integration tests.
pub fn create_dummy_chatgpt_auth_for_testing() -> Self {
let auth_dot_json = AuthDotJson {
auth_mode: Some(ApiAuthMode::Chatgpt),
auth_mode: Some(AuthMode::Chatgpt),
openai_api_key: None,
tokens: Some(TokenData {
id_token: Default::default(),
@@ -851,7 +852,7 @@ pub fn login_with_api_key(
keyring_backend_kind: AuthKeyringBackendKind,
) -> std::io::Result<()> {
let auth_dot_json = AuthDotJson {
auth_mode: Some(ApiAuthMode::ApiKey),
auth_mode: Some(AuthMode::ApiKey),
openai_api_key: Some(api_key.to_string()),
tokens: None,
last_refresh: None,
@@ -900,7 +901,7 @@ pub async fn login_with_access_token(
.to_string();
verified_record_from_jwt(jwt, &base_url, auth_route_config).await?;
AuthDotJson {
auth_mode: Some(ApiAuthMode::AgentIdentity),
auth_mode: Some(AuthMode::AgentIdentity),
openai_api_key: None,
tokens: None,
last_refresh: None,
@@ -1419,7 +1420,7 @@ impl AuthDotJson {
};
Ok(Self {
auth_mode: Some(ApiAuthMode::ChatgptAuthTokens),
auth_mode: Some(AuthMode::ChatgptAuthTokens),
openai_api_key: None,
tokens: Some(tokens),
last_refresh: Some(Utc::now()),
@@ -1442,27 +1443,27 @@ impl AuthDotJson {
Self::from_external_tokens(&external)
}
pub(super) fn resolved_mode(&self) -> ApiAuthMode {
pub(super) fn resolved_mode(&self) -> AuthMode {
if let Some(mode) = self.auth_mode {
return mode;
}
if self.personal_access_token.is_some() {
return ApiAuthMode::PersonalAccessToken;
return AuthMode::PersonalAccessToken;
}
if self.bedrock_api_key.is_some() {
return ApiAuthMode::BedrockApiKey;
return AuthMode::BedrockApiKey;
}
if self.openai_api_key.is_some() {
return ApiAuthMode::ApiKey;
return AuthMode::ApiKey;
}
ApiAuthMode::Chatgpt
AuthMode::Chatgpt
}
fn storage_mode(
&self,
auth_credentials_store_mode: AuthCredentialsStoreMode,
) -> AuthCredentialsStoreMode {
if self.resolved_mode() == ApiAuthMode::ChatgptAuthTokens {
if self.resolved_mode() == AuthMode::ChatgptAuthTokens {
AuthCredentialsStoreMode::Ephemeral
} else {
auth_credentials_store_mode
@@ -2072,19 +2073,19 @@ impl AuthManager {
match (a, b) {
(None, None) => true,
(Some(a), Some(b)) => match (a.api_auth_mode(), b.api_auth_mode()) {
(ApiAuthMode::ApiKey, ApiAuthMode::ApiKey) => a.api_key() == b.api_key(),
(ApiAuthMode::Chatgpt, ApiAuthMode::Chatgpt)
| (ApiAuthMode::ChatgptAuthTokens, ApiAuthMode::ChatgptAuthTokens) => {
(AuthMode::ApiKey, AuthMode::ApiKey) => a.api_key() == b.api_key(),
(AuthMode::Chatgpt, AuthMode::Chatgpt)
| (AuthMode::ChatgptAuthTokens, AuthMode::ChatgptAuthTokens) => {
a.get_current_auth_json() == b.get_current_auth_json()
}
(ApiAuthMode::AgentIdentity, ApiAuthMode::AgentIdentity) => match (a, b) {
(AuthMode::AgentIdentity, AuthMode::AgentIdentity) => match (a, b) {
(CodexAuth::AgentIdentity(a), CodexAuth::AgentIdentity(b)) => {
a.record() == b.record()
}
_ => false,
},
(ApiAuthMode::PersonalAccessToken, ApiAuthMode::PersonalAccessToken) => a == b,
(ApiAuthMode::BedrockApiKey, ApiAuthMode::BedrockApiKey) => a == b,
(AuthMode::PersonalAccessToken, AuthMode::PersonalAccessToken) => a == b,
(AuthMode::BedrockApiKey, AuthMode::BedrockApiKey) => a == b,
_ => false,
},
_ => false,
@@ -2401,13 +2402,15 @@ impl AuthManager {
Ok(result)
}
pub fn get_api_auth_mode(&self) -> Option<ApiAuthMode> {
/// Returns the precise kind of credentials backing the current authentication.
pub fn get_api_auth_mode(&self) -> Option<AuthMode> {
if self.has_external_api_key_auth() {
return Some(ApiAuthMode::ApiKey);
return Some(AuthMode::ApiKey);
}
self.auth_cached().as_ref().map(CodexAuth::api_auth_mode)
}
/// Returns the effective backend auth mode for the current authentication.
pub fn auth_mode(&self) -> Option<AuthMode> {
if self.has_external_api_key_auth() {
return Some(AuthMode::ApiKey);
+5 -5
View File
@@ -7,8 +7,8 @@
use serde::Serialize;
use std::time::Duration;
use codex_app_server_protocol::AuthMode as ApiAuthMode;
use codex_client::CodexHttpClient;
use codex_protocol::auth::AuthMode;
use super::manager::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
use super::manager::REVOKE_TOKEN_URL;
@@ -77,21 +77,21 @@ fn revocable_token(auth_dot_json: &AuthDotJson) -> Option<(&str, RevokeTokenKind
}
fn managed_chatgpt_tokens(auth_dot_json: &AuthDotJson) -> Option<&TokenData> {
if resolved_auth_mode(auth_dot_json) == ApiAuthMode::Chatgpt {
if resolved_auth_mode(auth_dot_json) == AuthMode::Chatgpt {
auth_dot_json.tokens.as_ref()
} else {
None
}
}
fn resolved_auth_mode(auth_dot_json: &AuthDotJson) -> ApiAuthMode {
fn resolved_auth_mode(auth_dot_json: &AuthDotJson) -> AuthMode {
if let Some(mode) = auth_dot_json.auth_mode {
return mode;
}
if auth_dot_json.openai_api_key.is_some() {
return ApiAuthMode::ApiKey;
return AuthMode::ApiKey;
}
ApiAuthMode::Chatgpt
AuthMode::Chatgpt
}
async fn revoke_oauth_token(
+1 -1
View File
@@ -22,12 +22,12 @@ use super::BedrockApiKeyAuth;
use crate::token_data::TokenData;
use codex_agent_identity::AgentIdentityJwtClaims;
use codex_agent_identity::decode_agent_identity_jwt;
use codex_app_server_protocol::AuthMode;
use codex_config::types::AuthCredentialsStoreMode;
pub use codex_config::types::AuthKeyringBackendKind;
use codex_keyring_store::DefaultKeyringStore;
use codex_keyring_store::KeyringStore;
use codex_protocol::account::PlanType as AccountPlanType;
use codex_protocol::auth::AuthMode;
use codex_secrets::LocalSecretsNamespace;
use codex_secrets::SecretName;
use codex_secrets::SecretScope;
+1 -1
View File
@@ -36,8 +36,8 @@ use crate::token_data::TokenData;
use crate::token_data::parse_chatgpt_jwt_claims;
use base64::Engine;
use chrono::Utc;
use codex_app_server_protocol::AuthMode;
use codex_config::types::AuthCredentialsStoreMode;
use codex_protocol::auth::AuthMode;
use codex_utils_template::Template;
use rand::RngCore;
use serde_json::Value as JsonValue;
+1 -1
View File
@@ -3,7 +3,6 @@ use anyhow::Result;
use base64::Engine;
use chrono::Duration;
use chrono::Utc;
use codex_app_server_protocol::AuthMode;
use codex_config::types::AuthCredentialsStoreMode;
use codex_login::AuthDotJson;
use codex_login::AuthKeyringBackendKind;
@@ -15,6 +14,7 @@ use codex_login::load_auth_dot_json;
use codex_login::save_auth;
use codex_login::token_data::IdTokenInfo;
use codex_login::token_data::TokenData;
use codex_protocol::auth::AuthMode;
use codex_protocol::auth::RefreshTokenFailedReason;
use core_test_support::skip_if_no_network;
use pretty_assertions::assert_eq;
+1 -1
View File
@@ -1,7 +1,6 @@
use anyhow::Context;
use anyhow::Result;
use base64::Engine;
use codex_app_server_protocol::AuthMode;
use codex_config::types::AuthCredentialsStoreMode;
use codex_login::AuthDotJson;
use codex_login::AuthKeyringBackendKind;
@@ -14,6 +13,7 @@ use codex_login::logout_with_revoke;
use codex_login::save_auth;
use codex_login::token_data::IdTokenInfo;
use codex_login::token_data::TokenData;
use codex_protocol::auth::AuthMode;
use core_test_support::skip_if_no_network;
use pretty_assertions::assert_eq;
use serde_json::Value;