Skip to content

Commit 7f266aa

Browse files
committed
fix(aistudio): ensure colon-spaced JSON in responses
1 parent f3f3127 commit 7f266aa

File tree

1 file changed

+51
-3
lines changed

1 file changed

+51
-3
lines changed

internal/runtime/executor/aistudio_executor.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package executor
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
67
"fmt"
78
"net/http"
89
"net/url"
@@ -87,7 +88,7 @@ func (e *AistudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth,
8788
reporter.publish(ctx, parseGeminiUsage(wsResp.Body))
8889
var param any
8990
out := sdktranslator.TranslateNonStream(ctx, body.toFormat, opts.SourceFormat, req.Model, bytes.Clone(opts.OriginalRequest), bytes.Clone(translatedReq), bytes.Clone(wsResp.Body), &param)
90-
resp = cliproxyexecutor.Response{Payload: []byte(out)}
91+
resp = cliproxyexecutor.Response{Payload: ensureColonSpacedJSON([]byte(out))}
9192
return resp, nil
9293
}
9394

@@ -156,7 +157,7 @@ func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth
156157
}
157158
lines := sdktranslator.TranslateStream(ctx, body.toFormat, opts.SourceFormat, req.Model, bytes.Clone(opts.OriginalRequest), translatedReq, bytes.Clone(filtered), &param)
158159
for i := range lines {
159-
out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])}
160+
out <- cliproxyexecutor.StreamChunk{Payload: ensureColonSpacedJSON([]byte(lines[i]))}
160161
}
161162
break
162163
}
@@ -172,7 +173,7 @@ func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth
172173
}
173174
lines := sdktranslator.TranslateStream(ctx, body.toFormat, opts.SourceFormat, req.Model, bytes.Clone(opts.OriginalRequest), translatedReq, bytes.Clone(event.Payload), &param)
174175
for i := range lines {
175-
out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])}
176+
out <- cliproxyexecutor.StreamChunk{Payload: ensureColonSpacedJSON([]byte(lines[i]))}
176177
}
177178
reporter.publish(ctx, parseGeminiUsage(event.Payload))
178179
return
@@ -346,3 +347,50 @@ func stripUsageMetadataFromJSON(rawJSON []byte) ([]byte, bool) {
346347
}
347348
return cleaned, true
348349
}
350+
351+
// ensureColonSpacedJSON normalizes JSON objects so that colons are followed by a single space while
352+
// keeping the payload otherwise compact. Non-JSON inputs are returned unchanged.
353+
func ensureColonSpacedJSON(payload []byte) []byte {
354+
trimmed := bytes.TrimSpace(payload)
355+
if len(trimmed) == 0 {
356+
return payload
357+
}
358+
359+
var decoded any
360+
if err := json.Unmarshal(trimmed, &decoded); err != nil {
361+
return payload
362+
}
363+
364+
indented, err := json.MarshalIndent(decoded, "", " ")
365+
if err != nil {
366+
return payload
367+
}
368+
369+
compacted := make([]byte, 0, len(indented))
370+
inString := false
371+
skipSpace := false
372+
373+
for i := 0; i < len(indented); i++ {
374+
ch := indented[i]
375+
if ch == '"' && (i == 0 || indented[i-1] != '\\') {
376+
inString = !inString
377+
}
378+
379+
if !inString {
380+
if ch == '\n' || ch == '\r' {
381+
skipSpace = true
382+
continue
383+
}
384+
if skipSpace {
385+
if ch == ' ' || ch == '\t' {
386+
continue
387+
}
388+
skipSpace = false
389+
}
390+
}
391+
392+
compacted = append(compacted, ch)
393+
}
394+
395+
return compacted
396+
}

0 commit comments

Comments
 (0)