mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
Replace Guardian tickets with parent response IDs (#43002)
## What changed Capture the ID from `response.created` and pass it as `parent_response_id` in Guardian review and classifier request metadata. Clear the current response ID before sampling retries so tool reviews cannot inherit a failed response's ID. Request Guardian credits with `guardian_credits_requested` on eligible parent requests. Remove the ticket protocol, transport attachment, and ticket-specific body redaction. Keep ordinary app-server client `parent_response_id` values inside `x-codex-turn-metadata` rather than promoting them to Guardian request metadata. ## Testing Update coverage for HTTP fallback, WebSocket review reuse, missing response IDs, classifier transport retries, and code-mode yield/resume attribution. Extend app-server tests to verify client metadata placement over HTTP and WebSocket. GitOrigin-RevId: c1665e56382ecec8d260fc2ca0ecd5e20b0b2391
This commit is contained in:
@@ -11,7 +11,7 @@ use std::time::Duration;
|
||||
/// Clones share the encoded allocation. Internally, the body can also hold the
|
||||
/// final compressed wire bytes while retaining the original JSON only when
|
||||
/// request-body trace logging is enabled.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EncodedJsonBody {
|
||||
bytes: Bytes,
|
||||
trace_bytes: Option<Bytes>,
|
||||
@@ -33,27 +33,11 @@ impl EncodedJsonBody {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
/// Omits credential-bearing bodies from tracing and debug output, including retries.
|
||||
pub fn without_body_logging(mut self) -> Self {
|
||||
self.trace_bytes = Some(Bytes::from_static(b"<redacted>"));
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn trace_bytes(&self) -> &[u8] {
|
||||
self.trace_bytes.as_ref().unwrap_or(&self.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EncodedJsonBody {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter
|
||||
.debug_struct("EncodedJsonBody")
|
||||
.field("body", &String::from_utf8_lossy(self.trace_bytes()))
|
||||
.field("prepared", &self.prepared)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum RequestCompression {
|
||||
#[default]
|
||||
@@ -136,13 +120,7 @@ impl Request {
|
||||
self.body,
|
||||
Some(RequestBody::Json(_) | RequestBody::EncodedJson(_))
|
||||
);
|
||||
let retained_trace_bytes = match self.body.as_ref() {
|
||||
Some(RequestBody::EncodedJson(body)) => body.trace_bytes.clone(),
|
||||
Some(RequestBody::Json(_) | RequestBody::Raw(_)) | None => None,
|
||||
};
|
||||
let trace_bytes = if retained_trace_bytes.is_some() {
|
||||
retained_trace_bytes
|
||||
} else if self.compression != RequestCompression::None
|
||||
let trace_bytes = if self.compression != RequestCompression::None
|
||||
&& tracing::enabled!(target: "codex_http_client::transport", tracing::Level::TRACE)
|
||||
{
|
||||
match self.body.as_ref() {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use super::*;
|
||||
use crate::request::EncodedJsonBody;
|
||||
use crate::request::RequestCompression;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
@@ -31,35 +28,6 @@ async fn disabled_request_logging_suppresses_transport_url_and_body() {
|
||||
assert!(!logs.contains("body-secret"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sensitive_encoded_body_stays_redacted_after_preparation_and_retries() {
|
||||
let payload = json!({"client_metadata": {"guardian_ticket": "secret-receipt"}});
|
||||
for compression in [RequestCompression::None, RequestCompression::Zstd] {
|
||||
let mut request = Request::new(Method::POST, "https://example.com/responses".into())
|
||||
.with_compression(compression);
|
||||
request.body = Some(RequestBody::EncodedJson(
|
||||
EncodedJsonBody::encode(&payload)
|
||||
.unwrap()
|
||||
.without_body_logging(),
|
||||
));
|
||||
assert_eq!(request_body_for_trace(&request), "<redacted>");
|
||||
for _ in 0..2 {
|
||||
request = request.clone().into_prepared().unwrap();
|
||||
assert_eq!(request_body_for_trace(&request), "<redacted>");
|
||||
assert!(!format!("{request:?}").contains("secret-receipt"));
|
||||
let body = request.prepare_body_for_send().unwrap().body.unwrap();
|
||||
let decoded = match compression {
|
||||
RequestCompression::None => body.to_vec(),
|
||||
RequestCompression::Zstd => zstd::stream::decode_all(body.as_ref()).unwrap(),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::from_slice::<serde_json::Value>(&decoded).unwrap(),
|
||||
payload
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connection_failures_are_classified_without_exposing_request_urls() {
|
||||
let unavailable_server =
|
||||
|
||||
Reference in New Issue
Block a user