Custom Providers
Extensions can register custom model providers viapi.registerProvider(). This enables:
- Proxies - Route requests through corporate proxies or API gateways
- Custom endpoints - Use self-hosted or private model deployments
- OAuth/SSO - Add authentication flows for enterprise providers
- Custom APIs - Implement streaming for non-standard LLM APIs
Example Extensions
See these complete provider examples:Table of Contents
- Example Extensions
- Quick Reference
- Override Existing Provider
- Register New Provider
- Unregister Provider
- OAuth Support
- Custom Streaming API
- Testing Your Implementation
- Config Reference
- Model Definition Reference
Quick Reference
async. For dynamic model discovery, fetch and register models in the factory instead of session_start. Atomic waits for the factory before startup continues, so the provider is available during interactive startup and to atomic --list-models.
Override Existing Provider
The simplest use case: redirect an existing provider through a proxy.baseUrl and/or headers are provided (no models), all existing models for that provider are preserved with the new endpoint.
Register New Provider
To add a completely new provider, specifymodels along with the required configuration.
If the model list comes from a remote endpoint, use an async extension factory:
models is provided, it replaces all existing models for that provider.
Unregister Provider
Usepi.unregisterProvider(name) to remove a provider that was previously registered via pi.registerProvider(name, ...):
/reload is required.
API Types
Theapi field determines which streaming implementation is used:
| API | Use for |
|---|---|
anthropic-messages | Anthropic Claude API and compatibles |
openai-completions | OpenAI Chat Completions API and compatibles |
openai-responses | OpenAI Responses API |
azure-openai-responses | Azure OpenAI Responses API |
openai-codex-responses | OpenAI Codex Responses API |
mistral-conversations | Mistral SDK Conversations/Chat streaming |
google-generative-ai | Google Generative AI API |
google-vertex | Google Vertex AI API |
bedrock-converse-stream | Amazon Bedrock Converse API |
openai-completions. Use model-level thinkingLevelMap for model-specific thinking levels, and compat for provider quirks:
openrouter for OpenRouter-style reasoning: { effort } controls. Use together for Together-style reasoning: { enabled } controls; with supportsReasoningEffort, it also sends reasoning_effort. Use qwen-chat-template instead for local Qwen-compatible servers that read chat_template_kwargs.enable_thinking.
Use cacheControlFormat: "anthropic" for OpenAI-compatible providers that expose Anthropic-style prompt caching via cache_control on the system prompt, last tool definition, and last user/assistant text content.
Migration note: Mistral moved fromopenai-completionstomistral-conversations. Usemistral-conversationsfor native Mistral models. If you intentionally route Mistral-compatible/custom endpoints throughopenai-completions, setcompatflags explicitly as needed.
Auth Header
If your provider expectsAuthorization: Bearer <key> but doesn’t use a standard API, set authHeader: true:
OAuth Support
Add OAuth/SSO authentication that integrates with/login:
/login corporate-ai.
OAuthLoginCallbacks
Thecallbacks object provides three ways to authenticate:
OAuthCredentials
Credentials are persisted in~/.atomic/agent/auth.json (legacy ~/.pi/agent/auth.json may be read for compatibility):
Custom Streaming API
For providers with non-standard APIs, implementstreamSimple. Study the existing provider implementations before writing your own:
Reference implementations:
Atomic uses provider implementations from its installed @earendil-works/pi-ai dependency. Inspect the compiled declarations and JavaScript under node_modules/@earendil-works/pi-ai/dist/providers/, including:
anthropic.d.ts/anthropic.js- Anthropic Messages APImistral.d.ts/mistral.js- Mistral Conversations APIopenai-completions.d.ts/openai-completions.js- OpenAI Chat Completionsopenai-responses.d.ts/openai-responses.js- OpenAI Responses APIgoogle.d.ts/google.js- Google Generative AIamazon-bedrock.d.ts/amazon-bedrock.js- AWS Bedrock
Stream Pattern
All providers follow the same pattern:Event Types
Push events viastream.push() in this order:
-
{ type: "start", partial: output }- Stream started -
Content events (repeatable, track
contentIndexfor each block):{ type: "text_start", contentIndex, partial }- Text block started{ type: "text_delta", contentIndex, delta, partial }- Text chunk{ type: "text_end", contentIndex, content, partial }- Text block ended{ type: "thinking_start", contentIndex, partial }- Thinking started{ type: "thinking_delta", contentIndex, delta, partial }- Thinking chunk{ type: "thinking_end", contentIndex, content, partial }- Thinking ended{ type: "toolcall_start", contentIndex, partial }- Tool call started{ type: "toolcall_delta", contentIndex, delta, partial }- Tool call JSON chunk{ type: "toolcall_end", contentIndex, toolCall, partial }- Tool call ended
-
{ type: "done", reason, message }or{ type: "error", reason, error }- Stream ended
partial field in each event contains the current AssistantMessage state. Update output.content as you receive data, then include output as the partial.
Content Blocks
Add content blocks tooutput.content as they arrive:
Tool Calls
Tool calls require accumulating JSON and parsing:Usage and Cost
Update usage from API response and calculate cost:Registration
Register your stream function:Testing Your Implementation
Test your provider against focused tests that mirror Atomic’s provider contract. If you are working from the source checkout, note that provider internals come from@earendil-works/pi-ai; this monorepo does not contain a packages/ai/test directory to copy from directly:
| Test | Purpose |
|---|---|
stream.test.ts | Basic streaming, text output |
tokens.test.ts | Token counting and usage |
abort.test.ts | AbortSignal handling |
empty.test.ts | Empty/minimal responses |
context-overflow.test.ts | Context window limits |
image-limits.test.ts | Image input handling |
unicode-surrogate.test.ts | Unicode edge cases |
tool-call-without-result.test.ts | Tool call edge cases |
image-tool-result.test.ts | Images in tool results |
total-tokens.test.ts | Total token calculation |
cross-provider-handoff.test.ts | Context handoff between providers |
Config Reference
Model Definition Reference
openrouter sends reasoning: { effort }. deepseek sends thinking: { type: "enabled" | "disabled" } and reasoning_effort when enabled. together sends reasoning: { enabled } and also reasoning_effort when supportsReasoningEffort is enabled. qwen is for DashScope-style top-level enable_thinking. Use qwen-chat-template for local Qwen-compatible servers that read chat_template_kwargs.enable_thinking.
cacheControlFormat: "anthropic" applies Anthropic-style cache_control markers to the system prompt, last tool definition, and last user/assistant text content.