Skip to content

Commit ecb3880

Browse files
authored
Add cache tests for UserTurn (#2432)
1 parent fc6cfd5 commit ecb3880

File tree

1 file changed

+108
-4
lines changed

1 file changed

+108
-4
lines changed

codex-rs/core/tests/prompt_caching.rs

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use codex_core::protocol::EventMsg;
66
use codex_core::protocol::InputItem;
77
use codex_core::protocol::Op;
88
use codex_core::protocol::SandboxPolicy;
9-
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
10-
use codex_core::protocol_config_types::ReasoningSummary as ReasoningSummaryConfig;
9+
use codex_core::protocol_config_types::ReasoningEffort;
10+
use codex_core::protocol_config_types::ReasoningSummary;
1111
use codex_login::CodexAuth;
1212
use core_test_support::load_default_config_for_test;
1313
use core_test_support::load_sse_fixture_with_id;
@@ -197,8 +197,8 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() {
197197
exclude_slash_tmp: true,
198198
}),
199199
model: Some("o3".to_string()),
200-
effort: Some(ReasoningEffortConfig::High),
201-
summary: Some(ReasoningSummaryConfig::Detailed),
200+
effort: Some(ReasoningEffort::High),
201+
summary: Some(ReasoningSummary::Detailed),
202202
})
203203
.await
204204
.unwrap();
@@ -256,3 +256,107 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() {
256256
);
257257
assert_eq!(body2["input"], expected_body2);
258258
}
259+
260+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
261+
async fn per_turn_overrides_keep_cached_prefix_and_key_constant() {
262+
use pretty_assertions::assert_eq;
263+
264+
let server = MockServer::start().await;
265+
266+
let sse = sse_completed("resp");
267+
let template = ResponseTemplate::new(200)
268+
.insert_header("content-type", "text/event-stream")
269+
.set_body_raw(sse, "text/event-stream");
270+
271+
// Expect two POSTs to /v1/responses
272+
Mock::given(method("POST"))
273+
.and(path("/v1/responses"))
274+
.respond_with(template)
275+
.expect(2)
276+
.mount(&server)
277+
.await;
278+
279+
let model_provider = ModelProviderInfo {
280+
base_url: Some(format!("{}/v1", server.uri())),
281+
..built_in_model_providers()["openai"].clone()
282+
};
283+
284+
let cwd = TempDir::new().unwrap();
285+
let codex_home = TempDir::new().unwrap();
286+
let mut config = load_default_config_for_test(&codex_home);
287+
config.cwd = cwd.path().to_path_buf();
288+
config.model_provider = model_provider;
289+
config.user_instructions = Some("be consistent and helpful".to_string());
290+
291+
let conversation_manager = ConversationManager::default();
292+
let codex = conversation_manager
293+
.new_conversation_with_auth(config, Some(CodexAuth::from_api_key("Test API Key")))
294+
.await
295+
.expect("create new conversation")
296+
.conversation;
297+
298+
// First turn
299+
codex
300+
.submit(Op::UserInput {
301+
items: vec![InputItem::Text {
302+
text: "hello 1".into(),
303+
}],
304+
})
305+
.await
306+
.unwrap();
307+
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
308+
309+
// Second turn using per-turn overrides via UserTurn
310+
let new_cwd = TempDir::new().unwrap();
311+
let writable = TempDir::new().unwrap();
312+
codex
313+
.submit(Op::UserTurn {
314+
items: vec![InputItem::Text {
315+
text: "hello 2".into(),
316+
}],
317+
cwd: new_cwd.path().to_path_buf(),
318+
approval_policy: AskForApproval::Never,
319+
sandbox_policy: SandboxPolicy::WorkspaceWrite {
320+
writable_roots: vec![writable.path().to_path_buf()],
321+
network_access: true,
322+
exclude_tmpdir_env_var: true,
323+
exclude_slash_tmp: true,
324+
},
325+
model: "o3".to_string(),
326+
effort: ReasoningEffort::High,
327+
summary: ReasoningSummary::Detailed,
328+
})
329+
.await
330+
.unwrap();
331+
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
332+
333+
// Verify we issued exactly two requests, and the cached prefix stayed identical.
334+
let requests = server.received_requests().await.unwrap();
335+
assert_eq!(requests.len(), 2, "expected two POST requests");
336+
337+
let body1 = requests[0].body_json::<serde_json::Value>().unwrap();
338+
let body2 = requests[1].body_json::<serde_json::Value>().unwrap();
339+
340+
// prompt_cache_key should remain constant across per-turn overrides
341+
assert_eq!(
342+
body1["prompt_cache_key"], body2["prompt_cache_key"],
343+
"prompt_cache_key should not change across per-turn overrides"
344+
);
345+
346+
// The entire prefix from the first request should be identical and reused
347+
// as the prefix of the second request.
348+
let expected_user_message_2 = serde_json::json!({
349+
"type": "message",
350+
"id": serde_json::Value::Null,
351+
"role": "user",
352+
"content": [ { "type": "input_text", "text": "hello 2" } ]
353+
});
354+
let expected_body2 = serde_json::json!(
355+
[
356+
body1["input"].as_array().unwrap().as_slice(),
357+
[expected_user_message_2].as_slice(),
358+
]
359+
.concat()
360+
);
361+
assert_eq!(body2["input"], expected_body2);
362+
}

0 commit comments

Comments
 (0)