Skip to content

Commit ec49933

Browse files
authored
Merge pull request #523 from aspotton/feature/azure-openai-provider-with-ui
feat: add complete Azure OpenAI provider support
2 parents 89fac4c + 389d9fb commit ec49933

File tree

14 files changed

+1200
-81
lines changed

14 files changed

+1200
-81
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,27 @@ api_key = "env:MY_PROVIDER_KEY"
195195
channel = "my-provider/my-model"
196196
```
197197

198+
**Azure OpenAI Service** — configure Azure OpenAI deployments:
199+
200+
```toml
201+
[llm.provider.azure]
202+
api_type = "azure"
203+
base_url = "https://{resource-name}.openai.azure.com"
204+
api_key = "env:AZURE_API_KEY"
205+
api_version = "2024-06-01" # required
206+
deployment = "gpt-4o" # required — your deployment name
207+
208+
[defaults.routing]
209+
channel = "azure/gpt-4o"
210+
worker = "azure/gpt-4o-mini"
211+
```
212+
213+
Important notes:
214+
- `base_url` must end with `.openai.azure.com`
215+
- `api_version` and `deployment` are required fields
216+
- API key authentication is handled automatically via the `api-key` header
217+
- 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
218+
198219
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]`.
199220

200221
### Skills

docs/content/docs/(configuration)/config.mdx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,11 @@ If you define a custom provider with the same ID as a legacy key, your custom co
376376
| `mistral_key` | string | None | Mistral API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
377377
| `opencode_zen_key` | string | None | OpenCode Zen API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
378378
| `opencode_go_key` | string | None | OpenCode Go API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
379+
| `gemini_key` | string | None | Gemini API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
380+
| `nvidia_key` | string | None | NVIDIA API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
381+
| `minimax_key` | string | None | MiniMax API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
382+
| `moonshot_key` | string | None | Moonshot API key (`secret:NAME`, `env:VAR_NAME`, or literal) |
383+
| `github_copilot_key` | string | None | GitHub Copilot PAT (`secret:NAME`, `env:VAR_NAME`, or literal) |
379384

380385
#### Custom Providers
381386

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

392397
| Field | Type | Required | Description |
393398
|-------|------|----------|-------------|
394-
| `api_type` | string | Yes | API protocol type. One of: `anthropic`, `openai_completions`, `openai_chat_completions`, `openai_responses`, `gemini`, or `kilo_gateway` |
395-
| `base_url` | string | Yes | Base URL of the API endpoint. Must be a valid URL (including protocol) |
399+
| `api_type` | string | Yes | API protocol type. One of: `anthropic`, `openai_completions`, `openai_chat_completions`, `openai_responses`, `gemini`, `kilo_gateway`, or `azure` |
400+
| `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` |
396401
| `api_key` | string | Yes | API key for authentication. Supports `secret:NAME` and `env:VAR_NAME` syntax |
397402
| `name` | string | No | Optional friendly name for the provider (displayed in logs and UI) |
403+
| `api_version` | string | Azure only | Azure API version (format: `YYYY-MM-DD` or `YYYY-MM-DD-preview`) |
404+
| `deployment` | string | Azure only | Azure deployment name (alphanumeric, hyphens, and dots allowed) |
398405

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

424-
**OpenAI Chat Completions provider:**
431+
**Azure OpenAI provider:**
425432
```toml
426-
[llm.provider.azure_openai]
427-
api_type = "openai_responses"
428-
base_url = "https://my-azure-openai.openai.azure.com"
429-
api_key = "env:AZURE_OPENAI_KEY"
430-
name = "Azure OpenAI GPT-4"
433+
[llm.provider.azure]
434+
api_type = "azure"
435+
base_url = "https://my-resource.openai.azure.com"
436+
api_key = "env:AZURE_API_KEY"
437+
api_version = "2024-02-15" # Required for Azure
438+
deployment = "gpt-4o" # Required for Azure (deployment name)
439+
name = "Azure OpenAI"
431440
```
432441

