Claude Code
Agent ID: claude
Claude Code from Anthropic. Speaks ACP via the @agentclientprotocol/claude-agent-acp bridge, which is bundled with agents-wire (no peer install needed).
Install
Follow the Claude Code setup guide, then authenticate:
claude /loginQuick Start
import { agents } from "@pivanov/agents-wire";
const result = await agents.ask(
"claude",
"Refactor src/auth.ts",
{
permission: "auto-allow",
maxCostUsd: 0.5,
},
);
console.log(result.text);
console.log(result.cost?.totalUsd);Model Selection
options.model is sent to Claude via ACP setSessionConfigOption (best-effort). Whether the running Claude process honors it depends on the Claude Code version installed.
await agents.ask(
"claude",
prompt,
{
model: "haiku",
},
);
await agents.ask(
"claude",
prompt,
{
model: "sonnet",
},
);
await agents.ask(
"claude",
prompt,
{
model: "opus",
},
);
// Full IDs also work if Claude's configOptions returns themTo see exactly which model knobs Claude currently advertises for a session, read session.configOptions:
import { createSession } from "@pivanov/agents-wire";
const session = await createSession("claude");
const opts = session.configOptions ?? [];
const modelOpt = opts.find(
(o) => o.configId === "model" && o.type === "select",
);
if (modelOpt && modelOpt.type === "select") {
console.log(modelOpt.options); // the exact values Claude accepts
}options.effort is sent via ACP setSessionConfigOption({ configId: "reasoning_effort" }). Claude itself uses token-budget thinking rather than a discrete effort enum, so the call is likely silently ignored. To set Claude's thinking budget directly, use the lower-level modelPreference: { configId: "thinking_budget", value: ... } (whatever configId Claude advertises in session.configOptions).
Capabilities
| Feature | Supported |
|---|---|
ask / stream / session | ✅ |
askJson | ✅ (strict — see below) |
| MCP stdio | ✅ |
| MCP http/sse | ✅ |
Session listing (listSessions) | ✅ |
| Tool call interception | ✅ |
| Thinking/extended reasoning | ✅ (model-dependent) |
Structured JSON (askJson)
For Claude, agents.askJson("claude", ...) and session.askJson route through @pivanov/claude-wire (a Claude-only specialist) so the spawned claude process gets --tools StructuredOutput and --json-schema at the CLI level. Output is token-constrained by the model, not just validated after the fact.
This is different from how askJson works for other vendors, which inject a JSON-formatting system prompt and parse-then-validate the response. The Claude strict path is essentially perfect on real prompts; the soft path is not. See the askJson docs for the per-vendor breakdown.
Routing is systemPrompt-aware:
- With
systemPrompt→ a pooled strict session keyed by(systemPrompt, schema fingerprint). The systemPrompt is Anthropic-prompt-cached across distinct schemas in the same session, so per-call cost drops to the diff. Use this for high-volume enrichments where a project catalog or preamble is shared across calls. - Without
systemPrompt→ claude-wire statelessclaude.askJson(cold spawn per call). Sessions accumulate per-turn context, so pooling without a cached prefix is a cost loss; the delegate routes around it.
@pivanov/claude-wire ships as a regular dependency of agents-wire — no extra install step.
Cost Tracking
Claude reports per-turn costUsd, inputTokens, outputTokens, cacheReadTokens, and cacheCreationTokens. Full cost tracking and maxCostUsd budget enforcement work.
Auth Failure Detection
If claude /login hasn't been run or the token has expired, the SDK catches the authentication-failure pattern in stderr and throws AgentUnauthenticatedError instead of a generic error.
Gotchas
- Claude Code requires a POSIX environment. Native Windows is not supported - use WSL.
- The
CLAUDE_CONFIG_DIRenv var controls where Claude reads its configuration. Override it viaoptions.envFilterif you need to point it elsewhere. - Large
systemPromptvalues benefit from prompt caching - checkcacheReadTokensin the cost snapshot to verify cache hits.