chore: unify conversation with thread name (#8830)

Done and verified by Codex + refactor feature of RustRover
This commit is contained in:
jif-oai
2026-01-09 11:37:07 +00:00
committed by pap
parent 137b26bd46
commit ef39b121f2
83 changed files with 1094 additions and 1203 deletions
@@ -14,7 +14,7 @@ use codex_core::ResponseItem;
use codex_core::WireApi;
use codex_core::models_manager::manager::ModelsManager;
use codex_otel::otel_manager::OtelManager;
use codex_protocol::ConversationId;
use codex_protocol::ThreadId;
use codex_protocol::models::ReasoningItemContent;
use codex_protocol::protocol::SessionSource;
use core_test_support::load_default_config_for_test;
@@ -73,7 +73,7 @@ async fn run_request(input: Vec<ResponseItem>) -> Value {
let summary = config.model_reasoning_summary;
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let conversation_id = ThreadId::new();
let model = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = ModelsManager::construct_model_family_offline(model.as_str(), &config);
let otel_manager = OtelManager::new(
+2 -2
View File
@@ -13,7 +13,7 @@ use codex_core::ResponseItem;
use codex_core::WireApi;
use codex_core::models_manager::manager::ModelsManager;
use codex_otel::otel_manager::OtelManager;
use codex_protocol::ConversationId;
use codex_protocol::ThreadId;
use codex_protocol::models::ReasoningItemContent;
use codex_protocol::protocol::SessionSource;
use core_test_support::load_default_config_for_test;
@@ -72,7 +72,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec<ResponseEvent> {
let summary = config.model_reasoning_summary;
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let conversation_id = ThreadId::new();
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let auth_mode = auth_manager.get_auth_mode();
let model = ModelsManager::get_model_offline(config.model.as_deref());
+4 -7
View File
@@ -2,7 +2,7 @@
use tempfile::TempDir;
use codex_core::CodexConversation;
use codex_core::CodexThread;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_core::config::ConfigOverrides;
@@ -170,10 +170,7 @@ pub fn load_sse_fixture_with_id(path: impl AsRef<std::path::Path>, id: &str) ->
.collect()
}
pub async fn wait_for_event<F>(
codex: &CodexConversation,
predicate: F,
) -> codex_core::protocol::EventMsg
pub async fn wait_for_event<F>(codex: &CodexThread, predicate: F) -> codex_core::protocol::EventMsg
where
F: FnMut(&codex_core::protocol::EventMsg) -> bool,
{
@@ -181,7 +178,7 @@ where
wait_for_event_with_timeout(codex, predicate, Duration::from_secs(1)).await
}
pub async fn wait_for_event_match<T, F>(codex: &CodexConversation, matcher: F) -> T
pub async fn wait_for_event_match<T, F>(codex: &CodexThread, matcher: F) -> T
where
F: Fn(&codex_core::protocol::EventMsg) -> Option<T>,
{
@@ -190,7 +187,7 @@ where
}
pub async fn wait_for_event_with_timeout<F>(
codex: &CodexConversation,
codex: &CodexThread,
mut predicate: F,
wait_time: tokio::time::Duration,
) -> codex_core::protocol::EventMsg
+11 -15
View File
@@ -5,9 +5,9 @@ use std::sync::Arc;
use anyhow::Result;
use codex_core::CodexAuth;
use codex_core::CodexConversation;
use codex_core::ConversationManager;
use codex_core::CodexThread;
use codex_core::ModelProviderInfo;
use codex_core::ThreadManager;
use codex_core::built_in_model_providers;
use codex_core::config::Config;
use codex_core::features::Feature;
@@ -138,34 +138,30 @@ impl TestCodexBuilder {
resume_from: Option<PathBuf>,
) -> anyhow::Result<TestCodex> {
let auth = self.auth.clone();
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
auth.clone(),
config.model_provider.clone(),
config.codex_home.clone(),
);
let conversation_manager = Arc::new(conversation_manager);
let thread_manager = Arc::new(thread_manager);
let new_conversation = match resume_from {
Some(path) => {
let auth_manager = codex_core::AuthManager::from_auth_for_testing(auth);
conversation_manager
.resume_conversation_from_rollout(config.clone(), path, auth_manager)
.await?
}
None => {
conversation_manager
.new_conversation(config.clone())
thread_manager
.resume_thread_from_rollout(config.clone(), path, auth_manager)
.await?
}
None => thread_manager.start_thread(config.clone()).await?,
};
Ok(TestCodex {
home,
cwd,
config,
codex: new_conversation.conversation,
codex: new_conversation.thread,
session_configured: new_conversation.session_configured,
conversation_manager,
thread_manager,
})
}
@@ -208,10 +204,10 @@ impl TestCodexBuilder {
pub struct TestCodex {
pub home: Arc<TempDir>,
pub cwd: Arc<TempDir>,
pub codex: Arc<CodexConversation>,
pub codex: Arc<CodexThread>,
pub session_configured: SessionConfiguredEvent,
pub config: Config,
pub conversation_manager: Arc<ConversationManager>,
pub thread_manager: Arc<ThreadManager>,
}
impl TestCodex {
+4 -4
View File
@@ -12,7 +12,7 @@ use codex_core::ResponseItem;
use codex_core::WireApi;
use codex_core::models_manager::manager::ModelsManager;
use codex_otel::otel_manager::OtelManager;
use codex_protocol::ConversationId;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
@@ -65,7 +65,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
config.model = Some(model.clone());
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let conversation_id = ThreadId::new();
let auth_mode = AuthMode::ChatGPT;
let session_source = SessionSource::SubAgent(SubAgentSource::Review);
let model_family = ModelsManager::construct_model_family_offline(model.as_str(), &config);
@@ -159,7 +159,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
config.model = Some(model.clone());
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let conversation_id = ThreadId::new();
let auth_mode = AuthMode::ChatGPT;
let session_source = SessionSource::SubAgent(SubAgentSource::Other("my-task".to_string()));
let model_family = ModelsManager::construct_model_family_offline(model.as_str(), &config);
@@ -251,7 +251,7 @@ async fn responses_respects_model_family_overrides_from_config() {
let model = config.model.clone().expect("model configured");
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let conversation_id = ThreadId::new();
let auth_mode =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")).get_auth_mode();
let session_source =
+1 -1
View File
@@ -72,7 +72,7 @@ async fn chat_mode_stream_cli() {
// Verify a new session rollout was created and is discoverable via list_conversations
let provider_filter = vec!["mock".to_string()];
let page = RolloutRecorder::list_conversations(
let page = RolloutRecorder::list_threads(
home.path(),
10,
None,
+57 -61
View File
@@ -1,16 +1,16 @@
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::ContentItem;
use codex_core::ConversationManager;
use codex_core::LocalShellAction;
use codex_core::LocalShellExecAction;
use codex_core::LocalShellStatus;
use codex_core::ModelClient;
use codex_core::ModelProviderInfo;
use codex_core::NewConversation;
use codex_core::NewThread;
use codex_core::Prompt;
use codex_core::ResponseEvent;
use codex_core::ResponseItem;
use codex_core::ThreadManager;
use codex_core::WireApi;
use codex_core::auth::AuthCredentialsStoreMode;
use codex_core::built_in_model_providers;
@@ -21,7 +21,7 @@ use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_core::protocol::SessionSource;
use codex_otel::otel_manager::OtelManager;
use codex_protocol::ConversationId;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::Verbosity;
use codex_protocol::models::ReasoningItemContent;
@@ -259,19 +259,19 @@ async fn resume_includes_initial_messages_and_sends_prior_items() {
// Also configure user instructions to ensure they are NOT delivered on resume.
config.user_instructions = Some("be nice".to_string());
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
config.codex_home.clone(),
);
let auth_manager =
codex_core::AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let NewConversation {
conversation: codex,
let NewThread {
thread: codex,
session_configured,
..
} = conversation_manager
.resume_conversation_from_rollout(config, session_path.clone(), auth_manager)
} = thread_manager
.resume_thread_from_rollout(config, session_path.clone(), auth_manager)
.await
.expect("resume conversation");
@@ -347,17 +347,18 @@ async fn includes_conversation_id_and_model_headers_in_request() {
let mut config = load_default_config_for_test(&codex_home).await;
config.model_provider = model_provider;
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
config.codex_home.clone(),
);
let NewConversation {
conversation: codex,
conversation_id,
let NewThread {
thread: codex,
thread_id: conversation_id,
session_configured: _,
} = conversation_manager
.new_conversation(config)
..
} = thread_manager
.start_thread(config)
.await
.expect("create new conversation");
@@ -410,16 +411,16 @@ async fn includes_base_instructions_override_in_request() {
config.base_instructions = Some("test instructions".to_string());
config.model_provider = model_provider;
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
config.codex_home.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create new conversation")
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -472,17 +473,18 @@ async fn chatgpt_auth_sends_correct_request() {
let codex_home = TempDir::new().unwrap();
let mut config = load_default_config_for_test(&codex_home).await;
config.model_provider = model_provider;
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
create_dummy_codex_auth(),
config.model_provider.clone(),
config.codex_home.clone(),
);
let NewConversation {
conversation: codex,
conversation_id,
let NewThread {
thread: codex,
thread_id: conversation_id,
session_configured: _,
} = conversation_manager
.new_conversation(config)
..
} = thread_manager
.start_thread(config)
.await
.expect("create new conversation");
@@ -572,12 +574,9 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
Ok(None) => panic!("No CodexAuth found in codex_home"),
Err(e) => panic!("Failed to load CodexAuth: {e}"),
};
let conversation_manager = ConversationManager::new(auth_manager, SessionSource::Exec);
let NewConversation {
conversation: codex,
..
} = conversation_manager
.new_conversation(config)
let thread_manager = ThreadManager::new(auth_manager, SessionSource::Exec);
let NewThread { thread: codex, .. } = thread_manager
.start_thread(config)
.await
.expect("create new conversation");
@@ -611,16 +610,16 @@ async fn includes_user_instructions_message_in_request() {
config.model_provider = model_provider;
config.user_instructions = Some("be nice".to_string());
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
config.codex_home.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create new conversation")
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -682,16 +681,16 @@ async fn skills_append_to_instructions() {
config.cwd = codex_home.path().to_path_buf();
config.features.enable(Feature::Skills);
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
config.codex_home.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create new conversation")
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -1049,16 +1048,16 @@ async fn includes_developer_instructions_message_in_request() {
config.user_instructions = Some("be nice".to_string());
config.developer_instructions = Some("be useful".to_string());
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
config.codex_home.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create new conversation")
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -1144,7 +1143,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
config.model = Some(model.clone());
let config = Arc::new(config);
let model_family = ModelsManager::construct_model_family_offline(model.as_str(), &config);
let conversation_id = ConversationId::new();
let conversation_id = ThreadId::new();
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let otel_manager = OtelManager::new(
conversation_id,
@@ -1280,16 +1279,16 @@ async fn token_count_includes_rate_limits_snapshot() {
let mut config = load_default_config_for_test(&home).await;
config.model_provider = provider;
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
CodexAuth::from_api_key("test"),
config.model_provider.clone(),
config.codex_home.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create conversation")
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -1639,16 +1638,16 @@ async fn azure_overrides_assign_properties_used_for_responses_url() {
let mut config = load_default_config_for_test(&codex_home).await;
config.model_provider = provider;
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
create_dummy_codex_auth(),
config.model_provider.clone(),
config.codex_home.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create new conversation")
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -1722,16 +1721,16 @@ async fn env_var_overrides_loaded_auth() {
let mut config = load_default_config_for_test(&codex_home).await;
config.model_provider = provider;
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
create_dummy_codex_auth(),
config.model_provider.clone(),
config.codex_home.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create new conversation")
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -1805,16 +1804,13 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
let mut config = load_default_config_for_test(&codex_home).await;
config.model_provider = model_provider;
let conversation_manager = ConversationManager::with_models_provider_and_home(
let thread_manager = ThreadManager::with_models_provider_and_home(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
config.codex_home.clone(),
);
let NewConversation {
conversation: codex,
..
} = conversation_manager
.new_conversation(config)
let NewThread { thread: codex, .. } = thread_manager
.start_thread(config)
.await
.expect("create new conversation");
+31 -42
View File
@@ -1,8 +1,8 @@
#![allow(clippy::expect_used)]
use codex_core::CodexAuth;
use codex_core::ConversationManager;
use codex_core::ModelProviderInfo;
use codex_core::NewConversation;
use codex_core::NewThread;
use codex_core::ThreadManager;
use codex_core::built_in_model_providers;
use codex_core::compact::SUMMARIZATION_PROMPT;
use codex_core::compact::SUMMARY_PREFIX;
@@ -62,7 +62,7 @@ const DUMMY_CALL_ID: &str = "call-multi-auto";
const FUNCTION_CALL_LIMIT_MSG: &str = "function call limit push";
const POST_AUTO_USER_MSG: &str = "post auto follow-up";
pub(super) const COMPACT_WARNING_MESSAGE: &str = "Heads up: Long conversations and multiple compactions can cause the model to be less accurate. Start a new conversation when possible to keep conversations small and targeted.";
pub(super) const COMPACT_WARNING_MESSAGE: &str = "Heads up: Long threads and multiple compactions can cause the model to be less accurate. Start a new thread when possible to keep threads small and targeted.";
fn auto_summary(summary: &str) -> String {
summary.to_string()
@@ -144,15 +144,15 @@ async fn summarize_context_three_requests_and_instructions() {
config.model_provider = model_provider;
set_test_compact_prompt(&mut config);
config.model_auto_compact_token_limit = Some(200_000);
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let NewConversation {
conversation: codex,
let NewThread {
thread: codex,
session_configured,
..
} = conversation_manager.new_conversation(config).await.unwrap();
} = thread_manager.start_thread(config).await.unwrap();
let rollout_path = session_configured.rollout_path;
// 1) Normal user input – should hit server once.
@@ -340,15 +340,15 @@ async fn manual_compact_uses_custom_prompt() {
config.model_provider = model_provider;
config.compact_prompt = Some(custom_prompt.to_string());
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create conversation")
.conversation;
.thread;
codex.submit(Op::Compact).await.expect("trigger compact");
let warning_event = wait_for_event(&codex, |ev| matches!(ev, EventMsg::Warning(_))).await;
@@ -420,14 +420,11 @@ async fn manual_compact_emits_api_and_local_token_usage_events() {
config.model_provider = model_provider;
set_test_compact_prompt(&mut config);
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let NewConversation {
conversation: codex,
..
} = conversation_manager.new_conversation(config).await.unwrap();
let NewThread { thread: codex, .. } = thread_manager.start_thread(config).await.unwrap();
// Trigger manual compact and collect TokenCount events for the compact turn.
codex.submit(Op::Compact).await.unwrap();
@@ -1072,15 +1069,11 @@ async fn auto_compact_runs_after_token_limit_hit() {
config.model_provider = model_provider;
set_test_compact_prompt(&mut config);
config.model_auto_compact_token_limit = Some(200_000);
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let codex = conversation_manager
.new_conversation(config)
.await
.unwrap()
.conversation;
let codex = thread_manager.start_thread(config).await.unwrap().thread;
codex
.submit(Op::UserInput {
@@ -1409,15 +1402,15 @@ async fn auto_compact_persists_rollout_entries() {
config.model_provider = model_provider;
set_test_compact_prompt(&mut config);
config.model_auto_compact_token_limit = Some(200_000);
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let NewConversation {
conversation: codex,
let NewThread {
thread: codex,
session_configured,
..
} = conversation_manager.new_conversation(config).await.unwrap();
} = thread_manager.start_thread(config).await.unwrap();
codex
.submit(Op::UserInput {
@@ -1524,14 +1517,14 @@ async fn manual_compact_retries_after_context_window_error() {
config.model_provider = model_provider;
set_test_compact_prompt(&mut config);
config.model_auto_compact_token_limit = Some(200_000);
let codex = ConversationManager::with_models_provider(
let codex = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
)
.new_conversation(config)
.start_thread(config)
.await
.unwrap()
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -1551,7 +1544,7 @@ async fn manual_compact_retries_after_context_window_error() {
panic!("expected background event after compact retry");
};
assert!(
event.message.contains("Trimmed 1 older conversation item"),
event.message.contains("Trimmed 1 older thread item"),
"background event should mention trimmed item count: {}",
event.message
);
@@ -1657,14 +1650,14 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
let mut config = load_default_config_for_test(&home).await;
config.model_provider = model_provider;
set_test_compact_prompt(&mut config);
let codex = ConversationManager::with_models_provider(
let codex = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
)
.new_conversation(config)
.start_thread(config)
.await
.unwrap()
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -1864,15 +1857,11 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_
config.model_provider = model_provider;
set_test_compact_prompt(&mut config);
config.model_auto_compact_token_limit = Some(200);
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let codex = conversation_manager
.new_conversation(config)
.await
.unwrap()
.conversation;
let codex = thread_manager.start_thread(config).await.unwrap().thread;
let mut auto_compact_lifecycle_events = Vec::new();
for user in [MULTI_AUTO_MSG, follow_up_user, final_user] {
@@ -1978,14 +1967,14 @@ async fn auto_compact_triggers_after_function_call_over_95_percent_usage() {
config.model_context_window = Some(context_window);
config.model_auto_compact_token_limit = Some(limit);
let codex = ConversationManager::with_models_provider(
let codex = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
)
.new_conversation(config)
.start_thread(config)
.await
.unwrap()
.conversation;
.thread;
codex
.submit(Op::UserInput {
@@ -11,10 +11,10 @@ use super::compact::COMPACT_WARNING_MESSAGE;
use super::compact::FIRST_REPLY;
use super::compact::SUMMARY_TEXT;
use codex_core::CodexAuth;
use codex_core::CodexConversation;
use codex_core::ConversationManager;
use codex_core::CodexThread;
use codex_core::ModelProviderInfo;
use codex_core::NewConversation;
use codex_core::NewThread;
use codex_core::ThreadManager;
use codex_core::built_in_model_providers;
use codex_core::compact::SUMMARIZATION_PROMPT;
use codex_core::config::Config;
@@ -171,7 +171,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
"compact+resume test expects resumed path {resumed_path:?} to exist",
);
let forked = fork_conversation(&manager, &config, resumed_path, 2).await;
let forked = fork_thread(&manager, &config, resumed_path, 2).await;
user_turn(&forked, "AFTER_FORK").await;
// 3. Capture the requests to the model and validate the history slices.
@@ -623,7 +623,7 @@ async fn compact_resume_after_second_compaction_preserves_history() {
"second compact test expects resumed path {resumed_path:?} to exist",
);
let forked = fork_conversation(&manager, &config, resumed_path, 3).await;
let forked = fork_thread(&manager, &config, resumed_path, 3).await;
user_turn(&forked, "AFTER_FORK").await;
compact_conversation(&forked).await;
@@ -855,7 +855,7 @@ async fn mount_second_compact_flow(server: &MockServer) {
async fn start_test_conversation(
server: &MockServer,
model: Option<&str>,
) -> (TempDir, Config, ConversationManager, Arc<CodexConversation>) {
) -> (TempDir, Config, ThreadManager, Arc<CodexThread>) {
let model_provider = ModelProviderInfo {
name: "Non-OpenAI Model provider".into(),
base_url: Some(format!("{}/v1", server.uri())),
@@ -868,19 +868,19 @@ async fn start_test_conversation(
if let Some(model) = model {
config.model = Some(model.to_string());
}
let manager = ConversationManager::with_models_provider(
let manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let NewConversation { conversation, .. } = manager
.new_conversation(config.clone())
let NewThread { thread, .. } = manager
.start_thread(config.clone())
.await
.expect("create conversation");
(home, config, manager, conversation)
(home, config, manager, thread)
}
async fn user_turn(conversation: &Arc<CodexConversation>, text: &str) {
async fn user_turn(conversation: &Arc<CodexThread>, text: &str) {
conversation
.submit(Op::UserInput {
items: vec![UserInput::Text { text: text.into() }],
@@ -891,7 +891,7 @@ async fn user_turn(conversation: &Arc<CodexConversation>, text: &str) {
wait_for_event(conversation, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
}
async fn compact_conversation(conversation: &Arc<CodexConversation>) {
async fn compact_conversation(conversation: &Arc<CodexThread>) {
conversation
.submit(Op::Compact)
.await
@@ -904,34 +904,34 @@ async fn compact_conversation(conversation: &Arc<CodexConversation>) {
wait_for_event(conversation, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
}
async fn fetch_conversation_path(conversation: &Arc<CodexConversation>) -> std::path::PathBuf {
async fn fetch_conversation_path(conversation: &Arc<CodexThread>) -> std::path::PathBuf {
conversation.rollout_path()
}
async fn resume_conversation(
manager: &ConversationManager,
manager: &ThreadManager,
config: &Config,
path: std::path::PathBuf,
) -> Arc<CodexConversation> {
) -> Arc<CodexThread> {
let auth_manager =
codex_core::AuthManager::from_auth_for_testing(CodexAuth::from_api_key("dummy"));
let NewConversation { conversation, .. } = manager
.resume_conversation_from_rollout(config.clone(), path, auth_manager)
let NewThread { thread, .. } = manager
.resume_thread_from_rollout(config.clone(), path, auth_manager)
.await
.expect("resume conversation");
conversation
thread
}
#[cfg(test)]
async fn fork_conversation(
manager: &ConversationManager,
async fn fork_thread(
manager: &ThreadManager,
config: &Config,
path: std::path::PathBuf,
nth_user_message: usize,
) -> Arc<CodexConversation> {
let NewConversation { conversation, .. } = manager
.fork_conversation(nth_user_message, config.clone(), path)
) -> Arc<CodexThread> {
let NewThread { thread, .. } = manager
.fork_thread(nth_user_message, config.clone(), path)
.await
.expect("fork conversation");
conversation
thread
}
@@ -1,7 +1,7 @@
use codex_core::CodexAuth;
use codex_core::ConversationManager;
use codex_core::ModelProviderInfo;
use codex_core::NewConversation;
use codex_core::NewThread;
use codex_core::ThreadManager;
use codex_core::built_in_model_providers;
use codex_core::parse_turn_item;
use codex_core::protocol::EventMsg;
@@ -26,7 +26,7 @@ fn sse_completed(id: &str) -> String {
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn fork_conversation_twice_drops_to_first_message() {
async fn fork_thread_twice_drops_to_first_message() {
skip_if_no_network!();
// Start a mock server that completes three turns.
@@ -55,15 +55,12 @@ async fn fork_conversation_twice_drops_to_first_message() {
config.model_provider = model_provider.clone();
let config_for_fork = config.clone();
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let NewConversation {
conversation: codex,
..
} = conversation_manager
.new_conversation(config)
let NewThread { thread: codex, .. } = thread_manager
.start_thread(config)
.await
.expect("create conversation");
@@ -129,11 +126,11 @@ async fn fork_conversation_twice_drops_to_first_message() {
// After dropping again (n=1 on fork1), compute expected relative to fork1's rollout.
// Fork once with n=1 → drops the last user input and everything after.
let NewConversation {
conversation: codex_fork1,
let NewThread {
thread: codex_fork1,
..
} = conversation_manager
.fork_conversation(1, config_for_fork.clone(), base_path.clone())
} = thread_manager
.fork_thread(1, config_for_fork.clone(), base_path.clone())
.await
.expect("fork 1");
@@ -147,11 +144,11 @@ async fn fork_conversation_twice_drops_to_first_message() {
);
// Fork again with n=0 → drops the (new) last user message, leaving only the first.
let NewConversation {
conversation: codex_fork2,
let NewThread {
thread: codex_fork2,
..
} = conversation_manager
.fork_conversation(0, config_for_fork.clone(), fork1_path.clone())
} = thread_manager
.fork_thread(0, config_for_fork.clone(), fork1_path.clone())
.await
.expect("fork 2");
+3 -3
View File
@@ -1,6 +1,6 @@
use anyhow::Result;
use codex_core::CodexAuth;
use codex_core::ConversationManager;
use codex_core::ThreadManager;
use codex_core::built_in_model_providers;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ReasoningEffort;
@@ -13,7 +13,7 @@ use tempfile::tempdir;
async fn list_models_returns_api_key_models() -> Result<()> {
let codex_home = tempdir()?;
let config = load_default_config_for_test(&codex_home).await;
let manager = ConversationManager::with_models_provider(
let manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("sk-test"),
built_in_model_providers()["openai"].clone(),
);
@@ -29,7 +29,7 @@ async fn list_models_returns_api_key_models() -> Result<()> {
async fn list_models_returns_chatgpt_models() -> Result<()> {
let codex_home = tempdir()?;
let config = load_default_config_for_test(&codex_home).await;
let manager = ConversationManager::with_models_provider(
let manager = ThreadManager::with_models_provider(
CodexAuth::create_dummy_chatgpt_auth_for_testing(),
built_in_model_providers()["openai"].clone(),
);
+1 -1
View File
@@ -28,7 +28,7 @@ mod compact_resume_fork;
mod deprecation_notice;
mod exec;
mod exec_policy;
mod fork_conversation;
mod fork_thread;
mod grep_files;
mod items;
mod json_result;
+9 -9
View File
@@ -1,5 +1,5 @@
use codex_core::CodexAuth;
use codex_core::ConversationManager;
use codex_core::ThreadManager;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::openai_models::ReasoningEffort;
@@ -22,15 +22,15 @@ async fn override_turn_context_does_not_persist_when_config_exists() {
let mut config = load_default_config_for_test(&codex_home).await;
config.model = Some("gpt-4o".to_string());
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create conversation")
.conversation;
.thread;
codex
.submit(Op::OverrideTurnContext {
@@ -64,15 +64,15 @@ async fn override_turn_context_does_not_create_config_file() {
let config = load_default_config_for_test(&codex_home).await;
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
);
let codex = conversation_manager
.new_conversation(config)
let codex = thread_manager
.start_thread(config)
.await
.expect("create conversation")
.conversation;
.thread;
codex
.submit(Op::OverrideTurnContext {
+2 -2
View File
@@ -75,7 +75,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
let TestCodex {
codex,
config,
conversation_manager,
thread_manager,
..
} = test_codex()
.with_config(|config| {
@@ -84,7 +84,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
})
.build(&server)
.await?;
let base_instructions = conversation_manager
let base_instructions = thread_manager
.get_models_manager()
.construct_model_family(
config
+13 -15
View File
@@ -4,9 +4,9 @@ use std::sync::Arc;
use anyhow::Result;
use codex_core::CodexAuth;
use codex_core::CodexConversation;
use codex_core::ConversationManager;
use codex_core::CodexThread;
use codex_core::ModelProviderInfo;
use codex_core::ThreadManager;
use codex_core::built_in_model_providers;
use codex_core::config::Config;
use codex_core::features::Feature;
@@ -103,11 +103,11 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
codex,
cwd,
config,
conversation_manager,
thread_manager,
..
} = harness;
let models_manager = conversation_manager.get_models_manager();
let models_manager = thread_manager.get_models_manager();
let available_model =
wait_for_model_available(&models_manager, REMOTE_MODEL_SLUG, &config).await;
@@ -249,11 +249,11 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
codex,
cwd,
config,
conversation_manager,
thread_manager,
..
} = harness;
let models_manager = conversation_manager.get_models_manager();
let models_manager = thread_manager.get_models_manager();
wait_for_model_available(&models_manager, model, &config).await;
codex
@@ -411,10 +411,10 @@ async fn wait_for_model_available(
}
struct RemoteModelsHarness {
codex: Arc<CodexConversation>,
codex: Arc<CodexThread>,
cwd: Arc<TempDir>,
config: Config,
conversation_manager: Arc<ConversationManager>,
thread_manager: Arc<ThreadManager>,
}
// todo(aibrahim): move this to with_model_provier in test_codex
@@ -441,18 +441,16 @@ where
mutate_config(&mut config);
let conversation_manager = ConversationManager::with_models_provider(auth, provider);
let conversation_manager = Arc::new(conversation_manager);
let thread_manager = ThreadManager::with_models_provider(auth, provider);
let thread_manager = Arc::new(thread_manager);
let new_conversation = conversation_manager
.new_conversation(config.clone())
.await?;
let new_conversation = thread_manager.start_thread(config.clone()).await?;
Ok(RemoteModelsHarness {
codex: new_conversation.conversation,
codex: new_conversation.thread,
cwd,
config,
conversation_manager,
thread_manager,
})
}
+10 -7
View File
@@ -2,15 +2,15 @@
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::ConversationManager;
use codex_core::NewConversation;
use codex_core::NewThread;
use codex_core::ThreadManager;
use codex_core::protocol::EventMsg;
use codex_core::protocol::InitialHistory;
use codex_core::protocol::ResumedHistory;
use codex_core::protocol::RolloutItem;
use codex_core::protocol::TurnContextItem;
use codex_core::protocol::WarningEvent;
use codex_protocol::ConversationId;
use codex_protocol::ThreadId;
use core::time::Duration;
use core_test_support::load_default_config_for_test;
use core_test_support::wait_for_event;
@@ -36,7 +36,7 @@ fn resume_history(
};
InitialHistory::Resumed(ResumedHistory {
conversation_id: ConversationId::default(),
conversation_id: ThreadId::default(),
history: vec![RolloutItem::TurnContext(turn_ctx)],
rollout_path: rollout_path.to_path_buf(),
})
@@ -56,15 +56,18 @@ async fn emits_warning_when_resumed_model_differs() {
let initial_history = resume_history(&config, "previous-model", &rollout_path);
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("test"),
config.model_provider.clone(),
);
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("test"));
// Act: resume the conversation.
let NewConversation { conversation, .. } = conversation_manager
.resume_conversation_with_history(config, initial_history, auth_manager)
let NewThread {
thread: conversation,
..
} = thread_manager
.resume_thread_with_history(config, initial_history, auth_manager)
.await
.expect("resume conversation");
+12 -12
View File
@@ -1,10 +1,10 @@
use codex_core::CodexAuth;
use codex_core::CodexConversation;
use codex_core::CodexThread;
use codex_core::ContentItem;
use codex_core::ConversationManager;
use codex_core::ModelProviderInfo;
use codex_core::REVIEW_PROMPT;
use codex_core::ResponseItem;
use codex_core::ThreadManager;
use codex_core::built_in_model_providers;
use codex_core::config::Config;
use codex_core::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG;
@@ -832,7 +832,7 @@ async fn new_conversation_for_server<F>(
server: &MockServer,
codex_home: &TempDir,
mutator: F,
) -> Arc<CodexConversation>
) -> Arc<CodexThread>
where
F: FnOnce(&mut Config),
{
@@ -843,15 +843,15 @@ where
let mut config = load_default_config_for_test(codex_home).await;
config.model_provider = model_provider;
mutator(&mut config);
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
);
conversation_manager
.new_conversation(config)
thread_manager
.start_thread(config)
.await
.expect("create conversation")
.conversation
.thread
}
/// Create a conversation resuming from a rollout file, configured to talk to the provided mock server.
@@ -861,7 +861,7 @@ async fn resume_conversation_for_server<F>(
codex_home: &TempDir,
resume_path: std::path::PathBuf,
mutator: F,
) -> Arc<CodexConversation>
) -> Arc<CodexThread>
where
F: FnOnce(&mut Config),
{
@@ -872,15 +872,15 @@ where
let mut config = load_default_config_for_test(codex_home).await;
config.model_provider = model_provider;
mutator(&mut config);
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
);
let auth_manager =
codex_core::AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
conversation_manager
.resume_conversation_from_rollout(config, resume_path, auth_manager)
thread_manager
.resume_thread_from_rollout(config, resume_path, auth_manager)
.await
.expect("resume conversation")
.conversation
.thread
}
@@ -3,7 +3,7 @@ use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use codex_core::find_conversation_path_by_id_str;
use codex_core::find_thread_path_by_id_str;
use tempfile::TempDir;
use uuid::Uuid;
@@ -44,7 +44,7 @@ async fn find_locates_rollout_file_by_id() {
let id = Uuid::new_v4();
let expected = write_minimal_rollout_with_id(home.path(), id);
let found = find_conversation_path_by_id_str(home.path(), &id.to_string())
let found = find_thread_path_by_id_str(home.path(), &id.to_string())
.await
.unwrap();
@@ -60,7 +60,7 @@ async fn find_handles_gitignore_covering_codex_home_directory() {
let id = Uuid::new_v4();
let expected = write_minimal_rollout_with_id(&codex_home, id);
let found = find_conversation_path_by_id_str(&codex_home, &id.to_string())
let found = find_thread_path_by_id_str(&codex_home, &id.to_string())
.await
.unwrap();
@@ -74,7 +74,7 @@ async fn find_ignores_granular_gitignore_rules() {
let expected = write_minimal_rollout_with_id(home.path(), id);
std::fs::write(home.path().join("sessions/.gitignore"), "*.jsonl\n").unwrap();
let found = find_conversation_path_by_id_str(home.path(), &id.to_string())
let found = find_thread_path_by_id_str(home.path(), &id.to_string())
.await
.unwrap();
+4 -4
View File
@@ -8,7 +8,7 @@ use std::sync::Arc;
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use codex_core::CodexConversation;
use codex_core::CodexThread;
use codex_core::features::Feature;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
@@ -108,7 +108,7 @@ async fn run_apply_patch_turn(
harness.submit(prompt).await
}
async fn invoke_undo(codex: &Arc<CodexConversation>) -> Result<UndoCompletedEvent> {
async fn invoke_undo(codex: &Arc<CodexThread>) -> Result<UndoCompletedEvent> {
codex.submit(Op::Undo).await?;
let event = wait_for_event_match(codex, |msg| match msg {
EventMsg::UndoCompleted(done) => Some(done.clone()),
@@ -118,7 +118,7 @@ async fn invoke_undo(codex: &Arc<CodexConversation>) -> Result<UndoCompletedEven
Ok(event)
}
async fn expect_successful_undo(codex: &Arc<CodexConversation>) -> Result<UndoCompletedEvent> {
async fn expect_successful_undo(codex: &Arc<CodexThread>) -> Result<UndoCompletedEvent> {
let event = invoke_undo(codex).await?;
assert!(
event.success,
@@ -128,7 +128,7 @@ async fn expect_successful_undo(codex: &Arc<CodexConversation>) -> Result<UndoCo
Ok(event)
}
async fn expect_failed_undo(codex: &Arc<CodexConversation>) -> Result<UndoCompletedEvent> {
async fn expect_failed_undo(codex: &Arc<CodexThread>) -> Result<UndoCompletedEvent> {
let event = invoke_undo(codex).await?;
assert!(
!event.success,
+8 -14
View File
@@ -1,6 +1,6 @@
use anyhow::Context;
use codex_core::ConversationManager;
use codex_core::NewConversation;
use codex_core::NewThread;
use codex_core::ThreadManager;
use codex_core::protocol::EventMsg;
use codex_core::protocol::ExecCommandEndEvent;
use codex_core::protocol::ExecCommandSource;
@@ -42,15 +42,12 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() {
let mut config = load_default_config_for_test(&codex_home).await;
config.cwd = cwd.path().to_path_buf();
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
codex_core::CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let NewConversation {
conversation: codex,
..
} = conversation_manager
.new_conversation(config)
let NewThread { thread: codex, .. } = thread_manager
.start_thread(config)
.await
.expect("create new conversation");
@@ -101,15 +98,12 @@ async fn user_shell_cmd_can_be_interrupted() {
// Set up isolated config and conversation.
let codex_home = TempDir::new().unwrap();
let config = load_default_config_for_test(&codex_home).await;
let conversation_manager = ConversationManager::with_models_provider(
let thread_manager = ThreadManager::with_models_provider(
codex_core::CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
);
let NewConversation {
conversation: codex,
..
} = conversation_manager
.new_conversation(config)
let NewThread { thread: codex, .. } = thread_manager
.start_thread(config)
.await
.expect("create new conversation");