Use ConversationId instead of raw Uuids (#3282)
We're trying to migrate from `session_id: Uuid` to `conversation_id: ConversationId`. Not only does this give us more type safety but it unifies our terminology across Codex and with the implementation of session resuming, a conversation (which can span multiple sessions) is more appropriate. I started this impl on https://github.com/openai/codex/pull/3219 as part of getting resume working in the extension but it's big enough that it should be broken out.
This commit is contained in:
@@ -4,6 +4,7 @@ use crate::pager_overlay::Overlay;
|
||||
use crate::tui;
|
||||
use crate::tui::TuiEvent;
|
||||
use codex_core::protocol::ConversationHistoryResponseEvent;
|
||||
use codex_protocol::mcp_protocol::ConversationId;
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
@@ -14,13 +15,13 @@ pub(crate) struct BacktrackState {
|
||||
/// True when Esc has primed backtrack mode in the main view.
|
||||
pub(crate) primed: bool,
|
||||
/// Session id of the base conversation to fork from.
|
||||
pub(crate) base_id: Option<uuid::Uuid>,
|
||||
pub(crate) base_id: Option<ConversationId>,
|
||||
/// Current step count (Nth last user message).
|
||||
pub(crate) count: usize,
|
||||
/// True when the transcript overlay is showing a backtrack preview.
|
||||
pub(crate) overlay_preview_active: bool,
|
||||
/// Pending fork request: (base_id, drop_count, prefill).
|
||||
pub(crate) pending: Option<(uuid::Uuid, usize, String)>,
|
||||
pub(crate) pending: Option<(ConversationId, usize, String)>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -91,7 +92,7 @@ impl App {
|
||||
pub(crate) fn request_backtrack(
|
||||
&mut self,
|
||||
prefill: String,
|
||||
base_id: uuid::Uuid,
|
||||
base_id: ConversationId,
|
||||
drop_last_messages: usize,
|
||||
) {
|
||||
self.backtrack.pending = Some((base_id, drop_last_messages, prefill));
|
||||
@@ -135,7 +136,7 @@ impl App {
|
||||
fn prime_backtrack(&mut self) {
|
||||
self.backtrack.primed = true;
|
||||
self.backtrack.count = 0;
|
||||
self.backtrack.base_id = self.chat_widget.session_id();
|
||||
self.backtrack.base_id = self.chat_widget.conversation_id();
|
||||
self.chat_widget.show_esc_backtrack_hint();
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ impl App {
|
||||
/// When overlay is already open, begin preview mode and select latest user message.
|
||||
fn begin_overlay_backtrack_preview(&mut self, tui: &mut tui::Tui) {
|
||||
self.backtrack.primed = true;
|
||||
self.backtrack.base_id = self.chat_widget.session_id();
|
||||
self.backtrack.base_id = self.chat_widget.conversation_id();
|
||||
self.backtrack.overlay_preview_active = true;
|
||||
let sel = self.compute_backtrack_selection(tui, 1);
|
||||
self.apply_backtrack_selection(sel);
|
||||
|
||||
@@ -85,7 +85,7 @@ use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
|
||||
use codex_file_search::FileMatch;
|
||||
use uuid::Uuid;
|
||||
use codex_protocol::mcp_protocol::ConversationId;
|
||||
|
||||
// Track information about an in-flight exec command.
|
||||
struct RunningCommand {
|
||||
@@ -121,7 +121,7 @@ pub(crate) struct ChatWidget {
|
||||
reasoning_buffer: String,
|
||||
// Accumulates full reasoning content for transcript-only recording
|
||||
full_reasoning_buffer: String,
|
||||
session_id: Option<Uuid>,
|
||||
conversation_id: Option<ConversationId>,
|
||||
frame_requester: FrameRequester,
|
||||
// Whether to include the initial welcome banner on session configured
|
||||
show_welcome_banner: bool,
|
||||
@@ -163,7 +163,7 @@ impl ChatWidget {
|
||||
fn on_session_configured(&mut self, event: codex_core::protocol::SessionConfiguredEvent) {
|
||||
self.bottom_pane
|
||||
.set_history_metadata(event.history_log_id, event.history_entry_count);
|
||||
self.session_id = Some(event.session_id);
|
||||
self.conversation_id = Some(event.session_id);
|
||||
let initial_messages = event.initial_messages.clone();
|
||||
if let Some(messages) = initial_messages {
|
||||
self.replay_initial_messages(messages);
|
||||
@@ -660,7 +660,7 @@ impl ChatWidget {
|
||||
interrupts: InterruptManager::new(),
|
||||
reasoning_buffer: String::new(),
|
||||
full_reasoning_buffer: String::new(),
|
||||
session_id: None,
|
||||
conversation_id: None,
|
||||
queued_user_messages: VecDeque::new(),
|
||||
show_welcome_banner: true,
|
||||
suppress_session_configured_redraw: false,
|
||||
@@ -712,7 +712,7 @@ impl ChatWidget {
|
||||
interrupts: InterruptManager::new(),
|
||||
reasoning_buffer: String::new(),
|
||||
full_reasoning_buffer: String::new(),
|
||||
session_id: None,
|
||||
conversation_id: None,
|
||||
queued_user_messages: VecDeque::new(),
|
||||
show_welcome_banner: false,
|
||||
suppress_session_configured_redraw: true,
|
||||
@@ -1159,7 +1159,7 @@ impl ChatWidget {
|
||||
self.add_to_history(history_cell::new_status_output(
|
||||
&self.config,
|
||||
usage_ref,
|
||||
&self.session_id,
|
||||
&self.conversation_id,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1360,8 +1360,8 @@ impl ChatWidget {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn session_id(&self) -> Option<Uuid> {
|
||||
self.session_id
|
||||
pub(crate) fn conversation_id(&self) -> Option<ConversationId> {
|
||||
self.conversation_id
|
||||
}
|
||||
|
||||
/// Return a reference to the widget's current config (includes any
|
||||
|
||||
@@ -25,6 +25,7 @@ use codex_core::protocol::PatchApplyEndEvent;
|
||||
use codex_core::protocol::StreamErrorEvent;
|
||||
use codex_core::protocol::TaskCompleteEvent;
|
||||
use codex_core::protocol::TaskStartedEvent;
|
||||
use codex_protocol::mcp_protocol::ConversationId;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyModifiers;
|
||||
@@ -35,11 +36,10 @@ use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn test_config() -> Config {
|
||||
// Use base defaults to avoid depending on host state.
|
||||
codex_core::config::Config::load_from_base_config_with_overrides(
|
||||
Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides::default(),
|
||||
std::env::temp_dir(),
|
||||
@@ -79,7 +79,7 @@ fn final_answer_without_newline_is_flushed_immediately() {
|
||||
// Set up a VT100 test terminal to capture ANSI visual output
|
||||
let width: u16 = 80;
|
||||
let height: u16 = 2000;
|
||||
let viewport = ratatui::layout::Rect::new(0, height - 1, width, 1);
|
||||
let viewport = Rect::new(0, height - 1, width, 1);
|
||||
let backend = ratatui::backend::TestBackend::new(width, height);
|
||||
let mut terminal = crate::custom_terminal::Terminal::with_options(backend)
|
||||
.expect("failed to construct terminal");
|
||||
@@ -132,13 +132,15 @@ fn final_answer_without_newline_is_flushed_immediately() {
|
||||
fn resumed_initial_messages_render_history() {
|
||||
let (mut chat, mut rx, _ops) = make_chatwidget_manual();
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
|
||||
let configured = codex_core::protocol::SessionConfiguredEvent {
|
||||
session_id: Uuid::nil(),
|
||||
session_id: conversation_id,
|
||||
model: "test-model".to_string(),
|
||||
history_log_id: 0,
|
||||
history_entry_count: 0,
|
||||
initial_messages: Some(vec![
|
||||
EventMsg::UserMessage(codex_core::protocol::UserMessageEvent {
|
||||
EventMsg::UserMessage(UserMessageEvent {
|
||||
message: "hello from user".to_string(),
|
||||
kind: Some(InputMessageKind::Plain),
|
||||
}),
|
||||
@@ -185,7 +187,7 @@ async fn helpers_are_available_and_do_not_panic() {
|
||||
)));
|
||||
let init = ChatWidgetInit {
|
||||
config: cfg,
|
||||
frame_requester: crate::tui::FrameRequester::test_dummy(),
|
||||
frame_requester: FrameRequester::test_dummy(),
|
||||
app_event_tx: tx,
|
||||
initial_prompt: None,
|
||||
initial_images: Vec::new(),
|
||||
@@ -208,7 +210,7 @@ fn make_chatwidget_manual() -> (
|
||||
let cfg = test_config();
|
||||
let bottom = BottomPane::new(BottomPaneParams {
|
||||
app_event_tx: app_event_tx.clone(),
|
||||
frame_requester: crate::tui::FrameRequester::test_dummy(),
|
||||
frame_requester: FrameRequester::test_dummy(),
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
@@ -228,10 +230,10 @@ fn make_chatwidget_manual() -> (
|
||||
interrupts: InterruptManager::new(),
|
||||
reasoning_buffer: String::new(),
|
||||
full_reasoning_buffer: String::new(),
|
||||
session_id: None,
|
||||
frame_requester: crate::tui::FrameRequester::test_dummy(),
|
||||
conversation_id: None,
|
||||
frame_requester: FrameRequester::test_dummy(),
|
||||
show_welcome_banner: true,
|
||||
queued_user_messages: std::collections::VecDeque::new(),
|
||||
queued_user_messages: VecDeque::new(),
|
||||
suppress_session_configured_redraw: false,
|
||||
};
|
||||
(widget, rx, op_rx)
|
||||
@@ -367,11 +369,10 @@ fn begin_exec(chat: &mut ChatWidget, call_id: &str, raw_cmd: &str) {
|
||||
// Build the full command vec and parse it using core's parser,
|
||||
// then convert to protocol variants for the event payload.
|
||||
let command = vec!["bash".to_string(), "-lc".to_string(), raw_cmd.to_string()];
|
||||
let parsed_cmd: Vec<codex_protocol::parse_command::ParsedCommand> =
|
||||
codex_core::parse_command::parse_command(&command)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
let parsed_cmd: Vec<ParsedCommand> = codex_core::parse_command::parse_command(&command)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
chat.handle_codex_event(Event {
|
||||
id: call_id.to_string(),
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
@@ -412,7 +413,7 @@ fn active_blob(chat: &ChatWidget) -> String {
|
||||
lines_to_single_string(&lines)
|
||||
}
|
||||
|
||||
fn open_fixture(name: &str) -> std::fs::File {
|
||||
fn open_fixture(name: &str) -> File {
|
||||
// 1) Prefer fixtures within this crate
|
||||
{
|
||||
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
@@ -620,7 +621,7 @@ async fn binary_size_transcript_snapshot() {
|
||||
// Set up a VT100 test terminal to capture ANSI visual output
|
||||
let width: u16 = 80;
|
||||
let height: u16 = 2000;
|
||||
let viewport = ratatui::layout::Rect::new(0, height - 1, width, 1);
|
||||
let viewport = Rect::new(0, height - 1, width, 1);
|
||||
let backend = ratatui::backend::TestBackend::new(width, height);
|
||||
let mut terminal = crate::custom_terminal::Terminal::with_options(backend)
|
||||
.expect("failed to construct terminal");
|
||||
@@ -805,7 +806,7 @@ fn approval_modal_exec_snapshot() {
|
||||
// Build a chat widget with manual channels to avoid spawning the agent.
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
|
||||
// Ensure policy allows surfacing approvals explicitly (not strictly required for direct event).
|
||||
chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest;
|
||||
chat.config.approval_policy = AskForApproval::OnRequest;
|
||||
// Inject an exec approval request to display the approval modal.
|
||||
let ev = ExecApprovalRequestEvent {
|
||||
call_id: "call-approve-cmd".into(),
|
||||
@@ -835,7 +836,7 @@ fn approval_modal_exec_snapshot() {
|
||||
#[test]
|
||||
fn approval_modal_exec_without_reason_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
|
||||
chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest;
|
||||
chat.config.approval_policy = AskForApproval::OnRequest;
|
||||
|
||||
let ev = ExecApprovalRequestEvent {
|
||||
call_id: "call-approve-cmd-noreason".into(),
|
||||
@@ -861,10 +862,10 @@ fn approval_modal_exec_without_reason_snapshot() {
|
||||
#[test]
|
||||
fn approval_modal_patch_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
|
||||
chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest;
|
||||
chat.config.approval_policy = AskForApproval::OnRequest;
|
||||
|
||||
// Build a small changeset and a reason/grant_root to exercise the prompt text.
|
||||
let mut changes = std::collections::HashMap::new();
|
||||
let mut changes = HashMap::new();
|
||||
changes.insert(
|
||||
PathBuf::from("README.md"),
|
||||
FileChange::Add {
|
||||
@@ -910,7 +911,7 @@ fn interrupt_restores_queued_messages_into_composer() {
|
||||
chat.handle_codex_event(Event {
|
||||
id: "turn-1".into(),
|
||||
msg: EventMsg::TurnAborted(codex_core::protocol::TurnAbortedEvent {
|
||||
reason: codex_core::protocol::TurnAbortReason::Interrupted,
|
||||
reason: TurnAbortReason::Interrupted,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1344,7 +1345,7 @@ fn apply_patch_full_flow_integration_like() {
|
||||
fn apply_patch_untrusted_shows_approval_modal() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
|
||||
// Ensure approval policy is untrusted (OnRequest)
|
||||
chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest;
|
||||
chat.config.approval_policy = AskForApproval::OnRequest;
|
||||
|
||||
// Simulate a patch approval request from backend
|
||||
let mut changes = HashMap::new();
|
||||
@@ -1363,8 +1364,8 @@ fn apply_patch_untrusted_shows_approval_modal() {
|
||||
});
|
||||
|
||||
// Render and ensure the approval modal title is present
|
||||
let area = ratatui::layout::Rect::new(0, 0, 80, 12);
|
||||
let mut buf = ratatui::buffer::Buffer::empty(area);
|
||||
let area = Rect::new(0, 0, 80, 12);
|
||||
let mut buf = Buffer::empty(area);
|
||||
(&chat).render_ref(area, &mut buf);
|
||||
|
||||
let mut contains_title = false;
|
||||
@@ -1389,7 +1390,7 @@ fn apply_patch_request_shows_diff_summary() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
|
||||
|
||||
// Ensure we are in OnRequest so an approval is surfaced
|
||||
chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest;
|
||||
chat.config.approval_policy = AskForApproval::OnRequest;
|
||||
|
||||
// Simulate backend asking to apply a patch adding two lines to README.md
|
||||
let mut changes = HashMap::new();
|
||||
@@ -1691,7 +1692,7 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() {
|
||||
let width: u16 = 80;
|
||||
let ui_height: u16 = chat.desired_height(width);
|
||||
let vt_height: u16 = 40;
|
||||
let viewport = ratatui::layout::Rect::new(0, vt_height - ui_height, width, ui_height);
|
||||
let viewport = Rect::new(0, vt_height - ui_height, width, ui_height);
|
||||
|
||||
// Use TestBackend for the terminal (no real ANSI emitted by drawing),
|
||||
// but capture VT100 escape stream for history insertion with a separate writer.
|
||||
@@ -1706,7 +1707,7 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() {
|
||||
}
|
||||
|
||||
// 2) Render the ChatWidget UI into an off-screen buffer using WidgetRef directly
|
||||
let mut ui_buf = ratatui::buffer::Buffer::empty(viewport);
|
||||
let mut ui_buf = Buffer::empty(viewport);
|
||||
(&chat).render_ref(viewport, &mut ui_buf);
|
||||
|
||||
// 3) Build VT100 visual from the captured ANSI
|
||||
|
||||
@@ -27,6 +27,7 @@ use codex_core::protocol::McpInvocation;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_core::protocol::SessionConfiguredEvent;
|
||||
use codex_core::protocol::TokenUsage;
|
||||
use codex_protocol::mcp_protocol::ConversationId;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use image::DynamicImage;
|
||||
use image::ImageReader;
|
||||
@@ -49,7 +50,6 @@ use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use tracing::error;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct CommandOutput {
|
||||
@@ -821,7 +821,7 @@ pub(crate) fn new_completed_mcp_tool_call(
|
||||
pub(crate) fn new_status_output(
|
||||
config: &Config,
|
||||
usage: &TokenUsage,
|
||||
session_id: &Option<Uuid>,
|
||||
session_id: &Option<ConversationId>,
|
||||
) -> PlainHistoryCell {
|
||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||
lines.push("/status".magenta().into());
|
||||
|
||||
Reference in New Issue
Block a user