From 07d21463cab75bee50bfdc6f5cfd7fabde667dab Mon Sep 17 00:00:00 2001 From: sususu Date: Mon, 8 Dec 2025 10:10:22 +0800 Subject: [PATCH 1/4] fix(gemini-cli): enhance 429 retry delay parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add fallback parsing for quota reset delay when RetryInfo is not present: - Try ErrorInfo.metadata.quotaResetDelay (e.g., "373.801628ms") - Parse from error.message "Your quota will reset after Xs." This ensures proper cooldown timing for rate-limited requests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../runtime/executor/gemini_cli_executor.go | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/internal/runtime/executor/gemini_cli_executor.go b/internal/runtime/executor/gemini_cli_executor.go index a2e0ecec..85448f41 100644 --- a/internal/runtime/executor/gemini_cli_executor.go +++ b/internal/runtime/executor/gemini_cli_executor.go @@ -8,6 +8,8 @@ import ( "fmt" "io" "net/http" + "regexp" + "strconv" "strings" "time" @@ -770,20 +772,45 @@ func parseRetryDelay(errorBody []byte) (*time.Duration, error) { // Try to parse the retryDelay from the error response // Format: error.details[].retryDelay where @type == "type.googleapis.com/google.rpc.RetryInfo" details := gjson.GetBytes(errorBody, "error.details") - if !details.Exists() || !details.IsArray() { - return nil, fmt.Errorf("no error.details found") + if details.Exists() && details.IsArray() { + for _, detail := range details.Array() { + typeVal := detail.Get("@type").String() + if typeVal == "type.googleapis.com/google.rpc.RetryInfo" { + retryDelay := detail.Get("retryDelay").String() + if retryDelay != "" { + // Parse duration string like "0.847655010s" + duration, err := time.ParseDuration(retryDelay) + if err != nil { + return nil, fmt.Errorf("failed to parse duration") + } + return &duration, nil + } + } + } + + // Fallback: try ErrorInfo.metadata.quotaResetDelay (e.g., "373.801628ms") + for _, detail := range details.Array() { + typeVal := detail.Get("@type").String() + if typeVal == "type.googleapis.com/google.rpc.ErrorInfo" { + quotaResetDelay := detail.Get("metadata.quotaResetDelay").String() + if quotaResetDelay != "" { + duration, err := time.ParseDuration(quotaResetDelay) + if err == nil { + return &duration, nil + } + } + } + } } - for _, detail := range details.Array() { - typeVal := detail.Get("@type").String() - if typeVal == "type.googleapis.com/google.rpc.RetryInfo" { - retryDelay := detail.Get("retryDelay").String() - if retryDelay != "" { - // Parse duration string like "0.847655010s" - duration, err := time.ParseDuration(retryDelay) - if err != nil { - return nil, fmt.Errorf("failed to parse duration") - } + // Fallback: parse from error.message "Your quota will reset after Xs." + message := gjson.GetBytes(errorBody, "error.message").String() + if message != "" { + re := regexp.MustCompile(`after\s+(\d+)s\.?`) + if matches := re.FindStringSubmatch(message); len(matches) > 1 { + seconds, err := strconv.Atoi(matches[1]) + if err == nil { + duration := time.Duration(seconds) * time.Second return &duration, nil } } From 52d5fd1a67c03e791a0008182da862178863817f Mon Sep 17 00:00:00 2001 From: teeverc <72298507+teeverc@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:17:53 -0800 Subject: [PATCH 2/4] fix: streaming for amp cli --- internal/api/modules/amp/response_rewriter.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/api/modules/amp/response_rewriter.go b/internal/api/modules/amp/response_rewriter.go index e906f143..04d45398 100644 --- a/internal/api/modules/amp/response_rewriter.go +++ b/internal/api/modules/amp/response_rewriter.go @@ -39,7 +39,11 @@ func (rw *ResponseRewriter) Write(data []byte) (int, error) { } if rw.isStreaming { - return rw.ResponseWriter.Write(rw.rewriteStreamChunk(data)) + n, err := rw.ResponseWriter.Write(rw.rewriteStreamChunk(data)) + if flusher, ok := rw.ResponseWriter.(http.Flusher); ok { + flusher.Flush() + } + return n, err } return rw.body.Write(data) } From cd8c86c6fb4aaf7ba639ec984d476b87d6f740d0 Mon Sep 17 00:00:00 2001 From: teeverc <72298507+teeverc@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:31:34 -0800 Subject: [PATCH 3/4] refactor: only flush stream response on successful write --- internal/api/modules/amp/response_rewriter.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/api/modules/amp/response_rewriter.go b/internal/api/modules/amp/response_rewriter.go index 04d45398..de6ba137 100644 --- a/internal/api/modules/amp/response_rewriter.go +++ b/internal/api/modules/amp/response_rewriter.go @@ -40,8 +40,10 @@ func (rw *ResponseRewriter) Write(data []byte) (int, error) { if rw.isStreaming { n, err := rw.ResponseWriter.Write(rw.rewriteStreamChunk(data)) - if flusher, ok := rw.ResponseWriter.(http.Flusher); ok { - flusher.Flush() + if err == nil { + if flusher, ok := rw.ResponseWriter.(http.Flusher); ok { + flusher.Flush() + } } return n, err } From f0a3eb574eb9c9f259e6aa3d536aded2dafdd6c0 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sun, 14 Dec 2025 16:17:11 +0800 Subject: [PATCH 4/4] fix(registry): update DeepSeek model definitions with new IDs and descriptions --- internal/registry/model_definitions.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/registry/model_definitions.go b/internal/registry/model_definitions.go index 414ed56c..3725b825 100644 --- a/internal/registry/model_definitions.go +++ b/internal/registry/model_definitions.go @@ -648,8 +648,9 @@ func GetIFlowModels() []*ModelInfo { {ID: "glm-4.6", DisplayName: "GLM-4.6", Description: "Zhipu GLM 4.6 general model", Created: 1759190400}, {ID: "kimi-k2", DisplayName: "Kimi-K2", Description: "Moonshot Kimi K2 general model", Created: 1752192000}, {ID: "kimi-k2-thinking", DisplayName: "Kimi-K2-Thinking", Description: "Moonshot Kimi K2 thinking model", Created: 1762387200, Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}}, - {ID: "deepseek-v3.2-chat", DisplayName: "DeepSeek-V3.2", Description: "DeepSeek V3.2", Created: 1764576000}, - {ID: "deepseek-v3.2", DisplayName: "DeepSeek-V3.2-Exp", Description: "DeepSeek V3.2 experimental", Created: 1759104000}, + {ID: "deepseek-v3.2-chat", DisplayName: "DeepSeek-V3.2", Description: "DeepSeek V3.2 Chat", Created: 1764576000}, + {ID: "deepseek-v3.2-reasoner", DisplayName: "DeepSeek-V3.2", Description: "DeepSeek V3.2 Reasoner", Created: 1764576000}, + {ID: "deepseek-v3.2-exp", DisplayName: "DeepSeek-V3.2-Exp", Description: "DeepSeek V3.2 experimental", Created: 1759104000}, {ID: "deepseek-v3.1", DisplayName: "DeepSeek-V3.1-Terminus", Description: "DeepSeek V3.1 Terminus", Created: 1756339200}, {ID: "deepseek-r1", DisplayName: "DeepSeek-R1", Description: "DeepSeek reasoning model R1", Created: 1737331200, Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}}, {ID: "deepseek-v3", DisplayName: "DeepSeek-V3-671B", Description: "DeepSeek V3 671B", Created: 1734307200},