API Integration
The harness talks to LLM providers over Zellij’s web_request() API (the
WebAccess permission). There is no synchronous HTTP call: the provider layer
only formats requests and parses responses, and Zellij delivers the result
asynchronously as an event.
Source: crates/core/src/provider/.
Providers
Five providers implement the LlmProvider trait, selected by ProviderType:
ClaudeProvider(Anthropic Messages API)OpenAIProvider(Chat Completions / Responses API)CodexProvider(ChatGPT subscription Responses API with SSE)OpenRouterProviderOllamaProvider(local, keyless)
The LlmProvider trait
The trait is deliberately not async — Zellij handles async via events. It only serializes requests and deserializes responses:
#![allow(unused)]
fn main() {
pub trait LlmProvider {
fn name(&self) -> &str;
fn format_request(
&self,
messages: &[Message],
tools: &[Tool],
system: &str,
) -> (String, BTreeMap<String, String>, Vec<u8>);
fn parse_response(&self, body: &[u8]) -> Result<LlmResponse, ProviderError>;
fn parse_error(&self, status: u16, body: &[u8]) -> ProviderError;
fn set_model(&mut self, model: &str);
fn api_key(&self) -> &str;
}
}
The request flow is:
format_request()produces(url, headers, body).- The harness issues
web_request()with that data. - Zellij delivers
Event::WebRequestResult. parse_response()(on HTTP 200) orparse_error()(otherwise) interprets it.
set_model() supports switching the model at runtime (the /model command).
Credentials
The agent does not hold real API keys. When the secrets proxy is enabled
(services.cowboy.secretsProxy.enable), the harness sends placeholder keys and
the proxy injects the real credentials on the wire based on per-domain mappings.
Codex is the same pattern: its rotating ChatGPT subscription token is injected
outside the agent. See Security Model. For keyless
providers (Ollama) api_key() is empty.
Provider defaults
ClaudeProvider targets /v1/messages. Its defaults: model
claude-sonnet-4-20250514, max_tokens 8192, extended thinking enabled with a
token budget. OpenAIProvider defaults to gpt-4o and auto-selects the
Responses API for reasoning models (o-series, gpt-5, codex) to capture reasoning
summaries, which are mapped onto thinking content blocks.
Message model
Messages carry a string role and a vector of ContentBlocks, matching the
Claude API’s native content-block model. A block’s block_type is one of
text, tool_use, tool_result, or thinking.
Errors and retries
ProviderError includes ParseError, ApiError, NetworkError,
RateLimited, Timeout, InvalidRequest, AuthenticationError, and
Overloaded. is_retryable() marks rate limits (429), network errors, server
errors (5xx), and timeouts as retryable; auth and other 4xx errors are not.
Automatic retry is wired in: a RetryState with exponential backoff drives a
bounded retry of failed LLM calls from the ranch handlers
(crates/core/src/handlers.rs, schedule_llm_retry) — a retryable error
schedules a backed-off retry until max_attempts is exhausted, after which the
failure surfaces as an error message.
Bridges
Bridge services (Discord, email) do not use the provider layer. They reach the agent through the pub/sub message system: inbound messages are polled and processed as user input, and replies flow back out the same way. See Approvals & Outbox.