diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index 47501b8d..24383e29 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -449,7 +449,7 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { } bodyJSON, _ := json.Marshal(bodyMap) - httpClient := util.SetProxy(h.cfg, &http.Client{}) + httpClient := util.SetProxy(&h.cfg.SDKConfig, &http.Client{}) req, _ := http.NewRequestWithContext(ctx, "POST", "https://console.anthropic.com/v1/oauth/token", strings.NewReader(string(bodyJSON))) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") @@ -818,7 +818,7 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { "redirect_uri": {"http://localhost:1455/auth/callback"}, "code_verifier": {pkceCodes.CodeVerifier}, } - httpClient := util.SetProxy(h.cfg, &http.Client{}) + httpClient := util.SetProxy(&h.cfg.SDKConfig, &http.Client{}) req, _ := http.NewRequestWithContext(ctx, "POST", "https://auth.openai.com/oauth/token", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") diff --git a/internal/api/server.go b/internal/api/server.go index 28bda337..5609c493 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -16,17 +16,17 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/claude" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/gemini" managementHandlers "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/management" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/openai" "github.com/router-for-me/CLIProxyAPI/v6/internal/api/middleware" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" "github.com/router-for-me/CLIProxyAPI/v6/internal/usage" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/claude" + gemini2 "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/gemini" + openai2 "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/openai" "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) @@ -187,7 +187,7 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk // Create server instance s := &Server{ engine: engine, - handlers: handlers.NewBaseAPIHandlers(cfg, authManager), + handlers: handlers.NewBaseAPIHandlers(&cfg.SDKConfig, authManager), cfg: cfg, accessManager: accessManager, requestLogger: requestLogger, @@ -224,11 +224,11 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk // setupRoutes configures the API routes for the server. // It defines the endpoints and associates them with their respective handlers. func (s *Server) setupRoutes() { - openaiHandlers := openai.NewOpenAIAPIHandler(s.handlers) - geminiHandlers := gemini.NewGeminiAPIHandler(s.handlers) - geminiCLIHandlers := gemini.NewGeminiCLIAPIHandler(s.handlers) + openaiHandlers := openai2.NewOpenAIAPIHandler(s.handlers) + geminiHandlers := gemini2.NewGeminiAPIHandler(s.handlers) + geminiCLIHandlers := gemini2.NewGeminiCLIAPIHandler(s.handlers) claudeCodeHandlers := claude.NewClaudeCodeAPIHandler(s.handlers) - openaiResponsesHandlers := openai.NewOpenAIResponsesAPIHandler(s.handlers) + openaiResponsesHandlers := openai2.NewOpenAIResponsesAPIHandler(s.handlers) // OpenAI compatible API routes v1 := s.engine.Group("/v1") @@ -469,7 +469,7 @@ func (s *Server) watchKeepAlive() { // that routes to different handlers based on the User-Agent header. // If User-Agent starts with "claude-cli", it routes to Claude handler, // otherwise it routes to OpenAI handler. -func (s *Server) unifiedModelsHandler(openaiHandler *openai.OpenAIAPIHandler, claudeHandler *claude.ClaudeCodeAPIHandler) gin.HandlerFunc { +func (s *Server) unifiedModelsHandler(openaiHandler *openai2.OpenAIAPIHandler, claudeHandler *claude.ClaudeCodeAPIHandler) gin.HandlerFunc { return func(c *gin.Context) { userAgent := c.GetHeader("User-Agent") @@ -622,7 +622,7 @@ func (s *Server) UpdateClients(cfg *config.Config) { s.applyAccessConfig(oldCfg, cfg) s.cfg = cfg - s.handlers.UpdateClients(cfg) + s.handlers.UpdateClients(&cfg.SDKConfig) if s.mgmt != nil { s.mgmt.SetConfig(cfg) s.mgmt.SetAuthManager(s.handlers.AuthManager) diff --git a/internal/auth/claude/anthropic_auth.go b/internal/auth/claude/anthropic_auth.go index 8eeb7e8c..07bd5b42 100644 --- a/internal/auth/claude/anthropic_auth.go +++ b/internal/auth/claude/anthropic_auth.go @@ -59,7 +59,7 @@ type ClaudeAuth struct { // - *ClaudeAuth: A new Claude authentication service instance func NewClaudeAuth(cfg *config.Config) *ClaudeAuth { return &ClaudeAuth{ - httpClient: util.SetProxy(cfg, &http.Client{}), + httpClient: util.SetProxy(&cfg.SDKConfig, &http.Client{}), } } diff --git a/internal/auth/codex/openai_auth.go b/internal/auth/codex/openai_auth.go index c2a750ba..c0299c3d 100644 --- a/internal/auth/codex/openai_auth.go +++ b/internal/auth/codex/openai_auth.go @@ -37,7 +37,7 @@ type CodexAuth struct { // It initializes an HTTP client with proxy settings from the provided configuration. func NewCodexAuth(cfg *config.Config) *CodexAuth { return &CodexAuth{ - httpClient: util.SetProxy(cfg, &http.Client{}), + httpClient: util.SetProxy(&cfg.SDKConfig, &http.Client{}), } } diff --git a/internal/auth/qwen/qwen_auth.go b/internal/auth/qwen/qwen_auth.go index 9a554745..cb58b86d 100644 --- a/internal/auth/qwen/qwen_auth.go +++ b/internal/auth/qwen/qwen_auth.go @@ -85,7 +85,7 @@ type QwenAuth struct { // NewQwenAuth creates a new QwenAuth instance with a proxy-configured HTTP client. func NewQwenAuth(cfg *config.Config) *QwenAuth { return &QwenAuth{ - httpClient: util.SetProxy(cfg, &http.Client{}), + httpClient: util.SetProxy(&cfg.SDKConfig, &http.Client{}), } } diff --git a/internal/cmd/gemini-web_auth.go b/internal/cmd/gemini-web_auth.go index ff1ea1cd..70cdb8ee 100644 --- a/internal/cmd/gemini-web_auth.go +++ b/internal/cmd/gemini-web_auth.go @@ -75,7 +75,7 @@ func DoGeminiWebAuth(cfg *config.Config) { // Build HTTP client with proxy settings respected. httpClient := &http.Client{Timeout: 15 * time.Second} - httpClient = util.SetProxy(cfg, httpClient) + httpClient = util.SetProxy(&cfg.SDKConfig, httpClient) // Request ListAccounts to extract email as label (use POST per upstream behavior). req, err := http.NewRequest(http.MethodPost, "https://accounts.google.com/ListAccounts", nil) @@ -88,8 +88,8 @@ func DoGeminiWebAuth(cfg *config.Config) { req.Header.Set("Origin", "https://accounts.google.com") req.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") - resp, err := httpClient.Do(req) - if err != nil { + resp, errDo := httpClient.Do(req) + if errDo != nil { fmt.Println("!! Request to ListAccounts failed:", err) } else { defer func() { _ = resp.Body.Close() }() diff --git a/internal/config/config.go b/internal/config/config.go index f063c7a6..cd3d9c48 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,12 +8,14 @@ import ( "fmt" "os" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v3" ) // Config represents the application's configuration, loaded from a YAML file. type Config struct { + config.SDKConfig // Port is the network port on which the API server will listen. Port int `yaml:"port" json:"-"` @@ -29,9 +31,6 @@ type Config struct { // UsageStatisticsEnabled toggles in-memory usage aggregation; when false, usage data is discarded. UsageStatisticsEnabled bool `yaml:"usage-statistics-enabled" json:"usage-statistics-enabled"` - // ProxyURL is the URL of an optional proxy server to use for outbound requests. - ProxyURL string `yaml:"proxy-url" json:"proxy-url"` - // APIKeys is a list of keys for authenticating clients to this proxy server. APIKeys []string `yaml:"api-keys" json:"api-keys"` @@ -44,9 +43,6 @@ type Config struct { // GlAPIKey is the API key for the generative language API. GlAPIKey []string `yaml:"generative-language-api-key" json:"generative-language-api-key"` - // RequestLog enables or disables detailed request logging functionality. - RequestLog bool `yaml:"request-log" json:"request-log"` - // RequestRetry defines the retry times when the request failed. RequestRetry int `yaml:"request-retry" json:"request-retry"` @@ -206,23 +202,23 @@ func LoadConfig(configFile string) (*Config, error) { } // Unmarshal the YAML data into the Config struct. - var config Config + var cfg Config // Set defaults before unmarshal so that absent keys keep defaults. - config.LoggingToFile = true - config.UsageStatisticsEnabled = true - config.GeminiWeb.Context = true - if err = yaml.Unmarshal(data, &config); err != nil { + cfg.LoggingToFile = true + cfg.UsageStatisticsEnabled = true + cfg.GeminiWeb.Context = true + if err = yaml.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) } // Hash remote management key if plaintext is detected (nested) // We consider a value to be already hashed if it looks like a bcrypt hash ($2a$, $2b$, or $2y$ prefix). - if config.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(config.RemoteManagement.SecretKey) { - hashed, errHash := hashSecret(config.RemoteManagement.SecretKey) + if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) { + hashed, errHash := hashSecret(cfg.RemoteManagement.SecretKey) if errHash != nil { return nil, fmt.Errorf("failed to hash remote management key: %w", errHash) } - config.RemoteManagement.SecretKey = hashed + cfg.RemoteManagement.SecretKey = hashed // Persist the hashed value back to the config file to avoid re-hashing on next startup. // Preserve YAML comments and ordering; update only the nested key. @@ -230,10 +226,10 @@ func LoadConfig(configFile string) (*Config, error) { } // Sync request authentication providers with inline API keys for backwards compatibility. - syncInlineAccessProvider(&config) + syncInlineAccessProvider(&cfg) // Return the populated configuration struct. - return &config, nil + return &cfg, nil } // SyncInlineAPIKeys updates the inline API key provider and top-level APIKeys field. diff --git a/internal/runtime/executor/gemini_executor.go b/internal/runtime/executor/gemini_executor.go index 17f5c1c0..e35d81a8 100644 --- a/internal/runtime/executor/gemini_executor.go +++ b/internal/runtime/executor/gemini_executor.go @@ -320,7 +320,7 @@ func (e *GeminiExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) ( conf := &oauth2.Config{ClientID: clientID, ClientSecret: clientSecret, Endpoint: endpoint} // Ensure proxy-aware HTTP client for token refresh - httpClient := util.SetProxy(e.cfg, &http.Client{}) + httpClient := util.SetProxy(&e.cfg.SDKConfig, &http.Client{}) ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) // Build base token diff --git a/internal/util/provider.go b/internal/util/provider.go index 0e2ddcd9..a6b327fd 100644 --- a/internal/util/provider.go +++ b/internal/util/provider.go @@ -26,7 +26,7 @@ import ( // // Returns: // - []string: All provider identifiers capable of serving the model, ordered by preference. -func GetProviderName(modelName string, cfg *config.Config) []string { +func GetProviderName(modelName string) []string { if modelName == "" { return nil } diff --git a/internal/util/proxy.go b/internal/util/proxy.go index ecbaf10e..fa51dd43 100644 --- a/internal/util/proxy.go +++ b/internal/util/proxy.go @@ -9,7 +9,7 @@ import ( "net/http" "net/url" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" log "github.com/sirupsen/logrus" "golang.org/x/net/proxy" ) @@ -17,7 +17,7 @@ import ( // SetProxy configures the provided HTTP client with proxy settings from the configuration. // It supports SOCKS5, HTTP, and HTTPS proxies. The function modifies the client's transport // to route requests through the configured proxy server. -func SetProxy(cfg *config.Config, httpClient *http.Client) *http.Client { +func SetProxy(cfg *config.SDKConfig, httpClient *http.Client) *http.Client { var transport *http.Transport // Attempt to parse the proxy URL from the configuration. proxyURL, errParse := url.Parse(cfg.ProxyURL) diff --git a/internal/api/handlers/claude/code_handlers.go b/sdk/api/handlers/claude/code_handlers.go similarity index 99% rename from internal/api/handlers/claude/code_handlers.go rename to sdk/api/handlers/claude/code_handlers.go index 1de542dc..6df003c9 100644 --- a/internal/api/handlers/claude/code_handlers.go +++ b/sdk/api/handlers/claude/code_handlers.go @@ -14,10 +14,10 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers" . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" "github.com/tidwall/gjson" ) diff --git a/internal/api/handlers/gemini/gemini-cli_handlers.go b/sdk/api/handlers/gemini/gemini-cli_handlers.go similarity index 99% rename from internal/api/handlers/gemini/gemini-cli_handlers.go rename to sdk/api/handlers/gemini/gemini-cli_handlers.go index 26beaf42..5224faf8 100644 --- a/internal/api/handlers/gemini/gemini-cli_handlers.go +++ b/sdk/api/handlers/gemini/gemini-cli_handlers.go @@ -14,10 +14,10 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers" . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) diff --git a/internal/api/handlers/gemini/gemini_handlers.go b/sdk/api/handlers/gemini/gemini_handlers.go similarity index 99% rename from internal/api/handlers/gemini/gemini_handlers.go rename to sdk/api/handlers/gemini/gemini_handlers.go index 3208160c..8dcd9cb1 100644 --- a/internal/api/handlers/gemini/gemini_handlers.go +++ b/sdk/api/handlers/gemini/gemini_handlers.go @@ -13,10 +13,10 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers" . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" ) // GeminiAPIHandler contains the handlers for Gemini API endpoints. diff --git a/internal/api/handlers/handlers.go b/sdk/api/handlers/handlers.go similarity index 95% rename from internal/api/handlers/handlers.go rename to sdk/api/handlers/handlers.go index 92d5817c..f9f86fd3 100644 --- a/internal/api/handlers/handlers.go +++ b/sdk/api/handlers/handlers.go @@ -8,11 +8,11 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" "golang.org/x/net/context" ) @@ -45,7 +45,7 @@ type BaseAPIHandler struct { AuthManager *coreauth.Manager // Cfg holds the current application configuration. - Cfg *config.Config + Cfg *config.SDKConfig } // NewBaseAPIHandlers creates a new API handlers instance. @@ -57,7 +57,7 @@ type BaseAPIHandler struct { // // Returns: // - *BaseAPIHandler: A new API handlers instance -func NewBaseAPIHandlers(cfg *config.Config, authManager *coreauth.Manager) *BaseAPIHandler { +func NewBaseAPIHandlers(cfg *config.SDKConfig, authManager *coreauth.Manager) *BaseAPIHandler { return &BaseAPIHandler{ Cfg: cfg, AuthManager: authManager, @@ -70,7 +70,7 @@ func NewBaseAPIHandlers(cfg *config.Config, authManager *coreauth.Manager) *Base // Parameters: // - clients: The new slice of AI service clients // - cfg: The new application configuration -func (h *BaseAPIHandler) UpdateClients(cfg *config.Config) { h.Cfg = cfg } +func (h *BaseAPIHandler) UpdateClients(cfg *config.SDKConfig) { h.Cfg = cfg } // GetAlt extracts the 'alt' parameter from the request query string. // It checks both 'alt' and '$alt' parameters and returns the appropriate value. @@ -133,7 +133,7 @@ func (h *BaseAPIHandler) GetContextWithCancel(handler interfaces.APIHandler, c * // ExecuteWithAuthManager executes a non-streaming request via the core auth manager. // This path is the only supported execution route. func (h *BaseAPIHandler) ExecuteWithAuthManager(ctx context.Context, handlerType, modelName string, rawJSON []byte, alt string) ([]byte, *interfaces.ErrorMessage) { - providers := util.GetProviderName(modelName, h.Cfg) + providers := util.GetProviderName(modelName) if len(providers) == 0 { return nil, &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)} } @@ -157,7 +157,7 @@ func (h *BaseAPIHandler) ExecuteWithAuthManager(ctx context.Context, handlerType // ExecuteCountWithAuthManager executes a non-streaming request via the core auth manager. // This path is the only supported execution route. func (h *BaseAPIHandler) ExecuteCountWithAuthManager(ctx context.Context, handlerType, modelName string, rawJSON []byte, alt string) ([]byte, *interfaces.ErrorMessage) { - providers := util.GetProviderName(modelName, h.Cfg) + providers := util.GetProviderName(modelName) if len(providers) == 0 { return nil, &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)} } @@ -181,7 +181,7 @@ func (h *BaseAPIHandler) ExecuteCountWithAuthManager(ctx context.Context, handle // ExecuteStreamWithAuthManager executes a streaming request via the core auth manager. // This path is the only supported execution route. func (h *BaseAPIHandler) ExecuteStreamWithAuthManager(ctx context.Context, handlerType, modelName string, rawJSON []byte, alt string) (<-chan []byte, <-chan *interfaces.ErrorMessage) { - providers := util.GetProviderName(modelName, h.Cfg) + providers := util.GetProviderName(modelName) if len(providers) == 0 { errChan := make(chan *interfaces.ErrorMessage, 1) errChan <- &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)} diff --git a/internal/api/handlers/openai/openai_handlers.go b/sdk/api/handlers/openai/openai_handlers.go similarity index 99% rename from internal/api/handlers/openai/openai_handlers.go rename to sdk/api/handlers/openai/openai_handlers.go index 504c2859..485afe4d 100644 --- a/internal/api/handlers/openai/openai_handlers.go +++ b/sdk/api/handlers/openai/openai_handlers.go @@ -14,10 +14,10 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers" . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/api/handlers/openai/openai_responses_handlers.go b/sdk/api/handlers/openai/openai_responses_handlers.go similarity index 98% rename from internal/api/handlers/openai/openai_responses_handlers.go rename to sdk/api/handlers/openai/openai_responses_handlers.go index 22bef82e..ace02313 100644 --- a/internal/api/handlers/openai/openai_responses_handlers.go +++ b/sdk/api/handlers/openai/openai_responses_handlers.go @@ -14,10 +14,10 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers" . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" "github.com/tidwall/gjson" ) diff --git a/sdk/config/config.go b/sdk/config/config.go new file mode 100644 index 00000000..178e7be2 --- /dev/null +++ b/sdk/config/config.go @@ -0,0 +1,14 @@ +// Package config provides configuration management for the CLI Proxy API server. +// It handles loading and parsing YAML configuration files, and provides structured +// access to application settings including server port, authentication directory, +// debug settings, proxy configuration, and API keys. +package config + +// SDKConfig represents the application's configuration, loaded from a YAML file. +type SDKConfig struct { + // ProxyURL is the URL of an optional proxy server to use for outbound requests. + ProxyURL string `yaml:"proxy-url" json:"proxy-url"` + + // RequestLog enables or disables detailed request logging functionality. + RequestLog bool `yaml:"request-log" json:"request-log"` +}