Expose Guardian assessments in response streams (#57757)

## What changed

Decode `response.metadata` Guardian assessments into typed `GuardianAssessment` events over SSE and WebSocket, carrying `response_id`, `item_id`, and `requires_review`. Ignore assessments with missing, empty, or incorrectly typed required fields while preserving independent delivery of other metadata.

Expose the assessment type to request-scoped extension interceptors. Keep assessments out of conversation history; they do not authorize tool execution.

## Testing

Add SSE coverage for assessment ordering, malformed metadata, and mixed metadata delivery. Extend model request tests to cover SSE and WebSocket interceptor delivery and exclusion from subsequent model input.

GitOrigin-RevId: 282b8817ee97346d76b40bcb412eef023df60ff1
This commit is contained in:
jif
2026-10-28 15:04:29 +00:00
committed by copyberry
parent ce2d47bdc4
commit fc916a22b5
12 changed files with 452 additions and 56 deletions
@@ -898,10 +898,9 @@ async fn run_websocket_response_stream(
{
let _ = turn_state.set(response_turn_state);
}
let model_verifications = event.model_verifications();
let turn_moderation_metadata = event.turn_moderation_metadata();
let safety_buffering =
safety_buffering_for_event(&event, &mut safety_buffering_treatment);
let metadata_events = event.metadata_events(safety_buffering);
if event.kind() == "codex.rate_limits" {
if let Some(snapshot) = parse_rate_limit_event(&text) {
let _ = tx_event.send(Ok(ResponseEvent::RateLimits(snapshot))).await;
@@ -925,35 +924,12 @@ async fn run_websocket_response_stream(
.await;
last_server_model = Some(model);
}
if let Some(verifications) = model_verifications
&& tx_event
.send(Ok(ResponseEvent::ModelVerifications(verifications)))
.await
.is_err()
{
return Err(ApiError::Stream(
"response event consumer dropped".to_string(),
));
}
if let Some(metadata) = turn_moderation_metadata
&& tx_event
.send(Ok(ResponseEvent::TurnModerationMetadata(metadata)))
.await
.is_err()
{
return Err(ApiError::Stream(
"response event consumer dropped".to_string(),
));
}
if let Some(buffering) = safety_buffering
&& tx_event
.send(Ok(ResponseEvent::SafetyBuffering(buffering)))
.await
.is_err()
{
return Err(ApiError::Stream(
"response event consumer dropped".to_string(),
));
for metadata_event in metadata_events {
if tx_event.send(Ok(metadata_event)).await.is_err() {
return Err(ApiError::Stream(
"response event consumer dropped".to_string(),
));
}
}
match event {
Ok(Some(event)) => {