From 688a4ec750d9f56b327258de838fc3e109b7e504 Mon Sep 17 00:00:00 2001 From: rensumo Date: Tue, 28 Apr 2026 09:06:06 +0800 Subject: [PATCH 1/3] fix: add /f flag to all reg add commands to prevent stdin blocking Two reg add commands for shell and shell\open subkeys were missing /f, causing reg.exe to wait for interactive Yes/No confirmation when the keys already existed from a previous login. This blocked the entire login flow. --- internal/auth/qoder/uri_handler_windows.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/auth/qoder/uri_handler_windows.go b/internal/auth/qoder/uri_handler_windows.go index 3337e87b1c..79fb4aad77 100644 --- a/internal/auth/qoder/uri_handler_windows.go +++ b/internal/auth/qoder/uri_handler_windows.go @@ -52,8 +52,8 @@ End Function regCmds := [][]string{ {"reg", "add", `HKCU\Software\Classes\qoder`, "/ve", "/t", "REG_SZ", "/d", "URL:QoderLogin", "/f"}, {"reg", "add", `HKCU\Software\Classes\qoder`, "/v", "URL Protocol", "/t", "REG_SZ", "/d", "", "/f"}, - {"reg", "add", `HKCU\Software\Classes\qoder\shell`}, - {"reg", "add", `HKCU\Software\Classes\qoder\shell\open`}, + {"reg", "add", `HKCU\Software\Classes\qoder\shell`, "/f"}, + {"reg", "add", `HKCU\Software\Classes\qoder\shell\open`, "/f"}, {"reg", "add", `HKCU\Software\Classes\qoder\shell\open\command`, "/ve", "/t", "REG_SZ", "/d", fmt.Sprintf(`wscript.exe "%s" %%1`, vbsPath), "/f"}, } From d89c05428a82733a73fae4625bc97bc5a122b740 Mon Sep 17 00:00:00 2001 From: rensumo Date: Tue, 28 Apr 2026 14:11:26 +0800 Subject: [PATCH 2/3] Update docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index cd8c21b97c..1490c3446d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: cli-proxy-api: - image: ${CLI_PROXY_IMAGE:-eceasy/cli-proxy-api-plus:latest} + image: rensumo/cli-proxy-api-plus:latest pull_policy: always build: context: . From ffc29d7f31f007917b9ab9a3749638d30b86f602 Mon Sep 17 00:00:00 2001 From: rensumo Date: Thu, 30 Apr 2026 20:36:52 +0800 Subject: [PATCH 3/3] fix: update CodeBuddy client identity to v4.9.7 and fix model list/thinking stream issues - Update UserAgent from CLI/2.63.2 to CodeBuddyIDE/4.9.7 - Update X-IDE-Type/X-IDE-Name/X-IDE-Version headers to match real IDE - Add missing X-Product-Version, X-Env-ID, Connection headers to FetchCodeBuddyModels - Sync static model list with upstream config API (add hy3-preview, deepseek-v4-flash, etc.) - Add SupportedInputModalities and Thinking metadata for multimodal/reasoning models - Filter default-1.1/default-1.2 from dynamic model list (internal Claude aliases) - Add cleanDeltaChunk to strip empty reasoning_content from stream chunks, preventing clients from interpreting spurious thinking transitions - Add new internal model prefixes (deepseek-r1, deepseek-v3-0324, glm-4, hunyuan-image) --- internal/auth/codebuddy/codebuddy_auth.go | 2 +- internal/registry/model_definitions.go | 147 ++++++++---------- .../runtime/executor/codebuddy_executor.go | 79 +++++++++- 3 files changed, 139 insertions(+), 89 deletions(-) diff --git a/internal/auth/codebuddy/codebuddy_auth.go b/internal/auth/codebuddy/codebuddy_auth.go index b43842005d..6dc329a625 100644 --- a/internal/auth/codebuddy/codebuddy_auth.go +++ b/internal/auth/codebuddy/codebuddy_auth.go @@ -22,7 +22,7 @@ import ( const ( BaseURL = "https://copilot.tencent.com" DefaultDomain = "www.codebuddy.cn" - UserAgent = "CLI/2.63.2 CodeBuddy/2.63.2" + UserAgent = "CodeBuddyIDE/4.9.7 CodeBuddy/4.9.7" codeBuddyStatePath = "/v2/plugin/auth/state" codeBuddyTokenPath = "/v2/plugin/auth/token" diff --git a/internal/registry/model_definitions.go b/internal/registry/model_definitions.go index 614d48057f..6fcfdb7e70 100644 --- a/internal/registry/model_definitions.go +++ b/internal/registry/model_definitions.go @@ -185,103 +185,88 @@ func GetQoderModels() []*ModelInfo { // GetCodeBuddyModels returns the available models for CodeBuddy (Tencent). // These models are served through the copilot.tencent.com API. func GetCodeBuddyModels() []*ModelInfo { - now := int64(1748044800) // 2025-05-24 + now := int64(1748044800) return []*ModelInfo{ { - ID: "auto", - Object: "model", - Created: now, - OwnedBy: "tencent", - Type: "codebuddy", - DisplayName: "Auto", - Description: "Automatic model selection via CodeBuddy", - ContextLength: 128000, - MaxCompletionTokens: 32768, - SupportedEndpoints: []string{"/chat/completions"}, + ID: "auto", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "Auto", Description: "Automatic model selection via CodeBuddy", + ContextLength: 168000, MaxCompletionTokens: 32000, SupportedEndpoints: []string{"/chat/completions"}, + SupportedInputModalities: []string{"TEXT", "IMAGE"}, }, { - ID: "glm-5v-turbo", - Object: "model", - Created: now, - OwnedBy: "tencent", - Type: "codebuddy", - DisplayName: "GLM-5v Turbo", - Description: "GLM-5v Turbo via CodeBuddy", - ContextLength: 200000, - MaxCompletionTokens: 32768, - SupportedEndpoints: []string{"/chat/completions"}, + ID: "hy3-preview", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "Hy3 Preview", Description: "Hunyuan thinking model with enhanced reasoning capabilities via CodeBuddy", + ContextLength: 192000, MaxCompletionTokens: 64000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, }, { - ID: "glm-5.1", - Object: "model", - Created: now, - OwnedBy: "tencent", - Type: "codebuddy", - DisplayName: "GLM-5.1", - Description: "GLM-5.1 via CodeBuddy", - ContextLength: 200000, - MaxCompletionTokens: 32768, - SupportedEndpoints: []string{"/chat/completions"}, + ID: "glm-5v-turbo", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "GLM-5v Turbo", Description: "Native multimodal model via CodeBuddy", + ContextLength: 200000, MaxCompletionTokens: 38000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, + SupportedInputModalities: []string{"TEXT", "IMAGE"}, }, { - ID: "glm-5.0-turbo", - Object: "model", - Created: now, - OwnedBy: "tencent", - Type: "codebuddy", - DisplayName: "GLM-5.0 Turbo", - Description: "GLM-5.0 Turbo via CodeBuddy", - ContextLength: 200000, - MaxCompletionTokens: 32768, - SupportedEndpoints: []string{"/chat/completions"}, + ID: "glm-5.1", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "GLM-5.1", Description: "GLM-5.1 via CodeBuddy", + ContextLength: 200000, MaxCompletionTokens: 48000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, }, { - ID: "kimi-k2.6", - Object: "model", - Created: now, - OwnedBy: "tencent", - Type: "codebuddy", - DisplayName: "Kimi K2.6", - Description: "Kimi K2.6 via CodeBuddy", - ContextLength: 256000, - MaxCompletionTokens: 32768, - SupportedEndpoints: []string{"/chat/completions"}, + ID: "glm-5.0-turbo", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "GLM-5.0 Turbo", Description: "GLM-5.0 Turbo via CodeBuddy", + ContextLength: 200000, MaxCompletionTokens: 48000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, }, { - ID: "kimi-k2.5", - Object: "model", - Created: now, - OwnedBy: "tencent", - Type: "codebuddy", - DisplayName: "Kimi K2.5", - Description: "Kimi K2.5 via CodeBuddy", - ContextLength: 256000, - MaxCompletionTokens: 32768, - SupportedEndpoints: []string{"/chat/completions"}, + ID: "kimi-k2.6", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "Kimi K2.6", Description: "Kimi K2.6 via CodeBuddy", + ContextLength: 256000, MaxCompletionTokens: 32000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, + SupportedInputModalities: []string{"TEXT", "IMAGE"}, }, { - ID: "minimax-m2.7", - Object: "model", - Created: now, - OwnedBy: "tencent", - Type: "codebuddy", - DisplayName: "MiniMax M2.7", - Description: "MiniMax M2.7 via CodeBuddy", - ContextLength: 200000, - MaxCompletionTokens: 32768, - SupportedEndpoints: []string{"/chat/completions"}, + ID: "kimi-k2.5", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "Kimi K2.5", Description: "Kimi K2.5 via CodeBuddy", + ContextLength: 256000, MaxCompletionTokens: 32000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, + SupportedInputModalities: []string{"TEXT", "IMAGE"}, }, { - ID: "deepseek-v3-2-volc", - Object: "model", - Created: now, - OwnedBy: "tencent", - Type: "codebuddy", - DisplayName: "DeepSeek V3.2 (Volc)", - Description: "DeepSeek V3.2 via CodeBuddy", - ContextLength: 128000, - MaxCompletionTokens: 32768, - SupportedEndpoints: []string{"/chat/completions"}, + ID: "minimax-m2.7", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "MiniMax M2.7", Description: "MiniMax M2.7 via CodeBuddy", + ContextLength: 200000, MaxCompletionTokens: 48000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, + SupportedInputModalities: []string{"TEXT", "IMAGE"}, + }, + { + ID: "deepseek-v4-flash", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "DeepSeek V4 Flash", Description: "DeepSeek V4 Flash via CodeBuddy", + ContextLength: 1000000, MaxCompletionTokens: 50000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, + SupportedInputModalities: []string{"TEXT", "IMAGE"}, + }, + { + ID: "deepseek-v3-2-volc", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "DeepSeek V3.2", Description: "DeepSeek V3.2 via CodeBuddy", + ContextLength: 96000, MaxCompletionTokens: 32000, SupportedEndpoints: []string{"/chat/completions"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, + SupportedInputModalities: []string{"TEXT", "IMAGE"}, + }, + { + ID: "deepseek-v3-1-volc", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "DeepSeek V3.1 Terminus", Description: "DeepSeek V3.1 Terminus via CodeBuddy", + ContextLength: 128000, MaxCompletionTokens: 32000, SupportedEndpoints: []string{"/chat/completions"}, + }, + { + ID: "deepseek-r1-0528-lkeap", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "DeepSeek R1 0528", Description: "DeepSeek R1 0528 via CodeBuddy", + ContextLength: 112000, MaxCompletionTokens: 16000, SupportedEndpoints: []string{"/chat/completions"}, + }, + { + ID: "hunyuan-chat", Object: "model", Created: now, OwnedBy: "tencent", + Type: "codebuddy", DisplayName: "Hunyuan Turbos", Description: "Tencent Hunyuan Turbos via CodeBuddy", + ContextLength: 128000, MaxCompletionTokens: 8192, SupportedEndpoints: []string{"/chat/completions"}, }, } } diff --git a/internal/runtime/executor/codebuddy_executor.go b/internal/runtime/executor/codebuddy_executor.go index 750db6b63e..81a29d9cb9 100644 --- a/internal/runtime/executor/codebuddy_executor.go +++ b/internal/runtime/executor/codebuddy_executor.go @@ -282,6 +282,14 @@ func (e *CodeBuddyExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut if !bytes.HasPrefix(line, []byte("data:")) { continue } + raw := bytes.TrimSpace(line[5:]) + if len(raw) > 0 && !bytes.Equal(raw, []byte("[DONE]")) { + if cleaned := cleanDeltaChunk(raw); cleaned == nil { + continue + } else if !bytes.Equal(cleaned, raw) { + line = append([]byte("data: "), cleaned...) + } + } chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, translated, bytes.Clone(line), ¶m) for i := range chunks { out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])} @@ -352,9 +360,10 @@ func (e *CodeBuddyExecutor) applyHeaders(req *http.Request, accessToken, userID, req.Header.Set("X-User-Id", userID) req.Header.Set("X-Domain", domain) req.Header.Set("X-Product", "SaaS") - req.Header.Set("X-IDE-Type", "CLI") - req.Header.Set("X-IDE-Name", "CLI") - req.Header.Set("X-IDE-Version", "2.63.2") + req.Header.Set("X-IDE-Type", "CodeBuddyIDE") + req.Header.Set("X-IDE-Name", "CodeBuddyIDE") + req.Header.Set("X-IDE-Version", "4.9.7") + req.Header.Set("X-Product-Version", "4.9.7") req.Header.Set("X-Requested-With", "XMLHttpRequest") } @@ -552,6 +561,48 @@ func aggregateOpenAIChatCompletionStream(raw []byte) ([]byte, usage.Detail, erro return out, usageDetail, nil } +// filterEmptyDelta returns nil if the SSE JSON chunk contains a delta with no +// meaningful content (both content and reasoning_content are empty/null). +// This prevents clients from interpreting empty reasoning_content deltas as +// interruptions in the thinking chain. Non-delta chunks (usage, finish_reason) +// are passed through. +// cleanDeltaChunk processes a single SSE JSON chunk for CodeBuddy streaming. +// It returns: +// - nil: chunk should be dropped (no meaningful content) +// - modified bytes: chunk cleaned up (e.g. empty reasoning_content removed) +// - original bytes: chunk passed through as-is +// +// The CodeBuddy upstream sends reasoning_content:"" alongside non-empty content +// during the thinking-to-content transition. Many clients interpret +// reasoning_content:"" as "thinking ended", then see the next chunk's +// reasoning_content:"" again and think "thinking restarted". By stripping the +// empty reasoning_content field, the client never sees spurious thinking +// transitions. +func cleanDeltaChunk(raw []byte) []byte { + delta := gjson.GetBytes(raw, "choices.0.delta") + if !delta.Exists() { + return raw + } + finishReason := gjson.GetBytes(raw, "choices.0.finish_reason").String() + if finishReason == "stop" || finishReason == "tool_calls" { + return raw + } + content := delta.Get("content").String() + reasoning := delta.Get("reasoning_content").String() + hasRole := delta.Get("role").Exists() + toolCalls := delta.Get("tool_calls") + hasToolCalls := toolCalls.Exists() && len(toolCalls.Array()) > 0 + if content == "" && reasoning == "" && !hasRole && !hasToolCalls { + return nil + } + if reasoning == "" && content != "" { + if cleaned, err := sjson.DeleteBytes(raw, "choices.0.delta.reasoning_content"); err == nil { + return cleaned + } + } + return raw +} + var codeBuddyInternalModelPrefixes = []string{ "completion-", "codewise-", @@ -559,20 +610,28 @@ var codeBuddyInternalModelPrefixes = []string{ "hunyuan-7b", "nes-", "default-", - "deepseek-r1-0528", - "deepseek-v3-0324-taco-", "chat-", - "glm-4.6", - "glm-4.6v", + "hunyuan-image-", } var codeBuddyAllowedInternalModels = map[string]bool{ "deepseek-r1-0528": true, + "deepseek-r1-0528-lkeap": true, "deepseek-v3-0324": true, + "deepseek-v3-0324-lkeap": true, "deepseek-v3-0324-taco-completion": true, "hunyuan-2.0-instruct": true, "hunyuan-chat": true, + "glm-4.6": true, + "glm-4.6v": true, "glm-4.7": true, + "glm-5.0": true, + "deepseek-v3-1": true, + "deepseek-v3-1-lkeap": true, + "deepseek-v3-1-volc": true, + "kimi-k2-instruct-taiji": true, + "kimi-k2-thinking": true, + "minimax-m2.5": true, } func isCodeBuddyInternalModel(id string) bool { @@ -603,10 +662,16 @@ func FetchCodeBuddyModels(ctx context.Context, auth *cliproxyauth.Auth, cfg *con req.Header.Set("User-Agent", codebuddy.UserAgent) req.Header.Set("Accept", "application/json, text/plain, */*") req.Header.Set("X-Requested-With", "XMLHttpRequest") + req.Header.Set("X-IDE-Type", "CodeBuddyIDE") + req.Header.Set("X-IDE-Name", "CodeBuddyIDE") + req.Header.Set("X-IDE-Version", "4.9.7") + req.Header.Set("X-Product-Version", "4.9.7") + req.Header.Set("X-Env-ID", "production") req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("X-User-Id", userID) req.Header.Set("X-Domain", domain) req.Header.Set("X-Product", "SaaS") + req.Header.Set("Connection", "close") resp, err := httpClient.Do(req) if err != nil {