Define exec-server protocol types for streamed file writes (#49778)

## What changed

- Add `read` and `replace` modes to `fs/open`, defaulting omitted modes to `read` for existing callers.
- Define `fs/writeBlock` request and response types for offset-based writes, with a maximum decoded chunk size of 1 MiB.
- Add the `fileWriteStreaming` capability, defaulting to `false`. Keep writable streams unsupported in the server and explicitly reject replacement opens.

## Testing

Add a protocol test verifying that legacy `fs/open` requests without a mode deserialize as read-only opens.

GitOrigin-RevId: 4d3997717e821cd1f62df12a03c8eec36332a66d
This commit is contained in:
Adam Perry @ OpenAI
2026-10-01 00:43:55 +00:00
committed by copyberry
parent 60947e2341
commit 875bf9209b
6 changed files with 80 additions and 1 deletions
+4
View File
@@ -87,6 +87,7 @@ pub use codex_file_system::EnvironmentAccessKey;
pub use codex_file_system::ExecutorFileSystem;
pub use codex_file_system::ExecutorFileSystemFuture;
pub use codex_file_system::FILE_READ_CHUNK_SIZE;
pub use codex_file_system::FILE_WRITE_CHUNK_SIZE;
pub use codex_file_system::FileMetadata;
pub use codex_file_system::FileSystemEnvironmentAccessor;
pub use codex_file_system::FileSystemReadStream;
@@ -184,6 +185,7 @@ pub use protocol::FsCreateDirectoryParams;
pub use protocol::FsCreateDirectoryResponse;
pub use protocol::FsGetMetadataParams;
pub use protocol::FsGetMetadataResponse;
pub use protocol::FsOpenMode;
pub use protocol::FsOpenParams;
pub use protocol::FsOpenResponse;
pub use protocol::FsReadBlockParams;
@@ -197,6 +199,8 @@ pub use protocol::FsRemoveParams;
pub use protocol::FsRemoveResponse;
pub use protocol::FsWalkParams;
pub use protocol::FsWalkResponse;
pub use protocol::FsWriteBlockParams;
pub use protocol::FsWriteBlockResponse;
pub use protocol::FsWriteFileParams;
pub use protocol::FsWriteFileResponse;
pub use protocol::HttpHeader;
@@ -11,6 +11,7 @@ use crate::FileSystemResult;
use crate::FileSystemSandboxContext;
use crate::protocol::FS_READ_BLOCK_METHOD;
use crate::protocol::FsCloseParams;
use crate::protocol::FsOpenMode;
use crate::protocol::FsOpenParams;
use crate::protocol::FsReadBlockParams;
@@ -37,6 +38,7 @@ pub(super) async fn open(
.fs_open(FsOpenParams {
handle_id: registration.handle_id.clone(),
path,
mode: FsOpenMode::Read,
sandbox: sandbox.cloned(),
})
.await
@@ -28,6 +28,7 @@ use crate::protocol::FsCreateDirectoryParams;
use crate::protocol::FsCreateDirectoryResponse;
use crate::protocol::FsGetMetadataParams;
use crate::protocol::FsGetMetadataResponse;
use crate::protocol::FsOpenMode;
use crate::protocol::FsOpenParams;
use crate::protocol::FsOpenResponse;
use crate::protocol::FsReadBlockParams;
@@ -122,6 +123,12 @@ impl FileSystemHandler {
params: FsOpenParams,
) -> Result<FsOpenResponse, JSONRPCErrorError> {
validate_file_handle_id(&params.handle_id)?;
// TODO(anp): Enable replacement opens when writable file streams are implemented.
if params.mode == FsOpenMode::Replace {
return Err(invalid_request(
"exec-server does not support writable file streams".to_string(),
));
}
let file = self
.file_system
.open_file_for_read(&params.path, params.sandbox.as_ref())
@@ -8,6 +8,7 @@ use codex_exec_server::ExecServerClient;
use codex_exec_server::ExecServerError;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::FsCloseParams;
use codex_exec_server::FsOpenMode;
use codex_exec_server::FsOpenParams;
use codex_exec_server::FsReadBlockParams;
use codex_exec_server::FsReadBlockResponse;
@@ -227,6 +228,7 @@ async fn read_block_supports_non_sequential_offsets_and_lengths() -> Result<()>
.fs_open(FsOpenParams {
handle_id: Uuid::new_v4().simple().to_string(),
path: PathUri::from_host_native_path(path)?,
mode: FsOpenMode::Read,
sandbox: None,
})
.await?;
@@ -293,6 +295,7 @@ async fn open_enforces_the_per_connection_limit_and_close_releases_capacity() ->
.fs_open(FsOpenParams {
handle_id: Uuid::new_v4().simple().to_string(),
path: path.clone(),
mode: FsOpenMode::Read,
sandbox: None,
})
.await?;
@@ -303,6 +306,7 @@ async fn open_enforces_the_per_connection_limit_and_close_releases_capacity() ->
.fs_open(FsOpenParams {
handle_id: Uuid::new_v4().simple().to_string(),
path: path.clone(),
mode: FsOpenMode::Read,
sandbox: None,
})
.await
@@ -327,6 +331,7 @@ async fn open_enforces_the_per_connection_limit_and_close_releases_capacity() ->
.fs_open(FsOpenParams {
handle_id: Uuid::new_v4().simple().to_string(),
path,
mode: FsOpenMode::Read,
sandbox: None,
})
.await?;
@@ -352,6 +357,7 @@ async fn open_rejects_handle_ids_longer_than_32_bytes() -> Result<()> {
.fs_open(FsOpenParams {
handle_id: "x".repeat(33),
path: PathUri::from_host_native_path(path)?,
mode: FsOpenMode::Read,
sandbox: None,
})
.await