From 3c3bb2fa1949c7e5d0dd4e9b4cb07cab61595cd3 Mon Sep 17 00:00:00 2001 From: Tamseno Date: Mon, 17 Aug 2026 15:23:49 +0800 Subject: [PATCH 1/2] fix(gemini): support includeServerSideToolInvocations in GeminiToolConfig - Add IncludeServerSideToolInvocations field to GeminiToolConfig to prevent dropping client tool settings. - Fix HTTP 400 error when mixing built-in tools (e.g. Google Search) with function calling on Gemini 3.6/3.7 models. - Add serialization/deserialization unit test TestGeminiToolConfig_IncludeServerSideToolInvocations. Fixes #5709 --- .../internal/pkg/antigravity/gemini_types.go | 3 ++- .../antigravity/request_transformer_test.go | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/backend/internal/pkg/antigravity/gemini_types.go b/backend/internal/pkg/antigravity/gemini_types.go index 033dccbd5d..4fd7882adc 100644 --- a/backend/internal/pkg/antigravity/gemini_types.go +++ b/backend/internal/pkg/antigravity/gemini_types.go @@ -112,7 +112,8 @@ type GeminiImageSearch struct { // GeminiToolConfig Gemini 工具配置 type GeminiToolConfig struct { - FunctionCallingConfig *GeminiFunctionCallingConfig `json:"functionCallingConfig,omitempty"` + FunctionCallingConfig *GeminiFunctionCallingConfig `json:"functionCallingConfig,omitempty"` + IncludeServerSideToolInvocations *bool `json:"includeServerSideToolInvocations,omitempty"` } // GeminiFunctionCallingConfig 函数调用配置 diff --git a/backend/internal/pkg/antigravity/request_transformer_test.go b/backend/internal/pkg/antigravity/request_transformer_test.go index ea95cc7ea6..008e54724a 100644 --- a/backend/internal/pkg/antigravity/request_transformer_test.go +++ b/backend/internal/pkg/antigravity/request_transformer_test.go @@ -565,3 +565,27 @@ func TestTransformClaudeToGeminiWithOptions_PreservesWebSearchAlongsideFunctions require.Equal(t, "get_weather", req.Request.Tools[0].FunctionDeclarations[0].Name) require.NotNil(t, req.Request.Tools[1].GoogleSearch) } + +func TestGeminiToolConfig_IncludeServerSideToolInvocations(t *testing.T) { + t.Run("serialize and deserialize toolConfig with includeServerSideToolInvocations", func(t *testing.T) { + trueVal := true + cfg := GeminiToolConfig{ + FunctionCallingConfig: &GeminiFunctionCallingConfig{ + Mode: "VALIDATED", + }, + IncludeServerSideToolInvocations: &trueVal, + } + + data, err := json.Marshal(cfg) + require.NoError(t, err) + require.Contains(t, string(data), `"includeServerSideToolInvocations":true`) + + var decoded GeminiToolConfig + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + require.NotNil(t, decoded.IncludeServerSideToolInvocations) + require.True(t, *decoded.IncludeServerSideToolInvocations) + require.Equal(t, "VALIDATED", decoded.FunctionCallingConfig.Mode) + }) +} + From 1ba92449c7372493b2c94d42e290c0959f4cf760 Mon Sep 17 00:00:00 2001 From: yaxin Date: Tue, 18 Aug 2026 15:05:54 +0800 Subject: [PATCH 2/2] fix(gemini): wire includeServerSideToolInvocations into the typed transform path The struct field alone never reached the wire: the raw passthrough pipeline is covered by enableMixedGeminiToolInvocations (#5711), but TransformClaudeToGeminiWithOptions builds GeminiToolConfig from scratch and never set the flag, so gemini-* models entering through the Claude format gateway could still hit the upstream 400 from issue #5709. - Set IncludeServerSideToolInvocations=true when the built tool declarations mix functionDeclarations with googleSearch, matching the raw-path injection semantics. - Replace the marshal-roundtrip-only test with behavior tests that drive TransformClaudeToGeminiWithOptions: mixed tools set the flag, function-only and web-search-only requests leave it unset. --- .../pkg/antigravity/request_transformer.go | 22 +++++++ .../antigravity/request_transformer_test.go | 66 ++++++++++++++----- 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/backend/internal/pkg/antigravity/request_transformer.go b/backend/internal/pkg/antigravity/request_transformer.go index 7d0a707959..9da3cc33a2 100644 --- a/backend/internal/pkg/antigravity/request_transformer.go +++ b/backend/internal/pkg/antigravity/request_transformer.go @@ -156,6 +156,13 @@ func TransformClaudeToGeminiWithOptions(claudeReq *ClaudeRequest, projectID, map Mode: "VALIDATED", }, } + // 内置工具(googleSearch)与函数调用混用时,上游要求显式开启 + // includeServerSideToolInvocations,否则返回 400(issue #5709)。 + // 与 raw 透传路的 enableMixedGeminiToolInvocations 注入保持同一语义。 + if hasMixedToolInvocations(tools) { + enabled := true + innerRequest.ToolConfig.IncludeServerSideToolInvocations = &enabled + } } if systemInstruction != nil { @@ -703,6 +710,21 @@ func isWebSearchTool(tool ClaudeTool) bool { } } +// hasMixedToolInvocations 判断构建后的工具声明是否同时包含函数声明与内置工具 +// (googleSearch)。仅在两者并存时需要开启 includeServerSideToolInvocations。 +func hasMixedToolInvocations(declarations []GeminiToolDeclaration) bool { + hasFunc, hasBuiltin := false, false + for _, d := range declarations { + if len(d.FunctionDeclarations) > 0 { + hasFunc = true + } + if d.GoogleSearch != nil { + hasBuiltin = true + } + } + return hasFunc && hasBuiltin +} + // buildTools 构建 tools func buildTools(tools []ClaudeTool) []GeminiToolDeclaration { if len(tools) == 0 { diff --git a/backend/internal/pkg/antigravity/request_transformer_test.go b/backend/internal/pkg/antigravity/request_transformer_test.go index 008e54724a..05764ef7ff 100644 --- a/backend/internal/pkg/antigravity/request_transformer_test.go +++ b/backend/internal/pkg/antigravity/request_transformer_test.go @@ -567,25 +567,57 @@ func TestTransformClaudeToGeminiWithOptions_PreservesWebSearchAlongsideFunctions } func TestGeminiToolConfig_IncludeServerSideToolInvocations(t *testing.T) { - t.Run("serialize and deserialize toolConfig with includeServerSideToolInvocations", func(t *testing.T) { - trueVal := true - cfg := GeminiToolConfig{ - FunctionCallingConfig: &GeminiFunctionCallingConfig{ - Mode: "VALIDATED", + functionTool := ClaudeTool{ + Name: "get_weather", + Description: "Get weather information", + InputSchema: map[string]any{"type": "object"}, + } + webSearchTool := ClaudeTool{ + Type: "web_search_20250305", + Name: "web_search", + } + + transform := func(t *testing.T, tools []ClaudeTool) (V1InternalRequest, string) { + t.Helper() + body, err := TransformClaudeToGeminiWithOptions(&ClaudeRequest{ + Model: "claude-3-5-sonnet-latest", + Messages: []ClaudeMessage{ + { + Role: "user", + Content: json.RawMessage(`[{"type":"text","text":"hello"}]`), + }, }, - IncludeServerSideToolInvocations: &trueVal, - } - - data, err := json.Marshal(cfg) + Tools: tools, + }, "project-1", "gemini-2.5-flash", DefaultTransformOptions()) require.NoError(t, err) - require.Contains(t, string(data), `"includeServerSideToolInvocations":true`) - var decoded GeminiToolConfig - err = json.Unmarshal(data, &decoded) - require.NoError(t, err) - require.NotNil(t, decoded.IncludeServerSideToolInvocations) - require.True(t, *decoded.IncludeServerSideToolInvocations) - require.Equal(t, "VALIDATED", decoded.FunctionCallingConfig.Mode) + var req V1InternalRequest + require.NoError(t, json.Unmarshal(body, &req)) + return req, string(body) + } + + t.Run("mixed builtin and function tools enable server-side tool invocations", func(t *testing.T) { + req, raw := transform(t, []ClaudeTool{functionTool, webSearchTool}) + + require.NotNil(t, req.Request.ToolConfig) + require.NotNil(t, req.Request.ToolConfig.IncludeServerSideToolInvocations) + require.True(t, *req.Request.ToolConfig.IncludeServerSideToolInvocations) + require.Contains(t, raw, `"includeServerSideToolInvocations":true`) + }) + + t.Run("function tools only leave the flag unset", func(t *testing.T) { + req, raw := transform(t, []ClaudeTool{functionTool}) + + require.NotNil(t, req.Request.ToolConfig) + require.Nil(t, req.Request.ToolConfig.IncludeServerSideToolInvocations) + require.NotContains(t, raw, "includeServerSideToolInvocations") + }) + + t.Run("web search only leaves the flag unset", func(t *testing.T) { + req, raw := transform(t, []ClaudeTool{webSearchTool}) + + require.NotNil(t, req.Request.ToolConfig) + require.Nil(t, req.Request.ToolConfig.IncludeServerSideToolInvocations) + require.NotContains(t, raw, "includeServerSideToolInvocations") }) } -