Skip to content

Providers

The providers config map declares which model providers the agent supports, and it is a gate, not a hint. What an agent advertises as selectable is exactly:

  • every provider listed in providers,
  • every provider named by runtime.model or a runtime.fallbacks[] entry — routing through a provider you did not mean to support is not possible, so those count as declared,
  • ollama and lmstudio when zero-config discovery finds them running.

Declaring a provider widens selection to that provider’s whole advertised catalog (up to maxAdvertisedModels, default 100), not just the models you route to. A Pi built-in that nobody declared and no route uses is not offered at all: advertising all 39 would let an operator pick a provider the agent holds no credential for, and the failure would surface only when the turn ran.

Coverage: config. Configure the map in mono-agent.config.json under providers, or via MONO_AGENT_PROVIDERS_JSON as a JSON object with the same shape.

Three keys inside providers are reserved for Pi runtime configuration; every other key is a provider id:

KeyPurpose
piAuthPathPath to the Pi auth store (default ~/.pi/agent/auth.json; env MONO_AGENT_PI_AUTH_PATH). OAuth/account credentials and API keys live here.
piNativePi transport and retry tuning: transport (auto/sse/websocket/websocket-cached), piMaxRetries (0–8, default 2), maxRetryDelayMs (default 60000), piSessionsRoot (durable JSONL session storage).
localLegacy compatibility projection for providers.local[]. New configs prefer the provider-map shape below.

Each non-reserved key is a provider id, and its value is a provider definition:

{
"providers": {
"ollama": {
"type": "ollama",
"baseUrl": "http://localhost:11434",
"models": [{ "name": "gemma4:31b", "capabilities": { "context_window": 32768 } }]
},
"my-gateway": {
"type": "openai_compat",
"baseUrl": "https://gateway.example.com/v1",
"apiKeyEnv": "GATEWAY_API_KEY"
}
}
}

Each definition accepts: enabled, type (ollama/lmstudio/openai_compat for a self-hosted endpoint — a baseUrl requires one of these unless the id is ollama or lmstudio), baseUrl, trustPublicUrl, apiKey / apiKeyEnv (give the secret’s variable name in .env, never an inline value), models[] (with name, optional alias/displayName, enabled, capabilities like context_window, and pricing), and maxAdvertisedModels.

In JSON config, providers are always the map above (or the legacy providers.local[]). providers.entries[] is the resolved shape — what resolveConfiguredProviders() returns and what a programmatic embedder constructing a MonoAgentConfig in code may set directly. It is not accepted from mono-agent.config.json or MONO_AGENT_PROVIDERS_JSON: entries is not a reserved key there, so it would be read as a provider whose id is entries. Duplicate provider ids are rejected.

ollama and lmstudio are checked against their localhost endpoints without any configuration. A route that names either id is admitted even when providers does not list it, and the running agent probes the endpoint live:

Provider idEndpoint
ollamahttp://localhost:11434
lmstudiohttp://localhost:1234

Probes run concurrently, fail independently, and a live probe lists the endpoint’s models into the operator-facing catalog (bounded by maxAdvertisedModels, default 100). A provider entry that declares enabled: false is not probed.

A declared provider’s catalog becomes selectable — every model it advertises, up to maxAdvertisedModels (default 100 per provider), or exactly the ids you list in that provider’s models allowlist. That applies to:

  • runtime.model and runtime.fallbacks[] (<provider>:<model> refs)
  • per-trigger model overrides (cron jobs, webhook endpoints and request bodies)
  • the per-conversation model selector surfaced by Telegram/Slack/web runtimeControls
  • agent-host memory.llm.model when you run BuJo capture through a declared provider

A route that names a provider in none of providers, Pi’s built-in catalog, nor the two autodiscoverable ids fails closed at startup with a repair message naming the endpoint it needs — for example Provider "private-provider" used by runtime.model is not available; add "providers": { "private-provider": { "type": "openai_compat", "baseUrl": "https://..." } } to mono-agent.config.json. A bare {} entry is deliberately not enough: Pi has no built-in catalog for an unknown id, so an entry with no baseUrl is rejected at load rather than validating and then failing at turn time.

  • Pi-level auth (OAuth/account providers such as Anthropic, GitHub Copilot, OpenAI Codex; API-key providers such as OpenCode-Go) resolves through providers.piAuthPath. Set it up with mono-agent auth login <provider> [--pi-auth-path ...].
  • Provider-level keys resolve through the definition’s apiKeyEnv: keep the value in .env and reference only its variable name in config, so the committed file carries no secret.

Every current app-owned running agent advertises provider auth by default. The web console’s Agent settings dialog lists only providers used by its effective primary/fallback routes, agent-host memory LLM, and enabled static cron/webhook overrides. Each row distinguishes credential detection (present, expired, missing, or keyless not applicable) from proof by a successful live model request. A completed login is therefore detected but remains unverified until a real turn succeeds. Recent process-local provider-auth and provider-unavailable failures are warnings alongside that state; they are not durable history.

