From 9be6fa1e640aeff2e685d90fa05454d81c5e1655 Mon Sep 17 00:00:00 2001 From: Francisco Javier Arceo Date: Mon, 13 Jul 2026 11:04:27 -0400 Subject: [PATCH] fix: preserve Responses cache salt Signed-off-by: Francisco Javier Arceo --- .../benches/executor_throughput.rs | 1 + .../src/executor/modes/conversation.rs | 1 + .../src/executor/modes/response.rs | 1 + .../src/types/request_response.rs | 20 +++++++++++++++++++ .../tests/dispatch_loop_cassette_test.rs | 1 + .../agentic-server-core/tests/support/mod.rs | 1 + .../tests/web_search_tool_test.rs | 16 +++++++++++++++ 7 files changed, 41 insertions(+) diff --git a/crates/agentic-server-core/benches/executor_throughput.rs b/crates/agentic-server-core/benches/executor_throughput.rs index 3a18797..f56bb5f 100644 --- a/crates/agentic-server-core/benches/executor_throughput.rs +++ b/crates/agentic-server-core/benches/executor_throughput.rs @@ -145,6 +145,7 @@ fn make_request(input: &str, stream: bool, prev_id: Option) -> RequestPa max_output_tokens: None, truncation: None, metadata: None, + cache_salt: None, } } diff --git a/crates/agentic-server-core/src/executor/modes/conversation.rs b/crates/agentic-server-core/src/executor/modes/conversation.rs index 0d1374d..524f0d7 100644 --- a/crates/agentic-server-core/src/executor/modes/conversation.rs +++ b/crates/agentic-server-core/src/executor/modes/conversation.rs @@ -141,6 +141,7 @@ mod tests { max_output_tokens: None, truncation: None, metadata: None, + cache_salt: None, }; RequestContext { enriched_request: req.clone(), diff --git a/crates/agentic-server-core/src/executor/modes/response.rs b/crates/agentic-server-core/src/executor/modes/response.rs index a67cf3f..c629dbf 100644 --- a/crates/agentic-server-core/src/executor/modes/response.rs +++ b/crates/agentic-server-core/src/executor/modes/response.rs @@ -120,6 +120,7 @@ mod tests { max_output_tokens: None, truncation: None, metadata: None, + cache_salt: None, }; RequestContext { enriched_request: req.clone(), diff --git a/crates/agentic-server-core/src/types/request_response.rs b/crates/agentic-server-core/src/types/request_response.rs index 5da33fd..31341ed 100644 --- a/crates/agentic-server-core/src/types/request_response.rs +++ b/crates/agentic-server-core/src/types/request_response.rs @@ -28,6 +28,8 @@ pub struct RequestPayload { pub max_output_tokens: Option, pub truncation: Option, pub metadata: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_salt: Option, } fn default_true() -> bool { @@ -61,6 +63,8 @@ pub struct UpstreamRequest<'a> { pub truncation: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option<&'a Value>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_salt: Option<&'a str>, } // serde's `skip_serializing_if` requires a `&Option` receiver, so the @@ -109,6 +113,7 @@ impl RequestPayload { max_output_tokens: self.max_output_tokens, truncation: self.truncation.as_deref(), metadata: self.metadata.as_ref(), + cache_salt: self.cache_salt.as_deref(), }) } } @@ -178,6 +183,21 @@ impl From<&ResponsesInput> for Vec { mod tests { use super::*; + #[test] + fn request_payload_forwards_cache_salt_upstream() { + let payload: RequestPayload = serde_json::from_value(serde_json::json!({ + "model": "test-model", + "input": "hello", + "cache_salt": "tenant-a" + })) + .expect("request should deserialize"); + + let upstream = serde_json::to_value(payload.to_upstream_request(false).expect("request should normalize")) + .expect("upstream request should serialize"); + + assert_eq!(upstream["cache_salt"], "tenant-a"); + } + #[test] fn request_payload_uses_option_tool_choice_for_missing_vs_explicit() { let absent: RequestPayload = serde_json::from_value(serde_json::json!({ diff --git a/crates/agentic-server-core/tests/dispatch_loop_cassette_test.rs b/crates/agentic-server-core/tests/dispatch_loop_cassette_test.rs index 4a99ffb..1813a0c 100644 --- a/crates/agentic-server-core/tests/dispatch_loop_cassette_test.rs +++ b/crates/agentic-server-core/tests/dispatch_loop_cassette_test.rs @@ -118,6 +118,7 @@ fn request(text: &str, tools: Option>) -> RequestPayload { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, } } diff --git a/crates/agentic-server-core/tests/support/mod.rs b/crates/agentic-server-core/tests/support/mod.rs index 85764cf..21fff39 100644 --- a/crates/agentic-server-core/tests/support/mod.rs +++ b/crates/agentic-server-core/tests/support/mod.rs @@ -365,6 +365,7 @@ pub fn make_request( max_output_tokens: None, truncation: None, metadata: None, + cache_salt: None, } } diff --git a/crates/agentic-server-core/tests/web_search_tool_test.rs b/crates/agentic-server-core/tests/web_search_tool_test.rs index 92813f5..3d28481 100644 --- a/crates/agentic-server-core/tests/web_search_tool_test.rs +++ b/crates/agentic-server-core/tests/web_search_tool_test.rs @@ -584,6 +584,7 @@ async fn execute_runs_web_search_and_sends_tool_output_back_to_model() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap(); @@ -667,6 +668,7 @@ async fn execute_relaxes_forced_tool_choice_after_web_search_result() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap(); @@ -714,6 +716,7 @@ async fn execute_returns_mixed_client_tool_calls_without_followup_model_request( max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap(); @@ -760,6 +763,7 @@ async fn execute_returns_mixed_client_tool_calls_without_followup_model_request( max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let continuation = ExecuteRequest::new(continuation_payload, exec_ctx).run().await.unwrap(); assert!(matches!(continuation, Either::Left(_))); @@ -829,6 +833,7 @@ async fn execute_accumulates_usage_across_web_search_model_rounds() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap(); @@ -871,6 +876,7 @@ async fn stream_emits_web_search_lifecycle_events_before_final_payload() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap(); @@ -952,6 +958,7 @@ async fn stream_hides_web_search_function_events_when_name_arrives_on_done() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap(); @@ -1017,6 +1024,7 @@ async fn execute_runs_multiple_web_search_calls_concurrently() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = tokio::time::timeout(Duration::from_secs(2), ExecuteRequest::new(payload, exec_ctx).run()) @@ -1064,6 +1072,7 @@ async fn execute_feeds_web_search_execution_errors_back_to_model() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap(); @@ -1113,6 +1122,7 @@ async fn execute_returns_incomplete_after_max_gateway_tool_rounds() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; // Budget exhausted while the model keeps requesting tools → the response is @@ -1162,6 +1172,7 @@ async fn execute_feeds_invalid_web_search_arguments_back_to_model() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap(); @@ -1218,6 +1229,7 @@ async fn execute_runs_large_gateway_fanout_without_hard_cap() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, exec_ctx) @@ -1280,6 +1292,7 @@ async fn stream_error_events_escape_error_messages() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap(); @@ -1354,6 +1367,7 @@ async fn incomplete_turn_persists_a_consistent_conversation_for_continuation() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap(); @@ -1379,6 +1393,7 @@ async fn incomplete_turn_persists_a_consistent_conversation_for_continuation() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let _ = ExecuteRequest::new(continuation_payload, exec_ctx).run().await.unwrap(); @@ -1448,6 +1463,7 @@ async fn stream_returns_incomplete_after_max_gateway_tool_rounds() { max_output_tokens: Some(1024), truncation: None, metadata: None, + cache_salt: None, }; let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap();