Compare commits

...

1 Commits

Author SHA1 Message Date
Alex Knittel
2b8b3d5043 Ensure CLI Responses requests include turn metadata 2026-05-07 10:51:28 -07:00
6 changed files with 96 additions and 13 deletions

View File

@@ -450,6 +450,7 @@ impl ModelClient {
settings.effort,
settings.summary,
settings.service_tier,
/*turn_metadata_header*/ None,
)?;
let ResponsesApiRequest {
model,
@@ -607,7 +608,7 @@ impl ModelClient {
extra_headers
}
fn build_ws_client_metadata(
fn build_responses_client_metadata(
&self,
turn_metadata_header: Option<&str>,
) -> HashMap<String, String> {
@@ -640,6 +641,13 @@ impl ModelClient {
client_metadata
}
fn build_ws_client_metadata(
&self,
turn_metadata_header: Option<&str>,
) -> HashMap<String, String> {
self.build_responses_client_metadata(turn_metadata_header)
}
/// Builds request telemetry for unary API calls (e.g., Compact endpoint).
fn build_request_telemetry(
session_telemetry: &SessionTelemetry,
@@ -684,6 +692,7 @@ impl ModelClient {
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
service_tier: Option<String>,
turn_metadata_header: Option<&str>,
) -> Result<ResponsesApiRequest> {
let instructions = &prompt.base_instructions.text;
let input = prompt.get_formatted_input();
@@ -725,10 +734,7 @@ impl ModelClient {
service_tier,
prompt_cache_key,
text,
client_metadata: Some(HashMap::from([(
X_CODEX_INSTALLATION_ID_HEADER.to_string(),
self.state.installation_id.clone(),
)])),
client_metadata: Some(self.build_responses_client_metadata(turn_metadata_header)),
};
Ok(request)
}
@@ -1224,6 +1230,7 @@ impl ModelClientSession {
effort,
summary,
service_tier.clone(),
turn_metadata_header,
)?;
let inference_trace_attempt = inference_trace.start_attempt();
inference_trace_attempt.record_started(&request);
@@ -1330,6 +1337,7 @@ impl ModelClientSession {
effort,
summary,
service_tier.clone(),
turn_metadata_header,
)?;
let mut ws_payload = ResponseCreateWsRequest {
client_metadata: response_create_client_metadata(

View File

@@ -194,6 +194,7 @@ pub use exec_policy::load_exec_policy;
pub use file_watcher::FileWatcherEvent;
pub use installation_id::resolve_installation_id;
pub use turn_metadata::build_turn_metadata_header;
pub use turn_metadata::build_turn_metadata_header_with_identity;
pub mod compact;
mod memory_usage;
pub mod otel_init;

View File

@@ -144,6 +144,21 @@ fn build_turn_metadata_bag(
pub async fn build_turn_metadata_header(
cwd: &AbsolutePathBuf,
sandbox: Option<&str>,
) -> Option<String> {
build_turn_metadata_header_with_identity(
cwd, sandbox, /*session_id*/ None, /*thread_id*/ None,
/*thread_source*/ None, /*turn_id*/ None,
)
.await
}
pub async fn build_turn_metadata_header_with_identity(
cwd: &AbsolutePathBuf,
sandbox: Option<&str>,
session_id: Option<String>,
thread_id: Option<String>,
thread_source: Option<ThreadSource>,
turn_id: Option<String>,
) -> Option<String> {
let repo_root = get_git_repo_root(cwd).map(|root| root.to_string_lossy().into_owned());
@@ -157,15 +172,19 @@ pub async fn build_turn_metadata_header(
&& associated_remote_urls.is_none()
&& has_changes.is_none()
&& sandbox.is_none()
&& session_id.is_none()
&& thread_id.is_none()
&& thread_source.is_none()
&& turn_id.is_none()
{
return None;
}
build_turn_metadata_bag(
/*session_id*/ None,
/*thread_id*/ None,
/*thread_source*/ None,
/*turn_id*/ None,
session_id,
thread_id,
thread_source,
turn_id,
sandbox.map(ToString::to_string),
repo_root,
Some(WorkspaceGitMetadata {

View File

@@ -85,6 +85,38 @@ async fn build_turn_metadata_header_includes_has_changes_for_clean_repo() {
);
}
#[tokio::test]
async fn build_turn_metadata_header_with_identity_keeps_turn_id_without_git_metadata() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = temp_dir.path().abs();
let header = build_turn_metadata_header_with_identity(
&cwd,
/*sandbox*/ None,
Some("session-a".to_string()),
Some("thread-a".to_string()),
Some(ThreadSource::MemoryConsolidation),
Some("turn-a".to_string()),
)
.await
.expect("header");
let json: Value = serde_json::from_str(&header).expect("json");
assert_eq!(
json.get("session_id").and_then(Value::as_str),
Some("session-a")
);
assert_eq!(
json.get("thread_id").and_then(Value::as_str),
Some("thread-a")
);
assert_eq!(
json.get("thread_source").and_then(Value::as_str),
Some("memory_consolidation")
);
assert_eq!(json.get("turn_id").and_then(Value::as_str), Some("turn-a"));
}
#[test]
fn turn_metadata_state_uses_platform_sandbox_tag() {
let temp_dir = TempDir::new().expect("temp dir");

View File

@@ -422,10 +422,15 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e()
test.submit_turn("hello")
.await
.expect("submit first turn prompt");
let initial_header = first_request
.single_request()
let initial_request = first_request.single_request();
let initial_header = initial_request
.header("x-codex-turn-metadata")
.expect("x-codex-turn-metadata header should be present");
assert_eq!(
initial_request.body_json()["client_metadata"]["x-codex-turn-metadata"].as_str(),
Some(initial_header.as_str()),
"HTTP client_metadata should mirror x-codex-turn-metadata"
);
let initial_parsed: serde_json::Value =
serde_json::from_str(&initial_header).expect("x-codex-turn-metadata should be valid JSON");
let initial_turn_id = initial_parsed
@@ -539,6 +544,16 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e()
.expect("second request should include turn metadata"),
)
.expect("second metadata should be valid json");
assert_eq!(
requests[0].body_json()["client_metadata"]["x-codex-turn-metadata"].as_str(),
requests[0].header("x-codex-turn-metadata").as_deref(),
"first HTTP client_metadata should mirror x-codex-turn-metadata"
);
assert_eq!(
requests[1].body_json()["client_metadata"]["x-codex-turn-metadata"].as_str(),
requests[1].header("x-codex-turn-metadata").as_deref(),
"second HTTP client_metadata should mirror x-codex-turn-metadata"
);
let first_turn_id = first_parsed
.get("turn_id")

View File

@@ -33,6 +33,7 @@ use codex_terminal_detection::user_agent;
use futures::StreamExt;
use std::sync::Arc;
use std::time::Duration;
use uuid::Uuid;
pub(crate) struct SpawnedConsolidationAgent {
pub(crate) thread_id: ThreadId,
@@ -145,8 +146,15 @@ impl MemoryStartupContext {
.get_models_manager()
.get_model_info(model_name, &config.to_models_manager_config())
.await;
let turn_metadata_header =
codex_core::build_turn_metadata_header(&config.cwd, /*sandbox*/ None).await;
let turn_metadata_header = codex_core::build_turn_metadata_header_with_identity(
&config.cwd,
/*sandbox*/ None,
Some(self.thread_id.to_string()),
Some(self.thread_id.to_string()),
Some(ThreadSource::MemoryConsolidation),
Some(format!("memory-stage-one-{}", Uuid::new_v4())),
)
.await;
let reasoning_summary = config
.model_reasoning_summary
.unwrap_or(model_info.default_reasoning_summary);