diff --git a/backend/internal/service/antigravity_gateway_compat.go b/backend/internal/service/antigravity_gateway_compat.go index 49ca076884..1d6d39e1aa 100644 --- a/backend/internal/service/antigravity_gateway_compat.go +++ b/backend/internal/service/antigravity_gateway_compat.go @@ -29,6 +29,8 @@ const ( AntigravityCredentialRejectedReason GatewayFailureReason = "antigravity_oauth_credential_rejected" ) +const antigravityCompatMaxTokens = 64000 + type antigravityCompatRequest struct { protocol antigravityCompatProtocol originalBody []byte @@ -158,7 +160,7 @@ func preserveChatCompletionTokenLimit(request *apicompat.ChatCompletionsRequest, limit = request.MaxCompletionTokens } if limit != nil && *limit > 0 { - claudeRequest.MaxTokens = *limit + claudeRequest.MaxTokens = min(*limit, antigravityCompatMaxTokens) } } diff --git a/backend/internal/service/antigravity_gateway_compat_test.go b/backend/internal/service/antigravity_gateway_compat_test.go index 3df0b0081a..a92b1654c2 100644 --- a/backend/internal/service/antigravity_gateway_compat_test.go +++ b/backend/internal/service/antigravity_gateway_compat_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/Wei-Shaw/sub2api/internal/pkg/apicompat" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -285,6 +286,21 @@ func TestAntigravityCompatPreservesChatTokenLimit(t *testing.T) { body: `{"model":"gemini-3.1-pro-high","messages":[{"role":"user","content":"ok"}],"max_tokens":8,"max_completion_tokens":13}`, want: 13, }, + { + name: "max_tokens at safe ceiling is preserved", + body: `{"model":"gemini-3.1-pro-high","messages":[{"role":"user","content":"ok"}],"max_tokens":64000}`, + want: 64000, + }, + { + name: "max_tokens above safe ceiling is clamped", + body: `{"model":"gemini-3.1-pro-high","messages":[{"role":"user","content":"ok"}],"max_tokens":64001}`, + want: 64000, + }, + { + name: "precedence applies before clamping", + body: `{"model":"gemini-3.1-pro-high","messages":[{"role":"user","content":"ok"}],"max_tokens":8,"max_completion_tokens":64001}`, + want: 64000, + }, } for _, tt := range tests { @@ -310,6 +326,27 @@ func TestAntigravityCompatPreservesChatTokenLimit(t *testing.T) { } } +func TestPreserveChatCompletionTokenLimitIgnoresAbsentAndNonPositiveValues(t *testing.T) { + tests := []struct { + name string + request apicompat.ChatCompletionsRequest + }{ + {name: "absent"}, + {name: "zero max_tokens", request: apicompat.ChatCompletionsRequest{MaxTokens: antigravityCompatIntPtr(0)}}, + {name: "negative max_completion_tokens takes precedence", request: apicompat.ChatCompletionsRequest{MaxTokens: antigravityCompatIntPtr(12), MaxCompletionTokens: antigravityCompatIntPtr(-1)}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + claudeRequest := &apicompat.AnthropicRequest{MaxTokens: 99} + preserveChatCompletionTokenLimit(&tt.request, claudeRequest) + require.Equal(t, 99, claudeRequest.MaxTokens) + }) + } +} + +func antigravityCompatIntPtr(v int) *int { return &v } + func TestAntigravityCompatRoutesByMappedModelFamily(t *testing.T) { gin.SetMode(gin.TestMode) tests := []struct {