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