Files
codex/codex-rs/codex-api/src/auth.rs
T
Adrian 51864b0b4b feat: use run agent task auth for inference (#19051)
## Stack

This is PR 3 of the simplified HAI single-run-task stack:

- [#19047](https://github.com/openai/codex/pull/19047) Agent Identity
assertion and task-registration primitives, including the shared
run-task helper used by existing Agent Identity JWT auth.
- [#19049](https://github.com/openai/codex/pull/19049)
Disabled-by-default ChatGPT auth opt-in that provisions/reuses persisted
Agent Identity runtime auth and its single run task.
- [#19051](https://github.com/openai/codex/pull/19051) Run-scoped
provider auth that uses one backend-owned task id for first-party
inference and compaction requests.

[#19054](https://github.com/openai/codex/pull/19054) collapsed out of
the active stack because the simplified design no longer needs a
separate background/control-plane task helper.

## Summary

This PR moves Agent Identity usage into provider auth resolution. That
keeps `AgentAssertion` auth tied to first-party OpenAI provider requests
instead of applying a late session-wide override that could affect
local, custom, Bedrock, API-key, or external-bearer providers.

What changed:

- adds a small `ProviderAuthScope` struct carrying the run auth policy
and session source needed by provider-scoped auth resolution
- lets `Session` opt the existing `ModelClient` into `ChatGptAuth`
policy when `use_agent_identity` is enabled, without adding a second
model-client constructor
- resolves Agent Identity only for first-party OpenAI provider auth
paths
- uses the persisted run task id from the `AgentIdentityAuth` record to
build `AgentAssertion` auth for Responses requests
- routes shared request setup through scoped provider auth so unary
compact requests use the same run-task assertion path as inference turns
- keeps local/custom/Bedrock/env-key/external-bearer provider auth
unchanged
- lets missing run-task state surface through the existing model-request
error path instead of silently falling back to bearer auth

This PR intentionally does not create thread-scoped, target-scoped, or
background-scoped task identities. The run task is the only task Codex
registers in this POC shape.

## Testing

- `just test -p codex-model-provider`
- `just test -p codex-core client::tests::provider_auth_scope_uses`
- `just test -p codex-core remote_compact_uses_agent_identity_assertion`
2026-06-24 22:31:41 -07:00

93 lines
3.1 KiB
Rust

use codex_client::Request;
use codex_client::TransportError;
use http::HeaderMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
/// Error returned while applying authentication to an outbound request.
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("request auth build error: {0}")]
Build(String),
#[error("transient auth error: {0}")]
Transient(String),
}
impl From<AuthError> for TransportError {
fn from(error: AuthError) -> Self {
match error {
AuthError::Build(message) => TransportError::Build(message),
AuthError::Transient(message) => TransportError::Network(message),
}
}
}
/// Applies authentication to API requests.
///
/// Header-only providers can implement `add_auth_headers`; providers that sign
/// complete requests can override `apply_auth`.
pub trait AuthProvider: Send + Sync {
/// Adds any auth headers that are available without request body access.
///
/// Implementations should be cheap and non-blocking. This method is also
/// used by telemetry and non-HTTP request paths.
fn add_auth_headers(&self, headers: &mut HeaderMap);
/// Returns any auth headers that are available without request body access.
fn to_auth_headers(&self) -> HeaderMap {
let mut headers = HeaderMap::new();
self.add_auth_headers(&mut headers);
headers
}
/// Applies auth to a complete outbound request and returns the request to send.
///
/// The input `request` is moved into this method. Implementations may mutate
/// the owned request, or replace it entirely, before returning.
///
/// Header-only auth providers can rely on the default implementation.
/// Request-signing providers can override this to inspect the final URL,
/// headers, and body bytes before the transport sends the request.
///
/// Callers must always use the returned request as authoritative.
/// If this returns [`AuthError`], the request should not be sent.
fn apply_auth(&self, request: Request) -> AuthProviderFuture<'_> {
Box::pin(async move {
let mut request = request;
self.add_auth_headers(&mut request.headers);
Ok(request)
})
}
}
pub type AuthProviderFuture<'a> =
Pin<Box<dyn Future<Output = Result<Request, AuthError>> + Send + 'a>>;
/// Shared auth handle passed through API clients.
pub type SharedAuthProvider = Arc<dyn AuthProvider>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AgentIdentityTelemetry {
pub agent_id: String,
pub task_id: String,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct AuthHeaderTelemetry {
pub attached: bool,
pub name: Option<&'static str>,
}
pub fn auth_header_telemetry(auth: &dyn AuthProvider) -> AuthHeaderTelemetry {
let mut headers = HeaderMap::new();
auth.add_auth_headers(&mut headers);
let name = headers
.contains_key(http::header::AUTHORIZATION)
.then_some("authorization");
AuthHeaderTelemetry {
attached: name.is_some(),
name,
}
}