Each provider implements ProviderPort for a specific LLM API. All providers share the same config structure but differ in defaults and capability implementation.
Every provider accepts these fields when created via the CRUD API:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Human-readable connection name |
provider_kind |
string | Yes | — | Provider type (see below) |
auth_type |
string | Yes | — | api_key or oauth |
credentials |
object | Yes | — | Key-value pairs (see per provider) |
base_url |
string | No | Provider-specific | API base URL |
is_active |
bool | No | false |
Whether connection is in rotation |
priority |
u8 | No | 0 | Priority for routing (higher = preferred) |
default_model |
string | No | — | Default model ID if provider has multiple |
Kind: openai
Auth: API key (Bearer token) or OAuth access token
Default base URL: https://api.openai.com
Default timeout: 60s
API example:
{
"name": "openai-primary",
"provider_kind": "openai",
"auth_type": "api_key",
"credentials": { "api_key": "${OPENAI_API_KEY}" },
"is_active": true,
"priority": 1
}Health check behavior:
- Makes a
GET /modelsrequest tobase_url - Returns
HealthStatus::Healthyif response is 2xx - Returns
HealthStatus::Unhealthywith error message on non-2xx or connection failure
Health status enum:
Providers return one of: Healthy { provider, latency_ms }, Unhealthy { provider, latency_ms, error }, or Unknown { provider, reason }. The /health endpoint renders backwards-compatible JSON with healthy (bool), latency_ms, and last_error derived from the enum.
Implementation status:
complete()— ✅ Implementedstream()— ❌ Not yet implemented
Kind: anthropic
Auth: API key (x-api-key header) or OAuth access token
Default base URL: https://api.anthropic.com
Default timeout: 60s
API example:
{
"name": "anthropic-primary",
"provider_kind": "anthropic",
"auth_type": "api_key",
"credentials": { "api_key": "${ANTHROPIC_API_KEY}" },
"is_active": true,
"priority": 1
}Health check behavior:
- Placeholder: always returns
HealthStatus::Unknown { reason: "health_check_not_supported" } - TODO: real health check against Anthropic API
Implementation status:
complete()— ❌ Not yet implementedstream()— ❌ Not yet implemented
Kind: ollama
Auth: None (local deployment)
Default base URL: http://localhost:11434
Default timeout: 300s (higher than cloud providers — local models are slower)
API example:
{
"name": "ollama-local",
"provider_kind": "ollama",
"base_url": "http://localhost:11434",
"is_active": true,
"priority": 1
}Note:
base_urlis required for Ollama since there is no sensible default for non-local deployments.
Health check behavior:
- Two-step probe:
GET /api/tags— verify host reachabilityPOST /api/chat(if API key configured) — verify credentials
- Returns
HealthStatus::Warningif no API key configured (for Ollama Cloud use cases)
Implementation status:
complete()— ✅ Implementedstream()— ✅ Implemented
Note: Ollama uses its native API format (/api/chat), NOT the OpenAI-compatible endpoint.
Kind: ollama-cloud
Auth: API key (Bearer token)
Default base URL: https://ollama.com
Default timeout: 300s (cloud models may take longer than standard API providers)
API example:
{
"name": "ollama-cloud-primary",
"provider_kind": "ollama-cloud",
"auth_type": "api_key",
"credentials": { "api_key": "${OLLAMA_CLOUD_API_KEY}" },
"is_active": true,
"priority": 1
}Getting an API key:
- Go to ollama.com/settings/keys — Sign in required; you'll be redirected to signin.ollama.com if not authenticated
- Generate a new API key
- Store it securely (env var, secrets manager, etc.)
Health check behavior:
- Two-step probe:
GET /api/tags— verifyollama.comis reachable (public endpoint)POST /api/chatwith Bearer auth — verify API key is valid
- Returns
HealthStatus::Warningif API key is missing (provider still works but credentials aren't validated) - Returns
HealthStatus::Unhealthywith error if auth is rejected (401/403)
Implementation status:
complete()— ✅ Implementedstream()— ✅ Implemented
Note: Both local Ollama and Ollama Cloud use the same providers_ollama::OllamaProvider adapter, driven by OllamaProviderConfig:
- Ollama Cloud (
ProviderKind::OllamaCloud):base_urldefaults tohttps://ollama.com,api_keyset to Bearer token enablingAuthorization: Bearer ...headers - Local Ollama (
ProviderKind::Ollama):base_urldefaults tohttp://localhost:11434,api_keyunset (no auth)
Kind: gemini
Auth: API key (query param ?key=...) or OAuth access token
Default timeout: 60s
API example:
{
"name": "gemini-primary",
"provider_kind": "gemini",
"auth_type": "api_key",
"credentials": { "api_key": "${GEMINI_API_KEY}" },
"is_active": true,
"priority": 1
}Health check behavior:
- Placeholder: always returns
HealthStatus::Unknown { reason: "health_check_not_supported" } - Per
HealthStatus::is_healthy(), this yieldsis_healthy() === falseand the/healthendpoint rendershealthy: false - TODO: real health check
Implementation status:
complete()— ❌ Not yet implementedstream()— ❌ Not yet implemented
Kind: groq
Auth: API key (Bearer token) or OAuth access token
Default timeout: 60s
API example:
{
"name": "groq-fast",
"provider_kind": "groq",
"auth_type": "api_key",
"credentials": { "api_key": "${GROQ_API_KEY}" },
"is_active": true,
"priority": 1
}Health check behavior:
- Placeholder: always returns
HealthStatus::Unknown { reason: "health_check_not_supported" } - Per
HealthStatus::is_healthy(), this yieldsis_healthy() === falseand the/healthendpoint rendershealthy: false - TODO: real health check
Implementation status:
complete()— ❌ Not yet implementedstream()— ❌ Not yet implemented
| Provider | complete() | stream() | Real health check | Default timeout |
|---|---|---|---|---|
| OpenAI | ✅ | ✅ | ✅ | 60s |
| Anthropic | ❌ | ❌ | ❌ (placeholder) | 60s |
| Ollama (1) | ✅ | ✅ | ✅ (2-step) | 300s |
| Ollama Cloud (1) | ✅ | ✅ | ✅ (2-step) | 300s |
(1) Ollama and Ollama Cloud share the same OllamaProvider implementation. Local Ollama uses http://localhost:11434 with no API key; Ollama Cloud uses https://ollama.com with Bearer auth configured via api_key.
| Gemini | ❌ | ❌ | ❌ (placeholder) | 60s | | Groq | ❌ | ❌ | ❌ (placeholder) | 60s |
All providers share the same circuit breaker behavior in FallbackRouter:
- Threshold: 3 consecutive failures
- Cooldown: 30 seconds before retry
- Behavior: Provider is skipped in selection until cooldown expires
When a provider fails, call RouterPort::on_failure(provider_id, error) to record the failure in the circuit breaker.
Providers now return an enum instead of a bool-shaped status:
pub enum HealthStatus {
Healthy { provider: ProviderId, latency_ms: u64 },
Unhealthy { provider: ProviderId, latency_ms: Option<u64>, error: String },
Unknown { provider: ProviderId, reason: String },
}OpenAI performs a real /models probe. Provider adapters without a real probe (Anthropic, Ollama, Gemini, Groq) return Unknown { reason: "health_check_not_supported" }. For these providers, HealthStatus::is_healthy() returns false, and the public /health endpoint renders healthy: false, latency_ms: null, and last_error containing the reason string.
When [provider_crud].enabled = true, Rook exposes /api/providers CRUD endpoints backed by SQLite. Credentials are encrypted at rest using enc:v1:{nonce}:{ciphertext_and_tag} and API responses always return credentials: {}.
The provider registry is populated from the database on startup via refresh_registry(), which lists all active connections, decrypts credentials, and builds providers. The registry is also refreshed after each create, update, or delete operation. If provider_crud.enabled = false, no providers are routed (all requests return 503).
- Create
crates/infrastructure/providers-{name}/src/lib.rs - Define a provider config struct and constructor
- Implement
ProviderPortwith#[async_trait] - Add to
Cargo.tomlworkspace members - Add match arm in
apps/rook/src/di.rs→build_provider_from_connection() - Add tests