Migrate agent identity to the shared HTTP client (#34631)

## Why

`codex-http-client` is the intended owner of direct `reqwest` usage.

## What changed

- Route agent identity registration, task registration, and JWKS requests through `HttpClient`.
- Preserve retry classification using `HttpError` and `http::StatusCode`.
- Remove `codex-agent-identity` from the temporary direct-`reqwest` dependency exceptions.

GitOrigin-RevId: 7445c9f2521e6d8ef0969a1483c81f797bf91316
This commit is contained in:
Michael Bolin
2026-07-21 22:44:43 +00:00
committed by copyberry
parent adb143a291
commit 1b77da35cc
5 changed files with 25 additions and 20 deletions
+2 -1
View File
@@ -1868,13 +1868,14 @@ dependencies = [
"anyhow",
"base64 0.22.1",
"chrono",
"codex-http-client",
"codex-protocol",
"crypto_box",
"ed25519-dalek",
"http 1.4.0",
"jsonwebtoken",
"pretty_assertions",
"rand 0.9.3",
"reqwest 0.12.28",
"serde",
"serde_json",
"sha2 0.10.9",
+2 -1
View File
@@ -16,12 +16,13 @@ workspace = true
anyhow = { workspace = true }
base64 = { workspace = true }
chrono = { workspace = true }
codex-http-client = { workspace = true }
codex-protocol = { workspace = true }
crypto_box = { workspace = true }
ed25519-dalek = { workspace = true }
http = { workspace = true }
jsonwebtoken = { workspace = true }
rand = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = { workspace = true }
+15 -12
View File
@@ -10,6 +10,8 @@ use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use chrono::SecondsFormat;
use chrono::Utc;
use codex_http_client::HttpClient;
use codex_http_client::HttpError;
use codex_protocol::auth::PlanType as AuthPlanType;
use codex_protocol::protocol::SessionSource;
use crypto_box::SecretKey as Curve25519SecretKey;
@@ -18,6 +20,7 @@ use ed25519_dalek::SigningKey;
use ed25519_dalek::VerifyingKey;
use ed25519_dalek::pkcs8::DecodePrivateKey;
use ed25519_dalek::pkcs8::EncodePrivateKey;
use http::StatusCode;
use jsonwebtoken::Algorithm;
use jsonwebtoken::DecodingKey;
use jsonwebtoken::Validation;
@@ -167,12 +170,12 @@ struct RegisterAgentResponse {
#[derive(Debug)]
pub struct AgentIdentityRegistrationHttpError {
operation: &'static str,
status: reqwest::StatusCode,
status: StatusCode,
body: String,
}
impl AgentIdentityRegistrationHttpError {
fn new(operation: &'static str, status: reqwest::StatusCode, body: String) -> Self {
fn new(operation: &'static str, status: StatusCode, body: String) -> Self {
Self {
operation,
status,
@@ -181,7 +184,7 @@ impl AgentIdentityRegistrationHttpError {
}
/// HTTP status returned by the registration endpoint.
pub fn status(&self) -> reqwest::StatusCode {
pub fn status(&self) -> StatusCode {
self.status
}
}
@@ -212,7 +215,7 @@ fn is_retryable_registration_cause(cause: &(dyn StdError + 'static)) -> bool {
return is_retryable_registration_status(error.status());
}
if let Some(error) = cause.downcast_ref::<reqwest::Error>() {
if let Some(error) = cause.downcast_ref::<HttpError>() {
if let Some(status) = error.status() {
return is_retryable_registration_status(status);
}
@@ -222,8 +225,8 @@ fn is_retryable_registration_cause(cause: &(dyn StdError + 'static)) -> bool {
false
}
fn is_retryable_registration_status(status: reqwest::StatusCode) -> bool {
status == reqwest::StatusCode::TOO_MANY_REQUESTS || status.is_server_error()
fn is_retryable_registration_status(status: StatusCode) -> bool {
status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error()
}
pub fn authorization_header_for_agent_task(
@@ -242,7 +245,7 @@ pub fn authorization_header_for_agent_task(
}
pub async fn fetch_agent_identity_jwks(
client: &reqwest::Client,
client: &HttpClient,
agent_identity_jwt_base_url: &str,
) -> Result<JwkSet> {
let response = client
@@ -310,7 +313,7 @@ pub fn sign_task_registration_payload(
}
pub async fn register_agent_task(
client: &reqwest::Client,
client: &HttpClient,
agent_identity_authapi_base_url: &str,
key: AgentIdentityKey<'_>,
) -> Result<String> {
@@ -353,7 +356,7 @@ pub async fn register_agent_task(
}
pub async fn register_agent_identity(
client: &reqwest::Client,
client: &HttpClient,
agent_identity_authapi_base_url: &str,
access_token: &str,
is_fedramp_account: bool,
@@ -937,12 +940,12 @@ J1bwkqKZTB5dHolX9A58e/xXnfZ5P8f3Z83+Izap3FwqQulk7b1WO1MQcHuVg2NN
fn retryable_registration_error_accepts_429_and_5xx() {
let too_many_requests = anyhow::Error::new(AgentIdentityRegistrationHttpError::new(
"agent registration",
reqwest::StatusCode::TOO_MANY_REQUESTS,
StatusCode::TOO_MANY_REQUESTS,
"rate limited".to_string(),
));
let unavailable = anyhow::Error::new(AgentIdentityRegistrationHttpError::new(
"agent registration",
reqwest::StatusCode::SERVICE_UNAVAILABLE,
StatusCode::SERVICE_UNAVAILABLE,
"try later".to_string(),
));
@@ -954,7 +957,7 @@ J1bwkqKZTB5dHolX9A58e/xXnfZ5P8f3Z83+Izap3FwqQulk7b1WO1MQcHuVg2NN
fn retryable_registration_error_rejects_hard_failures() {
let forbidden = anyhow::Error::new(AgentIdentityRegistrationHttpError::new(
"agent registration",
reqwest::StatusCode::FORBIDDEN,
StatusCode::FORBIDDEN,
"not allowed".to_string(),
));
let malformed = anyhow::anyhow!("failed to sign registration request");
-1
View File
@@ -239,7 +239,6 @@ deny = [
# Intended owner.
"codex-http-client",
# Temporary migration exceptions.
"codex-agent-identity",
"codex-api",
"codex-app-server",
"codex-core",
+6 -5
View File
@@ -14,11 +14,12 @@ use codex_agent_identity::is_retryable_registration_error;
use codex_agent_identity::public_key_ssh_from_private_key_pkcs8_base64;
use codex_agent_identity::register_agent_identity;
use codex_agent_identity::register_agent_task;
use codex_http_client::HttpClient;
use codex_protocol::account::PlanType as AccountPlanType;
use codex_protocol::protocol::SessionSource;
use thiserror::Error;
use crate::default_client::build_default_auth_reqwest_client;
use crate::default_client::create_default_auth_client;
use crate::outbound_proxy::AuthRouteConfig;
use super::storage::AgentIdentityAuthRecord;
@@ -178,7 +179,7 @@ pub(super) async fn register_managed_chatgpt_agent_identity(
) -> std::io::Result<AgentIdentityAuth> {
let key_material = generate_agent_key_material().map_err(std::io::Error::other)?;
let registration_url = agent_registration_url(agent_identity_authapi_base_url);
let client = build_default_auth_reqwest_client(&registration_url, auth_route_config)?;
let client = create_default_auth_client(&registration_url, auth_route_config)?;
let runtime_id = retry_registration(|| async {
register_agent_identity(
&client,
@@ -225,7 +226,7 @@ pub(super) async fn verified_record_from_jwt(
) -> std::io::Result<AgentIdentityAuthRecord> {
AgentIdentityAuthRecord::from_agent_identity_jwt(jwt)?;
let jwks_url = agent_identity_jwks_url(chatgpt_base_url);
let client = build_default_auth_reqwest_client(&jwks_url, auth_route_config)?;
let client = create_default_auth_client(&jwks_url, auth_route_config)?;
let jwks = fetch_agent_identity_jwks(&client, chatgpt_base_url)
.await
.map_err(std::io::Error::other)?;
@@ -305,7 +306,7 @@ async fn register_task_for_record_with_retries(
) -> std::io::Result<String> {
let task_registration_url =
agent_task_registration_url(agent_identity_authapi_base_url, &record.agent_runtime_id);
let client = build_default_auth_reqwest_client(&task_registration_url, auth_route_config)?;
let client = create_default_auth_client(&task_registration_url, auth_route_config)?;
retry_registration(|| async {
register_task_for_record(&client, record, agent_identity_authapi_base_url).await
})
@@ -313,7 +314,7 @@ async fn register_task_for_record_with_retries(
}
async fn register_task_for_record(
client: &reqwest::Client,
client: &HttpClient,
record: &AgentIdentityAuthRecord,
agent_identity_authapi_base_url: &str,
) -> std::io::Result<String> {