Agents/Providers/ — Multi-Model Provider Adapters

Unified adapter layer for 12+ LLM providers with model fallback and auth management

The providers sub-module handles all communication between OpenClaw agents and external LLM APIs. It abstracts provider differences behind a common interface—normalizing payload formats, managing API key rotation, implementing cascading model fallback with error categorization, and performing provider-specific schema sanitization. Each provider adapter encapsulates authentication, request formatting, and response parsing so the rest of the agent system can remain provider-agnostic.

12+
Provider Adapters
~24KB
model.ts
30+
Config Knobs
TS
TypeScript

Multi-Provider Auth Rotation Model Fallback Error Categorization

📚 What is this & why does it exist?

The providers layer is the translation bridge between OpenClaw’s agent runtime and the diverse world of LLM APIs. Each provider (OpenAI, Anthropic, Google Gemini, AWS Bedrock, Moonshot, Minimax, OpenRouter, Z AI, and others) expects requests in a different format, authenticates differently, and returns responses with different structures. This module normalizes all of that into a single interface the rest of the agent system consumes.

💡 Real-world analogy

Think of the United Nations interpreters’ booth. Delegates from 12+ countries each speak a different language (provider API). The interpreters (provider adapters) sit between the delegates and the assembly floor (agent runtime), translating every statement into a common protocol in real time. When one interpreter is unavailable (API key cooldown), the booth manager (auth-profiles.ts) seamlessly switches to a backup interpreter. If a delegate’s microphone fails entirely (provider outage), the fallback system (model-fallback.ts) routes the conversation to a different delegate who can cover the same topic. The assembly never sees the complexity—they just hear a smooth, unified feed.

Contents

Architecture

flowchart TD
  subgraph Resolution["Model Resolution"]
    HOOK["resolveHookModelSelection()\nHook-based model override"]
    RUNTIME["resolveEffectiveRuntimeModel()\nContext window calculation"]
    REG["model.ts\nModel registry & lookup"]
  end
  subgraph Auth["Auth & Credential Layer"]
    PROF["auth-profiles.ts\nProfile ordering & cooldown"]
    HEALTH["auth-health.ts\nAuthProfileHealth status"]
    SYNC["external CLI sync\nTTL-based refresh"]
  end
  subgraph Selection["Provider Selection"]
    NORM["model.provider-normalization.ts\nProvider ID normalization"]
    EXTRA["extra-params.ts\nProvider-specific parameters"]
    CAPS["openrouter-model-capabilities.ts\nCapability flags"]
  end
  subgraph Adapters["Provider Adapters"]
    OAI["OpenAI Adapter"]
    ANT["Anthropic Adapter"]
    GEM["Google/Gemini Adapter"]
    BED["AWS Bedrock Adapter"]
    MOON["Moonshot Adapter"]
    MINI["Minimax Adapter"]
    OR["OpenRouter Adapter"]
    ZAI["Z AI Adapter"]
    PROXY["Proxy Adapter"]
  end
  subgraph Sanitize["Schema Sanitization"]
    CLEAN["cleanForGemini()\nJSON Schema cleanup"]
    KEYWORDS["GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS"]
  end
  subgraph Fallback["Fallback & Recovery"]
    FB["model-fallback.ts\nCascading fallback chain"]
    ERR["FallbackSummaryError\nAll models exhausted"]
  end
  HOOK --> RUNTIME
  RUNTIME --> REG
  REG --> NORM
  NORM --> Auth
  Auth --> Selection
  Selection --> Adapters
  Adapters --> Sanitize
  Adapters --> Fallback
  Fallback -->|retry| Selection

Auth Profile State Machine

stateDiagram-v2
  [*] --> Valid
  Valid --> Cooldown : API error received
  Cooldown --> ValidProbe : soonestCooldownExpiry passes
  ValidProbe --> Valid : Probe succeeds
  ValidProbe --> Cooldown : Probe fails
  Valid --> Expired : Credential expires
  Expired --> Valid : Manual refresh / CLI sync
  Cooldown --> Expired : Credential expires during cooldown
  note right of Cooldown : Tracks (now, expiry) per profile
  note right of Valid : AuthProfileHealth = ok

Key Classes & Functions

