@@ -9,14 +9,12 @@ import (
9
9
"strings"
10
10
"time"
11
11
12
- "github.com/google/generative-ai-go/genai"
13
12
"github.com/google/uuid"
14
13
"github.com/opencode-ai/opencode/internal/config"
15
14
"github.com/opencode-ai/opencode/internal/llm/tools"
16
15
"github.com/opencode-ai/opencode/internal/logging"
17
16
"github.com/opencode-ai/opencode/internal/message"
18
- "google.golang.org/api/iterator"
19
- "google.golang.org/api/option"
17
+ "google.golang.org/genai"
20
18
)
21
19
22
20
type geminiOptions struct {
@@ -39,7 +37,7 @@ func newGeminiClient(opts providerClientOptions) GeminiClient {
39
37
o (& geminiOpts )
40
38
}
41
39
42
- client , err := genai .NewClient (context .Background (), option . WithAPIKey ( opts .apiKey ) )
40
+ client , err := genai .NewClient (context .Background (), & genai. ClientConfig { APIKey : opts .apiKey , Backend : genai . BackendGeminiAPI } )
43
41
if err != nil {
44
42
logging .Error ("Failed to create Gemini client" , "error" , err )
45
43
return nil
@@ -57,11 +55,14 @@ func (g *geminiClient) convertMessages(messages []message.Message) []*genai.Cont
57
55
for _ , msg := range messages {
58
56
switch msg .Role {
59
57
case message .User :
60
- var parts []genai.Part
61
- parts = append (parts , genai .Text ( msg .Content ().String ()) )
58
+ var parts []* genai.Part
59
+ parts = append (parts , & genai.Part { Text : msg .Content ().String ()} )
62
60
for _ , binaryContent := range msg .BinaryContent () {
63
61
imageFormat := strings .Split (binaryContent .MIMEType , "/" )
64
- parts = append (parts , genai .ImageData (imageFormat [1 ], binaryContent .Data ))
62
+ parts = append (parts , & genai.Part {InlineData : & genai.Blob {
63
+ MIMEType : imageFormat [1 ],
64
+ Data : binaryContent .Data ,
65
+ }})
65
66
}
66
67
history = append (history , & genai.Content {
67
68
Parts : parts ,
@@ -70,19 +71,21 @@ func (g *geminiClient) convertMessages(messages []message.Message) []*genai.Cont
70
71
case message .Assistant :
71
72
content := & genai.Content {
72
73
Role : "model" ,
73
- Parts : []genai.Part {},
74
+ Parts : []* genai.Part {},
74
75
}
75
76
76
77
if msg .Content ().String () != "" {
77
- content .Parts = append (content .Parts , genai .Text ( msg .Content ().String ()) )
78
+ content .Parts = append (content .Parts , & genai.Part { Text : msg .Content ().String ()} )
78
79
}
79
80
80
81
if len (msg .ToolCalls ()) > 0 {
81
82
for _ , call := range msg .ToolCalls () {
82
83
args , _ := parseJsonToMap (call .Input )
83
- content .Parts = append (content .Parts , genai.FunctionCall {
84
- Name : call .Name ,
85
- Args : args ,
84
+ content .Parts = append (content .Parts , & genai.Part {
85
+ FunctionCall : & genai.FunctionCall {
86
+ Name : call .Name ,
87
+ Args : args ,
88
+ },
86
89
})
87
90
}
88
91
}
@@ -110,10 +113,14 @@ func (g *geminiClient) convertMessages(messages []message.Message) []*genai.Cont
110
113
}
111
114
112
115
history = append (history , & genai.Content {
113
- Parts : []genai.Part {genai.FunctionResponse {
114
- Name : toolCall .Name ,
115
- Response : response ,
116
- }},
116
+ Parts : []* genai.Part {
117
+ {
118
+ FunctionResponse : & genai.FunctionResponse {
119
+ Name : toolCall .Name ,
120
+ Response : response ,
121
+ },
122
+ },
123
+ },
117
124
Role : "function" ,
118
125
})
119
126
}
@@ -157,18 +164,6 @@ func (g *geminiClient) finishReason(reason genai.FinishReason) message.FinishRea
157
164
}
158
165
159
166
func (g * geminiClient ) send (ctx context.Context , messages []message.Message , tools []tools.BaseTool ) (* ProviderResponse , error ) {
160
- model := g .client .GenerativeModel (g .providerOptions .model .APIModel )
161
- model .SetMaxOutputTokens (int32 (g .providerOptions .maxTokens ))
162
- model .SystemInstruction = & genai.Content {
163
- Parts : []genai.Part {
164
- genai .Text (g .providerOptions .systemMessage ),
165
- },
166
- }
167
- // Convert tools
168
- if len (tools ) > 0 {
169
- model .Tools = g .convertTools (tools )
170
- }
171
-
172
167
// Convert messages
173
168
geminiMessages := g .convertMessages (messages )
174
169
@@ -178,16 +173,26 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
178
173
logging .Debug ("Prepared messages" , "messages" , string (jsonData ))
179
174
}
180
175
176
+ history := geminiMessages [:len (geminiMessages )- 1 ] // All but last message
177
+ lastMsg := geminiMessages [len (geminiMessages )- 1 ]
178
+ chat , _ := g .client .Chats .Create (ctx , g .providerOptions .model .APIModel , & genai.GenerateContentConfig {
179
+ MaxOutputTokens : int32 (g .providerOptions .maxTokens ),
180
+ SystemInstruction : & genai.Content {
181
+ Parts : []* genai.Part {{Text : g .providerOptions .systemMessage }},
182
+ },
183
+ Tools : g .convertTools (tools ),
184
+ }, history )
185
+
181
186
attempts := 0
182
187
for {
183
188
attempts ++
184
189
var toolCalls []message.ToolCall
185
- chat := model .StartChat ()
186
- chat .History = geminiMessages [:len (geminiMessages )- 1 ] // All but last message
187
-
188
- lastMsg := geminiMessages [len (geminiMessages )- 1 ]
189
190
190
- resp , err := chat .SendMessage (ctx , lastMsg .Parts ... )
191
+ var lastMsgParts []genai.Part
192
+ for _ , part := range lastMsg .Parts {
193
+ lastMsgParts = append (lastMsgParts , * part )
194
+ }
195
+ resp , err := chat .SendMessage (ctx , lastMsgParts ... )
191
196
// If there is an error we are going to see if we can retry the call
192
197
if err != nil {
193
198
retry , after , retryErr := g .shouldRetry (attempts , err )
@@ -210,15 +215,15 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
210
215
211
216
if len (resp .Candidates ) > 0 && resp .Candidates [0 ].Content != nil {
212
217
for _ , part := range resp .Candidates [0 ].Content .Parts {
213
- switch p := part .( type ) {
214
- case genai .Text :
215
- content = string (p )
216
- case genai .FunctionCall :
218
+ switch {
219
+ case part .Text != "" :
220
+ content = string (part . Text )
221
+ case part .FunctionCall != nil :
217
222
id := "call_" + uuid .New ().String ()
218
- args , _ := json .Marshal (p .Args )
223
+ args , _ := json .Marshal (part . FunctionCall .Args )
219
224
toolCalls = append (toolCalls , message.ToolCall {
220
225
ID : id ,
221
- Name : p .Name ,
226
+ Name : part . FunctionCall .Name ,
222
227
Input : string (args ),
223
228
Type : "function" ,
224
229
Finished : true ,
@@ -244,18 +249,6 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
244
249
}
245
250
246
251
func (g * geminiClient ) stream (ctx context.Context , messages []message.Message , tools []tools.BaseTool ) <- chan ProviderEvent {
247
- model := g .client .GenerativeModel (g .providerOptions .model .APIModel )
248
- model .SetMaxOutputTokens (int32 (g .providerOptions .maxTokens ))
249
- model .SystemInstruction = & genai.Content {
250
- Parts : []genai.Part {
251
- genai .Text (g .providerOptions .systemMessage ),
252
- },
253
- }
254
- // Convert tools
255
- if len (tools ) > 0 {
256
- model .Tools = g .convertTools (tools )
257
- }
258
-
259
252
// Convert messages
260
253
geminiMessages := g .convertMessages (messages )
261
254
@@ -265,6 +258,16 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
265
258
logging .Debug ("Prepared messages" , "messages" , string (jsonData ))
266
259
}
267
260
261
+ history := geminiMessages [:len (geminiMessages )- 1 ] // All but last message
262
+ lastMsg := geminiMessages [len (geminiMessages )- 1 ]
263
+ chat , _ := g .client .Chats .Create (ctx , g .providerOptions .model .APIModel , & genai.GenerateContentConfig {
264
+ MaxOutputTokens : int32 (g .providerOptions .maxTokens ),
265
+ SystemInstruction : & genai.Content {
266
+ Parts : []* genai.Part {{Text : g .providerOptions .systemMessage }},
267
+ },
268
+ Tools : g .convertTools (tools ),
269
+ }, history )
270
+
268
271
attempts := 0
269
272
eventChan := make (chan ProviderEvent )
270
273
@@ -273,23 +276,19 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
273
276
274
277
for {
275
278
attempts ++
276
- chat := model .StartChat ()
277
- chat .History = geminiMessages [:len (geminiMessages )- 1 ]
278
- lastMsg := geminiMessages [len (geminiMessages )- 1 ]
279
-
280
- iter := chat .SendMessageStream (ctx , lastMsg .Parts ... )
281
279
282
280
currentContent := ""
283
281
toolCalls := []message.ToolCall {}
284
282
var finalResp * genai.GenerateContentResponse
285
283
286
284
eventChan <- ProviderEvent {Type : EventContentStart }
287
285
288
- for {
289
- resp , err := iter .Next ()
290
- if err == iterator .Done {
291
- break
292
- }
286
+ var lastMsgParts []genai.Part
287
+
288
+ for _ , part := range lastMsg .Parts {
289
+ lastMsgParts = append (lastMsgParts , * part )
290
+ }
291
+ for resp , err := range chat .SendMessageStream (ctx , lastMsgParts ... ) {
293
292
if err != nil {
294
293
retry , after , retryErr := g .shouldRetry (attempts , err )
295
294
if retryErr != nil {
@@ -318,22 +317,22 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
318
317
319
318
if len (resp .Candidates ) > 0 && resp .Candidates [0 ].Content != nil {
320
319
for _ , part := range resp .Candidates [0 ].Content .Parts {
321
- switch p := part .( type ) {
322
- case genai .Text :
323
- delta := string (p )
320
+ switch {
321
+ case part .Text != "" :
322
+ delta := string (part . Text )
324
323
if delta != "" {
325
324
eventChan <- ProviderEvent {
326
325
Type : EventContentDelta ,
327
326
Content : delta ,
328
327
}
329
328
currentContent += delta
330
329
}
331
- case genai .FunctionCall :
330
+ case part .FunctionCall != nil :
332
331
id := "call_" + uuid .New ().String ()
333
- args , _ := json .Marshal (p .Args )
332
+ args , _ := json .Marshal (part . FunctionCall .Args )
334
333
newCall := message.ToolCall {
335
334
ID : id ,
336
- Name : p .Name ,
335
+ Name : part . FunctionCall .Name ,
337
336
Input : string (args ),
338
337
Type : "function" ,
339
338
Finished : true ,
@@ -421,12 +420,12 @@ func (g *geminiClient) toolCalls(resp *genai.GenerateContentResponse) []message.
421
420
422
421
if len (resp .Candidates ) > 0 && resp .Candidates [0 ].Content != nil {
423
422
for _ , part := range resp .Candidates [0 ].Content .Parts {
424
- if funcCall , ok := part .(genai. FunctionCall ); ok {
423
+ if part .FunctionCall != nil {
425
424
id := "call_" + uuid .New ().String ()
426
- args , _ := json .Marshal (funcCall .Args )
425
+ args , _ := json .Marshal (part . FunctionCall .Args )
427
426
toolCalls = append (toolCalls , message.ToolCall {
428
427
ID : id ,
429
- Name : funcCall .Name ,
428
+ Name : part . FunctionCall .Name ,
430
429
Input : string (args ),
431
430
Type : "function" ,
432
431
})
0 commit comments