diff --git a/internal/db/db.go b/internal/db/db.go index aee1929f..b05fce6b 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -209,9 +209,9 @@ func (db *DB) RunMigrations() error { }{ {"deepseek-auto", "heuristic", `["deepseek-chat","deepseek-reasoner"]`, "", nil, nil, nil}, {"openai-auto", "heuristic", `["gpt-4o-mini","gpt-4o"]`, "", nil, nil, nil}, - {"gemini-auto", "heuristic", `["gemini-2.5-flash","gemini-2.5-pro"]`, "", nil, nil, nil}, + {"gemini-auto", "heuristic", `["gemini-3.5-flash-lite","gemini-3.1-flash-lite","gemini-2.5-flash"]`, "", nil, nil, nil}, {"heavy-logic", "heuristic", `["grok-4.3","kimi-k2.6","deepseek-v4-pro"]`, "", nil, intPtr(9), strPtr("Complex Coding, Logic, Agents.")}, - {"standard-pro", "heuristic", `["gpt-5.4-mini","gemini-3-flash-preview"]`, "", nil, intPtr(5), strPtr("General Assistant, Long Docs.")}, + {"standard-pro", "heuristic", `["gpt-5.4-mini","gemini-3.5-flash-lite"]`, "", nil, intPtr(5), strPtr("General Assistant, Long Docs.")}, {"fast-flow", "heuristic", `["deepseek-v4-flash","gpt-5.4-nano"]`, "", nil, intPtr(2), strPtr("Classification, JSON, Basic Q&A.")}, {"dispatcher", "classifier", `["fast-flow","standard-pro","heavy-logic"]`, "gpt-5.4-nano", intPtr(10), nil, strPtr("Auto-dispatches to tier groups by complexity.")}, } diff --git a/internal/providers/gemini.go b/internal/providers/gemini.go index 87a5ad7e..981d42aa 100644 --- a/internal/providers/gemini.go +++ b/internal/providers/gemini.go @@ -202,15 +202,7 @@ func (p *GeminiProvider) ResponsesStream(ctx context.Context, req *models.Respon } func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.UnifiedRequest) (*models.ChatCompletionResponse, error) { - // Map deprecated or preview model names to active equivalents - switch req.Model { - case "gemini-2.0-flash": - req.Model = "gemini-2.5-flash" - case "gemini-3-flash": - req.Model = "gemini-3-flash-preview" - case "gemini-3-pro": - req.Model = "gemini-3-pro-preview" - } + req.Model = normalizeGeminiModel(req.Model) // Gemini mapping var contents []GeminiContent @@ -273,7 +265,7 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified } if foundAny { - contents = append(contents, GeminiContent{Role: "function", Parts: functionParts}) + contents = append(contents, GeminiContent{Role: "user", Parts: functionParts}) i = j - 1 // Advance outer loop past the tool messages we consumed } else { // If no tool results found but assistant made calls, Gemini WILL error. @@ -364,8 +356,9 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified if strings.Contains(lowerModel, "preview") || strings.Contains(lowerModel, "thinking") || strings.Contains(lowerModel, "gemini-") || - hasMappedTools { - // Use v1beta for preview, newer models, or when using tools + hasMappedTools || + hasHistoryToolCalls(contents) { + // Use v1beta for preview, newer models, tool use, or historical tool calls if !strings.Contains(baseURL, "v1beta") { baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1) } @@ -478,15 +471,7 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified } func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.UnifiedRequest) (<-chan *models.ChatCompletionStreamResponse, error) { - // Map deprecated or preview model names to active equivalents - switch req.Model { - case "gemini-2.0-flash": - req.Model = "gemini-2.5-flash" - case "gemini-3-flash": - req.Model = "gemini-3-flash-preview" - case "gemini-3-pro": - req.Model = "gemini-3-pro-preview" - } + req.Model = normalizeGeminiModel(req.Model) // Simplified Gemini mapping var contents []GeminiContent @@ -535,7 +520,7 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U } if foundAny { - contents = append(contents, GeminiContent{Role: "function", Parts: functionParts}) + contents = append(contents, GeminiContent{Role: "user", Parts: functionParts}) i = j - 1 } continue @@ -617,8 +602,9 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U if strings.Contains(lowerModel, "preview") || strings.Contains(lowerModel, "thinking") || strings.Contains(lowerModel, "gemini-") || - hasMappedTools { - // Use v1beta for preview, newer models, or when using tools + hasMappedTools || + hasHistoryToolCalls(contents) { + // Use v1beta for preview, newer models, tool use, or historical tool calls if !strings.Contains(baseURL, "v1beta") { baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1) } @@ -748,3 +734,30 @@ func resolveToolName(toolMsg models.UnifiedMessage, toolCalls []models.ToolCall, return "unknown_function" } +func normalizeGeminiModel(model string) string { + switch model { + case "gemini-2.0-flash", "gemini-1.5-flash": + return "gemini-3.5-flash-lite" + case "gemini-3-flash", "gemini-3-flash-preview": + return "gemini-3.5-flash-lite" + case "gemini-3-pro", "gemini-3-pro-preview", "gemini-1.5-pro": + return "gemini-2.5-pro" + default: + return model + } +} + +func hasHistoryToolCalls(contents []GeminiContent) bool { + for _, c := range contents { + if c.Role == "function" { + return true + } + for _, p := range c.Parts { + if p.FunctionCall != nil || p.FunctionResponse != nil { + return true + } + } + } + return false +} +