Static inspection is intentionally conservative. A stored OAuth access token past expires remains usable-looking when it has a non-empty refresh token, because Pi can refresh it during a request. An expired access token without a refresh path is expired, while empty material, an unsupported credential type, an invalid expiry, an unsafe/unreadable store, or ambient file existence alone needs action. Environment API-key presence may establish only present/not_verified; it never establishes live health.

The headless login flow is provider-owned by Pi 0.85.1:

  • GitHub Copilot and OpenAI Codex use native device authorization: open the displayed URL on any browser, enter the displayed code, and leave the dialog open while the agent host polls.
  • Anthropic has no device-code flow in this Pi release. Open its authorization URL and, when the browser cannot reach the callback on the remote host, paste the complete final localhost redirect URL into the dialog. Pi validates the callback state before exchange.
  • OpenCode-Go and other Pi API-key providers render their provider-owned prompts in the dialog. Secret prompts are masked and cleared as they are submitted.

There is no --device-auth CLI flag. Browser login uses Pi’s real device-code or manual-code interaction contract; CLI syntax remains mono-agent auth login. The agent host alone reads and writes providers.piAuthPath, using the same owner-only, locked, no-clobber promotion path as auth login --api-key-stdin. The web service only proxies short-lived, no-store session projections and never persists submitted values. This feature does not inspect or modify Codex CLI credentials such as ~/.codex/auth.json.

A valid repeated login replaces the current login with a fresh session after provider/method validation; invalid requests do not cancel a usable prompt. Cancellation fences late prompts, events, results, and pre-promotion credential writes. A credential promotion that has already changed filesystem state must finish or recover under the existing atomic transaction. The replacement waits up to two seconds for that safe drain and otherwise fails with replacement_timeout without starting a competing write. Provider checks are not cancelled implicitly.

The agent routes are keyless when its operator endpoint has no API key and otherwise retain the endpoint’s normal bearer requirement. No additional auth configuration is required to make the surface available.

The explicit Run check action uses this agent’s effective piAuthPath, environment, local-provider definition, and Pi implementation. It creates one isolated request for the selected provider/model with a four-token output cap, no conversation history, memory, tools, MCP servers, fallback, or model retry, then discards provider output and raw errors. Candidates must accept text and be enabled. The lowest catalog estimate for 64 input plus 4 output tokens wins; ties follow effective route order then model id. Multiple candidates are not checked when any price is unknown; exactly one unknown-price candidate is the sole exception. A zero catalog price on subscription/OAuth providers means no cataloged incremental token price, not unlimited or free access. A pass proves only the checked provider/credential/model tuple at that moment and may refresh OAuth or consume quota.

Requests whose Pi provider id is opencode or opencode-go, or whose custom model uses the exact hostname opencode.ai, include these provider-facing headers automatically:

  • x-opencode-client: mono-agent
  • x-opencode-session: <provider session id>

The session value is the raw internal provider-session identity. It is not a Telegram chat id, Slack channel id, web thread id, cron id, or other channel conversation id. Continuous conversations reuse it across turns and rotate it after a session reset, invalidation, or requested-primary model change. A stable model override reuses the continuous conversation identity. Per-message, isolated, and otherwise unkeyed direct runtime calls use a fresh one-shot identity.

Existing auth, model, and request headers win case-insensitively; an explicit null suppresses the automatic value, and a caller header transform can replace or remove it. Other provider ids and hosts receive no x-opencode-* headers.

For a manual transport check, run a throwaway agent configured with opencode-go:deepseek-v4-pro through a local TLS inspection proxy that redacts Authorization, then inspect the outbound request to opencode.ai/zen/go/v1. A transient dummy OPENCODE_API_KEY is sufficient to capture header presence before the expected authentication failure; testing two successful turns requires an explicitly authorized real provider call. This manual check is not performed in CI and must never use a live agent directory.

MONO_AGENT_PROVIDERS_JSON is a JSON object of the same shape — provider ids plus the reserved local/piAuthPath/piNative keys. Prefer the config file, but the env override is the escape hatch for secrets-free ephemeral setups:

Terminal window
export MONO_AGENT_PROVIDERS_JSON='{"ollama": {"type": "ollama"}, "piAuthPath": "~/.pi/agent/auth.json"}'

MONO_AGENT_LOCAL_PROVIDERS_JSON is deprecated in favor of this map shape.

providers.piNative.promptCacheDiagnostics (default false; env MONO_AGENT_PI_PROMPT_CACHE_DIAGNOSTICS) enables metadata-only request fingerprints in existing run artifacts. It never emits prompt text, tool arguments, raw cache keys, endpoints or credentials. See Prompt-cache measurement for the artifact reader.