app-server: add v2 filesystem APIs (#14245)

Add a protocol-level filesystem surface to the v2 app-server so Codex
clients can read and write files, inspect directories, and subscribe to
path changes without relying on host-specific helpers.

High-level changes:
- define the new v2 fs/readFile, fs/writeFile, fs/createDirectory,
fs/getMetadata, fs/readDirectory, fs/remove, fs/copy RPCs
- implement the app-server handlers, including absolute-path validation,
base64 file payloads, recursive copy/remove semantics
- document the API, regenerate protocol schemas/types, and add
end-to-end tests for filesystem operations, copy edge cases

Testing plan:
- validate protocol serialization and generated schema output for the
new fs request, response, and notification types
- run app-server integration coverage for file and directory CRUD paths,
metadata/readDirectory responses, copy failure modes, and absolute-path
validation
This commit is contained in:
Ruslan Nigmatullin
2026-03-13 14:42:20 -07:00
committed by GitHub
parent 36dfb84427
commit f8f82bfc2b
46 changed files with 3391 additions and 8 deletions

View File

@@ -312,6 +312,34 @@ client_request_definitions! {
params: v2::AppsListParams,
response: v2::AppsListResponse,
},
FsReadFile => "fs/readFile" {
params: v2::FsReadFileParams,
response: v2::FsReadFileResponse,
},
FsWriteFile => "fs/writeFile" {
params: v2::FsWriteFileParams,
response: v2::FsWriteFileResponse,
},
FsCreateDirectory => "fs/createDirectory" {
params: v2::FsCreateDirectoryParams,
response: v2::FsCreateDirectoryResponse,
},
FsGetMetadata => "fs/getMetadata" {
params: v2::FsGetMetadataParams,
response: v2::FsGetMetadataResponse,
},
FsReadDirectory => "fs/readDirectory" {
params: v2::FsReadDirectoryParams,
response: v2::FsReadDirectoryResponse,
},
FsRemove => "fs/remove" {
params: v2::FsRemoveParams,
response: v2::FsRemoveResponse,
},
FsCopy => "fs/copy" {
params: v2::FsCopyParams,
response: v2::FsCopyResponse,
},
SkillsConfigWrite => "skills/config/write" {
params: v2::SkillsConfigWriteParams,
response: v2::SkillsConfigWriteResponse,
@@ -921,8 +949,17 @@ mod tests {
use serde_json::json;
use std::path::PathBuf;
fn absolute_path_string(path: &str) -> String {
let trimmed = path.trim_start_matches('/');
if cfg!(windows) {
format!(r"C:\{}", trimmed.replace('/', "\\"))
} else {
format!("/{trimmed}")
}
}
fn absolute_path(path: &str) -> AbsolutePathBuf {
AbsolutePathBuf::from_absolute_path(path).expect("absolute path")
AbsolutePathBuf::from_absolute_path(absolute_path_string(path)).expect("absolute path")
}
#[test]
@@ -1419,6 +1456,27 @@ mod tests {
Ok(())
}
#[test]
fn serialize_fs_get_metadata() -> Result<()> {
let request = ClientRequest::FsGetMetadata {
request_id: RequestId::Integer(9),
params: v2::FsGetMetadataParams {
path: absolute_path("tmp/example"),
},
};
assert_eq!(
json!({
"method": "fs/getMetadata",
"id": 9,
"params": {
"path": absolute_path_string("tmp/example")
}
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_list_experimental_features() -> Result<()> {
let request = ClientRequest::ExperimentalFeatureList {