Support client Responses API metadata in thread/sample (#56031)

## What changed

Add optional `responsesapiClientMetadata` to `ThreadSampleParams` and forward it to the sample turn's Responses API metadata. Update the experimental protocol schema.

## Testing

Extend the thread sample integration test to verify that client metadata appears in both the request header and body, preserves the explicit `turn_trigger` when client metadata supplies a conflicting value, and does not carry over to the next parent turn.

GitOrigin-RevId: eb1f80824d476fc6c584a2b35d97e59d51fd4273
This commit is contained in:
liame-oai
2026-10-22 00:33:28 +00:00
committed by copyberry
parent e8bb1b6641
commit 943c70d8d2
4 changed files with 26 additions and 0 deletions
@@ -6,6 +6,7 @@ use crate::TS;
use codex_protocol::openai_models::ReasoningEffort;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
@@ -21,6 +22,9 @@ pub struct ThreadSampleParams {
pub effort: Option<ReasoningEffort>,
#[ts(optional = nullable)]
pub turn_trigger: Option<String>,
/// Optional metadata to enrich Codex's ResponsesAPI turn metadata.
#[ts(optional = nullable)]
pub responsesapi_client_metadata: Option<HashMap<String, String>>,
/// Disable MCP servers when the inherited window uses incremental tools on Responses Lite.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub disable_mcp: bool,
+1
View File
@@ -259,6 +259,7 @@ impl SampleTask {
turn_trigger: params.turn_trigger,
..Default::default()
})
.with_responses_metadata(params.responsesapi_client_metadata)
.with_trace(trace);
if !matches!(
fork.thread
@@ -40,6 +40,7 @@ use core_test_support::skip_if_remote;
use pretty_assertions::assert_eq;
use serde_json::Value;
use serde_json::json;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempDir;
@@ -304,6 +305,13 @@ async fn sample_preserves_context_and_keeps_source_unchanged(
prompt: "Continue identically".to_string(),
effort: Some(ReasoningEffort::Medium),
turn_trigger: Some("composer_predictions".to_string()),
responsesapi_client_metadata: Some(HashMap::from([
(
"generation_trigger".to_string(),
"composer_prediction".to_string(),
),
("turn_trigger".to_string(), "client-supplied".to_string()),
])),
..Default::default()
},
)
@@ -383,6 +391,19 @@ async fn sample_preserves_context_and_keeps_source_unchanged(
.expect("turn metadata"),
)?;
assert_eq!(metadata["turn_trigger"], "composer_predictions");
assert_eq!(metadata["generation_trigger"], "composer_prediction");
let body_metadata: Value = serde_json::from_str(
prediction.body_json()["client_metadata"]["x-codex-turn-metadata"]
.as_str()
.expect("body turn metadata"),
)?;
assert_eq!(body_metadata, metadata);
let parent_metadata: Value = serde_json::from_str(
&requests[sample_index + 2]
.header("x-codex-turn-metadata")
.expect("parent turn metadata"),
)?;
assert!(parent_metadata.get("generation_trigger").is_none());
Ok(())
}