Wire shared runtime authentication through threads and API clients (#59177)

## What changed

- Share a lazily registered runtime across threads, model discovery, and extension clients when `runtime_agent_instances` is enabled, while honoring each thread's authentication mode and credential owner.
- Bind model catalogs to the effective provider and authentication context while preserving cache policy and live API-key discovery settings.
- Resolve credentials, routing, and transport policy together for each request. Preserve proxy and TLS settings, validate runtime account selectors, and prevent pooled connections from crossing runtime identities.
- Accept supported ChatGPT subdomains when selecting the registration environment.

## Testing

Add integration coverage for shared runtime registration, dual-header and assertion-only modes, disabled and third-party providers, credential replacement, subagents, and delegated review. Add coverage for catalog cache policy and runtime-aware connection reuse.

GitOrigin-RevId: 99dd9d987c29c93597a648f6586f98ac6a932c3c
This commit is contained in:
cooper-oai
2026-11-03 15:02:57 +00:00
committed by copyberry
parent f7167ac895
commit 079e17a422
71 changed files with 2636 additions and 820 deletions
+68 -9
View File
@@ -9,6 +9,7 @@ use crate::RuntimeAuth;
use crate::RuntimeRegistration;
use crate::RuntimeRegistrationClient;
use crate::RuntimeRegistrationMetadata;
use codex_agent_identity::ChatGptEnvironment;
use codex_http_client::HttpClientFactory;
use codex_http_client::NetworkPolicyDenied;
use codex_login::AuthChangeState;
@@ -23,12 +24,52 @@ use url::Url;
/// Owned alongside an AuthManager, not created for every thread or request.
pub struct RuntimeAuthManager {
credentials: Arc<AuthManager>,
host: AgentHostAuthorization,
registration: RegistrationTarget,
metadata: RuntimeRegistrationMetadata,
registration_url: Url,
state: Mutex<Option<CachedRuntime>>,
}
enum RegistrationTarget {
Explicit {
host: AgentHostAuthorization,
url: Url,
},
ChatGpt {
installation_id: String,
base_url: String,
},
}
impl RegistrationTarget {
fn host(&self) -> Result<AgentHostAuthorization, RegistrationError> {
match self {
Self::Explicit { host, .. } => Ok(host.clone()),
Self::ChatGpt {
installation_id, ..
} => Ok(AgentHostAuthorization::InstallationId {
installation_id: installation_id
.parse()
.map_err(|_| RegistrationError::InvalidRequest)?,
}),
}
}
fn url(&self) -> Result<Url, RegistrationError> {
match self {
Self::Explicit { url, .. } => Ok(url.clone()),
Self::ChatGpt { base_url, .. } => {
let url = match ChatGptEnvironment::from_chatgpt_base_url(base_url)
.map_err(|_| RegistrationError::InvalidEndpoint)?
{
ChatGptEnvironment::Production => crate::DEFAULT_REGISTRATION_URL,
ChatGptEnvironment::Staging => "https://auth.api.openai.org/agent/register",
};
url.parse().map_err(|_| RegistrationError::InvalidEndpoint)
}
}
}
}
struct CachedRuntime {
source_generation: u64,
selection: PlatformResourceSelection,
@@ -61,9 +102,29 @@ impl RuntimeAuthManager {
) -> Self {
Self {
credentials,
host,
registration: RegistrationTarget::Explicit {
host,
url: registration_url,
},
metadata,
state: Mutex::new(None),
}
}
/// Resolve the ChatGPT registration origin only when a first-party request needs a runtime.
pub fn for_chatgpt(
credentials: Arc<AuthManager>,
installation_id: String,
base_url: String,
metadata: RuntimeRegistrationMetadata,
) -> Self {
Self {
credentials,
registration: RegistrationTarget::ChatGpt {
installation_id,
base_url,
},
metadata,
registration_url,
state: Mutex::new(None),
}
}
@@ -149,7 +210,7 @@ impl RuntimeAuthManager {
state: RuntimeState::Pending {
registration: RuntimeRegistration::new(
actor_auth.clone(),
self.host.clone(),
self.registration.host()?,
self.metadata.clone(),
)
.map_err(|_| RuntimeAuthError::KeyGeneration)?,
@@ -172,10 +233,8 @@ impl RuntimeAuthManager {
registration
}
};
let client = RuntimeRegistrationClient::new(
http_client_factory.clone(),
self.registration_url.clone(),
)?;
let client =
RuntimeRegistrationClient::new(http_client_factory.clone(), self.registration.url()?)?;
let result = client.register(registration).await;
if self.auth_state(&auth)?.source_generation != change_state.source_generation {
// A late response must not publish an old identity after a credential switch.