diff --git a/codex-rs/app-server/src/identity_key.rs b/codex-rs/app-server/src/identity_key.rs new file mode 100644 index 0000000000..120752c183 --- /dev/null +++ b/codex-rs/app-server/src/identity_key.rs @@ -0,0 +1,61 @@ +use std::ffi::OsString; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IdentityKey { + bytes: Vec, +} + +impl IdentityKey { + pub fn from_bytes(bytes: impl Into>) -> Self { + Self { + bytes: bytes.into(), + } + } + + pub fn from_os_string(value: OsString) -> Self { + Self::from_bytes(os_string_to_bytes(value)) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } + + pub fn into_bytes(self) -> Vec { + self.bytes + } +} + +#[cfg(unix)] +fn os_string_to_bytes(value: OsString) -> Vec { + use std::os::unix::ffi::OsStrExt; + + value.as_os_str().as_bytes().to_vec() +} + +#[cfg(not(unix))] +fn os_string_to_bytes(value: OsString) -> Vec { + value.to_string_lossy().into_owned().into_bytes() +} + +#[cfg(test)] +mod tests { + use super::IdentityKey; + use pretty_assertions::assert_eq; + + #[test] + fn identity_key_preserves_opaque_bytes() { + let key = IdentityKey::from_bytes(b"tenant-key-\x00\xff".to_vec()); + assert_eq!(key.as_bytes(), &b"tenant-key-\x00\xff"[..]); + } + + #[cfg(unix)] + #[test] + fn identity_key_preserves_unix_argv_bytes() { + use std::ffi::OsString; + use std::os::unix::ffi::OsStringExt; + + let key = IdentityKey::from_os_string(OsString::from_vec(b"tenant-key-\xff".to_vec())); + + assert_eq!(key.as_bytes(), &b"tenant-key-\xff"[..]); + } +} diff --git a/codex-rs/app-server/src/in_process.rs b/codex-rs/app-server/src/in_process.rs index 9b013d87e5..0963e91641 100644 --- a/codex-rs/app-server/src/in_process.rs +++ b/codex-rs/app-server/src/in_process.rs @@ -50,8 +50,8 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::time::Duration; -use crate::config_manager::ConfigManager; use crate::IdentityKey; +use crate::config_manager::ConfigManager; use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use crate::error_code::OVERLOADED_ERROR_CODE; diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 3fffe398db..93bab0aa6c 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -13,7 +13,6 @@ use codex_login::AuthManager; use codex_utils_cli::CliConfigOverrides; use std::collections::HashMap; use std::collections::HashSet; -use std::ffi::OsString; use std::io::ErrorKind; use std::io::Result as IoResult; use std::sync::Arc; @@ -84,6 +83,7 @@ mod filters; mod fs_api; mod fs_watch; mod fuzzy_file_search; +mod identity_key; pub mod in_process; mod message_processor; mod models; @@ -95,6 +95,7 @@ mod transport; pub use crate::error_code::INPUT_TOO_LARGE_ERROR_CODE; pub use crate::error_code::INVALID_PARAMS_ERROR_CODE; +pub use crate::identity_key::IdentityKey; pub use crate::transport::AppServerTransport; pub use crate::transport::app_server_control_socket_path; pub use crate::transport::auth::AppServerWebsocketAuthArgs; @@ -103,43 +104,6 @@ pub use crate::transport::auth::WebsocketAuthCliMode; const LOG_FORMAT_ENV_VAR: &str = "LOG_FORMAT"; -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct IdentityKey { - bytes: Vec, -} - -impl IdentityKey { - pub fn from_bytes(bytes: impl Into>) -> Self { - Self { - bytes: bytes.into(), - } - } - - pub fn from_os_string(value: OsString) -> Self { - Self::from_bytes(os_string_to_bytes(value)) - } - - pub fn as_bytes(&self) -> &[u8] { - &self.bytes - } - - pub fn into_bytes(self) -> Vec { - self.bytes - } -} - -#[cfg(unix)] -fn os_string_to_bytes(value: OsString) -> Vec { - use std::os::unix::ffi::OsStrExt; - - value.as_os_str().as_bytes().to_vec() -} - -#[cfg(not(unix))] -fn os_string_to_bytes(value: OsString) -> Vec { - value.to_string_lossy().into_owned().into_bytes() -} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum LogFormat { Default, @@ -1013,27 +977,9 @@ fn analytics_rpc_transport(transport: &AppServerTransport) -> AppServerRpcTransp #[cfg(test)] mod tests { - use super::IdentityKey; use super::LogFormat; use pretty_assertions::assert_eq; - #[test] - fn identity_key_preserves_opaque_bytes() { - let key = IdentityKey::from_bytes(b"tenant-key-\x00\xff".to_vec()); - assert_eq!(key.as_bytes(), &b"tenant-key-\x00\xff"[..]); - } - - #[cfg(unix)] - #[test] - fn identity_key_preserves_unix_argv_bytes() { - use std::ffi::OsString; - use std::os::unix::ffi::OsStringExt; - - let key = IdentityKey::from_os_string(OsString::from_vec(b"tenant-key-\xff".to_vec())); - - assert_eq!(key.as_bytes(), &b"tenant-key-\xff"[..]); - } - #[test] fn log_format_from_env_value_matches_json_values_case_insensitively() { assert_eq!(LogFormat::from_env_value(Some("json")), LogFormat::Json); diff --git a/codex-rs/app-server/tests/suite/v2/mcp_resource.rs b/codex-rs/app-server/tests/suite/v2/mcp_resource.rs index 3b1a495576..acc01acc43 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_resource.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_resource.rs @@ -216,6 +216,7 @@ async fn mcp_resource_read_returns_error_for_unknown_thread() -> Result<()> { }, capabilities: None, }, + identity_key: None, channel_capacity: in_process::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY, }) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs b/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs index 27160bd787..248e8a4e34 100644 --- a/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs +++ b/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs @@ -90,6 +90,7 @@ async fn thread_start_with_non_local_thread_store_does_not_create_local_persiste }, capabilities: None, }, + identity_key: None, channel_capacity: in_process::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY, }) .await?; diff --git a/codex-rs/thread-store/src/remote/list_threads.rs b/codex-rs/thread-store/src/remote/list_threads.rs index e5498e4123..c879864085 100644 --- a/codex-rs/thread-store/src/remote/list_threads.rs +++ b/codex-rs/thread-store/src/remote/list_threads.rs @@ -272,10 +272,13 @@ mod tests { page_size: 2, cursor: Some("cursor-1".to_string()), sort_key: ThreadSortKey::UpdatedAt, + sort_direction: crate::SortDirection::Desc, allowed_sources: vec![SessionSource::Cli], model_providers: Some(vec!["openai".to_string()]), + cwd_filters: Some(vec![PathBuf::from("/workspace")]), archived: true, search_term: Some("needle".to_string()), + use_state_db_only: true, }) .await .expect("list threads");