442+
> **Azure Requirements:**
443+
> - `base_url` must end with `.openai.azure.com`
444+
> - `api_version` must match format: `YYYY-MM-DD` or `YYYY-MM-DD-preview`
445+
> - `deployment` can contain alphanumeric characters, hyphens, and dots (e.g., `gpt-4o`, `gpt-5.2`)
446+
> - Model names in routing should use the format: `azure/<deployment-name>`
447+
433448
**OpenAI Completions provider:**
434449
```toml
435450
[llm.provider.local_llm]

docs/content/docs/(core)/routing.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ branch = "anthropic/claude-sonnet-4-20250514"
2626
worker = "anthropic/claude-haiku-4.5-20250514"
2727
compactor = "anthropic/claude-haiku-4.5-20250514"
2828
cortex = "anthropic/claude-haiku-4.5-20250514"
29+
30+
# Azure example:
31+
# channel = "azure/gpt-4o"
32+
# worker = "azure/gpt-4o-mini"
2933
```
3034

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

43+
**Model Format:**
44+
- Standard providers: `<provider>/<model-name>` (e.g., `anthropic/claude-sonnet-4-20250514`, `openai/gpt-4o`)
45+
- Azure: `azure/<deployment-name>` (e.g., `azure/gpt-4o`, `azure/gpt-5.2`)
46+
- Custom providers: `<provider-id>/<model-name>` (e.g., `my_anthropic/claude-3.5-sonnet`)
47+
3948
### Level 2: Task-Type Overrides
4049

4150
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.

interface/src/api/client.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,28 +1593,44 @@ export const api = {
15931593

15941594
// Provider management
15951595
providers: () => fetchJson<Types.ProvidersResponse>("/providers"),
1596-
updateProvider: async (provider: string, apiKey: string, model: string) => {
1596+
updateProvider: async (provider: string, apiKey: string, model: string, baseUrl?: string, apiVersion?: string, deployment?: string) => {
15971597
const response = await fetch(`${getApiBase()}/providers`, {
1598-
method: "PUT",
1598+
method: "POST",
15991599
headers: { "Content-Type": "application/json" },
1600-
body: JSON.stringify({ provider, api_key: apiKey, model }),
1600+
body: JSON.stringify({ provider, api_key: apiKey, model, base_url: baseUrl, api_version: apiVersion, deployment }),
16011601
});
16021602
if (!response.ok) {
16031603
throw new Error(`API error: ${response.status}`);
16041604
}
16051605
return response.json() as Promise<Types.ProviderUpdateResponse>;
16061606
},
1607-
testProviderModel: async (provider: string, apiKey: string, model: string) => {
1608-
const response = await fetch(`${getApiBase()}/providers/test`, {
1607+
testProviderModel: async (provider: string, apiKey: string, model: string, baseUrl?: string, apiVersion?: string, deployment?: string) => {
1608+
const response = await fetch(`${getApiBase()}/providers/test-model`, {
16091609
method: "POST",
16101610
headers: { "Content-Type": "application/json" },
1611-
body: JSON.stringify({ provider, api_key: apiKey, model }),
1611+
body: JSON.stringify({ provider, api_key: apiKey, model, base_url: baseUrl, api_version: apiVersion, deployment }),
16121612
});
16131613
if (!response.ok) {
16141614
throw new Error(`API error: ${response.status}`);
16151615
}
16161616
return response.json() as Promise<Types.ProviderModelTestResponse>;
16171617
},
1618+
getProviderConfig: async (provider: string, options?: { signal?: AbortSignal }) => {
1619+
const response = await fetch(`${getApiBase()}/providers/${provider}/config`, {
1620+
method: "GET",
1621+
signal: options?.signal,
1622+
});
1623+
if (!response.ok) {
1624+
throw new Error(`API error: ${response.status}`);
1625+
}
1626+
return response.json() as Promise<{
1627+
success: boolean;
1628+
message: string;
1629+
base_url?: string | null;
1630+
api_version?: string | null;
1631+
deployment?: string | null;
1632+
}>;
1633+
},
16181634
startOpenAiOAuthBrowser: async (params: {model: string}) => {
16191635
const response = await fetch(`${getApiBase()}/providers/openai/oauth/browser/start`, {
16201636
method: "POST",

interface/src/lib/providerIcons.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ export function ProviderIcon({ provider, className = "text-ink-faint", size = 24
140140
"minimax-cn": Minimax,
141141
moonshot: Kimi, // Kimi is Moonshot AI's product brand
142142
"github-copilot": GithubCopilot,
143+
azure: OpenAI,
143144
};
144145

145146
const IconComponent = iconMap[provider.toLowerCase()];

0 commit comments

Comments
 (0)