use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use base64::Engine; use base64::engine::general_purpose::STANDARD; 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::FsReadDirectoryEntry; 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; use codex_exec_server::CopyOptions; use codex_exec_server::CreateDirectoryOptions; use codex_exec_server::Environment; use codex_exec_server::ExecutorFileSystem; use codex_exec_server::RemoveOptions; use std::io; use std::sync::Arc; #[derive(Clone)] pub(crate) struct FsApi { file_system: Arc, } impl Default for FsApi { fn default() -> Self { Self { file_system: Environment::default().get_filesystem(), } } } impl FsApi { pub(crate) async fn read_file( &self, params: FsReadFileParams, ) -> Result { let bytes = self .file_system .read_file(¶ms.path) .await .map_err(map_fs_error)?; Ok(FsReadFileResponse { data_base64: STANDARD.encode(bytes), }) } pub(crate) async fn write_file( &self, params: FsWriteFileParams, ) -> Result { let bytes = STANDARD.decode(params.data_base64).map_err(|err| { invalid_request(format!( "fs/writeFile requires valid base64 dataBase64: {err}" )) })?; self.file_system .write_file(¶ms.path, bytes) .await .map_err(map_fs_error)?; Ok(FsWriteFileResponse {}) } pub(crate) async fn create_directory( &self, params: FsCreateDirectoryParams, ) -> Result { self.file_system .create_directory( ¶ms.path, CreateDirectoryOptions { recursive: params.recursive.unwrap_or(true), }, ) .await .map_err(map_fs_error)?; Ok(FsCreateDirectoryResponse {}) } pub(crate) async fn get_metadata( &self, params: FsGetMetadataParams, ) -> Result { let metadata = self .file_system .get_metadata(¶ms.path) .await .map_err(map_fs_error)?; Ok(FsGetMetadataResponse { is_directory: metadata.is_directory, is_file: metadata.is_file, created_at_ms: metadata.created_at_ms, modified_at_ms: metadata.modified_at_ms, }) } pub(crate) async fn read_directory( &self, params: FsReadDirectoryParams, ) -> Result { let entries = self .file_system .read_directory(¶ms.path) .await .map_err(map_fs_error)?; Ok(FsReadDirectoryResponse { entries: entries .into_iter() .map(|entry| FsReadDirectoryEntry { file_name: entry.file_name, is_directory: entry.is_directory, is_file: entry.is_file, }) .collect(), }) } pub(crate) async fn remove( &self, params: FsRemoveParams, ) -> Result { self.file_system .remove( ¶ms.path, RemoveOptions { recursive: params.recursive.unwrap_or(true), force: params.force.unwrap_or(true), }, ) .await .map_err(map_fs_error)?; Ok(FsRemoveResponse {}) } pub(crate) async fn copy( &self, params: FsCopyParams, ) -> Result { self.file_system .copy( ¶ms.source_path, ¶ms.destination_path, CopyOptions { recursive: params.recursive, }, ) .await .map_err(map_fs_error)?; Ok(FsCopyResponse {}) } } fn invalid_request(message: impl Into) -> JSONRPCErrorError { JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: message.into(), data: None, } } fn map_fs_error(err: io::Error) -> JSONRPCErrorError { if err.kind() == io::ErrorKind::InvalidInput { invalid_request(err.to_string()) } else { JSONRPCErrorError { code: INTERNAL_ERROR_CODE, message: err.to_string(), data: None, } } }