Providers

A provider sends one request to a model and returns its answer. CommonAgent ships the Anthropic Messages API and a mock.

A provider is the only part of CommonAgent that talks to a model. It takes a request with the system blocks, messages, tool specs and token limit, and returns text, tool calls, a stop reason and token usage.

Rust
#[async_trait]
pub trait Provider: Send + Sync {
    /// Provider name for the ledger, e.g. `anthropic`.
    fn name(&self) -> &str;
    async fn complete(&self, request: &ProviderRequest) -> Result<ProviderResponse, ProviderError>;
}
ProviderRequest ProviderResponse
model text: Option<String>
system: Vec<SystemBlock> tool_calls: Vec<ToolCall>
messages: Vec<Message> stop: end_turn, tool_use, max_tokens or other
tools: Vec<ToolSpec> usage: input, output, cache read and cache write tokens
max_tokens model: the model that actually answered

Anthropic

AnthropicProvider calls the Messages API at POST /v1/messages, without streaming.

  • Key: ANTHROPIC_API_KEY from the profile’s .env, or else from the environment cos runs in. Without a key, cos warns at start-up and model calls fail.
  • Caching: the stable and context system blocks are marked with cache_control, so the prefix is cached across turns. See Prompt Tiers.
  • Tool results: adjacent messages with the same API role are merged, so several tool results become one user message, as the API expects.
  • Base URL: AnthropicProvider::new(key).with_base_url(url) points it at a proxy or gateway.

Choose it in a profile’s config.toml:

config.toml
provider = "anthropic"
model = "<model id>"

CommonOS ships no model ids. You choose the model per profile.

Mock

MockProvider never leaves the machine. It is how you run CommonOS with no key, and how the tests run.

It has two modes:

Scripted. Returns queued responses in order. Use it in tests.

Rust
let provider = MockProvider::scripted(vec![
    MockProvider::tool_call("c1", "weather", json!({ "city": "Charleston" })),
    MockProvider::text("Bring an umbrella."),
]);

By rule. When the queue is empty, it answers the last message:

Last message Answer
A tool result done: <results>
delegate: <goal> Calls delegate_task with that goal
remember: <fact> Calls memory with add
Anything else echo: <text>

Every mock call reports 100 input and 20 output tokens. Its model id is mock-1, and CommonBill has a built-in price for it, so cost reports work end to end without a key.

Use the mock for every profile at once with cos serve --mock or COMMONOS_PROVIDER=mock, or for one profile with provider = "mock".

Errors

Rust
pub enum ProviderError {
    Http { status: u16, body: String },
    Transport(String),
    BadResponse(String),
    NotConfigured(String),
}

retryable() is true for HTTP 429, 5xx and transport errors. The loop reports a failed call through api_request_error and transform_api_error_classification, then ends the turn with status failed. Fallback providers on 429 and 5xx are on the roadmap.

Edit this page on GitHub

Esc