use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use crate::protocol::ExecParams; use crate::protocol::ExecResponse; use crate::protocol::InitializeResponse; use crate::protocol::ReadParams; use crate::protocol::ReadResponse; use crate::protocol::TerminateParams; use crate::protocol::TerminateResponse; use crate::protocol::WriteParams; use crate::protocol::WriteResponse; use crate::rpc::RpcNotificationSender; use crate::rpc::invalid_request; use crate::server::filesystem::ExecServerFileSystem; use crate::server::process_handler::ProcessHandler; use codex_app_server_protocol::FsCopyParams; use codex_app_server_protocol::FsCopyResponse; use codex_app_server_protocol::FsCreateDirectoryParams; use codex_app_server_protocol::FsCreateDirectoryResponse; use codex_app_server_protocol::FsGetMetadataParams; use codex_app_server_protocol::FsGetMetadataResponse; use codex_app_server_protocol::FsReadDirectoryParams; use codex_app_server_protocol::FsReadDirectoryResponse; use codex_app_server_protocol::FsReadFileParams; use codex_app_server_protocol::FsReadFileResponse; use codex_app_server_protocol::FsRemoveParams; use codex_app_server_protocol::FsRemoveResponse; use codex_app_server_protocol::FsWriteFileParams; use codex_app_server_protocol::FsWriteFileResponse; use codex_app_server_protocol::JSONRPCErrorError; pub(crate) struct ExecServerHandler { file_system: ExecServerFileSystem, process_handler: ProcessHandler, initialize_requested: AtomicBool, initialized: AtomicBool, } impl ExecServerHandler { pub(crate) fn new(notifications: RpcNotificationSender) -> Self { Self { file_system: ExecServerFileSystem::default(), process_handler: ProcessHandler::new(notifications), initialize_requested: AtomicBool::new(false), initialized: AtomicBool::new(false), } } pub(crate) async fn shutdown(&self) { self.process_handler.shutdown().await; } pub(crate) fn initialize(&self) -> Result { if self.initialize_requested.swap(true, Ordering::SeqCst) { return Err(invalid_request( "initialize may only be sent once per connection".to_string(), )); } Ok(InitializeResponse {}) } pub(crate) fn initialized(&self) -> Result<(), String> { if !self.initialize_requested.load(Ordering::SeqCst) { return Err("received `initialized` notification before `initialize`".into()); } self.initialized.store(true, Ordering::SeqCst); Ok(()) } fn require_initialized_for(&self, method_family: &str) -> Result<(), JSONRPCErrorError> { if !self.initialize_requested.load(Ordering::SeqCst) { return Err(invalid_request(format!( "client must call initialize before using {method_family} methods" ))); } if !self.initialized.load(Ordering::SeqCst) { return Err(invalid_request(format!( "client must send initialized before using {method_family} methods" ))); } Ok(()) } pub(crate) async fn exec(&self, params: ExecParams) -> Result { self.require_initialized_for("exec")?; self.process_handler.exec(params).await } pub(crate) async fn exec_read( &self, params: ReadParams, ) -> Result { self.require_initialized_for("exec")?; self.process_handler.exec_read(params).await } pub(crate) async fn exec_write( &self, params: WriteParams, ) -> Result { self.require_initialized_for("exec")?; self.process_handler.exec_write(params).await } pub(crate) async fn terminate( &self, params: TerminateParams, ) -> Result { self.require_initialized_for("exec")?; self.process_handler.terminate(params).await } pub(crate) async fn fs_read_file( &self, params: FsReadFileParams, ) -> Result { self.require_initialized_for("filesystem")?; self.file_system.read_file(params).await } pub(crate) async fn fs_write_file( &self, params: FsWriteFileParams, ) -> Result { self.require_initialized_for("filesystem")?; self.file_system.write_file(params).await } pub(crate) async fn fs_create_directory( &self, params: FsCreateDirectoryParams, ) -> Result { self.require_initialized_for("filesystem")?; self.file_system.create_directory(params).await } pub(crate) async fn fs_get_metadata( &self, params: FsGetMetadataParams, ) -> Result { self.require_initialized_for("filesystem")?; self.file_system.get_metadata(params).await } pub(crate) async fn fs_read_directory( &self, params: FsReadDirectoryParams, ) -> Result { self.require_initialized_for("filesystem")?; self.file_system.read_directory(params).await } pub(crate) async fn fs_remove( &self, params: FsRemoveParams, ) -> Result { self.require_initialized_for("filesystem")?; self.file_system.remove(params).await } pub(crate) async fn fs_copy( &self, params: FsCopyParams, ) -> Result { self.require_initialized_for("filesystem")?; self.file_system.copy(params).await } } #[cfg(test)] mod tests;