Class / FunctionFilePurpose
FallbackSummaryErrormodel-fallback.tsError thrown when all models in the fallback chain are exhausted; aggregates per-model errors
resolveHookModelSelection()run/setup.tsResolves model selection through the hook system, allowing plugins to override model choice
resolveEffectiveRuntimeModel()run/setup.tsDetermines final runtime model with context window calculation and provider constraints
resolveAuthProfileOrder()auth-profiles.tsOrders auth credentials by health status, preferring non-cooldown profiles first
AuthProfileHealthauth-health.tsHealth enum: ok | expiring | expired | missing | static
cleanForGemini()schema/clean-for-gemini.tsStrips unsupported JSON Schema keywords from tool definitions before sending to Gemini
GEMINI_UNSUPPORTED_SCHEMA_KEYWORDSschema/clean-for-gemini.tsSet of JSON Schema keywords Gemini cannot process (e.g., $ref, allOf, oneOf)
extra-params.tspi-embedded-runner/Provider-specific extra parameters appended to request payloads
model.provider-normalization.tspi-embedded-runner/Normalizes provider ID strings across aliases and legacy names
openrouter-model-capabilities.tspi-embedded-runner/Capability flags for OpenRouter-hosted models (tool use, vision, streaming)
model.tspi-embedded-runner/Central model registry (~24KB); maps model IDs to provider, context window, and pricing
soonestCooldownExpiryauth-profiles.tsTracks per-profile cooldown expiry timestamps for automatic recovery scheduling
AUTH_STORE_LOCK_OPTIONSauth-profiles.tsFile lock config for concurrent auth store access (10 retries, 2× backoff)
EXTERNAL_CLI_SYNC_TTL_MSauth-profiles.tsTTL (900 000 ms / 15 min) for external CLI credential synchronization

Data Structures

TypeNameDescription
enumAuthProfileHealthHealth status: ok, expiring, expired, missing, static
typeModelRegistryEntryRegistry record: provider, model ID, context window, max output tokens, pricing tier
typeFallbackChainOrdered list of model candidates with per-model error categorization results
typeProviderPayloadNormalized request payload after provider-specific formatting
typeAuthProfileCredential record: API key, provider, cooldown state, expiry timestamp
constEXTERNAL_CLI_SYNC_TTL_MS900 000 (15 min) — TTL for external CLI auth synchronization
constAUTH_STORE_LOCK_OPTIONS{ retries: 10, factor: 2 } — File lock retry with exponential backoff
SetGEMINI_UNSUPPORTED_SCHEMA_KEYWORDSJSON Schema keywords stripped before Gemini requests

Interactions

DirectionPartnerInterfaceDescription
→LLM Provider APIsHTTP / SSESends formatted requests to provider endpoints via adapters
←Agent Runtimerun/setup.tsReceives model selection requests and returns resolved provider config
→Streaming PipelineStream wrappersHands off authenticated, formatted requests to provider-specific stream wrappers
→External CLIFile syncSynchronizes credentials from external CLI tools on a 15-min TTL
←Hook SystemresolveHookModelSelection()Plugins can override model selection through hook callbacks
→Schema SanitizercleanForGemini()Strips unsupported JSON Schema keywords before Gemini requests
→Fallback Enginemodel-fallback.tsCascades through alternative models on provider errors
←Config Systemagents.list[].modelReads primary model reference (provider/model) from agent configuration

Configuration

Knob / ParameterDefaultDescription
agents.list[].model—Primary model ref in provider/model format (e.g., openai/gpt-4o)
EXTERNAL_CLI_SYNC_TTL_MS900000External CLI auth synchronization TTL (15 minutes)
AUTH_STORE_LOCK_OPTIONS.retries10File lock retry count for concurrent auth store access
AUTH_STORE_LOCK_OPTIONS.factor2Exponential backoff multiplier for lock retries
soonestCooldownExpiry—Per-profile cooldown expiry timestamp; auto-set on API errors
model.contextWindowvariesContext window size per model from the model registry
model.maxOutputTokensvariesMaximum output token count per model from the registry
fallback.maxAttempts3Maximum number of fallback model attempts before throwing FallbackSummaryError
fallback.errorCategories—Error classification: retryable, fatal, auth, rate-limit, context-overflow
provider.streamingtrueWhether to use SSE streaming (some providers support non-streaming mode)
gemini.schemaSanitizationtrueEnable automatic stripping of unsupported JSON Schema keywords for Gemini
openrouter.capabilities—Per-model capability flags: tool_use, vision, streaming, function_calling

Error Categorization

The fallback engine classifies provider errors into categories that determine retry behavior:

CategoryHTTP StatusBehaviorExample
retryable500, 502, 503Retry same model with backoffProvider temporary outage
rate_limit429Mark profile cooldown, try next keyAPI rate limit exceeded
auth401, 403Mark profile expired, try next profileInvalid or expired API key
context_overflow400Cascade to model with larger contextInput exceeds context window
content_filter400Do not retry; surface to userProvider content policy violation
fatal*Abort immediatelyMalformed request structure

Provider Capability Matrix

