From 48bbd9e2143a75a2524f9efeb279865bacc6cbd6 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Wed, 24 Sep 2025 23:34:46 +0800 Subject: [PATCH] fix(gemini): handle "[DONE]" chunk, trim "data:" prefix, and remove session_id from requests - Adjusted stream handling to skip "[DONE]" chunks. - Ensured "data:" prefix is trimmed for non-prefixed input in translation. - Removed `session_id` from request bodies before processing. --- .../handlers/gemini/gemini-cli_handlers.go | 9 ++++++- internal/runtime/executor/gemini_executor.go | 5 ++++ .../gemini-cli/gemini_gemini-cli_response.go | 7 ++++++ sdk/translator/types.go | 25 ++++++++++++++----- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/internal/api/handlers/gemini/gemini-cli_handlers.go b/internal/api/handlers/gemini/gemini-cli_handlers.go index 75afda33..26beaf42 100644 --- a/internal/api/handlers/gemini/gemini-cli_handlers.go +++ b/internal/api/handlers/gemini/gemini-cli_handlers.go @@ -193,7 +193,14 @@ func (h *GeminiCLIAPIHandler) forwardCLIStream(c *gin.Context, flusher http.Flus return } if alt == "" { - _, _ = c.Writer.Write([]byte("data: ")) + if bytes.Equal(chunk, []byte("data: [DONE]")) || bytes.Equal(chunk, []byte("[DONE]")) { + continue + } + + if !bytes.HasPrefix(chunk, []byte("data:")) { + _, _ = c.Writer.Write([]byte("data: ")) + } + _, _ = c.Writer.Write(chunk) _, _ = c.Writer.Write([]byte("\n\n")) } else { diff --git a/internal/runtime/executor/gemini_executor.go b/internal/runtime/executor/gemini_executor.go index 40692c2d..fed30324 100644 --- a/internal/runtime/executor/gemini_executor.go +++ b/internal/runtime/executor/gemini_executor.go @@ -59,6 +59,8 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r url = url + fmt.Sprintf("?$alt=%s", opts.Alt) } + body, _ = sjson.DeleteBytes(body, "session_id") + recordAPIRequest(ctx, e.cfg, body) httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { @@ -112,6 +114,9 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A } else { url = url + fmt.Sprintf("?$alt=%s", opts.Alt) } + + body, _ = sjson.DeleteBytes(body, "session_id") + recordAPIRequest(ctx, e.cfg, body) httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { diff --git a/internal/translator/gemini/gemini-cli/gemini_gemini-cli_response.go b/internal/translator/gemini/gemini-cli/gemini_gemini-cli_response.go index 6bc038e2..39b8dfb6 100644 --- a/internal/translator/gemini/gemini-cli/gemini_gemini-cli_response.go +++ b/internal/translator/gemini/gemini-cli/gemini_gemini-cli_response.go @@ -12,6 +12,8 @@ import ( "github.com/tidwall/sjson" ) +var dataTag = []byte("data:") + // ConvertGeminiResponseToGeminiCLI converts Gemini streaming response format to Gemini CLI single-line JSON format. // This function processes various Gemini event types and transforms them into Gemini CLI-compatible JSON responses. // It handles thinking content, regular text content, and function calls, outputting single-line JSON @@ -26,6 +28,11 @@ import ( // Returns: // - []string: A slice of strings, each containing a Gemini CLI-compatible JSON response. func ConvertGeminiResponseToGeminiCLI(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) []string { + if !bytes.HasPrefix(rawJSON, dataTag) { + return []string{} + } + rawJSON = bytes.TrimSpace(rawJSON[5:]) + if bytes.Equal(rawJSON, []byte("[DONE]")) { return []string{} } diff --git a/sdk/translator/types.go b/sdk/translator/types.go index 9655ba23..ff69340a 100644 --- a/sdk/translator/types.go +++ b/sdk/translator/types.go @@ -1,21 +1,34 @@ +// Package translator provides types and functions for converting chat requests and responses between different schemas. package translator import "context" -// RequestTransform converts a request payload from one schema to another. +// RequestTransform is a function type that converts a request payload from a source schema to a target schema. +// It takes the model name, the raw JSON payload of the request, and a boolean indicating if the request is for a streaming response. +// It returns the converted request payload as a byte slice. type RequestTransform func(model string, rawJSON []byte, stream bool) []byte -// ResponseStreamTransform converts streaming responses between schemas. +// ResponseStreamTransform is a function type that converts a streaming response from a source schema to a target schema. +// It takes a context, the model name, the raw JSON of the original and converted requests, the raw JSON of the current response chunk, and an optional parameter. +// It returns a slice of strings, where each string is a chunk of the converted streaming response. type ResponseStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string -// ResponseNonStreamTransform converts non-stream responses between schemas. +// ResponseNonStreamTransform is a function type that converts a non-streaming response from a source schema to a target schema. +// It takes a context, the model name, the raw JSON of the original and converted requests, the raw JSON of the response, and an optional parameter. +// It returns the converted response as a single string. type ResponseNonStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) string +// ResponseTokenCountTransform is a function type that transforms a token count from a source format to a target format. +// It takes a context and the token count as an int64, and returns the transformed token count as a string. type ResponseTokenCountTransform func(ctx context.Context, count int64) string -// ResponseTransform groups streaming and non-streaming transforms. +// ResponseTransform is a struct that groups together the functions for transforming streaming and non-streaming responses, +// as well as token counts. type ResponseTransform struct { - Stream ResponseStreamTransform - NonStream ResponseNonStreamTransform + // Stream is the function for transforming streaming responses. + Stream ResponseStreamTransform + // NonStream is the function for transforming non-streaming responses. + NonStream ResponseNonStreamTransform + // TokenCount is the function for transforming token counts. TokenCount ResponseTokenCountTransform }