mirror of
https://github.com/openai/codex.git
synced 2026-04-24 06:35:50 +00:00
docs(exec-server): add protocol README
Document the standalone exec-server crate, its stdio JSON-RPC transport, and the current request/response and notification payloads. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
285
codex-rs/exec-server/README.md
Normal file
285
codex-rs/exec-server/README.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# codex-exec-server
|
||||
|
||||
`codex-exec-server` is a small standalone stdio JSON-RPC server for spawning
|
||||
and controlling subprocesses through `codex-utils-pty`.
|
||||
|
||||
It currently provides:
|
||||
|
||||
- a standalone binary: `codex-exec-server`
|
||||
- a Rust client: `ExecServerClient`
|
||||
- a small protocol module with shared request/response types
|
||||
|
||||
This crate is intentionally narrow. It is not wired into the main Codex CLI or
|
||||
unified-exec in this PR; it is only the standalone transport layer.
|
||||
|
||||
## Transport
|
||||
|
||||
The server speaks newline-delimited JSON-RPC 2.0 over stdio.
|
||||
|
||||
- `stdin`: one JSON-RPC message per line
|
||||
- `stdout`: one JSON-RPC message per line
|
||||
- `stderr`: reserved for logs / process errors
|
||||
|
||||
Like the app-server transport, messages on the wire omit the `"jsonrpc":"2.0"`
|
||||
field and use the shared `codex-app-server-protocol` envelope types.
|
||||
|
||||
The current protocol version is:
|
||||
|
||||
```text
|
||||
exec-server.v0
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
Each connection follows this sequence:
|
||||
|
||||
1. Send `initialize`.
|
||||
2. Wait for the `initialize` response.
|
||||
3. Send `initialized`.
|
||||
4. Start and manage processes with `command/exec`, `command/exec/write`, and
|
||||
`command/exec/terminate`.
|
||||
5. Read streaming notifications from `command/exec/outputDelta` and
|
||||
`command/exec/exited`.
|
||||
|
||||
If the server receives any notification other than `initialized`, it replies
|
||||
with an error using request id `-1`.
|
||||
|
||||
If the stdio connection closes, the server terminates any remaining managed
|
||||
processes before exiting.
|
||||
|
||||
## API
|
||||
|
||||
### `initialize`
|
||||
|
||||
Initial handshake request.
|
||||
|
||||
Request params:
|
||||
|
||||
```json
|
||||
{
|
||||
"clientName": "my-client"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"protocolVersion": "exec-server.v0"
|
||||
}
|
||||
```
|
||||
|
||||
### `initialized`
|
||||
|
||||
Handshake acknowledgement notification sent by the client after a successful
|
||||
`initialize` response.
|
||||
|
||||
Params are currently ignored. Sending any other notification method is treated
|
||||
as an invalid request.
|
||||
|
||||
### `command/exec`
|
||||
|
||||
Starts a new managed process.
|
||||
|
||||
Request params:
|
||||
|
||||
```json
|
||||
{
|
||||
"processId": "proc-1",
|
||||
"argv": ["bash", "-lc", "printf 'hello\\n'"],
|
||||
"cwd": "/absolute/working/directory",
|
||||
"env": {
|
||||
"PATH": "/usr/bin:/bin"
|
||||
},
|
||||
"tty": true,
|
||||
"outputBytesCap": 16384,
|
||||
"arg0": null
|
||||
}
|
||||
```
|
||||
|
||||
Field definitions:
|
||||
|
||||
- `processId`: caller-chosen stable id for this process within the connection.
|
||||
- `argv`: command vector. It must be non-empty.
|
||||
- `cwd`: absolute working directory used for the child process.
|
||||
- `env`: environment variables passed to the child process.
|
||||
- `tty`: when `true`, spawn a PTY-backed interactive process; when `false`,
|
||||
spawn a pipe-backed process with closed stdin.
|
||||
- `outputBytesCap`: maximum retained stdout/stderr bytes per stream for the
|
||||
in-memory buffer. Defaults to `codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP`.
|
||||
- `arg0`: optional argv0 override forwarded to `codex-utils-pty`.
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"processId": "proc-1",
|
||||
"running": true,
|
||||
"exitCode": null,
|
||||
"stdout": null,
|
||||
"stderr": null
|
||||
}
|
||||
```
|
||||
|
||||
Behavior notes:
|
||||
|
||||
- Reusing an existing `processId` is rejected.
|
||||
- PTY-backed processes accept later writes through `command/exec/write`.
|
||||
- Pipe-backed processes are launched with stdin closed and reject writes.
|
||||
- Output is streamed asynchronously via `command/exec/outputDelta`.
|
||||
- Exit is reported asynchronously via `command/exec/exited`.
|
||||
|
||||
### `command/exec/write`
|
||||
|
||||
Writes raw bytes to a running PTY-backed process stdin.
|
||||
|
||||
Request params:
|
||||
|
||||
```json
|
||||
{
|
||||
"processId": "proc-1",
|
||||
"chunk": "aGVsbG8K"
|
||||
}
|
||||
```
|
||||
|
||||
`chunk` is base64-encoded raw bytes. In the example above it is `hello\n`.
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"accepted": true
|
||||
}
|
||||
```
|
||||
|
||||
Behavior notes:
|
||||
|
||||
- Writes to an unknown `processId` are rejected.
|
||||
- Writes to a non-PTY process are rejected because stdin is already closed.
|
||||
|
||||
### `command/exec/terminate`
|
||||
|
||||
Terminates a running managed process.
|
||||
|
||||
Request params:
|
||||
|
||||
```json
|
||||
{
|
||||
"processId": "proc-1"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"running": true
|
||||
}
|
||||
```
|
||||
|
||||
If the process is already unknown or already removed, the server responds with:
|
||||
|
||||
```json
|
||||
{
|
||||
"running": false
|
||||
}
|
||||
```
|
||||
|
||||
## Notifications
|
||||
|
||||
### `command/exec/outputDelta`
|
||||
|
||||
Streaming output chunk from a running process.
|
||||
|
||||
Params:
|
||||
|
||||
```json
|
||||
{
|
||||
"processId": "proc-1",
|
||||
"stream": "stdout",
|
||||
"chunk": "aGVsbG8K"
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `processId`: process identifier
|
||||
- `stream`: `"stdout"` or `"stderr"`
|
||||
- `chunk`: base64-encoded output bytes
|
||||
|
||||
### `command/exec/exited`
|
||||
|
||||
Final process exit notification.
|
||||
|
||||
Params:
|
||||
|
||||
```json
|
||||
{
|
||||
"processId": "proc-1",
|
||||
"exitCode": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
The server returns JSON-RPC errors with these codes:
|
||||
|
||||
- `-32600`: invalid request
|
||||
- `-32602`: invalid params
|
||||
- `-32603`: internal error
|
||||
|
||||
Typical error cases:
|
||||
|
||||
- unknown method
|
||||
- malformed params
|
||||
- empty `argv`
|
||||
- duplicate `processId`
|
||||
- writes to unknown processes
|
||||
- writes to non-PTY processes
|
||||
|
||||
## Rust surface
|
||||
|
||||
The crate exports:
|
||||
|
||||
- `ExecServerClient`
|
||||
- `ExecServerLaunchCommand`
|
||||
- `ExecServerProcess`
|
||||
- `ExecServerError`
|
||||
- protocol structs such as `ExecParams`, `ExecResponse`,
|
||||
`WriteParams`, `TerminateParams`, `ExecOutputDeltaNotification`, and
|
||||
`ExecExitedNotification`
|
||||
- `run_main()` for embedding the stdio server in a binary
|
||||
|
||||
## Example session
|
||||
|
||||
Initialize:
|
||||
|
||||
```json
|
||||
{"id":1,"method":"initialize","params":{"clientName":"example-client"}}
|
||||
{"id":1,"result":{"protocolVersion":"exec-server.v0"}}
|
||||
{"method":"initialized","params":{}}
|
||||
```
|
||||
|
||||
Start a process:
|
||||
|
||||
```json
|
||||
{"id":2,"method":"command/exec","params":{"processId":"proc-1","argv":["bash","-lc","printf 'ready\\n'; while IFS= read -r line; do printf 'echo:%s\\n' \"$line\"; done"],"cwd":"/tmp","env":{"PATH":"/usr/bin:/bin"},"tty":true,"outputBytesCap":4096,"arg0":null}}
|
||||
{"id":2,"result":{"processId":"proc-1","running":true,"exitCode":null,"stdout":null,"stderr":null}}
|
||||
{"method":"command/exec/outputDelta","params":{"processId":"proc-1","stream":"stdout","chunk":"cmVhZHkK"}}
|
||||
```
|
||||
|
||||
Write to the process:
|
||||
|
||||
```json
|
||||
{"id":3,"method":"command/exec/write","params":{"processId":"proc-1","chunk":"aGVsbG8K"}}
|
||||
{"id":3,"result":{"accepted":true}}
|
||||
{"method":"command/exec/outputDelta","params":{"processId":"proc-1","stream":"stdout","chunk":"ZWNobzpoZWxsbwo="}}
|
||||
```
|
||||
|
||||
Terminate it:
|
||||
|
||||
```json
|
||||
{"id":4,"method":"command/exec/terminate","params":{"processId":"proc-1"}}
|
||||
{"id":4,"result":{"running":true}}
|
||||
{"method":"command/exec/exited","params":{"processId":"proc-1","exitCode":0}}
|
||||
```
|
||||
Reference in New Issue
Block a user