Resolve provider credentials from multiple sources (profiles, env vars, models.json) and probe each candidate to determine auth readiness.
Provider Flow
How OpenClaw resolves, probes, and selects provider authentication at runtime
When a user invokes a model command or an agent needs to make an LLM call, OpenClaw must decide which provider to use and how to authenticate. The provider flow pipeline resolves configuration, builds a list of probe targets from profiles, environment variables, and models.json, executes concurrent auth probes against each candidate, and returns a ranked summary. This module is the backbone of multi-provider credential management.
📚 What is this & why does it matter?
Provider flow is the authentication resolution pipeline that sits between user-facing model commands and the actual LLM API calls. Every time OpenClaw needs to verify a credential, rotate between providers, or report which auth sources are active, the provider flow module orchestrates the process. Understanding this pipeline is essential for debugging auth failures, configuring multi-provider setups, and extending the credential system with new provider types.
💡 Real-world analogy
Imagine an airport security checkpoint with multiple lanes — biometric, boarding pass, and priority access. Each traveler (API request) is checked against available lanes (providers) based on their ticket type (auth source). The system probes each lane for availability and wait time, then routes the traveler to the best open lane. If biometric is down, it falls back to boarding pass. The provider flow is that checkpoint controller — it probes every lane in parallel, records latency and status, and returns the fastest healthy option.
Contents
Overview
OpenClawConfig, a list of provider names, model candidates, and AuthProbeOptions controlling timeout and concurrency.
Which auth sources to include, how to order probes, which candidates pass validation, and how to handle rate-limited or expired credentials.
An AuthProbeSummary containing per-target results with status codes, latency measurements, and reason codes for failures. No mutations to config.
flowchart TD CFG["OpenClawConfig"] RESOLVE["resolveModelsAuthContext()"] PROVIDERS["Provider list"] MODELS["Model candidates"] BUILD["buildProbeTargets()"] TARGETS["AuthProbeTarget[]"] EARLY["Early-exit results"] PROBE["runAuthProbes()"] CONCURRENT["Concurrent HTTP probes"] COLLECT["Collect results + latency"] SUMMARY["AuthProbeSummary"] ORDER["Auth order filtering"] DISPLAY["Display to user / route to handler"] CFG --> RESOLVE RESOLVE --> PROVIDERS RESOLVE --> MODELS PROVIDERS --> BUILD MODELS --> BUILD BUILD --> TARGETS BUILD --> EARLY TARGETS --> PROBE PROBE --> CONCURRENT CONCURRENT --> COLLECT COLLECT --> SUMMARY EARLY --> SUMMARY SUMMARY --> ORDER ORDER --> DISPLAY
The diagram shows the full provider auth pipeline. Configuration enters at the top and is resolved into provider and model lists. The buildProbeTargets function generates concrete probe targets (some may be excluded early with reason codes). The remaining targets are probed concurrently, and results are collected into an AuthProbeSummary. Auth order commands can then filter or re-rank the results before they are displayed or used for routing.
Core Types
The provider flow module revolves around two key type families: probe results that capture the outcome of individual auth checks, and provider overviews that summarize the effective credential state for display commands.
AuthProbeResult & AuthProbeStatus
Each probe target produces an AuthProbeResult with a status enum indicating whether the credential is valid, rate-limited, expired, or otherwise unusable. The reasonCode field provides machine-readable detail for failures.
// src/commands/models/auth.ts
export type AuthProbeStatus =
| "ok"
| "auth"
| "rate_limit"
| "billing"
| "timeout"
| "format"
| "unknown"
| "no_model";
export type AuthProbeReasonCode =
| "excluded_by_auth_order"
| "missing_credential"
| "expired"
| "invalid_expires"
| "unresolved_ref"
| "ineligible_profile"
| "no_model";
export type AuthProbeResult = {
provider: string;
model?: string;
profileId?: string;
label: string;
source: "profile" | "env" | "models.json";
mode?: string;
status: AuthProbeStatus;
reasonCode?: AuthProbeReasonCode;
error?: string;
latencyMs?: number;
};
| Status | Meaning | Typical cause |
|---|---|---|
ok | Credential is valid and the model responded | Successful lightweight probe request |
auth | Authentication failed (401/403) | Invalid or revoked API key / token |
rate_limit | Provider returned 429 | Too many requests; credential is valid but throttled |
billing | Billing or quota error | Account has exceeded spend limits |
timeout | Probe did not respond in time | Network issue or slow provider endpoint |
format | Response was not valid JSON/expected schema | Provider returned HTML error page or unexpected format |
unknown | Unclassified failure | Catch-all for unexpected HTTP status codes |
no_model | The requested model is not available on this provider | Model name mismatch or model not deployed |
AuthProbeSummary
The summary type wraps all individual results with timing metadata. The durationMs field measures wall-clock time for the entire probe batch, including concurrency overhead.
// src/commands/models/auth.ts
export type AuthProbeSummary = {
startedAt: number;
finishedAt: number;
durationMs: number;
totalTargets: number;
options: AuthProbeOptions;
results: AuthProbeResult[];
};
ProviderAuthOverview
Used by the models list display layer, this type summarizes the effective auth configuration for a single provider without performing any live probes.
// src/commands/models/list.types.ts
export type ProviderAuthOverview = {
provider: string;
effective: {
kind: "profiles" | "env" | "models.json" | "missing";
detail: string;
};
profiles: {
count: number;
oauth: number;
token: number;
apiKey: number;
labels: string[];
};
env?: { value: string; source: string };
modelsJson?: { value: string; source: string };
};
Decision Points
| Decision | What controls it | Why it matters |
|---|---|---|
| Auth source priority | source field on AuthProbeResult — profiles take precedence over env, which takes precedence over models.json | Ensures user-configured profiles override implicit environment credentials |
| Auth order exclusion | modelsAuthOrderGetCommand retrieves a per-provider ordering; targets not in the order list receive excluded_by_auth_order | Allows users to pin specific credentials for specific providers without removing others |
| Probe concurrency | AuthProbeOptions controls max parallel probes and per-probe timeout | Prevents overwhelming providers with simultaneous requests while keeping total latency low |
| Early-exit vs live probe | buildProbeTargets can mark targets as early-exit (e.g. missing_credential, expired) without issuing an HTTP request | Saves network round-trips for credentials that are known-invalid from config alone |
| Model candidate filtering | The modelCandidates array intersected with provider-supported models | Avoids probing providers that cannot serve any of the requested models |
How It Works
The provider flow pipeline operates in four distinct phases: context resolution, target building, concurrent probing, and order management. Each phase is implemented as a separate async function, enabling callers to compose only the phases they need.
Phase 1 — Auth Context Resolution
Before any probing can occur, the pipeline must resolve the current configuration into a structured auth context. The resolveModelsAuthContext function loads and validates the config, resolves the default agent, and discovers all available plugin providers.
// src/commands/models/auth.ts
async function resolveModelsAuthContext(): Promise<ResolvedModelsAuthContext> {
const config = await loadValidConfigOrThrow();
const defaultAgentId = resolveDefaultAgentId(config);
const agentDir = resolveAgentDir(config, defaultAgentId);
const workspaceDir =
resolveAgentWorkspaceDir(config, defaultAgentId) ??
resolveDefaultAgentWorkspaceDir();
const providers = resolvePluginProviders({
config,
workspaceDir,
// ... additional options
});
return { config, agentDir, workspaceDir, providers };
}
Phase 2 — Probe Target Building
The buildProbeTargets function takes the resolved providers and model candidates and produces two outputs: a list of AuthProbeTarget objects for live probing and a list of AuthProbeResult objects for targets that were excluded early.
// src/commands/models/auth.ts
export async function buildProbeTargets(params: {
cfg: OpenClawConfig;
providers: string[];
modelCandidates: string[];
options: AuthProbeOptions;
}): Promise<{
targets: AuthProbeTarget[];
results: AuthProbeResult[];
}>
missing_credential, expired, invalid_expires, unresolved_ref, or ineligible_profile reason codes are returned in the results array without ever hitting the network. This is a critical performance optimization for setups with many configured but inactive providers.
Enumerate credential sources. For each provider in the providers array, the function collects credentials from three sources: auth profiles (OAuth tokens, API keys, bearer tokens), environment variables (OPENAI_API_KEY, etc.), and models.json entries. Each source produces one or more candidate targets.
Cross-product with model candidates. Each credential source is paired with each model candidate to produce the full target matrix. If a provider declares supported models, only the intersection is kept.
Apply early-exit filters. Targets with missing, expired, or invalid credentials are immediately converted to AuthProbeResult entries with the appropriate reasonCode and removed from the live-probe list.
Apply auth order filter. If the user has set an auth order for this provider (via modelsAuthOrderSetCommand), targets not in the order list are excluded with excluded_by_auth_order. Remaining targets are reordered to match the user-specified sequence.
Return split results. The function returns { targets, results } where targets are ready for live probing and results contain all early-exit entries.
Phase 3 — Concurrent Auth Probing
The runAuthProbes function takes the targets from Phase 2 and executes lightweight HTTP requests against each provider endpoint in parallel. It reports progress via an optional callback and returns the complete AuthProbeSummary.
// src/commands/models/auth.ts
export async function runAuthProbes(params: {
cfg: OpenClawConfig;
providers: string[];
modelCandidates: string[];
options: AuthProbeOptions;
onProgress?: (update: {
completed: number;
total: number;
label?: string;
}) => void;
}): Promise<AuthProbeSummary>
sequenceDiagram
participant Caller as Model Command
participant Run as runAuthProbes()
participant Build as buildProbeTargets()
participant P1 as Provider A Probe
participant P2 as Provider B Probe
participant P3 as Provider C Probe
participant Summary as AuthProbeSummary
Caller->>Run: providers, modelCandidates, options
Run->>Build: cfg, providers, modelCandidates
Build-->>Run: targets[A,B,C] + earlyExits[D]
par Concurrent probes
Run->>P1: HTTP HEAD /v1/models (API key)
Run->>P2: HTTP HEAD /v1/models (OAuth token)
Run->>P3: HTTP HEAD /v1/models (env key)
end
P1-->>Run: 200 OK (45ms)
P2-->>Run: 401 Unauthorized (32ms)
P3-->>Run: 429 Rate Limited (120ms)
Run->>Run: onProgress({ completed: 3, total: 3 })
Run->>Summary: merge earlyExits + probe results
Run-->>Caller: AuthProbeSummary
The sequence diagram shows how runAuthProbes orchestrates the full pipeline. It first calls buildProbeTargets to get the live targets and early-exit results. The live targets are probed concurrently (respecting the concurrency limit in AuthProbeOptions). Each probe returns an HTTP status code and latency measurement. The progress callback fires after each probe completes. Finally, early-exit results and live probe results are merged into a single AuthProbeSummary.
Phase 4 — Auth Order Management
Users can control which credentials are used for a given provider via three order management commands. These commands read and write a per-provider auth order that is respected by buildProbeTargets.
// src/commands/models/auth-order.ts
export async function modelsAuthOrderGetCommand(
opts: { provider: string; agent?: string; json?: boolean },
runtime: RuntimeEnv,
)
export async function modelsAuthOrderSetCommand(
opts: { provider: string; agent?: string; order: string[] },
runtime: RuntimeEnv,
)
export async function modelsAuthOrderClearCommand(
opts: { provider: string; agent?: string },
runtime: RuntimeEnv,
)
| Command | Effect | Use case |
|---|---|---|
auth-order get | Reads the current auth order for a provider; outputs JSON or human-readable list | Inspecting which credential labels are active and in what priority |
auth-order set | Writes a new ordered list of credential labels for a provider | Pinning a specific OAuth profile over an API key for production use |
auth-order clear | Removes any custom ordering, reverting to the default source-priority resolution | Resetting after debugging or switching between agent configurations |
Edge Cases
| Scenario | Behavior | Result | Rationale |
|---|---|---|---|
| All probes timeout | Every target returns status: "timeout" | Summary has zero "ok" results; caller surfaces a connectivity warning | Prevents silent fallback to a broken provider |
| No providers configured | buildProbeTargets returns empty targets and empty results | Summary with totalTargets: 0; caller prompts setup instructions | Graceful handling of fresh installations |
| Expired OAuth token | Early-exit with reasonCode: "expired" | No HTTP request is made; result is included in summary | Avoids wasting a probe on a credential that config alone shows is invalid |
| Rate-limited but valid | Probe returns status: "rate_limit" with valid latency | Credential is considered valid but deprioritized in ranking | A 429 means the credential works — the provider is just busy |
| Auth order excludes all | All targets receive excluded_by_auth_order | No live probes run; summary contains only early-exit results | User may have misconfigured the order; clear command is the fix |
| Duplicate credential sources | Same API key in both env var and profile | Both appear as separate targets; both are probed | Deduplication is intentionally not performed — allows users to see redundancy |
| Model not on provider | Provider does not list the model in supported set | Early-exit with reasonCode: "no_model" | Prevents a guaranteed 404 from reaching the network |
Configuration
| Setting | Location | Default | Description |
|---|---|---|---|
| Probe timeout | AuthProbeOptions.timeoutMs | 5000 | Maximum milliseconds to wait for a single probe response |
| Concurrency limit | AuthProbeOptions.concurrency | 5 | Maximum number of simultaneous HTTP probe requests |
| Provider list | params.providers (resolved from config) | All configured | Which providers to include in the probe sweep |
| Model candidates | params.modelCandidates | All models in config | Which models to cross-product with provider credentials |
| Auth order | Per-provider stored setting (managed via auth-order commands) | None (use source priority) | Custom ordering of credential labels within a single provider |
| Default agent | OpenClawConfig.defaultAgentId | First agent | Which agent context to use when resolving workspace and provider directories |
Related Topics
Uses probe results to select the optimal provider/model combination for a given request.
Provider configuration discovery and workspace search path resolution.
How slash commands like /models auth-order set are parsed before reaching the auth order handlers.
Source Files
Key files involved in provider flow
src/
├── commands/
│ └── models/
│ ├── auth.ts ← AuthProbeResult, buildProbeTargets(), runAuthProbes()
│ ├── auth-order.ts ← Auth order get/set/clear commands
│ ├── list.types.ts ← ProviderAuthOverview display type
│ └── list.ts ← Models list command (uses ProviderAuthOverview)
├── config/
│ ├── loader.ts ← loadValidConfigOrThrow()
│ └── agents.ts ← resolveDefaultAgentId(), resolveAgentDir()
└── plugins/
└── providers.ts ← resolvePluginProviders()