mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
Route Guardian requests through /responses with identifying headers (#45736)
## What changed - Replace dedicated Guardian endpoints with `/responses`, sending `x-codex-guardian: reviewer` or `x-codex-guardian: classifier` for eligible Codex backend requests over HTTP and WebSocket. - Add model-scoped thread headers, recheck backend authentication on each request attempt, and reconnect WebSockets when the applicable headers change. - Retain `features.guardianv2.free_guardian` for configuration compatibility while removing its routing gate; the backend now controls Guardian billing. ## Testing Update tests for reviewer and classifier headers, model and authentication scoping, HTTP fallback, WebSocket reuse, and parent-response metadata across retries. GitOrigin-RevId: 1c2b3c458ab77fb40aae4ee6784f6d827c60e76c
This commit is contained in:
@@ -25,7 +25,6 @@ pub use realtime_websocket::RealtimeWebsocketEvents;
|
||||
pub use realtime_websocket::RealtimeWebsocketWriter;
|
||||
pub use realtime_websocket::session_update_session_json;
|
||||
pub use responses::ResponsesClient;
|
||||
pub use responses::ResponsesEndpoint;
|
||||
pub use responses::ResponsesOptions;
|
||||
pub use responses_websocket::ResponsesWebsocketClient;
|
||||
pub use responses_websocket::ResponsesWebsocketClose;
|
||||
|
||||
@@ -23,33 +23,9 @@ use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use tracing::instrument;
|
||||
|
||||
/// Responses-compatible inference routes supported by Codex backend.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum ResponsesEndpoint {
|
||||
/// Regular user-owned model inference.
|
||||
#[default]
|
||||
Responses,
|
||||
/// Full Guardian approval-review agent inference.
|
||||
Guardian,
|
||||
/// Lightweight asynchronous Guardian risk classification.
|
||||
GuardianClassifier,
|
||||
}
|
||||
|
||||
impl ResponsesEndpoint {
|
||||
/// Returns the provider-relative path for this inference surface.
|
||||
pub const fn path(self) -> &'static str {
|
||||
match self {
|
||||
Self::Responses => "/responses",
|
||||
Self::Guardian => "/guardian",
|
||||
Self::GuardianClassifier => "/guardian-classifier",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResponsesClient<T: HttpTransport> {
|
||||
session: EndpointSession<T>,
|
||||
sse_telemetry: Option<Arc<dyn SseTelemetry>>,
|
||||
endpoint: ResponsesEndpoint,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -67,16 +43,9 @@ impl<T: HttpTransport> ResponsesClient<T> {
|
||||
Self {
|
||||
session: EndpointSession::new(transport, provider, auth),
|
||||
sse_telemetry: None,
|
||||
endpoint: ResponsesEndpoint::Responses,
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects a Responses-compatible backend route for subsequent requests.
|
||||
pub fn with_endpoint(mut self, endpoint: ResponsesEndpoint) -> Self {
|
||||
self.endpoint = endpoint;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_telemetry(
|
||||
self,
|
||||
request: Option<Arc<dyn RequestTelemetry>>,
|
||||
@@ -85,7 +54,6 @@ impl<T: HttpTransport> ResponsesClient<T> {
|
||||
Self {
|
||||
session: self.session.with_request_telemetry(request),
|
||||
sse_telemetry: sse,
|
||||
endpoint: self.endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +64,7 @@ impl<T: HttpTransport> ResponsesClient<T> {
|
||||
fields(
|
||||
transport = "responses_http",
|
||||
http.method = "POST",
|
||||
api.path = self.endpoint.path()
|
||||
api.path = "/responses"
|
||||
)
|
||||
)]
|
||||
pub async fn stream_request(
|
||||
@@ -135,7 +103,7 @@ impl<T: HttpTransport> ResponsesClient<T> {
|
||||
fields(
|
||||
transport = "responses_http",
|
||||
http.method = "POST",
|
||||
api.path = self.endpoint.path(),
|
||||
api.path = "/responses",
|
||||
turn.has_state = turn_state.is_some()
|
||||
)
|
||||
)]
|
||||
@@ -168,7 +136,7 @@ impl<T: HttpTransport> ResponsesClient<T> {
|
||||
.session
|
||||
.stream_encoded_json_with(
|
||||
Method::POST,
|
||||
self.endpoint.path(),
|
||||
"/responses",
|
||||
extra_headers,
|
||||
Some(body),
|
||||
|req| {
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::common::ResponseStream;
|
||||
use crate::common::ResponsesWsRequest;
|
||||
use crate::common::SafetyBufferingTreatment;
|
||||
use crate::common::WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY;
|
||||
use crate::endpoint::responses::ResponsesEndpoint;
|
||||
use crate::error::ApiError;
|
||||
use crate::provider::Provider;
|
||||
use crate::rate_limits::parse_rate_limit_event;
|
||||
@@ -182,7 +181,6 @@ struct ResponsesWebsocketTimingLogContext {
|
||||
|
||||
pub struct ResponsesWebsocketConnection {
|
||||
stream: Arc<Mutex<Option<WsStream>>>,
|
||||
endpoint: ResponsesEndpoint,
|
||||
// TODO (pakrym): is this the right place for timeout?
|
||||
idle_timeout: Duration,
|
||||
server_reasoning_included: bool,
|
||||
@@ -194,7 +192,6 @@ impl std::fmt::Debug for ResponsesWebsocketConnection {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ResponsesWebsocketConnection")
|
||||
.field("stream", &"<ws-stream>")
|
||||
.field("endpoint", &self.endpoint)
|
||||
.field("idle_timeout", &self.idle_timeout)
|
||||
.field("server_reasoning_included", &self.server_reasoning_included)
|
||||
.field("server_model", &self.server_model)
|
||||
@@ -210,11 +207,9 @@ impl ResponsesWebsocketConnection {
|
||||
server_reasoning_included: bool,
|
||||
server_model: Option<String>,
|
||||
telemetry: Option<Arc<dyn WebsocketTelemetry>>,
|
||||
endpoint: ResponsesEndpoint,
|
||||
) -> Self {
|
||||
Self {
|
||||
stream: Arc::new(Mutex::new(Some(stream))),
|
||||
endpoint,
|
||||
idle_timeout,
|
||||
server_reasoning_included,
|
||||
server_model,
|
||||
@@ -230,7 +225,7 @@ impl ResponsesWebsocketConnection {
|
||||
name = "responses_websocket.stream_request",
|
||||
level = "info",
|
||||
skip_all,
|
||||
fields(transport = "responses_websocket", api.path = self.endpoint.path())
|
||||
fields(transport = "responses_websocket", api.path = "/responses")
|
||||
)]
|
||||
pub async fn stream_request(
|
||||
&self,
|
||||
@@ -345,7 +340,6 @@ impl ResponsesWebsocketConnection {
|
||||
pub struct ResponsesWebsocketClient {
|
||||
provider: Provider,
|
||||
auth: SharedAuthProvider,
|
||||
endpoint: ResponsesEndpoint,
|
||||
}
|
||||
|
||||
/// Close frame information captured by a handshake probe.
|
||||
@@ -375,24 +369,14 @@ pub struct ResponsesWebsocketProbe {
|
||||
impl ResponsesWebsocketClient {
|
||||
/// Creates a Responses WebSocket client for an already-resolved provider and auth source.
|
||||
pub fn new(provider: Provider, auth: SharedAuthProvider) -> Self {
|
||||
Self {
|
||||
provider,
|
||||
auth,
|
||||
endpoint: ResponsesEndpoint::Responses,
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects a Responses-compatible backend route for subsequent connections.
|
||||
pub fn with_endpoint(mut self, endpoint: ResponsesEndpoint) -> Self {
|
||||
self.endpoint = endpoint;
|
||||
self
|
||||
Self { provider, auth }
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
name = "responses_websocket.connect",
|
||||
level = "info",
|
||||
skip_all,
|
||||
fields(transport = "responses_websocket", api.path = self.endpoint.path())
|
||||
fields(transport = "responses_websocket", api.path = "/responses")
|
||||
)]
|
||||
pub async fn connect(
|
||||
&self,
|
||||
@@ -404,7 +388,7 @@ impl ResponsesWebsocketClient {
|
||||
) -> Result<ResponsesWebsocketConnection, ApiError> {
|
||||
let ws_url = self
|
||||
.provider
|
||||
.websocket_url_for_path(self.endpoint.path())
|
||||
.websocket_url_for_path("/responses")
|
||||
.map_err(|err| ApiError::Stream(format!("failed to build websocket URL: {err}")))?;
|
||||
|
||||
let mut headers =
|
||||
@@ -419,7 +403,6 @@ impl ResponsesWebsocketClient {
|
||||
server_reasoning_included,
|
||||
server_model,
|
||||
telemetry,
|
||||
self.endpoint,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -439,7 +422,7 @@ impl ResponsesWebsocketClient {
|
||||
) -> Result<ResponsesWebsocketProbe, ApiError> {
|
||||
let ws_url = self
|
||||
.provider
|
||||
.websocket_url_for_path(self.endpoint.path())
|
||||
.websocket_url_for_path("/responses")
|
||||
.map_err(|err| ApiError::Stream(format!("failed to build websocket URL: {err}")))?;
|
||||
|
||||
let mut headers =
|
||||
|
||||
@@ -65,7 +65,6 @@ pub use crate::endpoint::RealtimeWebsocketConnection;
|
||||
pub use crate::endpoint::RealtimeWebsocketEvents;
|
||||
pub use crate::endpoint::RealtimeWebsocketWriter;
|
||||
pub use crate::endpoint::ResponsesClient;
|
||||
pub use crate::endpoint::ResponsesEndpoint;
|
||||
pub use crate::endpoint::ResponsesOptions;
|
||||
pub use crate::endpoint::ResponsesWebsocketClient;
|
||||
pub use crate::endpoint::ResponsesWebsocketClose;
|
||||
|
||||
Reference in New Issue
Block a user