Files
codex/file-streaming-api.md
T

191 lines
4.1 KiB
Markdown

# App Server File Streaming API
## Status
Implemented in app-server and exec-server.
The existing buffered `fs/readFile` and `fs/writeFile` methods remain unchanged.
## Goals
- Read and write files without buffering the entire file in memory.
- Provide backpressure through bounded request/response chunks.
- Allow positional reads.
- Support cancellation and deterministic cleanup.
- Match the existing whole-file write behavior, including preserving the
identity and metadata of existing destination files.
## Read API
### `fs/readFile/open`
Opens a file for positional reads.
```json
{
"handleId": "read-1",
"path": "/absolute/path/to/file"
}
```
Response:
```json
{
"maxChunkBytes": 262144
}
```
### `fs/readFile/read`
Reads at most `maxBytes` starting at the absolute byte offset. If `maxBytes`
is omitted or exceeds `maxChunkBytes`, the server uses `maxChunkBytes`.
Clients may use `maxChunkBytes` to size a reusable read buffer.
```json
{
"handleId": "read-1",
"offset": 0,
"maxBytes": 65536
}
```
Response:
```json
{
"dataBase64": "SGVsbG8=",
"eof": false
}
```
`eof` means no more bytes were available at the time of the read. A later read
at the same offset may return data if the file grows.
### `fs/readFile/stat`
Returns metadata for the open file.
```json
{
"handleId": "read-1"
}
```
Response:
```json
{
"sizeBytes": 1234,
"createdAtMs": 1730910000000,
"modifiedAtMs": 1730910000000
}
```
The metadata applies to the opened file handle, so the operation remains valid
if the path is renamed or replaced after it is opened.
### `fs/readFile/close`
Closes the read handle.
```json
{
"handleId": "read-1"
}
```
Response:
```json
{}
```
## Write API
### `fs/writeFile/open`
Creates the destination if needed or truncates the existing file. Existing file
identity and metadata, including hardlinks and platform access controls, are
preserved in the same way as `fs/writeFile`.
```json
{
"handleId": "write-1",
"path": "/absolute/path/to/file"
}
```
Response:
```json
{
"maxChunkBytes": 262144
}
```
### `fs/writeFile/write`
Decodes and appends the complete chunk to the open destination file. A
successful response acknowledges the entire decoded chunk; partial success is
not exposed.
```json
{
"handleId": "write-1",
"dataBase64": "SGVsbG8="
}
```
Response:
```json
{}
```
### `fs/writeFile/close`
Closes the write handle.
```json
{
"handleId": "write-1"
}
```
Response:
```json
{}
```
## Shared Semantics
- Handle IDs are client-supplied strings scoped to one connection.
- Opening a duplicate active handle ID returns `INVALID_REQUEST`.
- Each connection may have at most 64 opening or open file handles.
- Operations for one handle are serialized. Different handles may run
concurrently.
- Reads are positional and do not maintain a server-side cursor.
- Writes are sequential appends.
- 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.
- Close operations are idempotent and serialized with other operations for the
same handle. Close waits for an active operation to finish, rejects queued
operations, and prevents a pending open from becoming live.
- Closing a connection closes all of its handles.
- Any filesystem or I/O error closes the affected handle. Protocol errors such
as an unknown handle do not affect other handles.
- Streamed writes are visible as they complete. Opening truncates an existing
destination, and an I/O error, cancellation, or disconnect may leave a
partially written file.
- Errors use normal JSON-RPC error responses.
- App-server operations target the app-server host filesystem. Exec-server
exposes the same pull-based handle operations for remote filesystem clients.
- Streaming through the platform sandbox helper is not supported because the
helper is one-shot and cannot retain open-file identity across requests.