diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index c6755b7520..c3d7777c7c 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -916,7 +916,7 @@ "type": "string" }, "maxBytes": { - "description": "Requested upper bound. Values above the server limit are capped.", + "description": "Requested upper bound. Values above `maxChunkBytes` are capped.", "format": "uint32", "minimum": 0.0, "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 4bc2e2ac5b..751bd957b1 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -9640,6 +9640,17 @@ "FsReadFileOpenResponse": { "$schema": "http://json-schema.org/draft-07/schema#", "description": "Successful response for `fs/readFile/open`.", + "properties": { + "maxChunkBytes": { + "description": "Maximum decoded bytes returned by one read.\n\nClients may use this value to size reusable read buffers.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "maxChunkBytes" + ], "title": "FsReadFileOpenResponse", "type": "object" }, @@ -9670,7 +9681,7 @@ "type": "string" }, "maxBytes": { - "description": "Requested upper bound. Values above the server limit are capped.", + "description": "Requested upper bound. Values above `maxChunkBytes` are capped.", "format": "uint32", "minimum": 0.0, "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 5fcb6ba340..2971d9a11d 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -5982,6 +5982,17 @@ "FsReadFileOpenResponse": { "$schema": "http://json-schema.org/draft-07/schema#", "description": "Successful response for `fs/readFile/open`.", + "properties": { + "maxChunkBytes": { + "description": "Maximum decoded bytes returned by one read.\n\nClients may use this value to size reusable read buffers.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "maxChunkBytes" + ], "title": "FsReadFileOpenResponse", "type": "object" }, @@ -6012,7 +6023,7 @@ "type": "string" }, "maxBytes": { - "description": "Requested upper bound. Values above the server limit are capped.", + "description": "Requested upper bound. Values above `maxChunkBytes` are capped.", "format": "uint32", "minimum": 0.0, "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/FsReadFileOpenResponse.json b/codex-rs/app-server-protocol/schema/json/v2/FsReadFileOpenResponse.json index 6ec200f39d..2438699e77 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/FsReadFileOpenResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/FsReadFileOpenResponse.json @@ -1,6 +1,17 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "description": "Successful response for `fs/readFile/open`.", + "properties": { + "maxChunkBytes": { + "description": "Maximum decoded bytes returned by one read.\n\nClients may use this value to size reusable read buffers.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "maxChunkBytes" + ], "title": "FsReadFileOpenResponse", "type": "object" } \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/FsReadFileReadParams.json b/codex-rs/app-server-protocol/schema/json/v2/FsReadFileReadParams.json index fe79922366..a927981f3f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/FsReadFileReadParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/FsReadFileReadParams.json @@ -6,7 +6,7 @@ "type": "string" }, "maxBytes": { - "description": "Requested upper bound. Values above the server limit are capped.", + "description": "Requested upper bound. Values above `maxChunkBytes` are capped.", "format": "uint32", "minimum": 0.0, "type": [ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FsReadFileOpenResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FsReadFileOpenResponse.ts index 20b136f6c4..9e495e3d27 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/FsReadFileOpenResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FsReadFileOpenResponse.ts @@ -5,4 +5,10 @@ /** * Successful response for `fs/readFile/open`. */ -export type FsReadFileOpenResponse = Record; +export type FsReadFileOpenResponse = { +/** + * Maximum decoded bytes returned by one read. + * + * Clients may use this value to size reusable read buffers. + */ +maxChunkBytes: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FsReadFileReadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FsReadFileReadParams.ts index 6309f3ddc1..3c3d2b46ac 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/FsReadFileReadParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FsReadFileReadParams.ts @@ -7,6 +7,6 @@ */ export type FsReadFileReadParams = { handleId: string, offset: number, /** - * Requested upper bound. Values above the server limit are capped. + * Requested upper bound. Values above `maxChunkBytes` are capped. */ maxBytes?: number | null, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/fs.rs b/codex-rs/app-server-protocol/src/protocol/v2/fs.rs index f977fa4b31..4233df14d4 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/fs.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/fs.rs @@ -54,7 +54,12 @@ pub struct FsReadFileOpenParams { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct FsReadFileOpenResponse {} +pub struct FsReadFileOpenResponse { + /// Maximum decoded bytes returned by one read. + /// + /// Clients may use this value to size reusable read buffers. + pub max_chunk_bytes: u32, +} /// Read a bounded chunk from an open file at an absolute byte offset. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] @@ -64,7 +69,7 @@ pub struct FsReadFileReadParams { pub handle_id: String, #[ts(type = "number")] pub offset: u64, - /// Requested upper bound. Values above the server limit are capped. + /// Requested upper bound. Values above `maxChunkBytes` are capped. #[ts(optional = nullable)] pub max_bytes: Option, } diff --git a/codex-rs/app-server/src/request_processors/fs_processor.rs b/codex-rs/app-server/src/request_processors/fs_processor.rs index c1f4db7109..010452b425 100644 --- a/codex-rs/app-server/src/request_processors/fs_processor.rs +++ b/codex-rs/app-server/src/request_processors/fs_processor.rs @@ -133,7 +133,8 @@ impl FsRequestProcessor { connection_id: ConnectionId, params: FsReadFileOpenParams, ) -> Result { - self.handles(connection_id) + let max_chunk_bytes = self + .handles(connection_id) .await .open_read( self.file_system()?, @@ -143,7 +144,10 @@ impl FsRequestProcessor { ) .await .map_err(map_file_handle_error)?; - Ok(FsReadFileOpenResponse {}) + Ok(FsReadFileOpenResponse { + max_chunk_bytes: u32::try_from(max_chunk_bytes) + .map_err(|_| internal_error("file read chunk limit exceeds u32"))?, + }) } pub(crate) async fn read_file_read( diff --git a/codex-rs/app-server/tests/suite/v2/fs.rs b/codex-rs/app-server/tests/suite/v2/fs.rs index 79fde05297..51664de5f9 100644 --- a/codex-rs/app-server/tests/suite/v2/fs.rs +++ b/codex-rs/app-server/tests/suite/v2/fs.rs @@ -421,7 +421,7 @@ async fn fs_streaming_read_supports_stat_and_positional_reads() -> Result<()> { ) .await??, )?; - assert_eq!(open, FsReadFileOpenResponse {}); + assert!(open.max_chunk_bytes >= 4); let wrong_type_id = mcp .send_raw_request( diff --git a/file-streaming-api.md b/file-streaming-api.md index b9e323ab3a..763a636368 100644 --- a/file-streaming-api.md +++ b/file-streaming-api.md @@ -30,13 +30,16 @@ Opens a file for positional reads. Response: ```json -{} +{ + "maxChunkBytes": 262144 +} ``` ### `fs/readFile/read` Reads at most `maxBytes` starting at the absolute byte offset. If `maxBytes` -is omitted or exceeds the server's read limit, the server uses that limit. +is omitted or exceeds `maxChunkBytes`, the server uses `maxChunkBytes`. +Clients may use `maxChunkBytes` to size a reusable read buffer. ```json { @@ -182,10 +185,9 @@ Response: concurrently. - Reads are positional and do not maintain a server-side cursor. - Writes are sequential appends. -- Each read transfers at most the server's read limit. -- Each write transfers at most the `maxChunkBytes` returned by - `fs/writeFile/open`. -- Both limits are currently 262144 bytes. +- Each read or write transfers at most the `maxChunkBytes` returned by its + corresponding open operation. +- `maxChunkBytes` is currently 262144 bytes. - Backpressure comes from awaiting bounded read and write responses. Clients should use a bounded pipeline of up to two read requests to hide transport round-trip latency without accumulating unbounded response data.