@@ -3,6 +3,7 @@ package executor
33import (
44 "bytes"
55 "context"
6+ "encoding/json"
67 "fmt"
78 "net/http"
89 "net/url"
@@ -72,7 +73,7 @@ func (e *AistudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth,
7273 AuthValue : authValue ,
7374 })
7475
75- wsResp , err := e .relay .RoundTrip (ctx , e .provider , wsReq )
76+ wsResp , err := e .relay .NonStream (ctx , e .provider , wsReq )
7677 if err != nil {
7778 recordAPIResponseError (ctx , e .cfg , err )
7879 return resp , err
@@ -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
@@ -220,7 +221,7 @@ func (e *AistudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.A
220221 AuthType : authType ,
221222 AuthValue : authValue ,
222223 })
223- resp , err := e .relay .RoundTrip (ctx , e .provider , wsReq )
224+ resp , err := e .relay .NonStream (ctx , e .provider , wsReq )
224225 if err != nil {
225226 recordAPIResponseError (ctx , e .cfg , err )
226227 return cliproxyexecutor.Response {}, err
@@ -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