on main help sign in
feat: support VertexAI provider (#153)

* support: vertexai fix fix set default for vertexai added comment fix fix * create schema * fix README.md * fix order * added pupularity * set tools if tools is exists restore commentout * fix comment * set summarizer model

commita9fa9188bd30024e31b222d4056de94ef588258b authormineo <euonymuslke@gmail.com> dateFri 16 May 2025, 03:25 (1 year ago) parent49c710b
9 files changed+248−97
README.md+7 cmd/schema/main.go+1 internal/config/config.go+45 internal/llm/models/models.go+2 internal/llm/models/vertexai.go+38 internal/llm/provider/gemini.go+12−6 internal/llm/provider/provider.go+5 internal/llm/provider/vertexai.go+34 opencode-schema.json+104−91
README.md+7
@@ -89,6 +89,8 @@ You can configure OpenCode using environment variables:
8989| `ANTHROPIC_API_KEY` | For Claude models |
9090| `OPENAI_API_KEY` | For OpenAI models |
9191| `GEMINI_API_KEY` | For Google Gemini models |
92+| `VERTEXAI_PROJECT` | For Google Cloud VertexAI (Gemini) |
93+| `VERTEXAI_LOCATION` | For Google Cloud VertexAI (Gemini) |
9294| `GROQ_API_KEY` | For Groq models |
9395| `AWS_ACCESS_KEY_ID` | For AWS Bedrock (Claude) |
9496| `AWS_SECRET_ACCESS_KEY` | For AWS Bedrock (Claude) |
@@ -227,6 +229,11 @@ OpenCode supports a variety of AI models from different providers:
227229- O3 family (o3, o3-mini)
228230- O4 Mini
229231
232+### Google Cloud VertexAI
233+
234+- Gemini 2.5
235+- Gemini 2.5 Flash
236+
230237## Usage
231238
232239```bash
cmd/schema/main.go+1
@@ -199,6 +199,7 @@ func generateSchema() map[string]any {
199199 string(models.ProviderOpenRouter),
200200 string(models.ProviderBedrock),
201201 string(models.ProviderAzure),
202+ string(models.ProviderVertexAI),
202203 }
203204
204205 providerSchema["additionalProperties"].(map[string]any)["properties"].(map[string]any)["provider"] = map[string]any{
internal/config/config.go+45
@@ -276,6 +276,7 @@ func setProviderDefaults() {
276276 // 5. OpenRouter
277277 // 6. AWS Bedrock
278278 // 7. Azure
279+ // 8. Google Cloud VertexAI
279280
280281 // Anthropic configuration
281282 if key := viper.GetString("providers.anthropic.apiKey"); strings.TrimSpace(key) != "" {
@@ -348,6 +349,15 @@ func setProviderDefaults() {
348349 viper.SetDefault("agents.title.model", models.AzureGPT41Mini)
349350 return
350351 }
352+
353+ // Google Cloud VertexAI configuration
354+ if hasVertexAICredentials() {
355+ viper.SetDefault("agents.coder.model", models.VertexAIGemini25)
356+ viper.SetDefault("agents.summarizer.model", models.VertexAIGemini25)
357+ viper.SetDefault("agents.task.model", models.VertexAIGemini25Flash)
358+ viper.SetDefault("agents.title.model", models.VertexAIGemini25Flash)
359+ return
360+ }
351361}
352362
353363// hasAWSCredentials checks if AWS credentials are available in the environment.
@@ -376,6 +386,19 @@ func hasAWSCredentials() bool {
376386 return false
377387}
378388
389+// hasVertexAICredentials checks if VertexAI credentials are available in the environment.
390+func hasVertexAICredentials() bool {
391+ // Check for explicit VertexAI parameters
392+ if os.Getenv("VERTEXAI_PROJECT") != "" && os.Getenv("VERTEXAI_LOCATION") != "" {
393+ return true
394+ }
395+ // Check for Google Cloud project and location
396+ if os.Getenv("GOOGLE_CLOUD_PROJECT") != "" && (os.Getenv("GOOGLE_CLOUD_REGION") != "" || os.Getenv("GOOGLE_CLOUD_LOCATION") != "") {
397+ return true
398+ }
399+ return false
400+}
401+
379402// readConfig handles the result of reading a configuration file.
380403func readConfig(err error) error {
381404 if err == nil {
@@ -598,6 +621,10 @@ func getProviderAPIKey(provider models.ModelProvider) string {
598621 if hasAWSCredentials() {
599622 return "aws-credentials-available"
600623 }
624+ case models.ProviderVertexAI:
625+ if hasVertexAICredentials() {
626+ return "vertex-ai-credentials-available"
627+ }
601628 }
602629 return ""
603630}
@@ -718,6 +745,24 @@ func setDefaultModelForAgent(agent AgentName) bool {
718745 return true
719746 }
720747
748+ if hasVertexAICredentials() {
749+ var model models.ModelID
750+ maxTokens := int64(5000)
751+
752+ if agent == AgentTitle {
753+ model = models.VertexAIGemini25Flash
754+ maxTokens = 80
755+ } else {
756+ model = models.VertexAIGemini25
757+ }
758+
759+ cfg.Agents[agent] = Agent{
760+ Model: model,
761+ MaxTokens: maxTokens,
762+ }
763+ return true
764+ }
765+
721766 return false
722767}
723768
internal/llm/models/models.go+2
@@ -43,6 +43,7 @@ var ProviderPopularity = map[ModelProvider]int{
4343 ProviderOpenRouter: 5,
4444 ProviderBedrock: 6,
4545 ProviderAzure: 7,
46+ ProviderVertexAI: 8,
4647}
4748
4849var SupportedModels = map[ModelID]Model{
@@ -91,4 +92,5 @@ func init() {
9192 maps.Copy(SupportedModels, AzureModels)
9293 maps.Copy(SupportedModels, OpenRouterModels)
9394 maps.Copy(SupportedModels, XAIModels)
95+ maps.Copy(SupportedModels, VertexAIGeminiModels)
9496}
internal/llm/models/vertexai.gonew file+38
@@ -0,0 +1,38 @@
1+package models
2+
3+const (
4+ ProviderVertexAI ModelProvider = "vertexai"
5+
6+ // Models
7+ VertexAIGemini25Flash ModelID = "vertexai.gemini-2.5-flash"
8+ VertexAIGemini25 ModelID = "vertexai.gemini-2.5"
9+)
10+
11+var VertexAIGeminiModels = map[ModelID]Model{
12+ VertexAIGemini25Flash: {
13+ ID: VertexAIGemini25Flash,
14+ Name: "VertexAI: Gemini 2.5 Flash",
15+ Provider: ProviderVertexAI,
16+ APIModel: "gemini-2.5-flash-preview-04-17",
17+ CostPer1MIn: GeminiModels[Gemini25Flash].CostPer1MIn,
18+ CostPer1MInCached: GeminiModels[Gemini25Flash].CostPer1MInCached,
19+ CostPer1MOut: GeminiModels[Gemini25Flash].CostPer1MOut,
20+ CostPer1MOutCached: GeminiModels[Gemini25Flash].CostPer1MOutCached,
21+ ContextWindow: GeminiModels[Gemini25Flash].ContextWindow,
22+ DefaultMaxTokens: GeminiModels[Gemini25Flash].DefaultMaxTokens,
23+ SupportsAttachments: true,
24+ },
25+ VertexAIGemini25: {
26+ ID: VertexAIGemini25,
27+ Name: "VertexAI: Gemini 2.5 Pro",
28+ Provider: ProviderVertexAI,
29+ APIModel: "gemini-2.5-pro-preview-03-25",
30+ CostPer1MIn: GeminiModels[Gemini25].CostPer1MIn,
31+ CostPer1MInCached: GeminiModels[Gemini25].CostPer1MInCached,
32+ CostPer1MOut: GeminiModels[Gemini25].CostPer1MOut,
33+ CostPer1MOutCached: GeminiModels[Gemini25].CostPer1MOutCached,
34+ ContextWindow: GeminiModels[Gemini25].ContextWindow,
35+ DefaultMaxTokens: GeminiModels[Gemini25].DefaultMaxTokens,
36+ SupportsAttachments: true,
37+ },
38+}
internal/llm/provider/gemini.go+12−6
@@ -175,13 +175,16 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
175175
176176 history := geminiMessages[:len(geminiMessages)-1] // All but last message
177177 lastMsg := geminiMessages[len(geminiMessages)-1]
178− chat, _ := g.client.Chats.Create(ctx, g.providerOptions.model.APIModel, &genai.GenerateContentConfig{
178+ config := &genai.GenerateContentConfig{
179179 MaxOutputTokens: int32(g.providerOptions.maxTokens),
180180 SystemInstruction: &genai.Content{
181181 Parts: []*genai.Part{{Text: g.providerOptions.systemMessage}},
182182 },
183− Tools: g.convertTools(tools),
184− }, history)
183+ }
184+ if len(tools) > 0 {
185+ config.Tools = g.convertTools(tools)
186+ }
187+ chat, _ := g.client.Chats.Create(ctx, g.providerOptions.model.APIModel, config, history)
185188
186189 attempts := 0
187190 for {
@@ -260,13 +263,16 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
260263
261264 history := geminiMessages[:len(geminiMessages)-1] // All but last message
262265 lastMsg := geminiMessages[len(geminiMessages)-1]
263− chat, _ := g.client.Chats.Create(ctx, g.providerOptions.model.APIModel, &genai.GenerateContentConfig{
266+ config := &genai.GenerateContentConfig{
264267 MaxOutputTokens: int32(g.providerOptions.maxTokens),
265268 SystemInstruction: &genai.Content{
266269 Parts: []*genai.Part{{Text: g.providerOptions.systemMessage}},
267270 },
268− Tools: g.convertTools(tools),
269− }, history)
271+ }
272+ if len(tools) > 0 {
273+ config.Tools = g.convertTools(tools)
274+ }
275+ chat, _ := g.client.Chats.Create(ctx, g.providerOptions.model.APIModel, config, history)
270276
271277 attempts := 0
272278 eventChan := make(chan ProviderEvent)
internal/llm/provider/provider.go+5
@@ -120,6 +120,11 @@ func NewProvider(providerName models.ModelProvider, opts ...ProviderClientOption
120120 options: clientOptions,
121121 client: newAzureClient(clientOptions),
122122 }, nil
123+ case models.ProviderVertexAI:
124+ return &baseProvider[VertexAIClient]{
125+ options: clientOptions,
126+ client: newVertexAIClient(clientOptions),
127+ }, nil
123128 case models.ProviderOpenRouter:
124129 clientOptions.openaiOptions = append(clientOptions.openaiOptions,
125130 WithOpenAIBaseURL("https://openrouter.ai/api/v1"),
internal/llm/provider/vertexai.gonew file+34
@@ -0,0 +1,34 @@
1+package provider
2+
3+import (
4+ "context"
5+ "os"
6+
7+ "github.com/opencode-ai/opencode/internal/logging"
8+ "google.golang.org/genai"
9+)
10+
11+type VertexAIClient ProviderClient
12+
13+func newVertexAIClient(opts providerClientOptions) VertexAIClient {
14+ geminiOpts := geminiOptions{}
15+ for _, o := range opts.geminiOptions {
16+ o(&geminiOpts)
17+ }
18+
19+ client, err := genai.NewClient(context.Background(), &genai.ClientConfig{
20+ Project: os.Getenv("VERTEXAI_PROJECT"),
21+ Location: os.Getenv("VERTEXAI_LOCATION"),
22+ Backend: genai.BackendVertexAI,
23+ })
24+ if err != nil {
25+ logging.Error("Failed to create VertexAI client", "error", err)
26+ return nil
27+ }
28+
29+ return &geminiClient{
30+ providerOptions: opts,
31+ options: geminiOpts,
32+ client: client,
33+ }
34+}
opencode-schema.json+104−91
@@ -12,63 +12,69 @@
1212 "model": {
1313 "description": "Model ID for the agent",
1414 "enum": [
15− "gpt-4o-mini",
16− "o1-pro",
15+ "grok-3-fast-beta",
16+ "claude-3-opus",
17+ "gemini-2.5",
18+ "openrouter.claude-3-haiku",
19+ "grok-3-beta",
20+ "gpt-4.1",
1721 "azure.gpt-4o-mini",
22+ "openrouter.gpt-4o",
23+ "openrouter.o4-mini",
24+ "openrouter.o1-pro",
25+ "gpt-4.1-nano",
26+ "azure.gpt-4.5-preview",
27+ "openrouter.gpt-4o-mini",
28+ "claude-3.5-sonnet",
29+ "claude-3-haiku",
30+ "qwen-qwq",
31+ "openrouter.claude-3.7-sonnet",
32+ "gemini-2.5-flash",
33+ "azure.o4-mini",
1834 "openrouter.gpt-4.1-mini",
35+ "gpt-4o",
36+ "openrouter.gemini-2.5",
37+ "gpt-4.1-mini",
38+ "azure.gpt-4.1",
39+ "azure.o1-mini",
40+ "o1-pro",
41+ "claude-3.7-sonnet",
42+ "o3",
43+ "gpt-4.5-preview",
44+ "azure.o3-mini",
45+ "grok-3-mini-beta",
1946 "openrouter.o1-mini",
20− "bedrock.claude-3.7-sonnet",
2147 "meta-llama/llama-4-scout-17b-16e-instruct",
22− "openrouter.gpt-4o-mini",
48+ "azure.o1",
49+ "openrouter.gemini-2.5-flash",
50+ "openrouter.claude-3-opus",
51+ "o1-mini",
2352 "gemini-2.0-flash",
24− "deepseek-r1-distill-llama-70b",
25− "openrouter.claude-3.7-sonnet",
26− "openrouter.gpt-4.5-preview",
27− "azure.o3-mini",
53+ "openrouter.gpt-4.1",
2854 "openrouter.claude-3.5-haiku",
29− "azure.o1-mini",
30− "openrouter.o1",
31− "openrouter.gemini-2.5",
55+ "deepseek-r1-distill-llama-70b",
56+ "claude-3.5-haiku",
57+ "o3-mini",
3258 "llama-3.3-70b-versatile",
33− "gpt-4.5-preview",
34− "openrouter.claude-3-opus",
35− "openrouter.claude-3.5-sonnet",
36− "o4-mini",
59+ "azure.gpt-4.1-nano",
60+ "openrouter.gpt-4.5-preview",
3761 "gemini-2.0-flash-lite",
38− "azure.gpt-4.5-preview",
39− "openrouter.gpt-4o",
40− "o1",
4162 "azure.gpt-4o",
63+ "openrouter.o3-mini",
64+ "openrouter.o1",
4265 "openrouter.gpt-4.1-nano",
43− "o3",
44− "gpt-4.1",
45− "azure.o1",
46− "claude-3-haiku",
47− "claude-3-opus",
48− "gpt-4.1-mini",
49− "openrouter.o4-mini",
50− "openrouter.gemini-2.5-flash",
51− "claude-3.5-haiku",
52− "o3-mini",
66+ "grok-3-mini-fast-beta",
67+ "vertexai.gemini-2.5-flash",
68+ "o4-mini",
5369 "azure.o3",
54− "gpt-4o",
55− "azure.gpt-4.1",
56− "openrouter.claude-3-haiku",
57− "gpt-4.1-nano",
58− "azure.gpt-4.1-nano",
59− "claude-3.7-sonnet",
60− "gemini-2.5",
61− "azure.o4-mini",
62− "o1-mini",
63− "qwen-qwq",
64− "meta-llama/llama-4-maverick-17b-128e-instruct",
65− "openrouter.gpt-4.1",
66− "openrouter.o1-pro",
67− "openrouter.o3",
68− "claude-3.5-sonnet",
69− "gemini-2.5-flash",
7070 "azure.gpt-4.1-mini",
71− "openrouter.o3-mini"
71+ "openrouter.o3",
72+ "gpt-4o-mini",
73+ "o1",
74+ "vertexai.gemini-2.5",
75+ "bedrock.claude-3.7-sonnet",
76+ "meta-llama/llama-4-maverick-17b-128e-instruct",
77+ "openrouter.claude-3.5-sonnet"
7278 ],
7379 "type": "string"
7480 },
@@ -102,63 +108,69 @@
102108 "model": {
103109 "description": "Model ID for the agent",
104110 "enum": [
105− "gpt-4o-mini",
106− "o1-pro",
111+ "grok-3-fast-beta",
112+ "claude-3-opus",
113+ "gemini-2.5",
114+ "openrouter.claude-3-haiku",
115+ "grok-3-beta",
116+ "gpt-4.1",
107117 "azure.gpt-4o-mini",
118+ "openrouter.gpt-4o",
119+ "openrouter.o4-mini",
120+ "openrouter.o1-pro",
121+ "gpt-4.1-nano",
122+ "azure.gpt-4.5-preview",
123+ "openrouter.gpt-4o-mini",
124+ "claude-3.5-sonnet",
125+ "claude-3-haiku",
126+ "qwen-qwq",
127+ "openrouter.claude-3.7-sonnet",
128+ "gemini-2.5-flash",
129+ "azure.o4-mini",
108130 "openrouter.gpt-4.1-mini",
131+ "gpt-4o",
132+ "openrouter.gemini-2.5",
133+ "gpt-4.1-mini",
134+ "azure.gpt-4.1",
135+ "azure.o1-mini",
136+ "o1-pro",
137+ "claude-3.7-sonnet",
138+ "o3",
139+ "gpt-4.5-preview",
140+ "azure.o3-mini",
141+ "grok-3-mini-beta",
109142 "openrouter.o1-mini",
110− "bedrock.claude-3.7-sonnet",
111143 "meta-llama/llama-4-scout-17b-16e-instruct",
112− "openrouter.gpt-4o-mini",
144+ "azure.o1",
145+ "openrouter.gemini-2.5-flash",
146+ "openrouter.claude-3-opus",
147+ "o1-mini",
113148 "gemini-2.0-flash",
114− "deepseek-r1-distill-llama-70b",
115− "openrouter.claude-3.7-sonnet",
116− "openrouter.gpt-4.5-preview",
117− "azure.o3-mini",
149+ "openrouter.gpt-4.1",
118150 "openrouter.claude-3.5-haiku",
119− "azure.o1-mini",
120− "openrouter.o1",
121− "openrouter.gemini-2.5",
151+ "deepseek-r1-distill-llama-70b",
152+ "claude-3.5-haiku",
153+ "o3-mini",
122154 "llama-3.3-70b-versatile",
123− "gpt-4.5-preview",
124− "openrouter.claude-3-opus",
125− "openrouter.claude-3.5-sonnet",
126− "o4-mini",
155+ "azure.gpt-4.1-nano",
156+ "openrouter.gpt-4.5-preview",
127157 "gemini-2.0-flash-lite",
128− "azure.gpt-4.5-preview",
129− "openrouter.gpt-4o",
130− "o1",
131158 "azure.gpt-4o",
159+ "openrouter.o3-mini",
160+ "openrouter.o1",
132161 "openrouter.gpt-4.1-nano",
133− "o3",
134− "gpt-4.1",
135− "azure.o1",
136− "claude-3-haiku",
137− "claude-3-opus",
138− "gpt-4.1-mini",
139− "openrouter.o4-mini",
140− "openrouter.gemini-2.5-flash",
141− "claude-3.5-haiku",
142− "o3-mini",
162+ "grok-3-mini-fast-beta",
163+ "vertexai.gemini-2.5-flash",
164+ "o4-mini",
143165 "azure.o3",
144− "gpt-4o",
145− "azure.gpt-4.1",
146− "openrouter.claude-3-haiku",
147− "gpt-4.1-nano",
148− "azure.gpt-4.1-nano",
149− "claude-3.7-sonnet",
150− "gemini-2.5",
151− "azure.o4-mini",
152− "o1-mini",
153− "qwen-qwq",
154− "meta-llama/llama-4-maverick-17b-128e-instruct",
155− "openrouter.gpt-4.1",
156− "openrouter.o1-pro",
157− "openrouter.o3",
158− "claude-3.5-sonnet",
159− "gemini-2.5-flash",
160166 "azure.gpt-4.1-mini",
161− "openrouter.o3-mini"
167+ "openrouter.o3",
168+ "gpt-4o-mini",
169+ "o1",
170+ "vertexai.gemini-2.5",
171+ "bedrock.claude-3.7-sonnet",
172+ "meta-llama/llama-4-maverick-17b-128e-instruct",
173+ "openrouter.claude-3.5-sonnet"
162174 ],
163175 "type": "string"
164176 },
@@ -341,7 +353,8 @@
341353 "groq",
342354 "openrouter",
343355 "bedrock",
344− "azure"
356+ "azure",
357+ "vertexai"
345358 ],
346359 "type": "string"
347360 }