Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,27 @@ api_key = "env:MY_PROVIDER_KEY"
channel = "my-provider/my-model"
```

**Azure OpenAI Service** — configure Azure OpenAI deployments:

```toml
[llm.provider.azure]
api_type = "azure"
base_url = "https://{resource-name}.openai.azure.com"
api_key = "env:AZURE_API_KEY"
api_version = "2024-06-01" # required
deployment = "gpt-4o" # required — your deployment name

[defaults.routing]
channel = "azure/gpt-4o"
worker = "azure/gpt-4o-mini"
```

Important notes:
- `base_url` must end with `.openai.azure.com`
- `api_version` and `deployment` are required fields
- API key authentication is handled automatically via the `api-key` header
- For Azure AI Foundry (accessing Anthropic, Llama, or other models through Azure's model catalog), use `api_type = "openai_chat_completions"` instead and configure the deployment endpoint accordingly

Additional built-in providers include **Kilo Gateway**, **OpenCode Go**, **NVIDIA**, **MiniMax**, **Moonshot AI (Kimi)**, and **Z.AI Coding Plan** — configure with `kilo_key`, `opencode_go_key`, `nvidia_key`, `minimax_key`, `moonshot_key`, or `zai_coding_plan_key` in `[llm]`.

### Skills
Expand Down
31 changes: 23 additions & 8 deletions docs/content/docs/(configuration)/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,11 @@ If you define a custom provider with the same ID as a legacy key, your custom co
| `mistral_key` | string | None | Mistral API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
| `opencode_zen_key` | string | None | OpenCode Zen API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
| `opencode_go_key` | string | None | OpenCode Go API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
| `gemini_key` | string | None | Gemini API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
| `nvidia_key` | string | None | NVIDIA API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
| `minimax_key` | string | None | MiniMax API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
| `moonshot_key` | string | None | Moonshot API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
| `github_copilot_key` | string | None | GitHub Copilot PAT (`secret:NAME`, `env:VAR_NAME`, or literal) |

#### Custom Providers

Expand All @@ -391,10 +396,12 @@ name = "My Provider" # Optional - friendly name for display

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `api_type` | string | Yes | API protocol type. One of: `anthropic`, `openai_completions`, `openai_chat_completions`, `openai_responses`, `gemini`, or `kilo_gateway` |
| `base_url` | string | Yes | Base URL of the API endpoint. Must be a valid URL (including protocol) |
| `api_type` | string | Yes | API protocol type. One of: `anthropic`, `openai_completions`, `openai_chat_completions`, `openai_responses`, `gemini`, `kilo_gateway`, or `azure` |
| `base_url` | string | Yes | Base URL of the API endpoint. Must be a valid URL (including protocol). For Azure, must end with `.openai.azure.com` |
| `api_key` | string | Yes | API key for authentication. Supports `secret:NAME` and `env:VAR_NAME` syntax |
| `name` | string | No | Optional friendly name for the provider (displayed in logs and UI) |
| `api_version` | string | Azure only | Azure API version (format: `YYYY-MM-DD` or `YYYY-MM-DD-preview`) |
| `deployment` | string | Azure only | Azure deployment name (alphanumeric, hyphens, and dots allowed) |

> Note:
> - For `openai_completions`, `openai_chat_completions`, and `openai_responses`, configure `base_url` as the provider root URL (usually without a trailing `/v1`).
Expand All @@ -421,15 +428,23 @@ api_key = "env:CUSTOM_ANTHROPIC_KEY"
name = "Anthropic EU"
```

**OpenAI Chat Completions provider:**
**Azure OpenAI provider:**
```toml
[llm.provider.azure_openai]
api_type = "openai_responses"
base_url = "https://my-azure-openai.openai.azure.com"
api_key = "env:AZURE_OPENAI_KEY"
name = "Azure OpenAI GPT-4"
[llm.provider.azure]
api_type = "azure"
base_url = "https://my-resource.openai.azure.com"
api_key = "env:AZURE_API_KEY"
api_version = "2024-02-15" # Required for Azure
deployment = "gpt-4o" # Required for Azure (deployment name)
name = "Azure OpenAI"
```

