ccwire
A Rust SDK for Claude Code: it
drives the claude CLI over its stream-json protocol, the way the official
TypeScript and Python Agent SDKs do.
Status: early. This release has the protocol layer, a session's command
line (Options), the line framing and the session core, checked against Claude Code 2.1.296
(ccwire::TESTED_CLI_VERSION). Spawning and reaping the claude process,
query() and permission/hook/MCP helpers come next.
| Crate | Purpose | Package | API docs |
|---|---|---|---|
ccwire |
Protocol types, CLI options, framing, and async sessions | crates.io | rustdoc |
ccwire-oauth |
Claude account usage, profile, and OAuth token refresh | crates.io | rustdoc |
Rust 1.85 or newer is required. This project is not affiliated with or
endorsed by Anthropic. Running a live session requires an installed,
authenticated claude CLI; parsing saved transcripts does not.
Install
While the initial crates.io publication is pending, install from Git:
[]
= { = "https://github.com/talaagent/ccwire", = ["runtime"] }
The workspace also has ccwire-oauth, a typed client for
the Claude account endpoints an OAuth login can call.
ccwire::protocol
The stream-json protocol as Rust types: pure data and serde, with no I/O and no async runtime.
Inbound: every frame the CLI writes (user,assistant,system,result,stream_event,rate_limit_event,control_request,control_response,control_cancel_request).Message: the conversation part of it;Control: the control channel.Inbound::splitgives one or the other.UserInput: a user message to write to the CLI's stdin.ControlRequest,ControlResponse,PermissionResult: the control channel, both directions.Partial,PartialMessage:stream_eventdeltas (--include-partial-messages), read and assembled into a message's text.
It is built to survive a CLI that ships weekly:
- Parsing a JSON object never fails. An unknown
typeisOtherwith the raw JSON; a knowntypethat no longer parses isMalformedwith the error and the raw JSON, so a protocol change shows up instead of disappearing.protocol::stats::counts()counts both (and, with thetracingfeature, each is logged with its type). - Every struct keeps the keys it doesn't model (
extra), and optional keys tell absent fromnull(Field), so a frame serializes back to the same JSON value. - Public enums are
#[non_exhaustive]. - A
resultframe ends a turn, so it parses leniently, andInbound::ends_turnalso matches aresultthat came outMalformed.
use ;
ccwire::framing
One JSON frame per line. LineReader (sync) and AsyncLineReader split a
byte stream into lines under a maximum length (64 MiB by default): a longer
line is skipped and reported, and reading goes on. FrameReader turns lines
into Inbound frames and never fails on content: garbage, a cut-off last
line or an oversized line comes out as Inbound::Malformed (a result among
them still ends the turn). Every line carries the byte offset past it, so a
reader can resume a file where it stopped. FrameWriter writes frames.
ccwire::session (feature runtime)
A session over any AsyncRead (the CLI's output) and AsyncWrite (its
input), not tied to a child process:
Enable runtime and use a Tokio runtime. The caller starts and reaps the
child process; pass its stdout and stdin to Session::new:
use ;
use ;
async
For a complete executable that starts the CLI, sends a prompt, reads the
response, and waits for the child, see
examples/chat.rs:
The example disables tools and limits the turn count. A live run uses your Claude account. For offline transcript parsing:
- Control round trips:
request(withinterrupt,set_model,set_permission_mode,stop_taskandrequest_rawon top) gets a freshrequest_idand resolves on the matchingcontrol_response. - The CLI's own control requests (
can_use_tool, ...) go to aControlHandler, each on its own task, or come out of the stream to answer withrespond. Acontrol_cancel_requestaborts the handler. SessionState(Idle,InTurn,Closed), watchable.- The run-end rule:
finish()closes the input at once without callbacks, and once idle with them. - One writer task, so dropping a
sendorrequestfuture never leaves half a line; dropping theSessionaborts its tasks.
Features
runtime(off by default): the async framing and the session core, on tokio (io-util,sync,rt).tracing(off by default): log eachOther(debug) andMalformed(warn) frame with its type.
Dependencies
serde, serde_json and thiserror; tokio with runtime; tracing with
tracing.
Changelogs
See ccwire changes and ccwire-oauth changes. Each crate is versioned separately.
Contributing
See CONTRIBUTING.md for local checks, the workspace layout, protocol fixtures, and the release process. Report bugs and request features in GitHub Issues. For vulnerabilities, use the private reporting instructions in SECURITY.md.
License
Licensed under the Apache License, Version 2.0.