diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 049340a28..699ad5f52 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -1274,11 +1274,32 @@ pub struct ThreadListParams { /// Optional provider filter; when set, only sessions recorded under these /// providers are returned. When present but empty, includes all providers. pub model_providers: Option>, + /// Optional source filter; when set, only sessions from these source kinds + /// are returned. When omitted or empty, defaults to interactive sources. + pub source_kinds: Option>, /// Optional archived filter; when set to true, only archived threads are returned. /// If false or null, only non-archived threads are returned. pub archived: Option, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(rename_all = "camelCase", export_to = "v2/")] +pub enum ThreadSourceKind { + Cli, + #[serde(rename = "vscode")] + #[ts(rename = "vscode")] + VsCode, + Exec, + AppServer, + SubAgent, + SubAgentReview, + SubAgentCompact, + SubAgentThreadSpawn, + SubAgentOther, + Unknown, +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 38c3ecee5..adf8ef0b4 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -167,6 +167,7 @@ To branch from a stored session, call `thread/fork` with the `thread.id`. This c - `limit` — server defaults to a reasonable page size if unset. - `sortKey` — `created_at` (default) or `updated_at`. - `modelProviders` — restrict results to specific providers; unset, null, or an empty array will include all providers. +- `sourceKinds` — restrict results to specific sources; omit or pass `[]` for interactive sessions only (`cli`, `vscode`). - `archived` — when `true`, list archived threads only. When `false` or `null`, list non-archived threads (default). Example: diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 643d7b0c7..42a90d2e9 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -111,6 +111,7 @@ use codex_app_server_protocol::ThreadResumeParams; use codex_app_server_protocol::ThreadResumeResponse; use codex_app_server_protocol::ThreadRollbackParams; use codex_app_server_protocol::ThreadSortKey; +use codex_app_server_protocol::ThreadSourceKind; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; use codex_app_server_protocol::ThreadStartedNotification; @@ -132,7 +133,6 @@ use codex_chatgpt::connectors; use codex_core::AuthManager; use codex_core::CodexThread; use codex_core::Cursor as RolloutCursor; -use codex_core::INTERACTIVE_SESSION_SOURCES; use codex_core::InitialHistory; use codex_core::NewThread; use codex_core::RolloutRecorder; @@ -209,6 +209,9 @@ use tracing::info; use tracing::warn; use uuid::Uuid; +use crate::filters::compute_source_filters; +use crate::filters::source_kind_matches; + type PendingInterruptQueue = Vec<(RequestId, ApiVersion)>; pub(crate) type PendingInterrupts = Arc>>; @@ -1845,6 +1848,7 @@ impl CodexMessageProcessor { limit, sort_key, model_providers, + source_kinds, archived, } = params; @@ -1861,6 +1865,7 @@ impl CodexMessageProcessor { requested_page_size, cursor, model_providers, + source_kinds, core_sort_key, archived.unwrap_or(false), ) @@ -2538,6 +2543,7 @@ impl CodexMessageProcessor { requested_page_size, cursor, model_providers, + None, CoreThreadSortKey::UpdatedAt, false, ) @@ -2558,6 +2564,7 @@ impl CodexMessageProcessor { requested_page_size: usize, cursor: Option, model_providers: Option>, + source_kinds: Option>, sort_key: CoreThreadSortKey, archived: bool, ) -> Result<(Vec, Option), JSONRPCErrorError> { @@ -2587,6 +2594,8 @@ impl CodexMessageProcessor { None => Some(vec![self.config.model_provider_id.clone()]), }; let fallback_provider = self.config.model_provider_id.clone(); + let (allowed_sources_vec, source_kind_filter) = compute_source_filters(source_kinds); + let allowed_sources = allowed_sources_vec.as_slice(); while remaining > 0 { let page_size = remaining.min(THREAD_LIST_MAX_LIMIT); @@ -2596,7 +2605,7 @@ impl CodexMessageProcessor { page_size, cursor_obj.as_ref(), sort_key, - INTERACTIVE_SESSION_SOURCES, + allowed_sources, model_provider_filter.as_deref(), fallback_provider.as_str(), ) @@ -2612,7 +2621,7 @@ impl CodexMessageProcessor { page_size, cursor_obj.as_ref(), sort_key, - INTERACTIVE_SESSION_SOURCES, + allowed_sources, model_provider_filter.as_deref(), fallback_provider.as_str(), ) @@ -2641,6 +2650,11 @@ impl CodexMessageProcessor { updated_at, ) }) + .filter(|summary| { + source_kind_filter + .as_ref() + .is_none_or(|filter| source_kind_matches(&summary.source, filter)) + }) .collect::>(); if filtered.len() > remaining { filtered.truncate(remaining); diff --git a/codex-rs/app-server/src/filters.rs b/codex-rs/app-server/src/filters.rs new file mode 100644 index 000000000..bd784c3dc --- /dev/null +++ b/codex-rs/app-server/src/filters.rs @@ -0,0 +1,155 @@ +use codex_app_server_protocol::ThreadSourceKind; +use codex_core::INTERACTIVE_SESSION_SOURCES; +use codex_protocol::protocol::SessionSource as CoreSessionSource; +use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource; + +pub(crate) fn compute_source_filters( + source_kinds: Option>, +) -> (Vec, Option>) { + let Some(source_kinds) = source_kinds else { + return (INTERACTIVE_SESSION_SOURCES.to_vec(), None); + }; + + if source_kinds.is_empty() { + return (INTERACTIVE_SESSION_SOURCES.to_vec(), None); + } + + let requires_post_filter = source_kinds.iter().any(|kind| { + matches!( + kind, + ThreadSourceKind::Exec + | ThreadSourceKind::AppServer + | ThreadSourceKind::SubAgent + | ThreadSourceKind::SubAgentReview + | ThreadSourceKind::SubAgentCompact + | ThreadSourceKind::SubAgentThreadSpawn + | ThreadSourceKind::SubAgentOther + | ThreadSourceKind::Unknown + ) + }); + + if requires_post_filter { + (Vec::new(), Some(source_kinds)) + } else { + let interactive_sources = source_kinds + .iter() + .filter_map(|kind| match kind { + ThreadSourceKind::Cli => Some(CoreSessionSource::Cli), + ThreadSourceKind::VsCode => Some(CoreSessionSource::VSCode), + ThreadSourceKind::Exec + | ThreadSourceKind::AppServer + | ThreadSourceKind::SubAgent + | ThreadSourceKind::SubAgentReview + | ThreadSourceKind::SubAgentCompact + | ThreadSourceKind::SubAgentThreadSpawn + | ThreadSourceKind::SubAgentOther + | ThreadSourceKind::Unknown => None, + }) + .collect::>(); + (interactive_sources, Some(source_kinds)) + } +} + +pub(crate) fn source_kind_matches(source: &CoreSessionSource, filter: &[ThreadSourceKind]) -> bool { + filter.iter().any(|kind| match kind { + ThreadSourceKind::Cli => matches!(source, CoreSessionSource::Cli), + ThreadSourceKind::VsCode => matches!(source, CoreSessionSource::VSCode), + ThreadSourceKind::Exec => matches!(source, CoreSessionSource::Exec), + ThreadSourceKind::AppServer => matches!(source, CoreSessionSource::Mcp), + ThreadSourceKind::SubAgent => matches!(source, CoreSessionSource::SubAgent(_)), + ThreadSourceKind::SubAgentReview => { + matches!( + source, + CoreSessionSource::SubAgent(CoreSubAgentSource::Review) + ) + } + ThreadSourceKind::SubAgentCompact => { + matches!( + source, + CoreSessionSource::SubAgent(CoreSubAgentSource::Compact) + ) + } + ThreadSourceKind::SubAgentThreadSpawn => matches!( + source, + CoreSessionSource::SubAgent(CoreSubAgentSource::ThreadSpawn { .. }) + ), + ThreadSourceKind::SubAgentOther => matches!( + source, + CoreSessionSource::SubAgent(CoreSubAgentSource::Other(_)) + ), + ThreadSourceKind::Unknown => matches!(source, CoreSessionSource::Unknown), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use codex_protocol::ThreadId; + use pretty_assertions::assert_eq; + use uuid::Uuid; + + #[test] + fn compute_source_filters_defaults_to_interactive_sources() { + let (allowed_sources, filter) = compute_source_filters(None); + + assert_eq!(allowed_sources, INTERACTIVE_SESSION_SOURCES.to_vec()); + assert_eq!(filter, None); + } + + #[test] + fn compute_source_filters_empty_means_interactive_sources() { + let (allowed_sources, filter) = compute_source_filters(Some(Vec::new())); + + assert_eq!(allowed_sources, INTERACTIVE_SESSION_SOURCES.to_vec()); + assert_eq!(filter, None); + } + + #[test] + fn compute_source_filters_interactive_only_skips_post_filtering() { + let source_kinds = vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]; + let (allowed_sources, filter) = compute_source_filters(Some(source_kinds.clone())); + + assert_eq!( + allowed_sources, + vec![CoreSessionSource::Cli, CoreSessionSource::VSCode] + ); + assert_eq!(filter, Some(source_kinds)); + } + + #[test] + fn compute_source_filters_subagent_variant_requires_post_filtering() { + let source_kinds = vec![ThreadSourceKind::SubAgentReview]; + let (allowed_sources, filter) = compute_source_filters(Some(source_kinds.clone())); + + assert_eq!(allowed_sources, Vec::new()); + assert_eq!(filter, Some(source_kinds)); + } + + #[test] + fn source_kind_matches_distinguishes_subagent_variants() { + let parent_thread_id = + ThreadId::from_string(&Uuid::new_v4().to_string()).expect("valid thread id"); + let review = CoreSessionSource::SubAgent(CoreSubAgentSource::Review); + let spawn = CoreSessionSource::SubAgent(CoreSubAgentSource::ThreadSpawn { + parent_thread_id, + depth: 1, + }); + + assert!(source_kind_matches( + &review, + &[ThreadSourceKind::SubAgentReview] + )); + assert!(!source_kind_matches( + &review, + &[ThreadSourceKind::SubAgentThreadSpawn] + )); + assert!(source_kind_matches( + &spawn, + &[ThreadSourceKind::SubAgentThreadSpawn] + )); + assert!(!source_kind_matches( + &spawn, + &[ThreadSourceKind::SubAgentReview] + )); + } +} diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index b92e18266..ca588e59d 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -42,6 +42,7 @@ mod codex_message_processor; mod config_api; mod dynamic_tools; mod error_code; +mod filters; mod fuzzy_file_search; mod message_processor; mod models; diff --git a/codex-rs/app-server/tests/common/lib.rs b/codex-rs/app-server/tests/common/lib.rs index 809312140..12bd4049b 100644 --- a/codex-rs/app-server/tests/common/lib.rs +++ b/codex-rs/app-server/tests/common/lib.rs @@ -30,6 +30,7 @@ pub use responses::create_final_assistant_message_sse_response; pub use responses::create_request_user_input_sse_response; pub use responses::create_shell_command_sse_response; pub use rollout::create_fake_rollout; +pub use rollout::create_fake_rollout_with_source; pub use rollout::create_fake_rollout_with_text_elements; pub use rollout::rollout_path; use serde::de::DeserializeOwned; diff --git a/codex-rs/app-server/tests/common/rollout.rs b/codex-rs/app-server/tests/common/rollout.rs index e2e55a654..26316eddb 100644 --- a/codex-rs/app-server/tests/common/rollout.rs +++ b/codex-rs/app-server/tests/common/rollout.rs @@ -38,6 +38,27 @@ pub fn create_fake_rollout( preview: &str, model_provider: Option<&str>, git_info: Option, +) -> Result { + create_fake_rollout_with_source( + codex_home, + filename_ts, + meta_rfc3339, + preview, + model_provider, + git_info, + SessionSource::Cli, + ) +} + +/// Create a minimal rollout file with an explicit session source. +pub fn create_fake_rollout_with_source( + codex_home: &Path, + filename_ts: &str, + meta_rfc3339: &str, + preview: &str, + model_provider: Option<&str>, + git_info: Option, + source: SessionSource, ) -> Result { let uuid = Uuid::new_v4(); let uuid_str = uuid.to_string(); @@ -57,7 +78,7 @@ pub fn create_fake_rollout( cwd: PathBuf::from("/"), originator: "codex".to_string(), cli_version: "0.0.0".to_string(), - source: SessionSource::Cli, + source, model_provider: model_provider.map(str::to_string), base_instructions: None, }; diff --git a/codex-rs/app-server/tests/suite/v2/thread_list.rs b/codex-rs/app-server/tests/suite/v2/thread_list.rs index 8eb253df6..f310b6c56 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_list.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_list.rs @@ -1,6 +1,7 @@ use anyhow::Result; use app_test_support::McpProcess; use app_test_support::create_fake_rollout; +use app_test_support::create_fake_rollout_with_source; use app_test_support::rollout_path; use app_test_support::to_response; use chrono::DateTime; @@ -12,8 +13,12 @@ use codex_app_server_protocol::RequestId; use codex_app_server_protocol::SessionSource; use codex_app_server_protocol::ThreadListResponse; use codex_app_server_protocol::ThreadSortKey; +use codex_app_server_protocol::ThreadSourceKind; use codex_core::ARCHIVED_SESSIONS_SUBDIR; +use codex_protocol::ThreadId; use codex_protocol::protocol::GitInfo as CoreGitInfo; +use codex_protocol::protocol::SessionSource as CoreSessionSource; +use codex_protocol::protocol::SubAgentSource; use pretty_assertions::assert_eq; use std::cmp::Reverse; use std::fs; @@ -38,9 +43,10 @@ async fn list_threads( cursor: Option, limit: Option, providers: Option>, + source_kinds: Option>, archived: Option, ) -> Result { - list_threads_with_sort(mcp, cursor, limit, providers, None, archived).await + list_threads_with_sort(mcp, cursor, limit, providers, source_kinds, None, archived).await } async fn list_threads_with_sort( @@ -48,6 +54,7 @@ async fn list_threads_with_sort( cursor: Option, limit: Option, providers: Option>, + source_kinds: Option>, sort_key: Option, archived: Option, ) -> Result { @@ -57,6 +64,7 @@ async fn list_threads_with_sort( limit, sort_key, model_providers: providers, + source_kinds, archived, }) .await?; @@ -131,6 +139,7 @@ async fn thread_list_basic_empty() -> Result<()> { Some(10), Some(vec!["mock_provider".to_string()]), None, + None, ) .await?; assert!(data.is_empty()); @@ -194,6 +203,7 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> { Some(2), Some(vec!["mock_provider".to_string()]), None, + None, ) .await?; assert_eq!(data1.len(), 2); @@ -219,6 +229,7 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> { Some(2), Some(vec!["mock_provider".to_string()]), None, + None, ) .await?; assert!(data2.len() <= 2); @@ -269,6 +280,7 @@ async fn thread_list_respects_provider_filter() -> Result<()> { Some(10), Some(vec!["other_provider".to_string()]), None, + None, ) .await?; assert_eq!(data.len(), 1); @@ -287,6 +299,207 @@ async fn thread_list_respects_provider_filter() -> Result<()> { Ok(()) } +#[tokio::test] +async fn thread_list_empty_source_kinds_defaults_to_interactive_only() -> Result<()> { + let codex_home = TempDir::new()?; + create_minimal_config(codex_home.path())?; + + let cli_id = create_fake_rollout( + codex_home.path(), + "2025-02-01T10-00-00", + "2025-02-01T10:00:00Z", + "CLI", + Some("mock_provider"), + None, + )?; + let exec_id = create_fake_rollout_with_source( + codex_home.path(), + "2025-02-01T11-00-00", + "2025-02-01T11:00:00Z", + "Exec", + Some("mock_provider"), + None, + CoreSessionSource::Exec, + )?; + + let mut mcp = init_mcp(codex_home.path()).await?; + + let ThreadListResponse { data, next_cursor } = list_threads( + &mut mcp, + None, + Some(10), + Some(vec!["mock_provider".to_string()]), + Some(Vec::new()), + None, + ) + .await?; + + assert_eq!(next_cursor, None); + let ids: Vec<_> = data.iter().map(|thread| thread.id.as_str()).collect(); + assert_eq!(ids, vec![cli_id.as_str()]); + assert_ne!(cli_id, exec_id); + assert_eq!(data[0].source, SessionSource::Cli); + + Ok(()) +} + +#[tokio::test] +async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<()> { + let codex_home = TempDir::new()?; + create_minimal_config(codex_home.path())?; + + let cli_id = create_fake_rollout( + codex_home.path(), + "2025-02-01T10-00-00", + "2025-02-01T10:00:00Z", + "CLI", + Some("mock_provider"), + None, + )?; + + let parent_thread_id = ThreadId::from_string(&Uuid::new_v4().to_string())?; + let subagent_id = create_fake_rollout_with_source( + codex_home.path(), + "2025-02-01T11-00-00", + "2025-02-01T11:00:00Z", + "SubAgent", + Some("mock_provider"), + None, + CoreSessionSource::SubAgent(SubAgentSource::ThreadSpawn { + parent_thread_id, + depth: 1, + }), + )?; + + let mut mcp = init_mcp(codex_home.path()).await?; + + let ThreadListResponse { data, next_cursor } = list_threads( + &mut mcp, + None, + Some(10), + Some(vec!["mock_provider".to_string()]), + Some(vec![ThreadSourceKind::SubAgentThreadSpawn]), + None, + ) + .await?; + + assert_eq!(next_cursor, None); + let ids: Vec<_> = data.iter().map(|thread| thread.id.as_str()).collect(); + assert_eq!(ids, vec![subagent_id.as_str()]); + assert_ne!(cli_id, subagent_id); + assert!(matches!(data[0].source, SessionSource::SubAgent(_))); + + Ok(()) +} + +#[tokio::test] +async fn thread_list_filters_by_subagent_variant() -> Result<()> { + let codex_home = TempDir::new()?; + create_minimal_config(codex_home.path())?; + + let parent_thread_id = ThreadId::from_string(&Uuid::new_v4().to_string())?; + + let review_id = create_fake_rollout_with_source( + codex_home.path(), + "2025-02-02T09-00-00", + "2025-02-02T09:00:00Z", + "Review", + Some("mock_provider"), + None, + CoreSessionSource::SubAgent(SubAgentSource::Review), + )?; + let compact_id = create_fake_rollout_with_source( + codex_home.path(), + "2025-02-02T10-00-00", + "2025-02-02T10:00:00Z", + "Compact", + Some("mock_provider"), + None, + CoreSessionSource::SubAgent(SubAgentSource::Compact), + )?; + let spawn_id = create_fake_rollout_with_source( + codex_home.path(), + "2025-02-02T11-00-00", + "2025-02-02T11:00:00Z", + "Spawn", + Some("mock_provider"), + None, + CoreSessionSource::SubAgent(SubAgentSource::ThreadSpawn { + parent_thread_id, + depth: 1, + }), + )?; + let other_id = create_fake_rollout_with_source( + codex_home.path(), + "2025-02-02T12-00-00", + "2025-02-02T12:00:00Z", + "Other", + Some("mock_provider"), + None, + CoreSessionSource::SubAgent(SubAgentSource::Other("custom".to_string())), + )?; + + let mut mcp = init_mcp(codex_home.path()).await?; + + let review = list_threads( + &mut mcp, + None, + Some(10), + Some(vec!["mock_provider".to_string()]), + Some(vec![ThreadSourceKind::SubAgentReview]), + None, + ) + .await?; + let review_ids: Vec<_> = review + .data + .iter() + .map(|thread| thread.id.as_str()) + .collect(); + assert_eq!(review_ids, vec![review_id.as_str()]); + + let compact = list_threads( + &mut mcp, + None, + Some(10), + Some(vec!["mock_provider".to_string()]), + Some(vec![ThreadSourceKind::SubAgentCompact]), + None, + ) + .await?; + let compact_ids: Vec<_> = compact + .data + .iter() + .map(|thread| thread.id.as_str()) + .collect(); + assert_eq!(compact_ids, vec![compact_id.as_str()]); + + let spawn = list_threads( + &mut mcp, + None, + Some(10), + Some(vec!["mock_provider".to_string()]), + Some(vec![ThreadSourceKind::SubAgentThreadSpawn]), + None, + ) + .await?; + let spawn_ids: Vec<_> = spawn.data.iter().map(|thread| thread.id.as_str()).collect(); + assert_eq!(spawn_ids, vec![spawn_id.as_str()]); + + let other = list_threads( + &mut mcp, + None, + Some(10), + Some(vec!["mock_provider".to_string()]), + Some(vec![ThreadSourceKind::SubAgentOther]), + None, + ) + .await?; + let other_ids: Vec<_> = other.data.iter().map(|thread| thread.id.as_str()).collect(); + assert_eq!(other_ids, vec![other_id.as_str()]); + + Ok(()) +} + #[tokio::test] async fn thread_list_fetches_until_limit_or_exhausted() -> Result<()> { let codex_home = TempDir::new()?; @@ -319,6 +532,7 @@ async fn thread_list_fetches_until_limit_or_exhausted() -> Result<()> { Some(8), Some(vec!["target_provider".to_string()]), None, + None, ) .await?; assert_eq!( @@ -364,6 +578,7 @@ async fn thread_list_enforces_max_limit() -> Result<()> { Some(200), Some(vec!["mock_provider".to_string()]), None, + None, ) .await?; assert_eq!( @@ -410,6 +625,7 @@ async fn thread_list_stops_when_not_enough_filtered_results_exist() -> Result<() Some(10), Some(vec!["target_provider".to_string()]), None, + None, ) .await?; assert_eq!( @@ -457,6 +673,7 @@ async fn thread_list_includes_git_info() -> Result<()> { Some(10), Some(vec!["mock_provider".to_string()]), None, + None, ) .await?; let thread = data @@ -516,6 +733,7 @@ async fn thread_list_default_sorts_by_created_at() -> Result<()> { Some(vec!["mock_provider".to_string()]), None, None, + None, ) .await?; @@ -575,6 +793,7 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> { None, Some(10), Some(vec!["mock_provider".to_string()]), + None, Some(ThreadSortKey::UpdatedAt), None, ) @@ -639,6 +858,7 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> { None, Some(2), Some(vec!["mock_provider".to_string()]), + None, Some(ThreadSortKey::UpdatedAt), None, ) @@ -655,6 +875,7 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> { Some(cursor1), Some(2), Some(vec!["mock_provider".to_string()]), + None, Some(ThreadSortKey::UpdatedAt), None, ) @@ -696,6 +917,7 @@ async fn thread_list_created_at_tie_breaks_by_uuid() -> Result<()> { Some(10), Some(vec!["mock_provider".to_string()]), None, + None, ) .await?; @@ -747,6 +969,7 @@ async fn thread_list_updated_at_tie_breaks_by_uuid() -> Result<()> { None, Some(10), Some(vec!["mock_provider".to_string()]), + None, Some(ThreadSortKey::UpdatedAt), None, ) @@ -787,6 +1010,7 @@ async fn thread_list_updated_at_uses_mtime() -> Result<()> { None, Some(10), Some(vec!["mock_provider".to_string()]), + None, Some(ThreadSortKey::UpdatedAt), None, ) @@ -846,6 +1070,7 @@ async fn thread_list_archived_filter() -> Result<()> { Some(10), Some(vec!["mock_provider".to_string()]), None, + None, ) .await?; assert_eq!(data.len(), 1); @@ -856,6 +1081,7 @@ async fn thread_list_archived_filter() -> Result<()> { None, Some(10), Some(vec!["mock_provider".to_string()]), + None, Some(true), ) .await?; @@ -878,6 +1104,7 @@ async fn thread_list_invalid_cursor_returns_error() -> Result<()> { limit: Some(2), sort_key: None, model_providers: Some(vec!["mock_provider".to_string()]), + source_kinds: None, archived: None, }) .await?; diff --git a/codex-rs/debug-client/src/client.rs b/codex-rs/debug-client/src/client.rs index c0a2746ee..b5bac6fb5 100644 --- a/codex-rs/debug-client/src/client.rs +++ b/codex-rs/debug-client/src/client.rs @@ -173,6 +173,7 @@ impl AppServerClient { limit: None, sort_key: None, model_providers: None, + source_kinds: None, archived: None, }, };