CrowdControl.Agent adapter for the Claude Code stream-json wire format.
Also drives the open-code CLI, which speaks the same protocol; pass
executable: "open-code".
Argv construction lives in CrowdControl.CLI and framing in
CrowdControl.Protocol; this module only binds the two to the behaviour.
The CLI is launched with --input-format stream-json, so it reads prompts
from stdin for the life of the session and needs no handshake.
Credentials
All of these travel through the session's validated environment (a 0600 env
file locally, the exec Env array remotely) — never argv, never ps:
:api_key— pay-per-use API key. SetsANTHROPIC_API_KEY, sent asx-api-key.:oauth_token— subscription (Pro/Max/Team). SetsCLAUDE_CODE_OAUTH_TOKEN, the long-lived token minted byclaude setup-token. This is the headless way to bill a session to a Claude subscription instead of an API key, and it wins over:api_key.:auth_token— bearer credential for a gateway or a self-hosted endpoint. SetsANTHROPIC_AUTH_TOKEN, sent asAuthorization: Bearer, which is what most non-Anthropic servers expect.env: %{"CLAUDE_CONFIG_DIR" => "/path/to/.claude"}— point the CLI at an existing logged-in config directory. Use this whenclaude auth loginalready ran on the host and you want sessions to inherit that login wholesale; for containers, mount~/.claudein and set the variable to the mount path.
Self-hosted and gateway endpoints
Claude Code speaks only the Anthropic Messages API, so any endpoint you
point it at has to serve /v1/messages — not just an OpenAI-compatible
/v1/chat/completions. Recent vLLM builds expose both; LiteLLM has an
Anthropic passthrough; a plain OpenAI-only server will not work (use
CrowdControl.Agent.Omp's :custom_provider for those).
CrowdControl.run("Summarize this repo",
api_url: "http://10.0.0.5:8000", # no /v1 -- the CLI appends it
auth_token: System.fetch_env!("VLLM_KEY"),
model: "deepseek-v4-flash"
)Check before you wire it up:
curl -sS $BASE/v1/messages -H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d '{"model":"…","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'A JSON body with "type": "message" means Claude Code can drive it. Note that
"total_cost_usd" on the result is then meaningless — the CLI prices the
response against Anthropic's table for a model it does not know.