mirror of
https://github.com/hanzoai/codex.git
synced 2026-10-30 14:59:26 +00:00
## Why Injecting context before the first turn currently forces a full flush. Stores need a preparation checkpoint that can leave disposable context in memory, while local storage must keep prepared context durable before the first user input. ## What changed - Add `PersistContext::ThreadPreparation` and `CodexThread::checkpoint_preparation()`, and use the checkpoint in `inject_response_items()`. - Allow stores to defer rollout-derived metadata writes through `record_thread_metadata()`, including during preparation and execution checkpoints. Keep standard metadata updates synchronous. - Flush local storage at preparation checkpoints so injected context survives restart. ## Testing Add tests showing that preparation and first sampling can proceed without waiting for durable metadata. Cover prepared context in first and follow-up requests, plus durability before first input and restoration after restart, in both legacy and paginated history modes. GitOrigin-RevId: 74f6bb1a5f8d47192364b8bba6afd703a3b7d688
68 lines
2.3 KiB
Rust
68 lines
2.3 KiB
Rust
//! Verifies prepared context reaches both the first model request and a follow-up.
|
|
|
|
use super::*;
|
|
use codex_protocol::protocol::ThreadHistoryMode;
|
|
use pretty_assertions::assert_eq;
|
|
use test_case::test_case;
|
|
|
|
#[test_case(ThreadHistoryMode::Legacy; "legacy")]
|
|
#[test_case(ThreadHistoryMode::Paginated; "paginated")]
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn prepared_context_reaches_first_request_and_survives_follow_up(
|
|
history_mode: ThreadHistoryMode,
|
|
) -> Result<()> {
|
|
const PREPARED_CONTEXT: &str = "Prepared project context: the launch owner is Mira.";
|
|
|
|
skip_if_no_network!(Ok(()));
|
|
|
|
let server = start_mock_server().await;
|
|
let test = test_codex()
|
|
.with_history_mode(history_mode)
|
|
.build_with_auto_env(&server)
|
|
.await?;
|
|
let mock = mount_sse_sequence(
|
|
&server,
|
|
vec![
|
|
sse(vec![
|
|
ev_response_created("first-response"),
|
|
ev_assistant_message("first-message", "The launch owner is Mira."),
|
|
ev_completed("first-response"),
|
|
]),
|
|
sse(vec![
|
|
ev_response_created("follow-up-response"),
|
|
ev_assistant_message("follow-up-message", "Mira will send the launch update."),
|
|
ev_completed("follow-up-response"),
|
|
]),
|
|
],
|
|
)
|
|
.await;
|
|
|
|
// A hosted caller can inject preparation before the user starts the first turn.
|
|
test.codex
|
|
.inject_response_items(vec![serde_json::from_value(json!({
|
|
"type": "message",
|
|
"role": "developer",
|
|
"content": [{
|
|
"type": "input_text",
|
|
"text": PREPARED_CONTEXT
|
|
}],
|
|
}))?])
|
|
.await?;
|
|
assert!(mock.requests().is_empty());
|
|
|
|
test.submit_turn("Who owns the launch?").await?;
|
|
test.submit_turn("Who will send the launch update?").await?;
|
|
let requests = mock.requests();
|
|
assert_eq!(requests.len(), 2);
|
|
for request in requests {
|
|
let prepared_context = request
|
|
.message_input_texts("developer")
|
|
.into_iter()
|
|
.filter(|text| text == PREPARED_CONTEXT)
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(prepared_context, vec![PREPARED_CONTEXT]);
|
|
}
|
|
test.codex.shutdown_and_wait().await?;
|
|
Ok(())
|
|
}
|