ProviderStreamingTool UseVisionThinkingCache
OpenAI✓✓✓——
Anthropic✓✓✓✓✓
Google / Gemini✓✓✓——
AWS Bedrock✓✓✓——
Moonshot✓✓—✓—
Minimax✓✓———
OpenRouter✓variesvariesvaries—
Z AI✓✓———
Proxy✓pass-throughpass-through——

Directory Map

Source tree for providers
pi-embedded-runner/
├── model.ts                                  ← Central model registry (~24KB)
├── model.provider-normalization.ts           ← Provider ID normalization
├── model-fallback.ts                         ← Cascading model fallback logic
├── auth-profiles.ts                          ← Auth profile ordering & cooldown
├── auth-health.ts                            ← AuthProfileHealth enum
├── extra-params.ts                           ← Provider-specific parameters
├── openrouter-model-capabilities.ts          ← OpenRouter capability flags
├── schema/
│   └── clean-for-gemini.ts                   ← Gemini JSON Schema sanitizer
├── openai-stream-wrappers.ts                 ← OpenAI adapter
├── anthropic-cache-retention.ts              ← Anthropic adapter
├── google-stream-wrappers.ts                 ← Google/Gemini adapter
├── bedrock-stream-wrappers.ts                ← AWS Bedrock adapter
├── moonshot-stream-wrappers.ts               ← Moonshot adapter
├── minimax-stream-wrappers.ts                ← Minimax adapter
├── proxy-stream-wrappers.ts                  ← Generic proxy adapter
├── zai-stream-wrappers.ts                    ← Z AI adapter
└── run/
    └── setup.ts                              ← Model resolution functions

Deep-Dive Highlights

Core provider mechanisms have been extracted into dedicated focus pages for in-depth exploration. Each page covers one aspect of the provider adapter layer with diagrams, code walks, and configuration details.

Deep-Dive Pages

Adapter Pattern Focus

Provider adapter architecture and stream wrapper design. How each provider’s API is normalized into a common interface with provider-specific payload formatting.

Extracted topic page

Token Management Focus

API key rotation, cooldown tracking, and auth profile ordering. How credentials are managed across providers with automatic recovery and external CLI sync.

Extracted topic page

Model Selection Focus

Model resolution, fallback chains, and runtime model switching. How the system picks the right model and recovers when providers fail.

Extracted topic page

Code Examples

Example: FallbackSummaryError Construction

// When all models in the fallback chain fail, FallbackSummaryError
// aggregates every per-model error into one structured report.
import { FallbackSummaryError } from './model-fallback';

const perModelErrors = [
  { model: 'openai/gpt-4o',       error: 'rate_limit',      status: 429 },
  { model: 'anthropic/claude-3.5', error: 'auth_expired',    status: 401 },
  { model: 'google/gemini-pro',    error: 'context_overflow', status: 400 },
];

throw new FallbackSummaryError(
  `All ${perModelErrors.length} fallback models exhausted`,
  perModelErrors,           // per-model error details
  'openai/gpt-4o',         // original requested model
  3,                        // total attempts
);
// FallbackSummaryError.perModelErrors gives structured access
// to each failure for logging and user-facing diagnostics.

Example: Auth Profile Ordering Logic

// resolveAuthProfileOrder() sorts credentials so the agent
// always tries the healthiest key first.
import { resolveAuthProfileOrder } from './auth-profiles';

const profiles = await resolveAuthProfileOrder('openai');
// Returns profiles sorted by:
//   1. AuthProfileHealth.ok        — fully healthy, preferred
//   2. AuthProfileHealth.expiring  — near expiry, usable
//   3. AuthProfileHealth.static    — static key, no refresh
//   4. (skipped) expired / missing — excluded from rotation
//
// Each profile tracks `soonestCooldownExpiry` so the system
// knows when to re-probe a previously failed credential.

for (const profile of profiles) {
  if (profile.cooldownExpiry && profile.cooldownExpiry > Date.now()) {
    continue; // still in cooldown, skip
  }
  return profile; // use first available
}
// If none available: trigger EXTERNAL_CLI_SYNC_TTL_MS refresh
// (900_000 ms = 15 min TTL) then retry.

Example: Gemini Schema Sanitization

import { cleanForGemini, GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS } from './schema/clean-for-gemini';

// GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS includes:
// '$ref', 'allOf', 'oneOf', 'anyOf', 'not', 'if', 'then', 'else', ...

const toolSchema = {
  type: 'object',
  properties: {
    query: { type: 'string', minLength: 1 },
    filter: { oneOf: [{ type: 'string' }, { type: 'number' }] },
  },
  $ref: '#/definitions/SearchParams',
};

const cleaned = cleanForGemini(toolSchema);
// Result: { type: 'object', properties: { query: { type: 'string' }, filter: {} } }
// '$ref', 'oneOf', and 'minLength' stripped — Gemini cannot process them.