Universal Rust transport library for CLI AI agents. Spawn, stream, resume — for four different CLI agents through one unified API.
Not a harness. Not a sandbox. gate4agent is the thin wiring layer between your Rust app and the CLI agent's subprocess: spawn the binary, write the prompt, read structured events, resume by session id. That's it.
| Tool | Transport | Pipe mode | ACP | Resume | Notes |
|---|---|---|---|---|---|
| Claude Code | Pipe + PTY + ACP | ✓ stream-json | ✓ via claude-agent-acp |
✓ --resume <id> |
Prompt via stdin |
| Codex | Pipe + PTY + ACP | ✓ --json |
✓ via codex-acp |
✓ exec resume <id> |
Uses --full-auto for non-interactive |
| Gemini | Pipe + PTY + ACP | ✓ stream-json | ✓ native --experimental-acp |
✓ --resume <id> |
Prompt via -p flag |
OpenCode (sst/opencode) |
Pipe + ACP | ✓ --format json |
✓ native opencode acp |
✓ --session ses_XXX |
5-event NDJSON schema |
Transport classes:
- Pipe: spawn the CLI directly, read NDJSON over stdout
- PTY: spawn inside a pseudo-terminal, scrape the screen with vt100 (for agents without structured output)
- ACP (Agent Client Protocol): spawn the CLI in ACP mode, communicate via bidirectional JSON-RPC 2.0 over stdio. Multi-turn sessions, structured events, agent→host callbacks (fs, terminal, permissions).
use gate4agent::{CliTool, SessionConfig, AgentEvent, PipeSession, PipeProcessOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = SessionConfig {
tool: CliTool::ClaudeCode,
working_dir: std::env::current_dir()?,
env_vars: vec![],
name: None,
};
let session = PipeSession::spawn(config, "Say hello in 3 words", PipeProcessOptions::default()).await?;
let mut rx = session.subscribe();
while let Ok(event) = rx.recv().await {
match event {
AgentEvent::Text { text, .. } => print!("{text}"),
AgentEvent::SessionEnd { .. } => break,
_ => {}
}
}
Ok(())
}let opts = SpawnOptions {
resume_session_id: Some("abc-123-session".into()),
..opts
};Each CLI handles resume in its own way — Codex swaps exec → exec resume <id>, Claude uses --resume <id>, OpenCode uses --session <ses_XXX>. gate4agent hides the difference behind SpawnOptions::resume_session_id.
use gate4agent::{PipeSession, PipeProcessOptions, ClaudeOptions, SessionConfig, CliTool};
let config = SessionConfig {
tool: CliTool::ClaudeCode,
working_dir: std::env::current_dir()?,
env_vars: vec![],
name: None,
};
let opts = PipeProcessOptions {
claude: ClaudeOptions { model: Some("claude-opus-4".into()), ..Default::default() },
..Default::default()
};
let session = PipeSession::spawn(config, "hello", opts).await?;use gate4agent::acp::{AcpSession, AcpSessionOptions};
use gate4agent::{CliTool, AgentEvent};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let session = AcpSession::spawn(
CliTool::Gemini,
&std::env::current_dir()?,
AcpSessionOptions::default(),
).await?;
let mut rx = session.subscribe();
session.prompt("Say hello in 3 words").await?;
while let Ok(event) = rx.recv().await {
match event {
AgentEvent::Text { text, .. } => print!("{text}"),
AgentEvent::TurnComplete { .. } => break,
_ => {}
}
}
session.kill().await?;
Ok(())
}ACP provides multi-turn sessions — call prompt() repeatedly without respawning the agent process. The agent can also call back to the host for file access, terminal execution, and permission requests.
DaemonSession connects to long-running HTTP/WebSocket agent daemons (OpenCode serve, OpenClaw). Not yet functional — API surface documented for future implementation.
- Single API for 4 CLIs —
TransportSession::spawn(tool, cwd, prompt, options)(Pipe) orAcpSession::spawn(tool, cwd, options)(ACP) - Backwards-compatible
PipeSession— 0.1.x consumers that usedPipeSession::spawn(config, prompt, options)compile unchanged - SessionEnd synthesis — Codex has no terminal event; gate4agent synthesizes
SessionEnd { result: "exit_code=N", is_error: N != 0 }on child exit - Transport-neutral events —
AgentEvent::{Text, ToolStart, ToolResult, Thinking, TurnComplete, SessionStart, SessionEnd} - Cross-platform — Windows (ConPTY +
cmd /Cargv wrapping) and Unix (POSIX PTY + bare exec) - Rate-limit detection — pattern-based session/daily/weekly limit detection per CLI
- ACP (Agent Client Protocol) — bidirectional JSON-RPC 2.0 over stdio, multi-turn sessions, agent→host callbacks
- 4 CLI agents — Claude Code, Codex, Gemini, OpenCode
- Session history — per-CLI session listing with workdir scoping, preview extraction, and resume support (Claude JSONL, Codex JSONL, Gemini JSON, OpenCode SQLite)
- Probe + context tracking —
probe_all()discovers installed CLIs (sync, filesystem-only, cached 1h);ContextTrackeraccumulates token usage, computes remaining context window capacity - Cure (model discovery) —
cure()populates~/.gate4agent/models.jsonwith live model metadata from OpenCode cache or OpenRouter, sodiscover_capabilities()returns accurate context windows without hardcoding
gate4agent/
├── src/
│ ├── lib.rs — Library root, re-exports
│ ├── core/ — AgentEvent, CliTool, SessionConfig, AgentError
│ ├── transport/ — TransportSession (thin router over PipeSession), SpawnOptions
│ ├── pipe/ — PipeSession, PipeProcess, per-CLI NDJSON parsers + command builders
│ │ └── cli/ — claude.rs, codex.rs, gemini.rs, opencode.rs
│ ├── pty/ — PtyWrapper, PtySession, VTE/screen parsers, per-CLI PTY parsers
│ │ └── cli/ — Per-CLI PTY output parsers
│ ├── acp/ — ACP transport: AcpSession, protocol types, reader loop, host handler
│ │ ├── session.rs — AcpSession::spawn(), prompt(), cancel(), kill()
│ │ ├── protocol.rs — ACP wire types (InitializeParams, SessionUpdate, ContentBlock)
│ │ ├── reader.rs — Blocking JSON-RPC reader loop
│ │ ├── host.rs — AcpHostHandler trait, FilesystemAcpHandler, TerminalAcpHandler
│ │ └── spawn.rs — AcpProcess + per-CLI spawn specs
│ ├── rpc/ — Shared JSON-RPC 2.0 primitives (message, pending, handler, id)
│ │ Used internally by acp/. Not a standalone transport.
│ ├── probe/ — probe_all(), ProbeResult, CliProbe, cache logic
│ ├── context/ — ContextTracker, TurnCompleteData
│ ├── cure/ — Runtime model discovery (OpenCode cache → OpenRouter → hardcoded)
│ ├── daemon/ — DaemonSession, per-daemon adapters [skeleton]
│ ├── history/ — Session history readers (per-CLI format)
│ │ ├── claude.rs — JSONL from ~/.claude/projects/{cwd}/
│ │ ├── codex.rs — JSONL from ~/.codex/sessions/ (event_msg format)
│ │ ├── gemini.rs — JSON from ~/.gemini/tmp/{slug}/chats/
│ │ └── opencode.rs — SQLite from ~/.local/share/opencode/opencode.db
│ └── utils.rs — String utilities
| Tool | Pipe | PTY | ACP | Notes |
|---|---|---|---|---|
| Claude Code | ✓ live | ✗ | ✓ live | Pipe: stream-json. ACP: via claude-agent-acp adapter |
| Codex | ✓ live | ✗ | ✓ live | Pipe: --json. ACP: via codex-acp adapter |
| Gemini | ✓ live | ✗ | ✓ live | Pipe: stream-json. ACP: native --experimental-acp |
| OpenCode | ✓ live | ✗ | ✓ live | Pipe: --format json. ACP: native opencode acp |
All Pipe and ACP transports are live-verified against real CLI output. PTY parsers existed in 0.1.x and are structurally simple (screen scraping) — low risk of breakage.
On Windows, CLI tools are invoked through the appropriate shell:
- npm-installed CLIs (claude, codex, gemini, opencode):
cmd /C program.cmd arg1 arg2— the.cmdbatch wrapper is detected via PATH lookup - Bash scripts (native binaries without
.cmdwrapper):bash -c 'program arg1 arg2'— fallback when no.cmdwrapper exists - Unix: direct
Command::new("program")— no shell wrapping needed
Arguments are passed individually (not joined into a shell string) to avoid cmd.exe quote-mangling issues.
At least one CLI agent must be installed on the host. gate4agent does not install them.
| CLI | Install | ACP mode |
|---|---|---|
| Claude Code | npm install -g @anthropic-ai/claude-code |
via npx @agentclientprotocol/claude-agent-acp |
| Codex | npm install -g @openai/codex |
via npx @zed-industries/codex-acp |
| Gemini | npm install -g @google/gemini-cli |
native: gemini --experimental-acp |
| OpenCode | npm install -g opencode-ai |
native: opencode acp |
- 0.1.x — original 3-CLI library (Claude, Codex, Gemini)
- 0.2.0 — breaking: 6 CLIs,
TransportSession,AgentEventrenamed,PipeSessionremoved, OpenClaw fantasy transport - 0.2.1 — cleanup: OpenClaw removed (was never functional),
PipeSessionrestored for 0.1.x compatibility,TransportSessionis now a thin router overPipeSession - 0.2.2 — parser isolation: NdjsonParser trait extracted, per-CLI parser modules split out
- 0.2.3 — source tree restructure into core/pty/pipe layout; proper pipe builders+parsers for Codex, Gemini, Cursor, OpenCode (research-based, NOT yet tested against live CLI output)
- 0.2.4 — docs update, Codex flags fixed (
--full-autoreplaces removed--ask-for-approval) - 0.2.5 — live integration tests: fixed Codex flags, OpenCode
runsubcommand, Gemini-pflag, Windowscmd /Cquoting; all parsers verified against real CLI output - 0.2.6 — Gemini + OpenCode live-verified; OpenCode parser rewritten from real CLI output
- 0.2.7 — Cursor removed (no native Windows support, broken headless mode, closed-source CLI). 4 CLI tools remain: Claude Code, Codex, Gemini, OpenCode.
- 0.2.8 — SpawnOptions extended: continue_last, allowed_tools, permission_mode, mcp_config, max_turns, sandbox. Per-CLI builders updated.
- 0.2.9 — Daemon transport skeleton: DaemonSession, DaemonConfig, DaemonType (OpenCode, OpenClaw). Not yet functional — API surface documented for future implementation.
- 0.2.10 — Bidirectional JSON-RPC 2.0 primitives: RpcRequest, RpcResponse, RpcNotification, PendingRequests, HostHandler, MethodRouter. Shared infrastructure for ACP transport.
- 0.2.11 — Critical bugfixes: stale transport_session cleared on exit, send_prompt() returns BrokenPipe instead of silent no-op, OpenCode emits SessionStart, Gemini skips non-JSON banners silently, history readers for Codex/Gemini/OpenCode
- 0.2.12 — Test coverage: Gemini parser (14 tests), Claude parser (+8), builder argv parity (22 tests), PipeSession live test. README/DEBUGGING.md fixed. Examples added.
- 0.2.13–0.2.15 — OpenCode default model, env sanitization, test cleanup, TermCell improvements
- 0.2.16 — ACP transport: full Agent Client Protocol (JSON-RPC 2.0 over stdio) implementation. AcpSession with initialize + session/new handshake, multi-turn prompt(), session/update streaming, agent→host callbacks (fs, terminal, permissions). Live-verified with Gemini, OpenCode, Claude, Codex. 199 unit tests.
- 0.2.17 — Cursor removed again (no Windows binary:
node_sqlite3.nodeis a Linux ELF, crashes on Windows with "is not a valid Win32 application"; no official Windows build exists). 4 CLI tools remain: Claude Code, Codex, Gemini, OpenCode. - 0.2.18 — ACP host handler extended: TerminalAcpHandler with real terminal execution, FilesystemAcpHandler root whitelisting.
- 0.2.19 — RpcSession removed: standalone RPC transport was a pre-ACP intermediate step, now superseded by AcpSession. Shared JSON-RPC primitives (message, pending, handler, id) retained in
rpc/for ACP internal use. - 0.2.20 — History readers: workdir scoping for Codex (cwd field), Gemini (projects.json slug), OpenCode (directory field). All readers now filter sessions by working directory.
- 0.2.21 — Docs: fixed README Quick Start example, renamed rpc_hello → acp_hello example.
- 0.2.22 — History readers: preview extraction for Codex/Gemini/OpenCode (first real user message), system message filtering (Codex injected XML/AGENTS.md content excluded).
- 0.2.23 — History readers: Codex zombie session filter (sessions with no user input excluded), OpenCode SQLite reader (reads from ~/.local/share/opencode/opencode.db instead of nonexistent ~/.opencode/).
- 0.2.24 — History readers: Codex duplicate message fix (skip
response_itemwith role=user), old.jsonsession format removed (no cwd field = leaked into all projects). - 0.2.25–0.2.28 —
CliCapabilitiesAPI:ModelInfo,PermissionModeInfo,CliFeaturesper CLI tool. Gemini--modelflag support, Codex configurable permission modes, Claude conditional--dangerously-skip-permissions. - 0.2.29 — Dynamic model discovery:
discover_capabilities()reads CLI configs (Codex~/.codex/config.toml, OpenCodeopencode.json). Model picker enrichment at runtime. - 0.2.30 — Probe + Context tracking:
probe_all()discovers installed CLIs with caching (~/.gate4agent/probe-cache.json).ContextTrackeraccumulates tokens per session, computes remaining context. ExtendedTurnCompletewithcache_read_tokens,cache_write_tokens,reasoning_tokens,context_window,is_cumulative. Codexevent_msg/token_countparser (cumulative totals +model_context_window). Claude/Gemini/OpenCode parsers extract cache and reasoning tokens. Fixed Claude model IDs (4 → 4.6). Removedimage_to_prompt_reference()andPipeSession::tool(). - 0.2.31 — ContextTracker wired into runtime:
AgentInstancenow holds aContextTracker, updated on everyTurnCompleteevent.AgentRenderSnapshotgainscontext_percent: Option<f64>— consumers get live context window usage without any extra work. - 0.2.37 — Full OpenCode model catalog + remove Claude aliases. All 49 OpenCode built-in models (12 free first, 37 paid). Removed redundant
opus/sonnet/haikualias entries from Claude. - 0.2.36 — feat: cure runs lazily on first history load or session start.
ensure_cure_once()populates~/.gate4agent/models.jsonfrom OpenCode cache beforetool.capabilities()is called, so context windows are accurate from the first interaction. - 0.2.35 — feat(history): SessionUsage from loaded sessions.
load_session_with_usage()extracts token counts from Claude JSONL history. Context tracker is initialized when loading past sessions, socontext_percentshows real values in UI instead of 0%. - 0.2.34 — fix(context): correct usage_percent formula + cure module.
used_tokens()now =input + output + cache_read + cache_write(matches OpenCode's formula). Per-turn mode: input/cache REPLACE (snapshot), output ACCUMULATES. Codexevent_msgnormalizesinput_tokensby subtractingcached_input_tokensto avoid double-counting. Newcuremodule: runtime model discovery from OpenCode disk cache (~/.cache/opencode/models.json) with optional OpenRouter fallback (cure-networkfeature). Persists to~/.gate4agent/models.json, overlays context windows onto hardcoded capabilities. - 0.2.33 — fix(capabilities): correct context windows and model IDs for all 4 CLIs — Claude Opus/Sonnet 4.6 → 1M tokens, Codex all → 272K, Gemini preview IDs fixed, OpenCode models updated to current.
- 0.2.32 — Fix context_percent always 0%: Initialize
ContextTrackerfrom model capabilities atSessionStart(matches model ID →context_window). Reset tracker on new session spawn so stale data doesn't persist across sessions.
See ROADMAP.md for what's next and DEBUGGING.md for known issues and mitigations.
- OpenClaw removed —
CliTool::OpenClawno longer exists. If you matched on it, delete that arm. OpenClaw was never functional (unverified daemon protocol, fictional acpx API surface). PipeSessionrestored — 0.1.x callers that usedPipeSession::spawn(config, prompt, options)compile again. ThePipeSessionnow includes SessionEnd synthesis (previously only in the 0.2.0pipe_runner).TransportSessionis now a thin wrapper overPipeSession. Its public API (spawn,subscribe,session_id,send_prompt,kill) is unchanged. Internal: no moreTransportHandleenum, no deadPtyvariant.DaemonNotRunning/DaemonProbeTimeouterror variants removed — they were only reachable via OpenClaw. Remove any match arms for these.
RpcSessionremoved — if you were usinggate4agent::rpc::RpcSessionor the top-levelgate4agent::RpcSession/RpcSessionOptions/RpcSessionErrorre-exports, migrate to [AcpSession] instead. ACP does everything RpcSession did (bidirectional JSON-RPC 2.0, host handlers, multi-turn) but follows the standard Agent Client Protocol.- Shared
rpcprimitives unchanged —RpcRequest,RpcResponse,RpcError,RpcNotification,RpcId,HostHandler,MethodRouter,RejectAllHandler,PendingRequests,IdGen,classify_lineare all still exported. Only theRpcSessiontransport struct is gone.
-
Events:
AgentEvent::Pipe*→ neutral names. Rename all match arms:PipeText→TextPipeToolStart→ToolStartPipeToolResult→ToolResultPipeThinking→ThinkingPipeTurnComplete→TurnCompletePipeSessionStart→SessionStartPipeSessionEnd→SessionEnd
-
PipeSession::spawn— signature unchanged:PipeSession::spawn(config, prompt, options). Compiles directly. -
SpawnOptions: new unified struct. Fields:working_dir,prompt,resume_session_id,model,append_system_prompt,extra_args,env_vars. -
CliToolis now non-exhaustive in effect (new variant:OpenCode). Add arms or a_ =>fallback.
If you find this tool useful, consider supporting development:
| Currency | Network | Address |
|---|---|---|
| USDT | TRC20 | TNxMKsvVLYViQ5X5sgCYmkzH4qjhhh5U7X |
| USDC | Arbitrum | 0xEF3B94Fe845E21371b4C4C5F2032E1f23A13Aa6e |
| ETH | Ethereum | 0xEF3B94Fe845E21371b4C4C5F2032E1f23A13Aa6e |
| BTC | Bitcoin | bc1qjgzthxja8umt5tvrp5tfcf9zeepmhn0f6mnt40 |
| SOL | Solana | DZJjmH8Cs5wEafz5Ua86wBBkurSA4xdWXa3LWnBUR94c |
MIT