Expand description
codexwire: a client for OpenAI Codex’s app-server (codex app-server,
JSON-RPC over the child’s stdio), the Codex counterpart to ccwire.
Faithful to Codex’s own protocol: protocol is generated from the
pinned Codex’s JSON Schema (scripts/gen-codex-protocol; the schema and
its VERSION are in schema/, CODEX_VERSION is the version), rpc
frames its messages, client runs the child and matches responses to
requests, and auth gives a session its own copy of auth.json.
exec runs one-shot codex exec jobs in a private CODEX_HOME.
The README follows (its examples are compiled as doctests).
§codexwire
A Rust client for OpenAI Codex. Run
codex app-server over stdio for typed requests and streaming notifications,
or use codex exec --json for a single prompt in an isolated working directory.
Early release, targeting Codex 0.167.0, the stable release used to generate and check the protocol types. The client supports Unix; the protocol crate is platform independent. This project is not affiliated with or endorsed by OpenAI.
| Crate | Purpose | Package | API docs |
|---|---|---|---|
codexwire | Tokio client, JSON-RPC framing, child processes, isolated exec runs, auth helpers | crates.io | rustdoc |
codexwire-protocol | Generated serde types, with no process or async runtime dependency | crates.io | rustdoc |
codexwire::protocol re-exports codexwire-protocol, so applications can keep
using the client crate’s existing import paths.
§Install
Rust 1.85 or newer is required:
cargo add codexwire
cargo add tokio --features macros,rt,timeFor wire types alone, use cargo add codexwire-protocol --git https://github.com/talaagent/codexwire.
After publication, the equivalent registry commands are cargo add codexwire
and cargo add codexwire-protocol.
Live examples require the codex CLI on PATH. For authenticated work, sign in
with codex login first. Parsing protocol messages and running the normal tests
need no login, CLI, or network access.
§Initialize an app-server
Requests use the generated Request trait: each params type supplies its method
name and response type. Give calls a timeout; the client does not impose one.
use core::time::Duration;
use codexwire::client::spawn_app_server;
use codexwire::protocol::{ClientInfo, InitializeParams};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn core::error::Error>> {
let mut server = spawn_app_server("codex", &[], &[], ".".as_ref())?;
let params = InitializeParams {
capabilities: None,
client_info: ClientInfo {
name: "example".into(), title: None, version: "0.1.0".into(),
},
};
let init = tokio::time::timeout(
Duration::from_secs(10), server.client.request(¶ms),
).await??;
println!("codex: {}", init.user_agent);
server.client.notify("initialized", None).await?;
server.client.shutdown().await;
let status = tokio::time::timeout(Duration::from_secs(10), server.child.wait()).await??;
if !status.success() {
return Err(std::io::Error::other(format!("app-server exited with {status}")).into());
}
Ok(())
}Run the complete example:
cargo run -p codexwire --example initializeFor conversations, send ThreadStartParams and TurnStartParams, then consume
server.messages. Respond to approval requests explicitly. Notifications and
server requests arrive in order. Malformed JSON or UTF-8 becomes
ServerMessage::Malformed; once stdout closes, pending and later calls fail
with ClientError::Closed. Dropping the child kills it; for graceful cleanup,
close the client input and wait for the child under a timeout as above.
§Run a single prompt
Exec uses a fresh private CODEX_HOME and working directory, copies in only
auth.json, and copies refreshed credentials back when the original login has
not changed. Its default sandbox is read-only and its timeout is five minutes.
Use an existing ChatGPT login and a private temporary root:
use codexwire::exec::{Exec, Input};
use std::path::Path;
async fn query(codex_home: &Path, temporary_root: &Path)
-> Result<(), Box<dyn core::error::Error>>
{
let run = Exec::new("codex", codex_home, temporary_root)
.run(&Input { prompt: "Say hello in one sentence.".into(), images: Vec::new() })
.await?;
if let Some(error) = run.failure() {
return Err(std::io::Error::other(error.to_string()).into());
}
if run.code != Some(0) {
return Err(std::io::Error::other(format!("codex exited with {:?}", run.code)).into());
}
println!("{}", String::from_utf8_lossy(&run.last_message));
Ok(())
}Run the executable
with cargo run -p codexwire --example exec -- "Say hello.". It uses
CODEX_HOME, or $HOME/.codex, for the existing login. Live runs use your
account. The run’s files are removed when Run is dropped; copy generated images
out before dropping it if you need to retain them.
§Protocol compatibility
codexwire-protocol::CODEX_VERSION records the CLI version used for codegen.
The schema covers the methods and notifications this client currently uses;
raw JSON-RPC calls remain available for other methods. Generated enums keep
unrecognized values as Unknown(serde_json::Value) (or UnknownValue when the
schema already has an unknown variant). A known variant with an incompatible
body also falls back to raw JSON; inspect these values when diagnosing drift.
The client starts the CLI with an allowlisted environment. Additional variables are explicit. Stderr has best-effort secret redaction; returned events, prompts, auth files, and raw output can still contain sensitive data. Handle them as application data rather than assuming all output is safe to log.
The auth refresh lock serializes runs within this process. Applications sharing one login across processes must also serialize refresh and copy-back operations. See the auth API.
§Development and releases
The workspace keeps runtime code in crates/codexwire, generated wire types in
crates/codexwire-protocol, and the pinned schema and generators at the root.
See CONTRIBUTING.md
for local checks, fixture scrubbing, schema updates, and release-plz setup.
Report bugs in GitHub Issues.
Report vulnerabilities privately as described in
SECURITY.md.
§License
Apache-2.0. Generated types and the schema derive from OpenAI Codex; attribution is in NOTICE.
For development, nix develop supplies Rust and cargo-nextest.
See CONTRIBUTING.md for tests and protocol generation.
Re-exports§
pub use codexwire_protocol as protocol;
Modules§
- auth
- Codex’s
ChatGPTlogin (<CODEX_HOME>/auth.json) shared with a session’s ownCODEX_HOME: copied in before the app-server starts, copied back when Codex refreshed its tokens and nobody else changed the real one since. (The same rule a tool that refreshes the login and copies it back follows.) - client
- The app-server client: writes requests, matches responses by id, and hands the server’s notifications and requests to the caller in order. Generic over the transport (the child’s stdio, or an in-process duplex in tests).
- exec
- One-shot
codex execruns on aChatGPTlogin, each in a fresh privateCODEX_HOME. - rpc
- JSON-RPC framing for the app-server: one JSON object per line, no
"jsonrpc"member (Codex leaves it out). Requests go both ways (the server asks for approvals), so a line is one of four shapes.
Constants§
- CODEX_
VERSION - The Codex version this schema came from (
codex --version).
Functions§
- user_
agent_ version - The version in an app-server
userAgent(<client>/<version> (...)).