mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
## What changed Carry `ContextWindowId` through session metadata, compaction checkpoints, rollout recording, and thread creation instead of converting IDs to strings. Add deserialization and JSON schema support while preserving the serialized string format and legacy numeric compaction IDs. Validate deserialized IDs as UUIDv7 values before restoring context windows, preserving the existing handling of unsupported IDs. GitOrigin-RevId: fda329d0654f438171a955c9f75550817c32c047
235 lines
9.0 KiB
Rust
235 lines
9.0 KiB
Rust
use anyhow::Result;
|
|
use app_test_support::MockResponsesConfig;
|
|
use app_test_support::TestAppServer;
|
|
use app_test_support::create_mock_responses_server_repeating_assistant;
|
|
use codex_app_server_protocol::ClientRequest;
|
|
use codex_app_server_protocol::SortDirection;
|
|
use codex_app_server_protocol::ThreadItemPayload;
|
|
use codex_app_server_protocol::ThreadItemsListParams;
|
|
use codex_app_server_protocol::ThreadItemsListResponse;
|
|
use codex_app_server_protocol::ThreadRealtimeItemContent;
|
|
use codex_app_server_protocol::ThreadTimelineEntry;
|
|
use codex_app_server_protocol::ThreadTimelineListParams;
|
|
use codex_app_server_protocol::ThreadTimelineListResponse;
|
|
use codex_protocol::ThreadId;
|
|
use codex_protocol::items::TurnItem;
|
|
use codex_protocol::items::UserMessageItem;
|
|
use codex_protocol::models::BaseInstructions;
|
|
use codex_protocol::protocol::EventMsg;
|
|
use codex_protocol::protocol::ItemCompletedEvent;
|
|
use codex_protocol::protocol::SessionSource;
|
|
use codex_protocol::protocol::ThreadHistoryMode;
|
|
use codex_protocol::protocol::ThreadMemoryMode;
|
|
use codex_protocol::protocol::TurnCompleteEvent;
|
|
use codex_protocol::protocol::TurnStartedEvent;
|
|
use codex_protocol::realtime::RealtimeItem;
|
|
use codex_protocol::realtime::RealtimeItemContent;
|
|
use codex_protocol::realtime::RealtimeTranscriptRole;
|
|
use codex_protocol::user_input::UserInput;
|
|
use codex_rollout::RolloutItem;
|
|
use codex_thread_store::AppendThreadItemsParams;
|
|
use codex_thread_store::CreateThreadParams;
|
|
use codex_thread_store::LocalThreadStore;
|
|
use codex_thread_store::LocalThreadStoreConfig;
|
|
use codex_thread_store::PersistContext;
|
|
use codex_thread_store::ThreadPersistenceMetadata;
|
|
use codex_thread_store::ThreadStore;
|
|
use codex_utils_absolute_path::test_support::PathExt;
|
|
use pretty_assertions::assert_eq;
|
|
use tempfile::TempDir;
|
|
|
|
#[tokio::test]
|
|
async fn timeline_pages_mix_items_and_resolve_the_opening_realtime_session() -> Result<()> {
|
|
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
|
let codex_home = TempDir::new()?;
|
|
MockResponsesConfig::new(&server.uri()).write(codex_home.path())?;
|
|
|
|
let thread_id = ThreadId::default();
|
|
let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
|
let state_db =
|
|
codex_state::StateRuntime::init(sqlite.clone(), "mock_provider".to_string()).await?;
|
|
let store = LocalThreadStore::new(
|
|
LocalThreadStoreConfig {
|
|
codex_home: codex_home.path().to_path_buf(),
|
|
sqlite,
|
|
default_model_provider_id: "mock_provider".to_string(),
|
|
},
|
|
Some(state_db),
|
|
);
|
|
store
|
|
.create_thread(CreateThreadParams {
|
|
attached_thread: false,
|
|
creator_user_id: None,
|
|
creator_account_id: None,
|
|
session_id: thread_id.into(),
|
|
thread_id,
|
|
extra_config: None,
|
|
forked_from_id: None,
|
|
parent_thread_id: None,
|
|
source: SessionSource::Cli,
|
|
thread_source: None,
|
|
originator: "test_originator".to_string(),
|
|
base_instructions: BaseInstructions::default(),
|
|
dynamic_tools: Vec::new(),
|
|
selected_capability_roots: Vec::new(),
|
|
multi_agent_version: None,
|
|
history_mode: ThreadHistoryMode::Paginated,
|
|
history_base: None,
|
|
subagent_history_start_ordinal: None,
|
|
initial_window_id: codex_protocol::ContextWindowId::new_uuid_v7(),
|
|
runtime_workspace_roots: None,
|
|
metadata: ThreadPersistenceMetadata {
|
|
cwd: Some(codex_home.path().to_path_buf()),
|
|
model_provider: "mock_provider".to_string(),
|
|
memory_mode: ThreadMemoryMode::Enabled,
|
|
},
|
|
})
|
|
.await?;
|
|
store
|
|
.persist_thread(thread_id, PersistContext::Standard)
|
|
.await?;
|
|
store
|
|
.append_items(AppendThreadItemsParams {
|
|
thread_id,
|
|
items: vec![
|
|
RolloutItem::RealtimeItem(RealtimeItem {
|
|
id: "voice-1:started".to_string(),
|
|
realtime_session_id: "voice-1".to_string(),
|
|
content: RealtimeItemContent::RealtimeSessionStarted,
|
|
}),
|
|
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
|
|
turn_attribution: None,
|
|
turn_id: "turn-1".to_string(),
|
|
root_turn_id: None,
|
|
trace_id: None,
|
|
started_at: Some(10),
|
|
model_context_window: None,
|
|
collaboration_mode_kind: Default::default(),
|
|
})),
|
|
RolloutItem::EventMsg(EventMsg::ItemCompleted(ItemCompletedEvent {
|
|
content_token_count: None,
|
|
thread_id,
|
|
turn_id: "turn-1".to_string(),
|
|
item: TurnItem::UserMessage(UserMessageItem {
|
|
id: "user-1".to_string(),
|
|
client_id: None,
|
|
content: vec![UserInput::Text {
|
|
text: "check staging".to_string(),
|
|
text_elements: Vec::new(),
|
|
}],
|
|
}),
|
|
started_at_ms: Some(0),
|
|
completed_at_ms: 1,
|
|
})),
|
|
RolloutItem::RealtimeItem(RealtimeItem {
|
|
id: "voice-1:transcript-1".to_string(),
|
|
realtime_session_id: "voice-1".to_string(),
|
|
content: RealtimeItemContent::TranscriptSegment {
|
|
role: RealtimeTranscriptRole::Assistant,
|
|
text: "Checking staging".to_string(),
|
|
},
|
|
}),
|
|
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
|
|
root_turn_id: None,
|
|
turn_id: "turn-1".to_string(),
|
|
last_agent_message: None,
|
|
error: None,
|
|
started_at: Some(10),
|
|
completed_at: Some(12),
|
|
duration_ms: Some(2000),
|
|
time_to_first_token_ms: None,
|
|
})),
|
|
],
|
|
})
|
|
.await?;
|
|
store.shutdown_thread(thread_id).await?;
|
|
|
|
let mut app_server = TestAppServer::builder()
|
|
.with_codex_home(codex_home.path())
|
|
.without_auto_env()
|
|
.build_initialized()
|
|
.await?;
|
|
let page: ThreadTimelineListResponse = app_server
|
|
.request(|request_id| ClientRequest::ThreadTimelineList {
|
|
request_id,
|
|
params: ThreadTimelineListParams {
|
|
thread_id: thread_id.to_string(),
|
|
cursor: None,
|
|
limit: Some(3),
|
|
},
|
|
})
|
|
.await?;
|
|
assert_eq!(page.data.len(), 3);
|
|
assert!(matches!(
|
|
&page.data[0],
|
|
ThreadTimelineEntry::Item { turn_id, item, .. }
|
|
if turn_id == "turn-1"
|
|
&& matches!(&item.payload, ThreadItemPayload::UserMessage { .. })
|
|
&& item.started_at_ms == Some(0)
|
|
&& item.completed_at_ms == Some(1)
|
|
));
|
|
assert!(matches!(
|
|
&page.data[1],
|
|
ThreadTimelineEntry::Realtime { item, .. }
|
|
if matches!(
|
|
&item.content,
|
|
ThreadRealtimeItemContent::TranscriptSegment { text, .. }
|
|
if text == "Checking staging"
|
|
)
|
|
));
|
|
assert_eq!(
|
|
page.active_realtime_session_at_page_start.as_deref(),
|
|
Some("voice-1")
|
|
);
|
|
assert!(matches!(
|
|
&page.data[2],
|
|
ThreadTimelineEntry::TurnCompleted { turn_id, duration_ms: Some(2000), .. }
|
|
if turn_id == "turn-1"
|
|
));
|
|
|
|
let older: ThreadTimelineListResponse = app_server
|
|
.request(|request_id| ClientRequest::ThreadTimelineList {
|
|
request_id,
|
|
params: ThreadTimelineListParams {
|
|
thread_id: thread_id.to_string(),
|
|
cursor: page.next_cursor,
|
|
limit: Some(2),
|
|
},
|
|
})
|
|
.await?;
|
|
assert_eq!(older.data.len(), 2);
|
|
assert!(matches!(
|
|
&older.data[0],
|
|
ThreadTimelineEntry::Realtime { item, .. }
|
|
if matches!(
|
|
item.content,
|
|
ThreadRealtimeItemContent::RealtimeSessionStarted
|
|
)
|
|
));
|
|
assert!(matches!(
|
|
&older.data[1],
|
|
ThreadTimelineEntry::TurnStarted { turn_id, started_at: Some(10), .. }
|
|
if turn_id == "turn-1"
|
|
));
|
|
assert_eq!(older.active_realtime_session_at_page_start, None);
|
|
assert_eq!(older.next_cursor, None);
|
|
|
|
let ordinary: ThreadItemsListResponse = app_server
|
|
.request(|request_id| ClientRequest::ThreadItemsList {
|
|
request_id,
|
|
params: ThreadItemsListParams {
|
|
thread_id: thread_id.to_string(),
|
|
turn_id: None,
|
|
cursor: None,
|
|
limit: Some(10),
|
|
sort_direction: Some(SortDirection::Asc),
|
|
defer_output: None,
|
|
},
|
|
})
|
|
.await?;
|
|
assert_eq!(ordinary.data.len(), 1);
|
|
assert_eq!(ordinary.data[0].turn_id, "turn-1");
|
|
|
|
Ok(())
|
|
}
|