on main help sign in
feat: add azure openai models (#74)
commit805aeff83cad4c17e25acdd671d2731be104b3e0 authorYJG <ytzhxk@gmail.com> dateMon 28 Apr 2025, 10:42 (2 years ago) parentbce2ec5
10 files changed+291−11
README.md+23−10 cmd/schema/main.go+1 go.mod+7 go.sum+15 internal/config/config.go+11 internal/llm/models/azure.go+157 internal/llm/models/models.go+1 internal/llm/provider/azure.go+47 internal/llm/provider/provider.go+5 opencode-schema.json+24−1
README.md+23−10
@@ -11,7 +11,7 @@ OpenCode is a Go-based CLI application that brings AI assistance to your termina
1111## Features
1212
1313- **Interactive TUI**: Built with [Bubble Tea](https://github.com/charmbracelet/bubbletea) for a smooth terminal experience
14−- **Multiple AI Providers**: Support for OpenAI, Anthropic Claude, Google Gemini, AWS Bedrock, and Groq
14+- **Multiple AI Providers**: Support for OpenAI, Anthropic Claude, Google Gemini, AWS Bedrock, Groq, and Azure OpenAI
1515- **Session Management**: Save and manage multiple conversation sessions
1616- **Tool Integration**: AI can execute commands, search files, and modify code
1717- **Vim-like Editor**: Integrated editor with text input capabilities
@@ -66,15 +66,19 @@ OpenCode looks for configuration in the following locations:
6666
6767You can configure OpenCode using environment variables:
6868
69−| Environment Variable | Purpose |
70−| ----------------------- | ------------------------ |
71−| `ANTHROPIC_API_KEY` | For Claude models |
72−| `OPENAI_API_KEY` | For OpenAI models |
73−| `GEMINI_API_KEY` | For Google Gemini models |
74−| `GROQ_API_KEY` | For Groq models |
75−| `AWS_ACCESS_KEY_ID` | For AWS Bedrock (Claude) |
76−| `AWS_SECRET_ACCESS_KEY` | For AWS Bedrock (Claude) |
77−| `AWS_REGION` | For AWS Bedrock (Claude) |
69+| Environment Variable | Purpose |
70+|----------------------------|--------------------------------------------------------|
71+| `ANTHROPIC_API_KEY` | For Claude models |
72+| `OPENAI_API_KEY` | For OpenAI models |
73+| `GEMINI_API_KEY` | For Google Gemini models |
74+| `GROQ_API_KEY` | For Groq models |
75+| `AWS_ACCESS_KEY_ID` | For AWS Bedrock (Claude) |
76+| `AWS_SECRET_ACCESS_KEY` | For AWS Bedrock (Claude) |
77+| `AWS_REGION` | For AWS Bedrock (Claude) |
78+| `AZURE_OPENAI_ENDPOINT` | For Azure OpenAI models |
79+| `AZURE_OPENAI_API_KEY` | For Azure OpenAI models (optional when using Entra ID) |
80+| `AZURE_OPENAI_API_VERSION` | For Azure OpenAI models |
81+
7882
7983### Configuration File Structure
8084
@@ -170,6 +174,15 @@ OpenCode supports a variety of AI models from different providers:
170174- Deepseek R1 distill Llama 70b
171175- Llama 3.3 70b Versatile
172176
177+### Azure OpenAI
178+
179+- GPT-4.1 family (gpt-4.1, gpt-4.1-mini, gpt-4.1-nano)
180+- GPT-4.5 Preview
181+- GPT-4o family (gpt-4o, gpt-4o-mini)
182+- O1 family (o1, o1-mini)
183+- O3 family (o3, o3-mini)
184+- O4 Mini
185+
173186## Usage
174187
175188```bash
cmd/schema/main.go+1
@@ -174,6 +174,7 @@ func generateSchema() map[string]any {
174174 string(models.ProviderGemini),
175175 string(models.ProviderGROQ),
176176 string(models.ProviderBedrock),
177+ string(models.ProviderAzure),
177178 }
178179
179180 providerSchema["additionalProperties"].(map[string]any)["properties"].(map[string]any)["provider"] = map[string]any{
go.mod+7
@@ -5,6 +5,7 @@ go 1.24.0
55toolchain go1.24.2
66
77require (
8+ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
89 github.com/JohannesKaufmann/html-to-markdown v1.6.0
910 github.com/PuerkitoBio/goquery v1.9.2
1011 github.com/alecthomas/chroma/v2 v2.15.0
@@ -44,6 +45,9 @@ require (
4445 cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
4546 cloud.google.com/go/compute/metadata v0.6.0 // indirect
4647 cloud.google.com/go/longrunning v0.5.7 // indirect
48+ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
49+ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
50+ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
4751 github.com/andybalholm/cascadia v1.3.2 // indirect
4852 github.com/atotto/clipboard v0.1.4 // indirect
4953 github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
@@ -74,11 +78,13 @@ require (
7478 github.com/go-logr/logr v1.4.2 // indirect
7579 github.com/go-logr/stdr v1.2.2 // indirect
7680 github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
81+ github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
7782 github.com/google/s2a-go v0.1.8 // indirect
7883 github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
7984 github.com/googleapis/gax-go/v2 v2.14.1 // indirect
8085 github.com/gorilla/css v1.0.1 // indirect
8186 github.com/inconshreveable/mousetrap v1.1.0 // indirect
87+ github.com/kylelemons/godebug v1.1.0 // indirect
8288 github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
8389 github.com/mattn/go-isatty v0.0.20 // indirect
8490 github.com/mattn/go-localereader v0.0.1 // indirect
@@ -89,6 +95,7 @@ require (
8995 github.com/muesli/cancelreader v0.2.2 // indirect
9096 github.com/ncruces/julianday v1.0.0 // indirect
9197 github.com/pelletier/go-toml/v2 v2.2.3 // indirect
98+ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
9299 github.com/pmezard/go-difflib v1.0.0 // indirect
93100 github.com/rivo/uniseg v0.4.7 // indirect
94101 github.com/rogpeppe/go-internal v1.14.1 // indirect
go.sum+15
@@ -10,6 +10,14 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
1010cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
1111cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
1212cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
13+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
14+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
15+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
16+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
17+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
18+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
19+github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
20+github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
1321github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5x+rHJnb1ssNmqpLH/k=
1422github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ=
1523github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
@@ -113,6 +121,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
113121github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
114122github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
115123github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
124+github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
125+github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
116126github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
117127github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
118128github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
@@ -140,6 +150,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
140150github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
141151github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
142152github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
153+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
154+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
143155github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 h1:9rjt7AfnrXKNSZhp36A3/4QAZAwGGCGD/p8Bse26zms=
144156github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4=
145157github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
@@ -177,6 +189,8 @@ github.com/openai/openai-go v0.1.0-beta.2 h1:Ra5nCFkbEl9w+UJwAciC4kqnIBUCcJazhmM
177189github.com/openai/openai-go v0.1.0-beta.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
178190github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
179191github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
192+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
193+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
180194github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
181195github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
182196github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -295,6 +309,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
295309golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
296310golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
297311golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
312+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
298313golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
299314golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
300315golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
internal/config/config.go+11
@@ -272,6 +272,15 @@ func setProviderDefaults() {
272272 viper.SetDefault("agents.title.model", models.BedrockClaude37Sonnet)
273273 return
274274 }
275+
276+ if os.Getenv("AZURE_OPENAI_ENDPOINT") != "" {
277+ // api-key may be empty when using Entra ID credentials – that's okay
278+ viper.SetDefault("providers.azure.apiKey", os.Getenv("AZURE_OPENAI_API_KEY"))
279+ viper.SetDefault("agents.coder.model", models.AzureGPT41)
280+ viper.SetDefault("agents.task.model", models.AzureGPT41Mini)
281+ viper.SetDefault("agents.title.model", models.AzureGPT41Mini)
282+ return
283+ }
275284}
276285
277286// hasAWSCredentials checks if AWS credentials are available in the environment.
@@ -506,6 +515,8 @@ func getProviderAPIKey(provider models.ModelProvider) string {
506515 return os.Getenv("GEMINI_API_KEY")
507516 case models.ProviderGROQ:
508517 return os.Getenv("GROQ_API_KEY")
518+ case models.ProviderAzure:
519+ return os.Getenv("AZURE_OPENAI_API_KEY")
509520 case models.ProviderBedrock:
510521 if hasAWSCredentials() {
511522 return "aws-credentials-available"
internal/llm/models/azure.gonew file+157
@@ -0,0 +1,157 @@
1+package models
2+
3+const ProviderAzure ModelProvider = "azure"
4+
5+const (
6+ AzureGPT41 ModelID = "azure.gpt-4.1"
7+ AzureGPT41Mini ModelID = "azure.gpt-4.1-mini"
8+ AzureGPT41Nano ModelID = "azure.gpt-4.1-nano"
9+ AzureGPT45Preview ModelID = "azure.gpt-4.5-preview"
10+ AzureGPT4o ModelID = "azure.gpt-4o"
11+ AzureGPT4oMini ModelID = "azure.gpt-4o-mini"
12+ AzureO1 ModelID = "azure.o1"
13+ AzureO1Mini ModelID = "azure.o1-mini"
14+ AzureO3 ModelID = "azure.o3"
15+ AzureO3Mini ModelID = "azure.o3-mini"
16+ AzureO4Mini ModelID = "azure.o4-mini"
17+)
18+
19+var AzureModels = map[ModelID]Model{
20+ AzureGPT41: {
21+ ID: AzureGPT41,
22+ Name: "Azure OpenAI – GPT 4.1",
23+ Provider: ProviderAzure,
24+ APIModel: "gpt-4.1",
25+ CostPer1MIn: OpenAIModels[GPT41].CostPer1MIn,
26+ CostPer1MInCached: OpenAIModels[GPT41].CostPer1MInCached,
27+ CostPer1MOut: OpenAIModels[GPT41].CostPer1MOut,
28+ CostPer1MOutCached: OpenAIModels[GPT41].CostPer1MOutCached,
29+ ContextWindow: OpenAIModels[GPT41].ContextWindow,
30+ DefaultMaxTokens: OpenAIModels[GPT41].DefaultMaxTokens,
31+ },
32+ AzureGPT41Mini: {
33+ ID: AzureGPT41Mini,
34+ Name: "Azure OpenAI – GPT 4.1 mini",
35+ Provider: ProviderAzure,
36+ APIModel: "gpt-4.1-mini",
37+ CostPer1MIn: OpenAIModels[GPT41Mini].CostPer1MIn,
38+ CostPer1MInCached: OpenAIModels[GPT41Mini].CostPer1MInCached,
39+ CostPer1MOut: OpenAIModels[GPT41Mini].CostPer1MOut,
40+ CostPer1MOutCached: OpenAIModels[GPT41Mini].CostPer1MOutCached,
41+ ContextWindow: OpenAIModels[GPT41Mini].ContextWindow,
42+ DefaultMaxTokens: OpenAIModels[GPT41Mini].DefaultMaxTokens,
43+ },
44+ AzureGPT41Nano: {
45+ ID: AzureGPT41Nano,
46+ Name: "Azure OpenAI – GPT 4.1 nano",
47+ Provider: ProviderAzure,
48+ APIModel: "gpt-4.1-nano",
49+ CostPer1MIn: OpenAIModels[GPT41Nano].CostPer1MIn,
50+ CostPer1MInCached: OpenAIModels[GPT41Nano].CostPer1MInCached,
51+ CostPer1MOut: OpenAIModels[GPT41Nano].CostPer1MOut,
52+ CostPer1MOutCached: OpenAIModels[GPT41Nano].CostPer1MOutCached,
53+ ContextWindow: OpenAIModels[GPT41Nano].ContextWindow,
54+ DefaultMaxTokens: OpenAIModels[GPT41Nano].DefaultMaxTokens,
55+ },
56+ AzureGPT45Preview: {
57+ ID: AzureGPT45Preview,
58+ Name: "Azure OpenAI – GPT 4.5 preview",
59+ Provider: ProviderAzure,
60+ APIModel: "gpt-4.5-preview",
61+ CostPer1MIn: OpenAIModels[GPT45Preview].CostPer1MIn,
62+ CostPer1MInCached: OpenAIModels[GPT45Preview].CostPer1MInCached,
63+ CostPer1MOut: OpenAIModels[GPT45Preview].CostPer1MOut,
64+ CostPer1MOutCached: OpenAIModels[GPT45Preview].CostPer1MOutCached,
65+ ContextWindow: OpenAIModels[GPT45Preview].ContextWindow,
66+ DefaultMaxTokens: OpenAIModels[GPT45Preview].DefaultMaxTokens,
67+ },
68+ AzureGPT4o: {
69+ ID: AzureGPT4o,
70+ Name: "Azure OpenAI – GPT-4o",
71+ Provider: ProviderAzure,
72+ APIModel: "gpt-4o",
73+ CostPer1MIn: OpenAIModels[GPT4o].CostPer1MIn,
74+ CostPer1MInCached: OpenAIModels[GPT4o].CostPer1MInCached,
75+ CostPer1MOut: OpenAIModels[GPT4o].CostPer1MOut,
76+ CostPer1MOutCached: OpenAIModels[GPT4o].CostPer1MOutCached,
77+ ContextWindow: OpenAIModels[GPT4o].ContextWindow,
78+ DefaultMaxTokens: OpenAIModels[GPT4o].DefaultMaxTokens,
79+ },
80+ AzureGPT4oMini: {
81+ ID: AzureGPT4oMini,
82+ Name: "Azure OpenAI – GPT-4o mini",
83+ Provider: ProviderAzure,
84+ APIModel: "gpt-4o-mini",
85+ CostPer1MIn: OpenAIModels[GPT4oMini].CostPer1MIn,
86+ CostPer1MInCached: OpenAIModels[GPT4oMini].CostPer1MInCached,
87+ CostPer1MOut: OpenAIModels[GPT4oMini].CostPer1MOut,
88+ CostPer1MOutCached: OpenAIModels[GPT4oMini].CostPer1MOutCached,
89+ ContextWindow: OpenAIModels[GPT4oMini].ContextWindow,
90+ DefaultMaxTokens: OpenAIModels[GPT4oMini].DefaultMaxTokens,
91+ },
92+ AzureO1: {
93+ ID: AzureO1,
94+ Name: "Azure OpenAI – O1",
95+ Provider: ProviderAzure,
96+ APIModel: "o1",
97+ CostPer1MIn: OpenAIModels[O1].CostPer1MIn,
98+ CostPer1MInCached: OpenAIModels[O1].CostPer1MInCached,
99+ CostPer1MOut: OpenAIModels[O1].CostPer1MOut,
100+ CostPer1MOutCached: OpenAIModels[O1].CostPer1MOutCached,
101+ ContextWindow: OpenAIModels[O1].ContextWindow,
102+ DefaultMaxTokens: OpenAIModels[O1].DefaultMaxTokens,
103+ CanReason: OpenAIModels[O1].CanReason,
104+ },
105+ AzureO1Mini: {
106+ ID: AzureO1Mini,
107+ Name: "Azure OpenAI – O1 mini",
108+ Provider: ProviderAzure,
109+ APIModel: "o1-mini",
110+ CostPer1MIn: OpenAIModels[O1Mini].CostPer1MIn,
111+ CostPer1MInCached: OpenAIModels[O1Mini].CostPer1MInCached,
112+ CostPer1MOut: OpenAIModels[O1Mini].CostPer1MOut,
113+ CostPer1MOutCached: OpenAIModels[O1Mini].CostPer1MOutCached,
114+ ContextWindow: OpenAIModels[O1Mini].ContextWindow,
115+ DefaultMaxTokens: OpenAIModels[O1Mini].DefaultMaxTokens,
116+ CanReason: OpenAIModels[O1Mini].CanReason,
117+ },
118+ AzureO3: {
119+ ID: AzureO3,
120+ Name: "Azure OpenAI – O3",
121+ Provider: ProviderAzure,
122+ APIModel: "o3",
123+ CostPer1MIn: OpenAIModels[O3].CostPer1MIn,
124+ CostPer1MInCached: OpenAIModels[O3].CostPer1MInCached,
125+ CostPer1MOut: OpenAIModels[O3].CostPer1MOut,
126+ CostPer1MOutCached: OpenAIModels[O3].CostPer1MOutCached,
127+ ContextWindow: OpenAIModels[O3].ContextWindow,
128+ DefaultMaxTokens: OpenAIModels[O3].DefaultMaxTokens,
129+ CanReason: OpenAIModels[O3].CanReason,
130+ },
131+ AzureO3Mini: {
132+ ID: AzureO3Mini,
133+ Name: "Azure OpenAI – O3 mini",
134+ Provider: ProviderAzure,
135+ APIModel: "o3-mini",
136+ CostPer1MIn: OpenAIModels[O3Mini].CostPer1MIn,
137+ CostPer1MInCached: OpenAIModels[O3Mini].CostPer1MInCached,
138+ CostPer1MOut: OpenAIModels[O3Mini].CostPer1MOut,
139+ CostPer1MOutCached: OpenAIModels[O3Mini].CostPer1MOutCached,
140+ ContextWindow: OpenAIModels[O3Mini].ContextWindow,
141+ DefaultMaxTokens: OpenAIModels[O3Mini].DefaultMaxTokens,
142+ CanReason: OpenAIModels[O3Mini].CanReason,
143+ },
144+ AzureO4Mini: {
145+ ID: AzureO4Mini,
146+ Name: "Azure OpenAI – O4 mini",
147+ Provider: ProviderAzure,
148+ APIModel: "o4-mini",
149+ CostPer1MIn: OpenAIModels[O4Mini].CostPer1MIn,
150+ CostPer1MInCached: OpenAIModels[O4Mini].CostPer1MInCached,
151+ CostPer1MOut: OpenAIModels[O4Mini].CostPer1MOut,
152+ CostPer1MOutCached: OpenAIModels[O4Mini].CostPer1MOutCached,
153+ ContextWindow: OpenAIModels[O4Mini].ContextWindow,
154+ DefaultMaxTokens: OpenAIModels[O4Mini].DefaultMaxTokens,
155+ CanReason: OpenAIModels[O4Mini].CanReason,
156+ },
157+}
internal/llm/models/models.go+1
@@ -76,4 +76,5 @@ func init() {
7676 maps.Copy(SupportedModels, OpenAIModels)
7777 maps.Copy(SupportedModels, GeminiModels)
7878 maps.Copy(SupportedModels, GroqModels)
79+ maps.Copy(SupportedModels, AzureModels)
7980}
internal/llm/provider/azure.gonew file+47
@@ -0,0 +1,47 @@
1+package provider
2+
3+import (
4+ "os"
5+
6+ "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
7+ "github.com/openai/openai-go"
8+ "github.com/openai/openai-go/azure"
9+ "github.com/openai/openai-go/option"
10+)
11+
12+type azureClient struct {
13+ *openaiClient
14+}
15+
16+type AzureClient ProviderClient
17+
18+func newAzureClient(opts providerClientOptions) AzureClient {
19+
20+ endpoint := os.Getenv("AZURE_OPENAI_ENDPOINT") // ex: https://foo.openai.azure.com
21+ apiVersion := os.Getenv("AZURE_OPENAI_API_VERSION") // ex: 2025-04-01-preview
22+
23+ if endpoint == "" || apiVersion == "" {
24+ return &azureClient{openaiClient: newOpenAIClient(opts).(*openaiClient)}
25+ }
26+
27+ reqOpts := []option.RequestOption{
28+ azure.WithEndpoint(endpoint, apiVersion),
29+ }
30+
31+ if opts.apiKey != "" || os.Getenv("AZURE_OPENAI_API_KEY") != "" {
32+ key := opts.apiKey
33+ if key == "" {
34+ key = os.Getenv("AZURE_OPENAI_API_KEY")
35+ }
36+ reqOpts = append(reqOpts, azure.WithAPIKey(key))
37+ } else if cred, err := azidentity.NewDefaultAzureCredential(nil); err == nil {
38+ reqOpts = append(reqOpts, azure.WithTokenCredential(cred))
39+ }
40+
41+ base := &openaiClient{
42+ providerOptions: opts,
43+ client: openai.NewClient(reqOpts...),
44+ }
45+
46+ return &azureClient{openaiClient: base}
47+}
internal/llm/provider/provider.go+5
@@ -115,6 +115,11 @@ func NewProvider(providerName models.ModelProvider, opts ...ProviderClientOption
115115 options: clientOptions,
116116 client: newOpenAIClient(clientOptions),
117117 }, nil
118+ case models.ProviderAzure:
119+ return &baseProvider[AzureClient]{
120+ options: clientOptions,
121+ client: newAzureClient(clientOptions),
122+ }, nil
118123 case models.ProviderMock:
119124 // TODO: implement mock client for test
120125 panic("not implemented")
opencode-schema.json+24−1
@@ -17,26 +17,37 @@
1717 "claude-3.7-sonnet",
1818 "claude-3.5-haiku",
1919 "o3",
20+ "azure.o3",
2021 "gpt-4.5-preview",
22+ "azure.gpt-4.5-preview",
2123 "o1-pro",
2224 "o4-mini",
25+ "azure.o4-mini",
2326 "gpt-4.1",
27+ "azure.gpt-4.1",
2428 "o3-mini",
29+ "azure.o3-mini",
2530 "gpt-4.1-nano",
31+ "azure.gpt-4.1-nano",
2632 "gpt-4o-mini",
33+ "azure.gpt-4o-mini",
2734 "o1",
35+ "azure.o1",
2836 "gemini-2.5-flash",
2937 "qwen-qwq",
3038 "meta-llama/llama-4-maverick-17b-128e-instruct",
3139 "claude-3-opus",
3240 "gpt-4o",
41+ "azure.gpt-4o",
3342 "gemini-2.0-flash-lite",
3443 "gemini-2.0-flash",
3544 "deepseek-r1-distill-llama-70b",
3645 "llama-3.3-70b-versatile",
3746 "claude-3.5-sonnet",
3847 "o1-mini",
48+ "azure.o1-mini",
3949 "gpt-4.1-mini",
50+ "azure.gpt-4.1-mini",
4051 "gemini-2.5",
4152 "meta-llama/llama-4-scout-17b-16e-instruct"
4253 ],
@@ -77,26 +88,37 @@
7788 "claude-3.7-sonnet",
7889 "claude-3.5-haiku",
7990 "o3",
91+ "azure.o3",
8092 "gpt-4.5-preview",
93+ "azure.gpt-4.5-preview",
8194 "o1-pro",
8295 "o4-mini",
96+ "azure.o4-mini",
8397 "gpt-4.1",
98+ "azure.gpt-4.1",
8499 "o3-mini",
100+ "azure.o3-mini",
85101 "gpt-4.1-nano",
102+ "azure.gpt-4.1-nano",
86103 "gpt-4o-mini",
104+ "azure.gpt-4o-mini",
87105 "o1",
106+ "azure.o1",
88107 "gemini-2.5-flash",
89108 "qwen-qwq",
90109 "meta-llama/llama-4-maverick-17b-128e-instruct",
91110 "claude-3-opus",
92111 "gpt-4o",
112+ "azure.gpt-4o",
93113 "gemini-2.0-flash-lite",
94114 "gemini-2.0-flash",
95115 "deepseek-r1-distill-llama-70b",
96116 "llama-3.3-70b-versatile",
97117 "claude-3.5-sonnet",
98118 "o1-mini",
119+ "azure.o1-mini",
99120 "gpt-4.1-mini",
121+ "azure.gpt-4.1-mini",
100122 "gemini-2.5",
101123 "meta-llama/llama-4-scout-17b-16e-instruct"
102124 ],
@@ -279,7 +301,8 @@
279301 "openai",
280302 "gemini",
281303 "groq",
282− "bedrock"
304+ "bedrock",
305+ "azure"
283306 ],
284307 "type": "string"
285308 }