> **Azure Requirements:**
> - `base_url` must end with `.openai.azure.com`
> - `api_version` must match format: `YYYY-MM-DD` or `YYYY-MM-DD-preview`
> - `deployment` can contain alphanumeric characters, hyphens, and dots (e.g., `gpt-4o`, `gpt-5.2`)
> - Model names in routing should use the format: `azure/<deployment-name>`

**OpenAI Completions provider:**
```toml
[llm.provider.local_llm]
Expand Down
9 changes: 9 additions & 0 deletions docs/content/docs/(core)/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ branch = "anthropic/claude-sonnet-4-20250514"
worker = "anthropic/claude-haiku-4.5-20250514"
compactor = "anthropic/claude-haiku-4.5-20250514"
cortex = "anthropic/claude-haiku-4.5-20250514"

# Azure example:
# channel = "azure/gpt-4o"
# worker = "azure/gpt-4o-mini"
```

| Process | Why this model tier |
Expand All @@ -36,6 +40,11 @@ cortex = "anthropic/claude-haiku-4.5-20250514"
| Compactor | Summarization and memory extraction. Fast and cheap. No personality needed. |
| Cortex | System-level observation. Small context, simple signal processing. Cheapest tier. |

**Model Format:**
- Standard providers: `<provider>/<model-name>` (e.g., `anthropic/claude-sonnet-4-20250514`, `openai/gpt-4o`)
- Azure: `azure/<deployment-name>` (e.g., `azure/gpt-4o`, `azure/gpt-5.2`)
- Custom providers: `<provider-id>/<model-name>` (e.g., `my_anthropic/claude-3.5-sonnet`)

### Level 2: Task-Type Overrides

Workers and branches are generic. Different tasks benefit from different models. The channel or branch specifies a task type when spawning, and the routing config maps task types to models.
Expand Down
28 changes: 22 additions & 6 deletions interface/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1593,28 +1593,44 @@ export const api = {

// Provider management
providers: () => fetchJson<Types.ProvidersResponse>("/providers"),
updateProvider: async (provider: string, apiKey: string, model: string) => {
updateProvider: async (provider: string, apiKey: string, model: string, baseUrl?: string, apiVersion?: string, deployment?: string) => {
const response = await fetch(`${getApiBase()}/providers`, {
method: "PUT",
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ provider, api_key: apiKey, model }),
body: JSON.stringify({ provider, api_key: apiKey, model, base_url: baseUrl, api_version: apiVersion, deployment }),
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json() as Promise<Types.ProviderUpdateResponse>;
},
testProviderModel: async (provider: string, apiKey: string, model: string) => {
const response = await fetch(`${getApiBase()}/providers/test`, {
testProviderModel: async (provider: string, apiKey: string, model: string, baseUrl?: string, apiVersion?: string, deployment?: string) => {
const response = await fetch(`${getApiBase()}/providers/test-model`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ provider, api_key: apiKey, model }),
body: JSON.stringify({ provider, api_key: apiKey, model, base_url: baseUrl, api_version: apiVersion, deployment }),
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json() as Promise<Types.ProviderModelTestResponse>;
},
getProviderConfig: async (provider: string, options?: { signal?: AbortSignal }) => {
const response = await fetch(`${getApiBase()}/providers/${provider}/config`, {
method: "GET",
signal: options?.signal,
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json() as Promise<{
success: boolean;
message: string;
base_url?: string | null;
api_version?: string | null;
deployment?: string | null;
}>;
},
startOpenAiOAuthBrowser: async (params: {model: string}) => {
const response = await fetch(`${getApiBase()}/providers/openai/oauth/browser/start`, {
method: "POST",
Expand Down
1 change: 1 addition & 0 deletions interface/src/lib/providerIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export function ProviderIcon({ provider, className = "text-ink-faint", size = 24
"minimax-cn": Minimax,
moonshot: Kimi, // Kimi is Moonshot AI's product brand
"github-copilot": GithubCopilot,
azure: OpenAI,
};

const IconComponent = iconMap[provider.toLowerCase()];
Expand Down
Loading
Loading