From d32fc0400e60478ea8f18b0ea36a4baa9a0287f5 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Tue, 23 Sep 2025 02:01:57 +0800 Subject: [PATCH] refactor(headers): centralize header logic using `EnsureHeader` utility - Introduced `EnsureHeader` in `internal/misc/header_utils.go` to streamline header setting across executors. - Updated Codex, Claude, and Gemini executors to utilize `EnsureHeader` for consistent header application. - Incorporated Gin context headers (if available) into request header manipulation for better integration. --- internal/misc/header_utils.go | 24 +++++++++++++ internal/runtime/executor/claude_executor.go | 34 ++++++++++++------- internal/runtime/executor/codex_executor.go | 19 +++++++---- .../runtime/executor/gemini_cli_executor.go | 13 +++++-- 4 files changed, 68 insertions(+), 22 deletions(-) create mode 100644 internal/misc/header_utils.go diff --git a/internal/misc/header_utils.go b/internal/misc/header_utils.go new file mode 100644 index 00000000..a43f03ba --- /dev/null +++ b/internal/misc/header_utils.go @@ -0,0 +1,24 @@ +package misc + +import ( + "net/http" + "strings" +) + +func EnsureHeader(target http.Header, source http.Header, key, defaultValue string) { + if target == nil { + return + } + if source != nil { + if val := strings.TrimSpace(source.Get(key)); val != "" { + target.Set(key, val) + return + } + } + if strings.TrimSpace(target.Get(key)) != "" { + return + } + if val := strings.TrimSpace(defaultValue); val != "" { + target.Set(key, val) + } +} diff --git a/internal/runtime/executor/claude_executor.go b/internal/runtime/executor/claude_executor.go index 0a079c08..984acc69 100644 --- a/internal/runtime/executor/claude_executor.go +++ b/internal/runtime/executor/claude_executor.go @@ -18,6 +18,8 @@ import ( sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/sjson" + + "github.com/gin-gonic/gin" ) // ClaudeExecutor is a stateless executor for Anthropic Claude over the messages API. @@ -177,22 +179,28 @@ func (e *ClaudeExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) ( func applyClaudeHeaders(r *http.Request, apiKey string, stream bool) { r.Header.Set("Authorization", "Bearer "+apiKey) r.Header.Set("Content-Type", "application/json") - r.Header.Set("Anthropic-Version", "2023-06-01") - r.Header.Set("Anthropic-Dangerous-Direct-Browser-Access", "true") - r.Header.Set("Anthropic-Beta", "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14") + + var ginHeaders http.Header + if ginCtx, ok := r.Context().Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { + ginHeaders = ginCtx.Request.Header + } + + misc.EnsureHeader(r.Header, ginHeaders, "Anthropic-Version", "2023-06-01") + misc.EnsureHeader(r.Header, ginHeaders, "Anthropic-Dangerous-Direct-Browser-Access", "true") + misc.EnsureHeader(r.Header, ginHeaders, "Anthropic-Beta", "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14") + misc.EnsureHeader(r.Header, ginHeaders, "X-App", "cli") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Helper-Method", "stream") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Retry-Count", "0") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Runtime-Version", "v24.3.0") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Package-Version", "0.55.1") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Runtime", "node") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Lang", "js") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Arch", "arm64") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Os", "MacOS") + misc.EnsureHeader(r.Header, ginHeaders, "X-Stainless-Timeout", "60") r.Header.Set("Connection", "keep-alive") r.Header.Set("User-Agent", "claude-cli/1.0.83 (external, cli)") r.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd") - r.Header.Set("X-App", "cli") - r.Header.Set("X-Stainless-Helper-Method", "stream") - r.Header.Set("X-Stainless-Retry-Count", "0") - r.Header.Set("X-Stainless-Runtime-Version", "v24.3.0") - r.Header.Set("X-Stainless-Package-Version", "0.55.1") - r.Header.Set("X-Stainless-Runtime", "node") - r.Header.Set("X-Stainless-Lang", "js") - r.Header.Set("X-Stainless-Arch", "arm64") - r.Header.Set("X-Stainless-Os", "MacOS") - r.Header.Set("X-Stainless-Timeout", "60") if stream { r.Header.Set("Accept", "text/event-stream") return diff --git a/internal/runtime/executor/codex_executor.go b/internal/runtime/executor/codex_executor.go index f003b32b..3d455f8a 100644 --- a/internal/runtime/executor/codex_executor.go +++ b/internal/runtime/executor/codex_executor.go @@ -11,6 +11,7 @@ import ( codexauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" "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" @@ -18,6 +19,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/tidwall/sjson" + "github.com/gin-gonic/gin" "github.com/google/uuid" ) @@ -232,9 +234,16 @@ func (e *CodexExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (* func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string) { r.Header.Set("Content-Type", "application/json") r.Header.Set("Authorization", "Bearer "+token) - r.Header.Set("Version", "0.21.0") - r.Header.Set("Openai-Beta", "responses=experimental") - r.Header.Set("Session_id", uuid.NewString()) + + var ginHeaders http.Header + if ginCtx, ok := r.Context().Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { + ginHeaders = ginCtx.Request.Header + } + + misc.EnsureHeader(r.Header, ginHeaders, "Version", "0.21.0") + misc.EnsureHeader(r.Header, ginHeaders, "Openai-Beta", "responses=experimental") + misc.EnsureHeader(r.Header, ginHeaders, "Session_id", uuid.NewString()) + r.Header.Set("Accept", "text/event-stream") r.Header.Set("Connection", "Keep-Alive") @@ -248,9 +257,7 @@ func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string) { r.Header.Set("Originator", "codex_cli_rs") if auth != nil && auth.Metadata != nil { if accountID, ok := auth.Metadata["account_id"].(string); ok { - if trimmed := strings.TrimSpace(accountID); trimmed != "" { - r.Header.Set("Chatgpt-Account-Id", trimmed) - } + r.Header.Set("Chatgpt-Account-Id", accountID) } } } diff --git a/internal/runtime/executor/gemini_cli_executor.go b/internal/runtime/executor/gemini_cli_executor.go index 8951b553..a2836053 100644 --- a/internal/runtime/executor/gemini_cli_executor.go +++ b/internal/runtime/executor/gemini_cli_executor.go @@ -11,7 +11,9 @@ import ( "strings" "time" + "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" 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" @@ -393,9 +395,14 @@ func stringValue(m map[string]any, key string) string { // applyGeminiCLIHeaders sets required headers for the Gemini CLI upstream. func applyGeminiCLIHeaders(r *http.Request) { - r.Header.Set("User-Agent", "google-api-nodejs-client/9.15.1") - r.Header.Set("X-Goog-Api-Client", "gl-node/22.17.0") - r.Header.Set("Client-Metadata", geminiCLIClientMetadata()) + var ginHeaders http.Header + if ginCtx, ok := r.Context().Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { + ginHeaders = ginCtx.Request.Header + } + + misc.EnsureHeader(r.Header, ginHeaders, "User-Agent", "google-api-nodejs-client/9.15.1") + misc.EnsureHeader(r.Header, ginHeaders, "X-Goog-Api-Client", "gl-node/22.17.0") + misc.EnsureHeader(r.Header, ginHeaders, "Client-Metadata", geminiCLIClientMetadata()) } // geminiCLIClientMetadata returns a compact metadata string required by upstream.