Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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)
  • OpenRouterProvider
  • OllamaProvider (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:

  1. format_request() produces (url, headers, body).
  2. The harness issues web_request() with that data.
  3. Zellij delivers Event::WebRequestResult.
  4. parse_response() (on HTTP 200) or parse_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.