Skip to content

Commit 51864b0

Browse files
feat: use run agent task auth for inference (#19051)
## Stack This is PR 3 of the simplified HAI single-run-task stack: - [#19047](#19047) Agent Identity assertion and task-registration primitives, including the shared run-task helper used by existing Agent Identity JWT auth. - [#19049](#19049) Disabled-by-default ChatGPT auth opt-in that provisions/reuses persisted Agent Identity runtime auth and its single run task. - [#19051](#19051) Run-scoped provider auth that uses one backend-owned task id for first-party inference and compaction requests. [#19054](#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`
1 parent f66d793 commit 51864b0

22 files changed

Lines changed: 1020 additions & 20 deletions

codex-rs/codex-api/src/auth.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ pub type AuthProviderFuture<'a> =
6767
/// Shared auth handle passed through API clients.
6868
pub type SharedAuthProvider = Arc<dyn AuthProvider>;
6969

70+
#[derive(Clone, Debug, PartialEq, Eq)]
71+
pub struct AgentIdentityTelemetry {
72+
pub agent_id: String,
73+
pub task_id: String,
74+
}
75+
7076
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7177
pub struct AuthHeaderTelemetry {
7278
pub attached: bool,

codex-rs/codex-api/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub use codex_client::ReqwestTransport;
1919
pub use codex_client::TransportError;
2020

2121
pub use crate::api_bridge::map_api_error;
22+
pub use crate::auth::AgentIdentityTelemetry;
2223
pub use crate::auth::AuthError;
2324
pub use crate::auth::AuthHeaderTelemetry;
2425
pub use crate::auth::AuthProvider;

codex-rs/core/src/client.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use std::sync::OnceLock;
3030
use std::sync::atomic::AtomicBool;
3131
use std::sync::atomic::Ordering;
3232

33+
use codex_api::AgentIdentityTelemetry;
3334
use codex_api::ApiError;
3435
use codex_api::AuthProvider;
3536
use codex_api::CompactClient as ApiCompactClient;
@@ -115,8 +116,11 @@ use crate::responses_metadata::subagent_header_value;
115116
use crate::util::emit_feedback_auth_recovery_tags;
116117
use codex_feedback::FeedbackRequestTags;
117118
use codex_feedback::emit_feedback_request_tags_with_auth_env;
119+
use codex_login::auth::AgentIdentityAuthPolicy;
118120
use codex_login::auth_env_telemetry::AuthEnvTelemetry;
119121
use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
122+
use codex_model_provider::AgentIdentitySessionFallback;
123+
use codex_model_provider::ProviderAuthScope;
120124
use codex_model_provider::SharedModelProvider;
121125
use codex_model_provider::create_model_provider;
122126
#[cfg(test)]
@@ -202,6 +206,7 @@ struct ModelClientState {
202206
include_attestation: bool,
203207
attestation_provider: Option<Arc<dyn AttestationProvider>>,
204208
disable_websockets: AtomicBool,
209+
agent_identity_session_fallback: AgentIdentitySessionFallback,
205210
cached_websocket_session: StdMutex<WebsocketSession>,
206211
}
207212

@@ -213,6 +218,7 @@ struct CurrentClientSetup {
213218
auth: Option<CodexAuth>,
214219
api_provider: ApiProvider,
215220
api_auth: SharedAuthProvider,
221+
agent_identity_telemetry: Option<AgentIdentityTelemetry>,
216222
}
217223

218224
#[derive(Clone, Copy)]
@@ -240,6 +246,7 @@ impl RequestRouteTelemetry {
240246
#[derive(Debug, Clone)]
241247
pub struct ModelClient {
242248
state: Arc<ModelClientState>,
249+
agent_identity_policy: AgentIdentityAuthPolicy,
243250
prompt_cache_key_override: Option<String>,
244251
}
245252

@@ -392,6 +399,7 @@ impl ModelClient {
392399
/// are passed to [`ModelClientSession::stream`] (and other turn-scoped methods) explicitly.
393400
pub fn new(
394401
auth_manager: Option<Arc<AuthManager>>,
402+
agent_identity_policy: AgentIdentityAuthPolicy,
395403
thread_id: ThreadId,
396404
provider_info: ModelProviderInfo,
397405
session_source: SessionSource,
@@ -426,8 +434,10 @@ impl ModelClient {
426434
include_attestation,
427435
attestation_provider,
428436
disable_websockets: AtomicBool::new(false),
437+
agent_identity_session_fallback: AgentIdentitySessionFallback::default(),
429438
cached_websocket_session: StdMutex::new(WebsocketSession::default()),
430439
}),
440+
agent_identity_policy,
431441
prompt_cache_key_override: None,
432442
}
433443
}
@@ -528,6 +538,7 @@ impl ModelClient {
528538
AuthRequestTelemetryContext::new(
529539
client_setup.auth.as_ref().map(CodexAuth::auth_mode),
530540
client_setup.api_auth.as_ref(),
541+
client_setup.agent_identity_telemetry.clone(),
531542
PendingUnauthorizedRetry::default(),
532543
),
533544
RequestRouteTelemetry::for_endpoint(RESPONSES_COMPACT_ENDPOINT),
@@ -660,6 +671,7 @@ impl ModelClient {
660671
AuthRequestTelemetryContext::new(
661672
client_setup.auth.as_ref().map(CodexAuth::auth_mode),
662673
client_setup.api_auth.as_ref(),
674+
client_setup.agent_identity_telemetry.clone(),
663675
PendingUnauthorizedRetry::default(),
664676
),
665677
RequestRouteTelemetry::for_endpoint(MEMORIES_SUMMARIZE_ENDPOINT),
@@ -909,14 +921,27 @@ impl ModelClient {
909921
async fn current_client_setup(&self) -> Result<CurrentClientSetup> {
910922
let auth = self.state.provider.auth().await;
911923
let api_provider = self.state.provider.api_provider().await?;
912-
let api_auth = self.state.provider.api_auth().await?;
924+
let resolved_auth = self
925+
.state
926+
.provider
927+
.api_auth_for_scope(ProviderAuthScope {
928+
agent_identity_policy: self.agent_identity_policy,
929+
session_source: self.state.session_source.clone(),
930+
agent_identity_session_fallback: self.state.agent_identity_session_fallback.clone(),
931+
})
932+
.await?;
913933
Ok(CurrentClientSetup {
914934
auth,
915935
api_provider,
916-
api_auth,
936+
api_auth: resolved_auth.auth,
937+
agent_identity_telemetry: resolved_auth.agent_identity_telemetry,
917938
})
918939
}
919940

941+
pub(crate) async fn prewarm_auth(&self) -> Result<()> {
942+
self.current_client_setup().await.map(|_| ())
943+
}
944+
920945
/// Opens a websocket connection using the same header and telemetry wiring as normal turns.
921946
///
922947
/// Both startup prewarm and in-turn `needs_new` reconnects call this path so handshake
@@ -934,7 +959,7 @@ impl ModelClient {
934959
let headers = self.build_websocket_headers(responses_metadata).await;
935960
let websocket_telemetry = ModelClientSession::build_websocket_telemetry(
936961
session_telemetry,
937-
auth_context,
962+
auth_context.clone(),
938963
request_route_telemetry,
939964
self.state.auth_env_telemetry.clone(),
940965
);
@@ -976,6 +1001,7 @@ impl ModelClient {
9761001
response_debug.cf_ray.as_deref(),
9771002
response_debug.auth_error.as_deref(),
9781003
response_debug.auth_error_code.as_deref(),
1004+
auth_context.agent_identity_telemetry(),
9791005
);
9801006
emit_feedback_request_tags_with_auth_env(
9811007
&FeedbackRequestTags {
@@ -1205,6 +1231,7 @@ impl ModelClientSession {
12051231
let auth_context = AuthRequestTelemetryContext::new(
12061232
client_setup.auth.as_ref().map(CodexAuth::auth_mode),
12071233
client_setup.api_auth.as_ref(),
1234+
client_setup.agent_identity_telemetry.clone(),
12081235
PendingUnauthorizedRetry::default(),
12091236
);
12101237
let connection = self
@@ -1343,6 +1370,7 @@ impl ModelClientSession {
13431370
let request_auth_context = AuthRequestTelemetryContext::new(
13441371
client_setup.auth.as_ref().map(CodexAuth::auth_mode),
13451372
client_setup.api_auth.as_ref(),
1373+
client_setup.agent_identity_telemetry.clone(),
13461374
pending_retry,
13471375
);
13481376
let (request_telemetry, sse_telemetry) = Self::build_streaming_telemetry(
@@ -1470,6 +1498,7 @@ impl ModelClientSession {
14701498
let request_auth_context = AuthRequestTelemetryContext::new(
14711499
client_setup.auth.as_ref().map(CodexAuth::auth_mode),
14721500
client_setup.api_auth.as_ref(),
1501+
client_setup.agent_identity_telemetry.clone(),
14731502
pending_retry,
14741503
);
14751504
let request = self.client.build_responses_request(
@@ -2035,11 +2064,12 @@ impl PendingUnauthorizedRetry {
20352064
}
20362065
}
20372066

2038-
#[derive(Clone, Copy, Debug, Default)]
2067+
#[derive(Clone, Debug, Default)]
20392068
struct AuthRequestTelemetryContext {
20402069
auth_mode: Option<&'static str>,
20412070
auth_header_attached: bool,
20422071
auth_header_name: Option<&'static str>,
2072+
agent_identity_telemetry: Option<AgentIdentityTelemetry>,
20432073
retry_after_unauthorized: bool,
20442074
recovery_mode: Option<&'static str>,
20452075
recovery_phase: Option<&'static str>,
@@ -2049,6 +2079,7 @@ impl AuthRequestTelemetryContext {
20492079
fn new(
20502080
auth_mode: Option<AuthMode>,
20512081
api_auth: &dyn AuthProvider,
2082+
agent_identity_telemetry: Option<AgentIdentityTelemetry>,
20522083
retry: PendingUnauthorizedRetry,
20532084
) -> Self {
20542085
let auth_telemetry = auth_header_telemetry(api_auth);
@@ -2062,11 +2093,16 @@ impl AuthRequestTelemetryContext {
20622093
}),
20632094
auth_header_attached: auth_telemetry.attached,
20642095
auth_header_name: auth_telemetry.name,
2096+
agent_identity_telemetry,
20652097
retry_after_unauthorized: retry.retry_after_unauthorized,
20662098
recovery_mode: retry.recovery_mode,
20672099
recovery_phase: retry.recovery_phase,
20682100
}
20692101
}
2102+
2103+
fn agent_identity_telemetry(&self) -> Option<&AgentIdentityTelemetry> {
2104+
self.agent_identity_telemetry.as_ref()
2105+
}
20702106
}
20712107

20722108
struct WebsocketConnectParams<'a> {
@@ -2253,6 +2289,7 @@ impl RequestTelemetry for ApiTelemetry {
22532289
debug.cf_ray.as_deref(),
22542290
debug.auth_error.as_deref(),
22552291
debug.auth_error_code.as_deref(),
2292+
self.auth_context.agent_identity_telemetry(),
22562293
);
22572294
emit_feedback_request_tags_with_auth_env(
22582295
&FeedbackRequestTags {
@@ -2307,6 +2344,7 @@ impl WebsocketTelemetry for ApiTelemetry {
23072344
duration,
23082345
error_message.as_deref(),
23092346
connection_reused,
2347+
self.auth_context.agent_identity_telemetry(),
23102348
);
23112349
emit_feedback_request_tags_with_auth_env(
23122350
&FeedbackRequestTags {

0 commit comments

Comments
 (0)