mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 11:43:23 +08:00
Merge pull request #5711 from wucm667/fix/issue-5709-antigravity-tool-config
fix(antigravity): preserve mixed Gemini tool config
This commit is contained in:
@@ -264,6 +264,10 @@ func (s *AntigravityGatewayService) buildAntigravityCompatGeminiBody(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err = enableMixedGeminiToolInvocations(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body = ensureGeminiFunctionCallThoughtSignatures(body)
|
||||
body, err = injectIdentityPatchToGeminiRequest(body)
|
||||
if err != nil {
|
||||
@@ -280,6 +284,38 @@ func (s *AntigravityGatewayService) buildAntigravityCompatGeminiBody(
|
||||
return antigravity.TransformClaudeToGeminiWithOptions(claudeRequest, projectID, mappedModel, options)
|
||||
}
|
||||
|
||||
func enableMixedGeminiToolInvocations(body []byte) ([]byte, error) {
|
||||
var request map[string]any
|
||||
if err := json.Unmarshal(body, &request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hasGoogleSearch, hasFunctionDeclarations bool
|
||||
if tools, ok := request["tools"].([]any); ok {
|
||||
for _, rawTool := range tools {
|
||||
tool, ok := rawTool.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
_, hasSearch := tool["googleSearch"]
|
||||
declarations, hasFunctions := tool["functionDeclarations"].([]any)
|
||||
hasGoogleSearch = hasGoogleSearch || hasSearch
|
||||
hasFunctionDeclarations = hasFunctionDeclarations || hasFunctions && len(declarations) > 0
|
||||
}
|
||||
}
|
||||
if !hasGoogleSearch || !hasFunctionDeclarations {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
toolConfig, _ := request["toolConfig"].(map[string]any)
|
||||
if toolConfig == nil {
|
||||
toolConfig = make(map[string]any)
|
||||
request["toolConfig"] = toolConfig
|
||||
}
|
||||
toolConfig["includeServerSideToolInvocations"] = true
|
||||
return json.Marshal(request)
|
||||
}
|
||||
|
||||
func antigravityCompatProxyURL(account *Account) string {
|
||||
if account.ProxyID == nil || account.Proxy == nil {
|
||||
return ""
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -222,6 +223,51 @@ func TestAntigravityCompatRejectsUnsupportedAccountType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAntigravityCompatGeminiBody_ConfiguresMixedToolInvocations(t *testing.T) {
|
||||
svc := &AntigravityGatewayService{}
|
||||
tests := []struct {
|
||||
name string
|
||||
tools string
|
||||
wantField bool
|
||||
}{
|
||||
{
|
||||
name: "mixed server and client tools",
|
||||
tools: `[{"name":"get_weather","input_schema":{"type":"object"}},{"type":"web_search_20250305","name":"web_search"}]`,
|
||||
wantField: true,
|
||||
},
|
||||
{
|
||||
name: "client tools only",
|
||||
tools: `[{"name":"get_weather","input_schema":{"type":"object"}}]`,
|
||||
},
|
||||
{
|
||||
name: "server tools only",
|
||||
tools: `[{"type":"web_search_20250305","name":"web_search"}]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
claudeBody := []byte(`{"messages":[{"role":"user","content":"hello"}],"tools":` + tt.tools + `}`)
|
||||
claudeBody = bytes.ReplaceAll(claudeBody, []byte{92}, nil)
|
||||
body, err := svc.buildAntigravityCompatGeminiBody(context.Background(), claudeBody, nil, "project-1", "gemini-2.5-flash")
|
||||
require.NoError(t, err)
|
||||
|
||||
var wrapped map[string]any
|
||||
require.NoError(t, json.Unmarshal(body, &wrapped))
|
||||
request, ok := wrapped["request"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
toolConfig, exists := request["toolConfig"].(map[string]any)
|
||||
if !tt.wantField {
|
||||
require.False(t, exists)
|
||||
return
|
||||
}
|
||||
require.True(t, exists)
|
||||
require.Equal(t, true, toolConfig["includeServerSideToolInvocations"])
|
||||
require.NotContains(t, toolConfig, "include_server_side_tool_invocations")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAntigravityCompatPreservesChatTokenLimit(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
tests := []struct {
|
||||
|
||||
@@ -340,6 +340,44 @@ func TestAntigravityGatewayService_ForwardGemini_UsesConfiguredProjectFallback(t
|
||||
require.Equal(t, "configured-project", wrapped["project"])
|
||||
}
|
||||
|
||||
func TestAntigravityGatewayService_ForwardGemini_PreservesServerSideToolInvocationConfig(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
body := []byte(`{"contents":[{"role":"user","parts":[{"text":"hello"}]}],"tools":[{"functionDeclarations":[{"name":"get_weather","parameters":{"type":"object","additionalProperties":false}}]},{"googleSearch":{}}],"toolConfig":{"includeServerSideToolInvocations":true}}`)
|
||||
writer := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(writer)
|
||||
body = bytes.ReplaceAll(body, []byte{92}, nil)
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/v1beta/models/gemini-2.5-flash:generateContent", bytes.NewReader(body))
|
||||
|
||||
upstream := &queuedHTTPUpstreamStub{responses: []*http.Response{{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{"Content-Type": []string{"text/event-stream"}},
|
||||
Body: io.NopCloser(strings.NewReader("data: {\"response\":{\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"ok\"}]},\"finishReason\":\"STOP\"}],\"usageMetadata\":{}}}\n\n")),
|
||||
}}}
|
||||
svc := &AntigravityGatewayService{
|
||||
settingService: NewSettingService(&antigravitySettingRepoStub{}, &config.Config{Gateway: config.GatewayConfig{MaxLineSize: defaultMaxLineSize}}),
|
||||
tokenProvider: &AntigravityTokenProvider{},
|
||||
httpUpstream: upstream,
|
||||
}
|
||||
account := &Account{
|
||||
ID: 103, Name: "native-gemini", Platform: PlatformAntigravity, Type: AccountTypeOAuth, Status: StatusActive, Concurrency: 1,
|
||||
Credentials: map[string]any{"access_token": "token", "project_id": "project-103", "model_mapping": map[string]any{"gemini-2.5-flash": "gemini-2.5-flash"}},
|
||||
}
|
||||
|
||||
result, err := svc.ForwardGemini(context.Background(), c, account, "gemini-2.5-flash", "generateContent", false, body, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.Len(t, upstream.requestBodies, 1)
|
||||
|
||||
var wrapped map[string]any
|
||||
require.NoError(t, json.Unmarshal(upstream.requestBodies[0], &wrapped))
|
||||
request, ok := wrapped["request"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
toolConfig, ok := request["toolConfig"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, true, toolConfig["includeServerSideToolInvocations"])
|
||||
require.NotContains(t, toolConfig, "include_server_side_tool_invocations")
|
||||
}
|
||||
|
||||
func TestAntigravityGatewayService_ForwardGemini_MissingProjectReturnsLocalError(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
writer := httptest.NewRecorder()
|
||||
|
||||
Reference in New Issue
Block a user