Add per-turn cyber access program selection (#40687)

## What changed

- Add the experimental `turn/start.cyberAccessProgram` option with `standard`,
  `daybreakBlue`, and `daybreakRed` values.
- Forward the selection as `access_programs.cyber` on Responses, WebSocket,
  and remote-compaction requests made with ChatGPT authentication, while
  omitting it for API-key and custom-provider requests.
- Preserve the per-turn selection across recovery, compaction, and child-agent
  turns without making it a persistent thread setting.

## Testing

- Cover app-server forwarding, authentication boundaries, WebSocket reuse,
  turn recovery, compaction, and child-agent inheritance.

GitOrigin-RevId: d2eb468f365b2214c5099bc21741cf30f8bd2eb5
This commit is contained in:
faizan-oai
2026-08-25 19:54:52 +00:00
committed by copyberry
parent a6e63f9f32
commit 94967e03e5
72 changed files with 1323 additions and 273 deletions
+26
View File
@@ -8,6 +8,7 @@ use codex_protocol::protocol::RateLimitSnapshot;
use codex_protocol::protocol::TokenUsage;
use codex_protocol::protocol::TurnModerationMetadataEvent;
use codex_protocol::protocol::W3cTraceContext;
use codex_protocol::turn_input::CyberAccessProgram;
use futures::Stream;
use serde::Deserialize;
use serde::Serialize;
@@ -23,6 +24,24 @@ use tokio::sync::mpsc;
pub const WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY: &str = "ws_request_header_traceparent";
pub const WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY: &str = "ws_request_header_tracestate";
/// Explicit per-request access selection using the Responses API wire values.
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct AccessPrograms {
cyber: &'static str,
}
impl From<CyberAccessProgram> for AccessPrograms {
fn from(program: CyberAccessProgram) -> Self {
Self {
cyber: match program {
CyberAccessProgram::Standard => "standard",
CyberAccessProgram::DaybreakBlue => "daybreak_blue",
CyberAccessProgram::DaybreakRed => "daybreak_red",
},
}
}
}
/// Canonical input payload for the compaction endpoint.
#[derive(Debug, Clone, Serialize)]
pub struct CompactionInput<'a> {
@@ -41,6 +60,8 @@ pub struct CompactionInput<'a> {
pub prompt_cache_key: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<TextControls>,
#[serde(skip_serializing_if = "Option::is_none")]
pub access_programs: Option<AccessPrograms>,
}
/// Canonical input payload for the memory summarize endpoint.
@@ -272,6 +293,8 @@ pub struct ResponsesApiRequest {
pub text: Option<TextControls>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_metadata: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub access_programs: Option<AccessPrograms>,
}
impl<'a> From<&'a ResponsesApiRequest> for ResponseCreateWsRequest<'a> {
@@ -294,6 +317,7 @@ impl<'a> From<&'a ResponsesApiRequest> for ResponseCreateWsRequest<'a> {
text: request.text.as_ref(),
generate: None,
client_metadata: request.client_metadata.clone(),
access_programs: request.access_programs,
}
}
}
@@ -326,6 +350,8 @@ pub struct ResponseCreateWsRequest<'a> {
pub generate: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_metadata: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub access_programs: Option<AccessPrograms>,
}
pub fn response_create_client_metadata(
@@ -955,6 +955,9 @@ mod tests {
service_tier: Some("priority".to_string()),
prompt_cache_key: Some("cache-key".to_string()),
text: None,
access_programs: Some(
codex_protocol::turn_input::CyberAccessProgram::DaybreakBlue.into(),
),
client_metadata: Some(HashMap::from([(
"traceparent".to_string(),
"00-0123456789abcdef0123456789abcdef-0123456789abcdef-01".to_string(),
+1
View File
@@ -27,6 +27,7 @@ pub use crate::auth::AuthProvider;
pub use crate::auth::AuthProviderFuture;
pub use crate::auth::SharedAuthProvider;
pub use crate::auth::auth_header_telemetry;
pub use crate::common::AccessPrograms;
pub use crate::common::CompactionInput;
pub use crate::common::MemorySummarizeInput;
pub use crate::common::MemorySummarizeOutput;
+3
View File
@@ -333,6 +333,7 @@ async fn responses_client_stream_request_preserves_item_ids() -> Result<()> {
prompt_cache_key: None,
text: None,
client_metadata: None,
access_programs: None,
};
let expected = serde_json::to_value(&request)?;
@@ -420,6 +421,7 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> {
prompt_cache_key: None,
text: None,
client_metadata: None,
access_programs: None,
};
let client = ResponsesClient::new(transport.clone(), provider, Arc::new(NoAuth));
@@ -540,6 +542,7 @@ async fn azure_store_sends_ids_and_headers() -> Result<()> {
prompt_cache_key: None,
text: None,
client_metadata: None,
access_programs: None,
};
let mut extra_headers = HeaderMap::new();