Skip to content

Commit da0bbf2

Browse files
authored
Merge pull request #1298 from sususu98/fix/restore-usageMetadata-in-gemini-translator
fix(translator): restore usageMetadata in Gemini responses from Antigravity
2 parents 4eb1e60 + 9dc0e6d commit da0bbf2

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

internal/translator/antigravity/gemini/antigravity_gemini_response.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func ConvertAntigravityResponseToGemini(ctx context.Context, _ string, originalR
4141
responseResult := gjson.GetBytes(rawJSON, "response")
4242
if responseResult.Exists() {
4343
chunk = []byte(responseResult.Raw)
44+
chunk = restoreUsageMetadata(chunk)
4445
}
4546
} else {
4647
chunkTemplate := "[]"
@@ -76,11 +77,24 @@ func ConvertAntigravityResponseToGemini(ctx context.Context, _ string, originalR
7677
func ConvertAntigravityResponseToGeminiNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
7778
responseResult := gjson.GetBytes(rawJSON, "response")
7879
if responseResult.Exists() {
79-
return responseResult.Raw
80+
chunk := restoreUsageMetadata([]byte(responseResult.Raw))
81+
return string(chunk)
8082
}
8183
return string(rawJSON)
8284
}
8385

8486
func GeminiTokenCount(ctx context.Context, count int64) string {
8587
return fmt.Sprintf(`{"totalTokens":%d,"promptTokensDetails":[{"modality":"TEXT","tokenCount":%d}]}`, count, count)
8688
}
89+
90+
// restoreUsageMetadata renames cpaUsageMetadata back to usageMetadata.
91+
// The executor renames usageMetadata to cpaUsageMetadata in non-terminal chunks
92+
// to preserve usage data while hiding it from clients that don't expect it.
93+
// When returning standard Gemini API format, we must restore the original name.
94+
func restoreUsageMetadata(chunk []byte) []byte {
95+
if cpaUsage := gjson.GetBytes(chunk, "cpaUsageMetadata"); cpaUsage.Exists() {
96+
chunk, _ = sjson.SetRawBytes(chunk, "usageMetadata", []byte(cpaUsage.Raw))
97+
chunk, _ = sjson.DeleteBytes(chunk, "cpaUsageMetadata")
98+
}
99+
return chunk
100+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package gemini
2+
3+
import (
4+
"context"
5+
"testing"
6+
)
7+
8+
func TestRestoreUsageMetadata(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
input []byte
12+
expected string
13+
}{
14+
{
15+
name: "cpaUsageMetadata renamed to usageMetadata",
16+
input: []byte(`{"modelVersion":"gemini-3-pro","cpaUsageMetadata":{"promptTokenCount":100,"candidatesTokenCount":200}}`),
17+
expected: `{"modelVersion":"gemini-3-pro","usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":200}}`,
18+
},
19+
{
20+
name: "no cpaUsageMetadata unchanged",
21+
input: []byte(`{"modelVersion":"gemini-3-pro","usageMetadata":{"promptTokenCount":100}}`),
22+
expected: `{"modelVersion":"gemini-3-pro","usageMetadata":{"promptTokenCount":100}}`,
23+
},
24+
{
25+
name: "empty input",
26+
input: []byte(`{}`),
27+
expected: `{}`,
28+
},
29+
}
30+
31+
for _, tt := range tests {
32+
t.Run(tt.name, func(t *testing.T) {
33+
result := restoreUsageMetadata(tt.input)
34+
if string(result) != tt.expected {
35+
t.Errorf("restoreUsageMetadata() = %s, want %s", string(result), tt.expected)
36+
}
37+
})
38+
}
39+
}
40+
41+
func TestConvertAntigravityResponseToGeminiNonStream(t *testing.T) {
42+
tests := []struct {
43+
name string
44+
input []byte
45+
expected string
46+
}{
47+
{
48+
name: "cpaUsageMetadata restored in response",
49+
input: []byte(`{"response":{"modelVersion":"gemini-3-pro","cpaUsageMetadata":{"promptTokenCount":100}}}`),
50+
expected: `{"modelVersion":"gemini-3-pro","usageMetadata":{"promptTokenCount":100}}`,
51+
},
52+
{
53+
name: "usageMetadata preserved",
54+
input: []byte(`{"response":{"modelVersion":"gemini-3-pro","usageMetadata":{"promptTokenCount":100}}}`),
55+
expected: `{"modelVersion":"gemini-3-pro","usageMetadata":{"promptTokenCount":100}}`,
56+
},
57+
}
58+
59+
for _, tt := range tests {
60+
t.Run(tt.name, func(t *testing.T) {
61+
result := ConvertAntigravityResponseToGeminiNonStream(context.Background(), "", nil, nil, tt.input, nil)
62+
if result != tt.expected {
63+
t.Errorf("ConvertAntigravityResponseToGeminiNonStream() = %s, want %s", result, tt.expected)
64+
}
65+
})
66+
}
67+
}
68+
69+
func TestConvertAntigravityResponseToGeminiStream(t *testing.T) {
70+
ctx := context.WithValue(context.Background(), "alt", "")
71+
72+
tests := []struct {
73+
name string
74+
input []byte
75+
expected string
76+
}{
77+
{
78+
name: "cpaUsageMetadata restored in streaming response",
79+
input: []byte(`data: {"response":{"modelVersion":"gemini-3-pro","cpaUsageMetadata":{"promptTokenCount":100}}}`),
80+
expected: `{"modelVersion":"gemini-3-pro","usageMetadata":{"promptTokenCount":100}}`,
81+
},
82+
}
83+
84+
for _, tt := range tests {
85+
t.Run(tt.name, func(t *testing.T) {
86+
results := ConvertAntigravityResponseToGemini(ctx, "", nil, nil, tt.input, nil)
87+
if len(results) != 1 {
88+
t.Fatalf("expected 1 result, got %d", len(results))
89+
}
90+
if results[0] != tt.expected {
91+
t.Errorf("ConvertAntigravityResponseToGemini() = %s, want %s", results[0], tt.expected)
92+
}
93+
})
94+
}
95+
}

0 commit comments

Comments
 (0)