From c52ef08e6707df8c9aca96aed04b71f8674a4e07 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:40:35 -0500 Subject: [PATCH 1/4] docs: add CCS to projects list --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 90d5d465..948936a6 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,10 @@ Native macOS menu bar app to use your Claude Code & ChatGPT subscriptions with A Browser-based tool to translate SRT subtitles using your Gemini subscription via CLIProxyAPI with automatic validation/error correction - no API keys needed +### [CCS (Claude Code Switch)](https://github.com/kaitranntt/ccs) + +CLI wrapper for instant switching between multiple Claude accounts and alternative models (Gemini, Codex, AGY) via CLIProxyAPI OAuth - no API keys needed + > [!NOTE] > If you developed a project based on CLIProxyAPI, please open a PR to add it to this list. From 85eb926482a96fb6dc2fb9a1226f898d0ad7545f Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:43:12 -0500 Subject: [PATCH 2/4] fix: change AGY to Antigravity --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 948936a6..1ab5ca16 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Browser-based tool to translate SRT subtitles using your Gemini subscription via ### [CCS (Claude Code Switch)](https://github.com/kaitranntt/ccs) -CLI wrapper for instant switching between multiple Claude accounts and alternative models (Gemini, Codex, AGY) via CLIProxyAPI OAuth - no API keys needed +CLI wrapper for instant switching between multiple Claude accounts and alternative models (Gemini, Codex, Antigravity) via CLIProxyAPI OAuth - no API keys needed > [!NOTE] > If you developed a project based on CLIProxyAPI, please open a PR to add it to this list. From 1c6f4be8ae3e3dfb07ec73bb9de46054c9b91347 Mon Sep 17 00:00:00 2001 From: auroraflux <14947763+auroraflux@users.noreply.github.com> Date: Sun, 30 Nov 2025 01:53:16 -0800 Subject: [PATCH 3/4] refactor(executor): dedupe thinking metadata helpers across Gemini executors Extract applyThinkingMetadata and applyThinkingMetadataCLI helpers to payload_helpers.go and use them across all four Gemini-based executors: - gemini_executor.go (Execute, ExecuteStream, CountTokens) - gemini_cli_executor.go (Execute, ExecuteStream, CountTokens) - aistudio_executor.go (translateRequest) - antigravity_executor.go (Execute, ExecuteStream) This eliminates code duplication introduced in the -reasoning suffix PR and centralizes the thinking config application logic. Net reduction: 28 lines of code. --- .../runtime/executor/aistudio_executor.go | 8 +---- .../runtime/executor/antigravity_executor.go | 19 ++--------- .../runtime/executor/gemini_cli_executor.go | 27 ++-------------- internal/runtime/executor/gemini_executor.go | 24 ++------------ internal/runtime/executor/payload_helpers.go | 32 +++++++++++++++++++ 5 files changed, 41 insertions(+), 69 deletions(-) diff --git a/internal/runtime/executor/aistudio_executor.go b/internal/runtime/executor/aistudio_executor.go index 9b59488f..61a06721 100644 --- a/internal/runtime/executor/aistudio_executor.go +++ b/internal/runtime/executor/aistudio_executor.go @@ -308,13 +308,7 @@ func (e *AIStudioExecutor) translateRequest(req cliproxyexecutor.Request, opts c from := opts.SourceFormat to := sdktranslator.FromString("gemini") payload := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), stream) - if budgetOverride, includeOverride, ok := util.GeminiThinkingFromMetadata(req.Metadata); ok && util.ModelSupportsThinking(req.Model) { - if budgetOverride != nil { - norm := util.NormalizeThinkingBudget(req.Model, *budgetOverride) - budgetOverride = &norm - } - payload = util.ApplyGeminiThinkingConfig(payload, budgetOverride, includeOverride) - } + payload = applyThinkingMetadata(payload, req.Metadata, req.Model) payload = util.ConvertThinkingLevelToBudget(payload) payload = util.StripThinkingConfigIfUnsupported(req.Model, payload) payload = fixGeminiImageAspectRatio(req.Model, payload) diff --git a/internal/runtime/executor/antigravity_executor.go b/internal/runtime/executor/antigravity_executor.go index bcc64310..d702ef36 100644 --- a/internal/runtime/executor/antigravity_executor.go +++ b/internal/runtime/executor/antigravity_executor.go @@ -17,7 +17,6 @@ import ( "github.com/google/uuid" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" @@ -59,20 +58,6 @@ func (e *AntigravityExecutor) Identifier() string { return antigravityAuthType } // PrepareRequest implements ProviderExecutor. func (e *AntigravityExecutor) PrepareRequest(_ *http.Request, _ *cliproxyauth.Auth) error { return nil } -// applyThinkingMetadata applies thinking config from model suffix metadata (e.g., -reasoning, -thinking-N). -// It trusts user intent when suffix is used, even if registry doesn't have Thinking metadata. -func applyThinkingMetadata(translated []byte, metadata map[string]any, model string) []byte { - budgetOverride, includeOverride, ok := util.GeminiThinkingFromMetadata(metadata) - if !ok { - return translated - } - if budgetOverride != nil && util.ModelSupportsThinking(model) { - norm := util.NormalizeThinkingBudget(model, *budgetOverride) - budgetOverride = &norm - } - return util.ApplyGeminiCLIThinkingConfig(translated, budgetOverride, includeOverride) -} - // Execute handles non-streaming requests via the antigravity generate endpoint. func (e *AntigravityExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (resp cliproxyexecutor.Response, err error) { token, updatedAuth, errToken := e.ensureAccessToken(ctx, auth) @@ -90,7 +75,7 @@ func (e *AntigravityExecutor) Execute(ctx context.Context, auth *cliproxyauth.Au to := sdktranslator.FromString("antigravity") translated := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false) - translated = applyThinkingMetadata(translated, req.Metadata, req.Model) + translated = applyThinkingMetadataCLI(translated, req.Metadata, req.Model) baseURLs := antigravityBaseURLFallbackOrder(auth) httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0) @@ -183,7 +168,7 @@ func (e *AntigravityExecutor) ExecuteStream(ctx context.Context, auth *cliproxya to := sdktranslator.FromString("antigravity") translated := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), true) - translated = applyThinkingMetadata(translated, req.Metadata, req.Model) + translated = applyThinkingMetadataCLI(translated, req.Metadata, req.Model) baseURLs := antigravityBaseURLFallbackOrder(auth) httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0) diff --git a/internal/runtime/executor/gemini_cli_executor.go b/internal/runtime/executor/gemini_cli_executor.go index f49d0133..0a4477f7 100644 --- a/internal/runtime/executor/gemini_cli_executor.go +++ b/internal/runtime/executor/gemini_cli_executor.go @@ -62,15 +62,8 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth from := opts.SourceFormat to := sdktranslator.FromString("gemini-cli") - budgetOverride, includeOverride, hasOverride := util.GeminiThinkingFromMetadata(req.Metadata) basePayload := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false) - if hasOverride && util.ModelSupportsThinking(req.Model) { - if budgetOverride != nil { - norm := util.NormalizeThinkingBudget(req.Model, *budgetOverride) - budgetOverride = &norm - } - basePayload = util.ApplyGeminiCLIThinkingConfig(basePayload, budgetOverride, includeOverride) - } + basePayload = applyThinkingMetadataCLI(basePayload, req.Metadata, req.Model) basePayload = util.StripThinkingConfigIfUnsupported(req.Model, basePayload) basePayload = fixGeminiCLIImageAspectRatio(req.Model, basePayload) basePayload = applyPayloadConfigWithRoot(e.cfg, req.Model, "gemini", "request", basePayload) @@ -204,15 +197,8 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut from := opts.SourceFormat to := sdktranslator.FromString("gemini-cli") - budgetOverride, includeOverride, hasOverride := util.GeminiThinkingFromMetadata(req.Metadata) basePayload := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), true) - if hasOverride && util.ModelSupportsThinking(req.Model) { - if budgetOverride != nil { - norm := util.NormalizeThinkingBudget(req.Model, *budgetOverride) - budgetOverride = &norm - } - basePayload = util.ApplyGeminiCLIThinkingConfig(basePayload, budgetOverride, includeOverride) - } + basePayload = applyThinkingMetadataCLI(basePayload, req.Metadata, req.Model) basePayload = util.StripThinkingConfigIfUnsupported(req.Model, basePayload) basePayload = fixGeminiCLIImageAspectRatio(req.Model, basePayload) basePayload = applyPayloadConfigWithRoot(e.cfg, req.Model, "gemini", "request", basePayload) @@ -408,16 +394,9 @@ func (e *GeminiCLIExecutor) CountTokens(ctx context.Context, auth *cliproxyauth. var lastStatus int var lastBody []byte - budgetOverride, includeOverride, hasOverride := util.GeminiThinkingFromMetadata(req.Metadata) for _, attemptModel := range models { payload := sdktranslator.TranslateRequest(from, to, attemptModel, bytes.Clone(req.Payload), false) - if hasOverride && util.ModelSupportsThinking(req.Model) { - if budgetOverride != nil { - norm := util.NormalizeThinkingBudget(req.Model, *budgetOverride) - budgetOverride = &norm - } - payload = util.ApplyGeminiCLIThinkingConfig(payload, budgetOverride, includeOverride) - } + payload = applyThinkingMetadataCLI(payload, req.Metadata, req.Model) payload = deleteJSONField(payload, "project") payload = deleteJSONField(payload, "model") payload = deleteJSONField(payload, "request.safetySettings") diff --git a/internal/runtime/executor/gemini_executor.go b/internal/runtime/executor/gemini_executor.go index 520d6474..fc7b8e19 100644 --- a/internal/runtime/executor/gemini_executor.go +++ b/internal/runtime/executor/gemini_executor.go @@ -79,13 +79,7 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r from := opts.SourceFormat to := sdktranslator.FromString("gemini") body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false) - if budgetOverride, includeOverride, ok := util.GeminiThinkingFromMetadata(req.Metadata); ok && util.ModelSupportsThinking(req.Model) { - if budgetOverride != nil { - norm := util.NormalizeThinkingBudget(req.Model, *budgetOverride) - budgetOverride = &norm - } - body = util.ApplyGeminiThinkingConfig(body, budgetOverride, includeOverride) - } + body = applyThinkingMetadata(body, req.Metadata, req.Model) body = util.StripThinkingConfigIfUnsupported(req.Model, body) body = fixGeminiImageAspectRatio(req.Model, body) body = applyPayloadConfig(e.cfg, req.Model, body) @@ -174,13 +168,7 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A from := opts.SourceFormat to := sdktranslator.FromString("gemini") body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), true) - if budgetOverride, includeOverride, ok := util.GeminiThinkingFromMetadata(req.Metadata); ok && util.ModelSupportsThinking(req.Model) { - if budgetOverride != nil { - norm := util.NormalizeThinkingBudget(req.Model, *budgetOverride) - budgetOverride = &norm - } - body = util.ApplyGeminiThinkingConfig(body, budgetOverride, includeOverride) - } + body = applyThinkingMetadata(body, req.Metadata, req.Model) body = util.StripThinkingConfigIfUnsupported(req.Model, body) body = fixGeminiImageAspectRatio(req.Model, body) body = applyPayloadConfig(e.cfg, req.Model, body) @@ -288,13 +276,7 @@ func (e *GeminiExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut from := opts.SourceFormat to := sdktranslator.FromString("gemini") translatedReq := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false) - if budgetOverride, includeOverride, ok := util.GeminiThinkingFromMetadata(req.Metadata); ok && util.ModelSupportsThinking(req.Model) { - if budgetOverride != nil { - norm := util.NormalizeThinkingBudget(req.Model, *budgetOverride) - budgetOverride = &norm - } - translatedReq = util.ApplyGeminiThinkingConfig(translatedReq, budgetOverride, includeOverride) - } + translatedReq = applyThinkingMetadata(translatedReq, req.Metadata, req.Model) translatedReq = util.StripThinkingConfigIfUnsupported(req.Model, translatedReq) translatedReq = fixGeminiImageAspectRatio(req.Model, translatedReq) respCtx := context.WithValue(ctx, "alt", opts.Alt) diff --git a/internal/runtime/executor/payload_helpers.go b/internal/runtime/executor/payload_helpers.go index 4055f895..1465533a 100644 --- a/internal/runtime/executor/payload_helpers.go +++ b/internal/runtime/executor/payload_helpers.go @@ -4,10 +4,42 @@ import ( "strings" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v6/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) +// applyThinkingMetadata applies thinking config from model suffix metadata (e.g., -reasoning, -thinking-N) +// for standard Gemini format payloads. It normalizes the budget when the model supports thinking. +func applyThinkingMetadata(payload []byte, metadata map[string]any, model string) []byte { + budgetOverride, includeOverride, ok := util.GeminiThinkingFromMetadata(metadata) + if !ok { + return payload + } + if !util.ModelSupportsThinking(model) { + return payload + } + if budgetOverride != nil { + norm := util.NormalizeThinkingBudget(model, *budgetOverride) + budgetOverride = &norm + } + return util.ApplyGeminiThinkingConfig(payload, budgetOverride, includeOverride) +} + +// applyThinkingMetadataCLI applies thinking config from model suffix metadata (e.g., -reasoning, -thinking-N) +// for Gemini CLI format payloads (nested under "request"). It normalizes the budget when the model supports thinking. +func applyThinkingMetadataCLI(payload []byte, metadata map[string]any, model string) []byte { + budgetOverride, includeOverride, ok := util.GeminiThinkingFromMetadata(metadata) + if !ok { + return payload + } + if budgetOverride != nil && util.ModelSupportsThinking(model) { + norm := util.NormalizeThinkingBudget(model, *budgetOverride) + budgetOverride = &norm + } + return util.ApplyGeminiCLIThinkingConfig(payload, budgetOverride, includeOverride) +} + // applyPayloadConfig applies payload default and override rules from configuration // to the given JSON payload for the specified model. // Defaults only fill missing fields, while overrides always overwrite existing values. From 717c703bff56f547da4fc96cc3759deda3f71b12 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Mon, 1 Dec 2025 07:22:42 +0800 Subject: [PATCH 4/4] docs(readme): add CCS (Claude Code Switch) to projects list --- README_CN.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README_CN.md b/README_CN.md index be0aa234..d6c0c9bf 100644 --- a/README_CN.md +++ b/README_CN.md @@ -89,6 +89,10 @@ CLIProxyAPI 已内置对 [Amp CLI](https://ampcode.com) 和 Amp IDE 扩展的支 一款基于浏览器的 SRT 字幕翻译工具,可通过 CLI 代理 API 使用您的 Gemini 订阅。内置自动验证与错误修正功能,无需 API 密钥。 +### [CCS (Claude Code Switch)](https://github.com/kaitranntt/ccs) + +CLI 封装器,用于通过 CLIProxyAPI OAuth 即时切换多个 Claude 账户和替代模型(Gemini, Codex, Antigravity),无需 API 密钥。 + > [!NOTE] > 如果你开发了基于 CLIProxyAPI 的项目,请提交一个 PR(拉取请求)将其添加到此列表中。