agent-call
agent-call is a Rust library for one-shot calls to four local agent CLIs:
Claude, Codex, Devin, and OpenCode v2. It uses each CLI's saved authentication
and starts a fresh process for every request. The caller supplies a prompt and
gets final text, elapsed time, and token usage when the CLI reports it.
Requirements
Install and authenticate the CLI you want to use. AgentCli::discover finds
it on PATH. OpenCode must report major version 2; version 1 is rejected.
No provider API key is required by the library itself. Use env_remove if your
application needs to remove environment variables before spawning the CLI.
Add the dependency
[]
= "0.4.1"
= { = "1", = ["macros", "rt"] }
Run a prompt
use ;
use Duration;
async
Change Provider::Claude to select another CLI. The repository also includes
a runnable four-provider example:
Run those commands from this repository after authenticating the selected CLI. The example accepts an optional model as a second argument.
Provider examples
The following calls use the same RunRequest shape shown above. Set model
to None to inherit the selected CLI's configured default.
| Provider | Rust selection | Example model |
Native command |
|---|---|---|---|
| Claude | Provider::Claude |
Some("sonnet@high".into()) |
claude -p --output-format json |
| Codex | Provider::Codex |
Some("gpt-5.6-sol@high".into()) |
codex exec --json |
| Devin | Provider::Devin |
Some("swe-2-medium".into()) |
devin -p --prompt-file <file> |
| OpenCode v2 | Provider::OpenCodeV2 |
Some("openai/gpt-5#high".into()) |
opencode run --standalone --format json |
To try an explicit model for each provider:
Choose model IDs available in your authenticated CLI configuration.
Tools, MCP servers and system prompts
By default each CLI keeps its historical permissions (see Execution behavior),
which suits trusted batch jobs. For untrusted input, such as a chat bot forwarding user messages,
choose a restricted tool_policy. No restricted policy gives the agent a shell, file access or code
execution.
tool_policy |
Agent may use | Claude | Codex | Devin, OpenCode |
|---|---|---|---|---|
Default |
everything the CLI allowed before 0.4 | ✅ | ✅ | ✅ |
McpOnly |
tools of the request's mcp_servers |
✅ | ✅ | InvalidRequest |
Web |
McpOnly plus built-in web search and fetch |
✅ | ✅ | InvalidRequest |
use ;
let request = RunRequest ;
- Claude runs restricted policies with
--tools ""(orWebSearch,WebFetch),--allowedTools mcp__<name>…,--permission-mode dontAsk,--strict-mcp-configand--disable-slash-commands, and withCLAUDE_CODE_DISABLE_ATTACHMENTS=1so@pathmentions in the prompt are not inlined as file contents; MCP servers are passed with--mcp-configand the token header reads${VAR}from the child environment. - Codex runs restricted policies with its shell, exec, image, browser, computer-use, apps and
plugins features disabled, the read-only sandbox,
web_searchset toliveordisabled, and the user's ownmcp_serversreplaced by the request's (bearer_token_env_var). - System prompts are appended to the CLI's own (Claude
--append-system-prompt, Codexdeveloper_instructions); Devin and OpenCode receive them before the prompt.AgentCli::native_system_prompt()tells which case applies. - A CLI that cannot enforce a policy or MCP servers returns
Error::InvalidRequestand starts nothing.
The opt-in tests claude_restricted_runs_cannot_read_files and codex_restricted_runs_cannot_read_files
check with the real CLIs that a restricted run cannot read a file in its working directory, with a
tool or through an @path mention.
Sessions and progress events
Runs are ephemeral unless asked otherwise. Set persist_session: true to keep the CLI's session,
then pass RunResult::session_id as resume in a later request with the same cwd:
| Persist | Resume | Unknown session | |
|---|---|---|---|
| Claude | drops --no-session-persistence |
--resume <id> |
Error::SessionNotFound |
| Codex | drops --ephemeral |
codex exec resume <id> (sandbox via -c sandbox_mode) |
Error::SessionNotFound |
| OpenCode v2 | always | --session <id> |
the CLI starts a new session with that id |
| Devin | — | InvalidRequest |
— |
on_event: Some(EventHandler::new(|event| …)) reports progress while the run goes on:
AgentEvent::TextDelta (Claude streams the answer with --output-format stream-json),
AgentEvent::Text (whole messages from Codex and OpenCode) and AgentEvent::ToolUse { name }
(web searches, MCP tools). The final RunResult is the same as without events.
Structured errors
Use discover_with_context, with_binary_with_context, and
run_with_context when the caller needs the selected provider alongside a
failure. ProviderError exposes provider and the original source: Error;
exit_code() and stderr_tail() read the fields of an Error::Exit without
parsing its display text. The original methods remain available.
Real CLI smoke tests
The opt-in smoke tests make one short request per provider. Set the model to one supported by your authenticated CLI, then run a single test:
AGENT_CALL_CODEX_MODEL='gpt-5.6-sol@high' \
Replace CODEX and codex with CLAUDE/claude, DEVIN/devin, or
OPENCODE/opencode. These calls use real quota and have a 180-second timeout.
Claude and Codex receive a native JSON Schema; Devin and OpenCode output is
checked as JSON by the test because their adapters do not enforce schemas.
The regular cargo test suite uses fake CLIs and does not make real calls.
Releases
Release tags use vMAJOR.MINOR.PATCH and must match the version in
Cargo.toml. After updating Cargo.toml, Cargo.lock, and the README's
dependency example, publish to crates.io, then push the commit and its tag:
The release workflow tests and packages the
tagged crate, then creates a GitHub Release. Its body and attached
CHANGELOG.md list every commit since the previous version tag, including
commits made outside pull requests. To create a GitHub Release for an existing
tag, run the Release workflow manually and enter the tag. Re-running the
workflow for the same tag is safe.
The workflow creates GitHub Releases; crates.io publishing uses cargo publish.
Claude and Codex accept an optional json_schema and enforce it through their
native CLI flags. Devin and OpenCode return text for the caller to validate.
RunResult::usage is None when usage is unavailable or incomplete. For the
exact command and output contracts, see SPEC.md.
Execution behavior
Each request starts a new CLI process. A timeout or cancellation terminates
that process; the library does not retry automatically. Claude uses
bypassPermissions, Codex uses a read-only sandbox, Devin uses its auto
permission mode, and OpenCode follows its headless permission defaults without
--auto.
License: MIT or Apache-2.0.