Repository URLs, version strings, and example creds normalized for the
public git.sulkta.com endpoint. No code-behavior change.
Audit-applied by the public-flip rolling-audit pass — see
kayos/openclaw-workspace memory/2026-05-27 logs for the campaign
context.
- Re-export TransportHandle / TransportReader / TransportWriter alongside
SubprocessTransport. Without these in the public API, rustdoc refused
to resolve [`TransportHandle::close`] from SubprocessTransport's docs.
- Inline the close()/end_input() link prose where the broken-link
warning fired.
cargo doc --no-deps now builds clean.
Three examples:
- basic.rs: query() one-shot prompt + cost reporting.
- with_options.rs: system_prompt, model selection, effort, permission mode.
- interactive.rs: Client multi-turn session with two back-to-back sends.
Integration tests in tests/transport_end_to_end.rs spawn a tiny POSIX
shell script (tests/fake_cli/fake-claude.sh) as a stand-in for the real
claude CLI. The fake answers -v with a version, reads one user-message
frame on stdin, and emits a fixed assistant + result pair on stdout, then
blocks on stdin until close. This proves the spawn / write / read /
disconnect lifecycle works without an authenticated claude install.
Coverage:
- query() round-trip: stream yields Assistant then Result.
- Client round-trip: connect + send + drain to Result + disconnect.
- CLI-not-found surfaces as a typed Error::CliNotFound.
Cargo.lock is committed since this is, in practice, both a library and a
binary (the examples link the crate). Locking dev-deps avoids surprise
churn in CI.
Two entry points mirroring the Python SDK's surface:
- query(prompt, options) returns an impl Stream<Item = Result<Message>>
that terminates after the CLI emits its terminal result message. The
stream owns the underlying Client and tears down the subprocess via a
spawned disconnect task either on Result observation or on Drop.
- Client (mirror of ClaudeSDKClient) supports bidirectional multi-turn
sessions: connect, send (or send_raw for tool-result frames), drain
the messages stream, repeat. Drop is intentionally a no-op for the
subprocess — callers should call disconnect() for a clean shutdown
that surfaces non-zero exit codes as Error::Process.
lib.rs re-exports the public API and carries the crate-level docs +
quick-start example. The v0.1 / v0.2 split is documented inline: control
protocol (interrupt, set_permission_mode, etc.), can_use_tool, in-process
MCP servers, HookMatcher, SessionStore, sandbox, plugins, and the agents
dataclass are all deferred.
Ports _internal/transport/subprocess_cli.py. Spawns the claude CLI with
--output-format stream-json --input-format stream-json --verbose, then
exposes a split (TransportReader, TransportWriter, TransportHandle) trio.
The split is the key difference from the Python single-class transport:
the reader half owns stdout exclusively and is moved into a background
task; the writer half is Arc/Mutex over stdin and clones freely. A single
mutex over the whole transport would deadlock the moment the reader
blocked on stdin — which it does after each turn.
Other notes:
- find_cli() mirrors the Python search path (PATH, then ~/.npm-global/bin,
/usr/local/bin, ~/.local/bin, ~/node_modules/.bin, ~/.yarn/bin,
~/.claude/local/claude).
- build_command() faithfully ports _build_command() with the v0.1 option
subset.
- Env handling matches Python: filter CLAUDECODE on inherit, set
CLAUDE_CODE_ENTRYPOINT=sdk-rust, layer user env, stamp
CLAUDE_AGENT_SDK_VERSION last.
- Stdout JSON parsing speculatively accumulates until serde_json succeeds
or max_buffer_size (1 MiB default) overflows — same buffer-and-retry
loop as the Python TextReceiveStream path. Non-JSON chatter
([SandboxDebug] etc.) is skipped between frames.
- TransportHandle::close() gives the subprocess a 5s graceful shutdown
window after stdin EOF before SIGKILL, mirroring the #625 fix in the
Python SDK.
- Drop on TransportHandle starts a best-effort kill so abandoned clients
do not leak claude processes.
Unit tests cover the JSON accumulator (full + partial + complete,
non-JSON skip, overflow, multiline split) and the version parser.
- Errors: single enum mirroring the Python SDK's exception hierarchy
(CliNotFound, CliConnection, Process, JsonDecode, MessageParse). Each
Python class becomes a variant; the Process variant carries exit code +
optional captured stderr.
- Options: ClaudeAgentOptions as a plain struct with a builder. Covers the
full subprocess-arg surface (system_prompt with String / PresetAppend /
File forms, tools / allowed / disallowed, permission_mode, model and
fallback, cwd / cli_path / settings, env, extra_args, effort, output_format,
mcp_servers, fork_session, include_partial_messages, etc.). Deferred
fields documented inline.
- Messages: ContentBlock enum (Text / Thinking / ToolUse / ToolResult /
ServerToolUse / ServerToolResult / Unknown) with serde tag dispatch and
an Unknown fallthrough for forward compatibility. Top-level Message enum
uses a hand-written Deserialize that dispatches on the "type" tag so
unrecognised top-level frames land in Message::Unknown with the raw JSON
preserved (matches the Python SDK's "skip unknown" behaviour, but lets
Rust callers introspect).
Unit tests cover roundtrip for the common shapes (assistant text +
thinking + tool_use, user with both string and structured content, result,
system, unknown top-level).
Initial layout for an async Rust port of the Python claude-agent-sdk. Sets
up Cargo.toml on 2024 edition, MIT LICENSE matching the upstream Python SDK's
text with the Sulkta Coop copyright appended, a workspace .gitignore, and a
README laying out the v0.1 scope and the Python -> Rust mapping table.
No source modules yet — those land in the following commits.