mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
fix: fully revert agent identity runtime wiring (#18757)
## Summary This PR fully reverts the previously merged Agent Identity runtime integration from the old stack: https://github.com/openai/codex/pull/17387/changes It removes the Codex-side task lifecycle wiring, rollout/session persistence, feature flag plumbing, lazy `auth.json` mutation, background task auth paths, and request callsite changes introduced by that stack. This leaves the repo in a clean pre-AgentIdentity integration state so the follow-up PRs can reintroduce the pieces in smaller reviewable layers. ## Stack 1. This PR: full revert 2. https://github.com/openai/codex/pull/18871: move Agent Identity business logic into a crate 3. https://github.com/openai/codex/pull/18785: add explicit AgentIdentity auth mode and startup task allocation 4. https://github.com/openai/codex/pull/18811: migrate auth callsites through AuthProvider ## Testing Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI.
This commit is contained in:
1 parent
69c3d12274
commit
be75785504
85 files changed
+334
-5409
No files matched your search
@@ -1,4 +1,3 @@
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use reqwest::StatusCode;
|
||||
@@ -16,18 +15,14 @@ use std::sync::RwLock;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::sync::watch;
|
||||
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_app_server_protocol::AuthMode as ApiAuthMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
|
||||
use super::agent_assertion;
|
||||
use super::agent_assertion::AgentTaskAuthorizationTarget;
|
||||
use super::external_bearer::BearerTokenRefresher;
|
||||
use super::revoke::revoke_auth_tokens;
|
||||
pub use crate::auth::storage::AgentIdentityAuthRecord;
|
||||
pub use crate::auth::storage::AuthDotJson;
|
||||
use crate::auth::storage::AuthStorageBackend;
|
||||
use crate::auth::storage::create_auth_storage;
|
||||
@@ -43,12 +38,9 @@ use codex_protocol::auth::KnownPlan as InternalKnownPlan;
|
||||
use codex_protocol::auth::PlanType as InternalPlanType;
|
||||
use codex_protocol::auth::RefreshTokenFailedError;
|
||||
use codex_protocol::auth::RefreshTokenFailedReason;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use serde_json::Value;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::agent_identity::BackgroundAgentTaskAuthMode;
|
||||
use crate::agent_identity::BackgroundAgentTaskManager;
|
||||
/// Authentication mechanism used by the current user.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CodexAuth {
|
||||
@@ -71,7 +63,6 @@ pub struct ChatgptAuth {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChatgptAuthTokens {
|
||||
state: ChatgptAuthState,
|
||||
storage: Arc<dyn AuthStorageBackend>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -219,13 +210,14 @@ impl CodexAuth {
|
||||
client,
|
||||
};
|
||||
|
||||
let storage = create_auth_storage(codex_home.to_path_buf(), storage_mode);
|
||||
match auth_mode {
|
||||
ApiAuthMode::Chatgpt => Ok(Self::Chatgpt(ChatgptAuth { state, storage })),
|
||||
ApiAuthMode::ChatgptAuthTokens => Ok(Self::ChatgptAuthTokens(ChatgptAuthTokens {
|
||||
state,
|
||||
storage,
|
||||
})),
|
||||
ApiAuthMode::Chatgpt => {
|
||||
let storage = create_auth_storage(codex_home.to_path_buf(), storage_mode);
|
||||
Ok(Self::Chatgpt(ChatgptAuth { state, storage }))
|
||||
}
|
||||
ApiAuthMode::ChatgptAuthTokens => {
|
||||
Ok(Self::ChatgptAuthTokens(ChatgptAuthTokens { state }))
|
||||
}
|
||||
ApiAuthMode::ApiKey => unreachable!("api key mode is handled above"),
|
||||
}
|
||||
}
|
||||
@@ -372,59 +364,6 @@ impl CodexAuth {
|
||||
self.get_current_auth_json().and_then(|t| t.tokens)
|
||||
}
|
||||
|
||||
pub fn get_agent_identity(&self, workspace_id: &str) -> Option<AgentIdentityAuthRecord> {
|
||||
self.get_current_auth_json()
|
||||
.and_then(|auth| auth.agent_identity)
|
||||
.filter(|identity| identity.workspace_id == workspace_id)
|
||||
}
|
||||
|
||||
pub fn set_agent_identity(&self, mut record: AgentIdentityAuthRecord) -> std::io::Result<()> {
|
||||
let (state, storage) = match self {
|
||||
Self::Chatgpt(auth) => (&auth.state, &auth.storage),
|
||||
Self::ChatgptAuthTokens(auth) => (&auth.state, &auth.storage),
|
||||
Self::ApiKey(_) => return Ok(()),
|
||||
};
|
||||
let mut guard = state
|
||||
.auth_dot_json
|
||||
.lock()
|
||||
.map_err(|_| std::io::Error::other("failed to lock auth state"))?;
|
||||
let mut auth = guard
|
||||
.clone()
|
||||
.ok_or_else(|| std::io::Error::other("auth data is not available"))?;
|
||||
if record.background_task_id.is_none()
|
||||
&& let Some(existing) = auth.agent_identity.as_ref()
|
||||
&& existing.workspace_id == record.workspace_id
|
||||
&& existing.agent_runtime_id == record.agent_runtime_id
|
||||
{
|
||||
record.background_task_id = existing.background_task_id.clone();
|
||||
}
|
||||
auth.agent_identity = Some(record);
|
||||
storage.save(&auth)?;
|
||||
*guard = Some(auth);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_agent_identity(&self) -> std::io::Result<bool> {
|
||||
let (state, storage) = match self {
|
||||
Self::Chatgpt(auth) => (&auth.state, &auth.storage),
|
||||
Self::ChatgptAuthTokens(auth) => (&auth.state, &auth.storage),
|
||||
Self::ApiKey(_) => return Ok(false),
|
||||
};
|
||||
let mut guard = state
|
||||
.auth_dot_json
|
||||
.lock()
|
||||
.map_err(|_| std::io::Error::other("failed to lock auth state"))?;
|
||||
let Some(mut auth) = guard.clone() else {
|
||||
return Ok(false);
|
||||
};
|
||||
let removed = auth.agent_identity.take().is_some();
|
||||
if removed {
|
||||
storage.save(&auth)?;
|
||||
*guard = Some(auth);
|
||||
}
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
/// Consider this private to integration tests.
|
||||
pub fn create_dummy_chatgpt_auth_for_testing() -> Self {
|
||||
let auth_dot_json = AuthDotJson {
|
||||
@@ -437,7 +376,6 @@ impl CodexAuth {
|
||||
account_id: Some("account_id".to_string()),
|
||||
}),
|
||||
last_refresh: Some(Utc::now()),
|
||||
agent_identity: None,
|
||||
};
|
||||
|
||||
let client = create_client();
|
||||
@@ -530,7 +468,6 @@ pub fn login_with_api_key(
|
||||
openai_api_key: Some(api_key.to_string()),
|
||||
tokens: None,
|
||||
last_refresh: None,
|
||||
agent_identity: None,
|
||||
};
|
||||
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode)
|
||||
}
|
||||
@@ -904,7 +841,6 @@ impl AuthDotJson {
|
||||
openai_api_key: None,
|
||||
tokens: Some(tokens),
|
||||
last_refresh: Some(Utc::now()),
|
||||
agent_identity: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1198,11 +1134,8 @@ pub struct AuthManager {
|
||||
enable_codex_api_key_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
forced_chatgpt_workspace_id: RwLock<Option<String>>,
|
||||
chatgpt_base_url: RwLock<Option<String>>,
|
||||
background_agent_task_auth_mode: RwLock<BackgroundAgentTaskAuthMode>,
|
||||
refresh_lock: Semaphore,
|
||||
external_auth: RwLock<Option<Arc<dyn ExternalAuth>>>,
|
||||
auth_state_tx: watch::Sender<()>,
|
||||
}
|
||||
|
||||
/// Configuration view required to construct a shared [`AuthManager`].
|
||||
@@ -1220,16 +1153,6 @@ pub trait AuthManagerConfig {
|
||||
|
||||
/// Returns the workspace ID that ChatGPT auth should be restricted to, if any.
|
||||
fn forced_chatgpt_workspace_id(&self) -> Option<String>;
|
||||
|
||||
/// Returns the ChatGPT backend base URL used for first-party backend authorization.
|
||||
fn chatgpt_base_url(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns whether default ChatGPT backend authorization may use background AgentAssertion.
|
||||
fn background_agent_task_auth_mode(&self) -> BackgroundAgentTaskAuthMode {
|
||||
BackgroundAgentTaskAuthMode::Disabled
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for AuthManager {
|
||||
@@ -1246,11 +1169,6 @@ impl Debug for AuthManager {
|
||||
"forced_chatgpt_workspace_id",
|
||||
&self.forced_chatgpt_workspace_id,
|
||||
)
|
||||
.field("chatgpt_base_url", &self.chatgpt_base_url)
|
||||
.field(
|
||||
"background_agent_task_auth_mode",
|
||||
&self.background_agent_task_auth_mode,
|
||||
)
|
||||
.field("has_external_auth", &self.has_external_auth())
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
@@ -1266,7 +1184,6 @@ impl AuthManager {
|
||||
enable_codex_api_key_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
) -> Self {
|
||||
let (auth_state_tx, _) = watch::channel(());
|
||||
let managed_auth = load_auth(
|
||||
&codex_home,
|
||||
enable_codex_api_key_env,
|
||||
@@ -1283,17 +1200,13 @@ impl AuthManager {
|
||||
enable_codex_api_key_env,
|
||||
auth_credentials_store_mode,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
chatgpt_base_url: RwLock::new(None),
|
||||
background_agent_task_auth_mode: RwLock::new(BackgroundAgentTaskAuthMode::Disabled),
|
||||
refresh_lock: Semaphore::new(/*permits*/ 1),
|
||||
external_auth: RwLock::new(None),
|
||||
auth_state_tx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an AuthManager with a specific CodexAuth, for testing only.
|
||||
pub fn from_auth_for_testing(auth: CodexAuth) -> Arc<Self> {
|
||||
let (auth_state_tx, _) = watch::channel(());
|
||||
let cached = CachedAuth {
|
||||
auth: Some(auth),
|
||||
permanent_refresh_failure: None,
|
||||
@@ -1305,17 +1218,13 @@ impl AuthManager {
|
||||
enable_codex_api_key_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
chatgpt_base_url: RwLock::new(None),
|
||||
background_agent_task_auth_mode: RwLock::new(BackgroundAgentTaskAuthMode::Disabled),
|
||||
refresh_lock: Semaphore::new(/*permits*/ 1),
|
||||
external_auth: RwLock::new(None),
|
||||
auth_state_tx,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create an AuthManager with a specific CodexAuth and codex home, for testing only.
|
||||
pub fn from_auth_for_testing_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc<Self> {
|
||||
let (auth_state_tx, _) = watch::channel(());
|
||||
let cached = CachedAuth {
|
||||
auth: Some(auth),
|
||||
permanent_refresh_failure: None,
|
||||
@@ -1326,16 +1235,12 @@ impl AuthManager {
|
||||
enable_codex_api_key_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
chatgpt_base_url: RwLock::new(None),
|
||||
background_agent_task_auth_mode: RwLock::new(BackgroundAgentTaskAuthMode::Disabled),
|
||||
refresh_lock: Semaphore::new(/*permits*/ 1),
|
||||
external_auth: RwLock::new(None),
|
||||
auth_state_tx,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn external_bearer_only(config: ModelProviderAuthInfo) -> Arc<Self> {
|
||||
let (auth_state_tx, _) = watch::channel(());
|
||||
Arc::new(Self {
|
||||
codex_home: PathBuf::from("non-existent"),
|
||||
inner: RwLock::new(CachedAuth {
|
||||
@@ -1345,13 +1250,10 @@ impl AuthManager {
|
||||
enable_codex_api_key_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
chatgpt_base_url: RwLock::new(None),
|
||||
background_agent_task_auth_mode: RwLock::new(BackgroundAgentTaskAuthMode::Disabled),
|
||||
refresh_lock: Semaphore::new(/*permits*/ 1),
|
||||
external_auth: RwLock::new(Some(
|
||||
Arc::new(BearerTokenRefresher::new(config)) as Arc<dyn ExternalAuth>
|
||||
)),
|
||||
auth_state_tx,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1491,7 +1393,6 @@ impl AuthManager {
|
||||
}
|
||||
tracing::info!("Reloaded auth, changed: {changed}");
|
||||
guard.auth = new_auth;
|
||||
self.auth_state_tx.send_replace(());
|
||||
changed
|
||||
} else {
|
||||
false
|
||||
@@ -1501,23 +1402,18 @@ impl AuthManager {
|
||||
pub fn set_external_auth(&self, external_auth: Arc<dyn ExternalAuth>) {
|
||||
if let Ok(mut guard) = self.external_auth.write() {
|
||||
*guard = Some(external_auth);
|
||||
self.auth_state_tx.send_replace(());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_external_auth(&self) {
|
||||
if let Ok(mut guard) = self.external_auth.write() {
|
||||
*guard = None;
|
||||
self.auth_state_tx.send_replace(());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_forced_chatgpt_workspace_id(&self, workspace_id: Option<String>) {
|
||||
if let Ok(mut guard) = self.forced_chatgpt_workspace_id.write()
|
||||
&& *guard != workspace_id
|
||||
{
|
||||
if let Ok(mut guard) = self.forced_chatgpt_workspace_id.write() {
|
||||
*guard = workspace_id;
|
||||
self.auth_state_tx.send_replace(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1528,131 +1424,6 @@ impl AuthManager {
|
||||
.and_then(|guard| guard.clone())
|
||||
}
|
||||
|
||||
pub fn chatgpt_agent_task_authorization_header_for_auth(
|
||||
&self,
|
||||
auth: &CodexAuth,
|
||||
target: AgentTaskAuthorizationTarget<'_>,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
let Some(record) = self.agent_identity_for_chatgpt_auth(auth)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
agent_assertion::authorization_header_for_agent_task(&record, target).map(Some)
|
||||
}
|
||||
|
||||
fn agent_identity_for_chatgpt_auth(
|
||||
&self,
|
||||
auth: &CodexAuth,
|
||||
) -> anyhow::Result<Option<AgentIdentityAuthRecord>> {
|
||||
if !auth.is_chatgpt_auth() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let token_data = auth
|
||||
.get_token_data()
|
||||
.context("ChatGPT token data is not available")?;
|
||||
let workspace_id = self
|
||||
.forced_chatgpt_workspace_id()
|
||||
.filter(|value| !value.is_empty())
|
||||
.or(token_data.account_id.filter(|value| !value.is_empty()));
|
||||
|
||||
let Some(workspace_id) = workspace_id else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(record) = auth.get_agent_identity(&workspace_id) else {
|
||||
anyhow::bail!("agent identity is not available for workspace {workspace_id}");
|
||||
};
|
||||
|
||||
Ok(Some(record))
|
||||
}
|
||||
|
||||
pub fn set_chatgpt_backend_auth_config(
|
||||
&self,
|
||||
chatgpt_base_url: Option<String>,
|
||||
background_agent_task_auth_mode: BackgroundAgentTaskAuthMode,
|
||||
) {
|
||||
let mut changed = false;
|
||||
if let Ok(mut guard) = self.chatgpt_base_url.write()
|
||||
&& *guard != chatgpt_base_url
|
||||
{
|
||||
*guard = chatgpt_base_url;
|
||||
changed = true;
|
||||
}
|
||||
if let Ok(mut guard) = self.background_agent_task_auth_mode.write()
|
||||
&& *guard != background_agent_task_auth_mode
|
||||
{
|
||||
*guard = background_agent_task_auth_mode;
|
||||
changed = true;
|
||||
}
|
||||
if changed {
|
||||
self.auth_state_tx.send_replace(());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_chatgpt_backend_base_url(&self, chatgpt_base_url: Option<String>) {
|
||||
let (_, auth_mode) = self.chatgpt_backend_auth_config();
|
||||
self.set_chatgpt_backend_auth_config(chatgpt_base_url, auth_mode);
|
||||
}
|
||||
|
||||
fn chatgpt_backend_auth_config(&self) -> (Option<String>, BackgroundAgentTaskAuthMode) {
|
||||
let chatgpt_base_url = self
|
||||
.chatgpt_base_url
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|guard| guard.clone());
|
||||
let auth_mode = self
|
||||
.background_agent_task_auth_mode
|
||||
.read()
|
||||
.ok()
|
||||
.map(|guard| *guard)
|
||||
.unwrap_or_default();
|
||||
(chatgpt_base_url, auth_mode)
|
||||
}
|
||||
|
||||
/// Returns the default authorization header for ChatGPT backend requests.
|
||||
///
|
||||
/// This uses background AgentAssertion when configured and available, otherwise it falls back
|
||||
/// to the ChatGPT bearer token. Low-level bootstrap calls that must never use AgentAssertion
|
||||
/// should use [`Self::chatgpt_bearer_authorization_header_for_auth`] instead.
|
||||
pub async fn chatgpt_authorization_header(self: &Arc<Self>) -> Option<String> {
|
||||
let auth = self.auth().await?;
|
||||
self.chatgpt_authorization_header_for_auth(&auth).await
|
||||
}
|
||||
|
||||
pub async fn chatgpt_authorization_header_for_auth(
|
||||
self: &Arc<Self>,
|
||||
auth: &CodexAuth,
|
||||
) -> Option<String> {
|
||||
if !auth.is_chatgpt_auth() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (chatgpt_base_url, auth_mode) = self.chatgpt_backend_auth_config();
|
||||
let Some(chatgpt_base_url) = chatgpt_base_url else {
|
||||
return Self::chatgpt_bearer_authorization_header_for_auth(auth);
|
||||
};
|
||||
|
||||
BackgroundAgentTaskManager::new_with_auth_mode(
|
||||
Arc::clone(self),
|
||||
chatgpt_base_url,
|
||||
SessionSource::Cli,
|
||||
auth_mode,
|
||||
)
|
||||
.authorization_header_value_or_bearer(auth)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn chatgpt_bearer_token_for_auth(auth: &CodexAuth) -> Option<String> {
|
||||
auth.get_token().ok().filter(|token| !token.is_empty())
|
||||
}
|
||||
|
||||
pub fn chatgpt_bearer_authorization_header_for_auth(auth: &CodexAuth) -> Option<String> {
|
||||
Self::chatgpt_bearer_token_for_auth(auth).map(|token| format!("Bearer {token}"))
|
||||
}
|
||||
|
||||
pub fn subscribe_auth_state(&self) -> watch::Receiver<()> {
|
||||
self.auth_state_tx.subscribe()
|
||||
}
|
||||
|
||||
pub fn has_external_auth(&self) -> bool {
|
||||
self.external_auth().is_some()
|
||||
}
|
||||
@@ -1691,10 +1462,6 @@ impl AuthManager {
|
||||
config.cli_auth_credentials_store_mode(),
|
||||
);
|
||||
auth_manager.set_forced_chatgpt_workspace_id(config.forced_chatgpt_workspace_id());
|
||||
auth_manager.set_chatgpt_backend_auth_config(
|
||||
config.chatgpt_base_url(),
|
||||
config.background_agent_task_auth_mode(),
|
||||
);
|
||||
auth_manager
|
||||
}
|
||||
|
||||
@@ -1923,14 +1690,8 @@ impl AuthManager {
|
||||
),
|
||||
)));
|
||||
}
|
||||
let mut auth_dot_json =
|
||||
let auth_dot_json =
|
||||
AuthDotJson::from_external_tokens(&refreshed).map_err(RefreshTokenError::Transient)?;
|
||||
if let Some(previous_auth) = self
|
||||
.auth_cached()
|
||||
.and_then(|auth| auth.get_current_auth_json())
|
||||
{
|
||||
auth_dot_json.agent_identity = previous_auth.agent_identity;
|
||||
}
|
||||
save_auth(
|
||||
&self.codex_home,
|
||||
&auth_dot_json,
|
||||
|
||||
Reference in new issue
Block a user