Refactor API handlers organization and simplify error response handling

- Modularized handlers into dedicated packages (`gemini`, `claude`, `cli`) for better structure.
- Centralized `ErrorResponse` and `ErrorDetail` types under `handlers` package for reuse.
- Updated all handlers to utilize the shared `ErrorResponse` model.
- Introduced specialization of handler structs (`GeminiAPIHandlers`, `ClaudeCodeAPIHandlers`, `GeminiCLIAPIHandlers`) for improved clarity and separation of concerns.
- Refactored `getClient` logic with additional properties and better state management.

Refactor `translator` package by modularizing code for `claude` and `gemini`

- Moved Claude-specific logic (`PrepareClaudeRequest`, `ConvertCliToClaude`) to `translator/claude/code`.
- Moved Gemini-specific logic (`FixCLIToolResponse`) to `translator/gemini/cli` for better package structure.
- Updated affected handler imports and method references.

Add comprehensive package-level documentation across key modules

- Introduced detailed package-level documentation for core modules: `auth`, `client`, `cmd`, `handlers`, `util`, `watcher`, `config`, `translator`, and `api`.
- Enhanced code readability and maintainability by clarifying the purpose and functionality of each package.
- Aligned documentation style and tone with existing codebase conventions.

Refactor API handlers and translator modules for improved clarity and consistency

- Standardized handler struct names (`GeminiAPIHandlers`, `ClaudeCodeAPIHandlers`, `GeminiCLIAPIHandlers`, `OpenAIAPIHandlers`) and updated related comments.
- Fixed unnecessary `else` blocks in streaming logic for cleaner error handling.
- Renamed variables for better readability (`responseIdResult` to `responseIDResult`, `activationUrl` to `activationURL`, etc.).
- Addressed minor inconsistencies in API handler comments and SSE header initialization.
- Improved modularization of `claude` and `gemini` translator components.

Standardize configuration field naming for consistency across modules

- Renamed `ProxyUrl` to `ProxyURL`, `ApiKeys` to `APIKeys`, and `ConfigQuotaExceeded` to `QuotaExceeded`.
- Updated all relevant references and comments in `config`, `auth`, `api`, `util`, and `watcher`.
- Ensured consistent casing for `GlAPIKey` debug logs.
This commit is contained in:
Luis Pater
2025-08-05 17:37:00 +08:00
parent 00f33f5f3a
commit 1483c31c73
23 changed files with 1484 additions and 1267 deletions

View File

@@ -1,3 +1,7 @@
// Package api provides the HTTP API server implementation for the CLI Proxy API.
// It includes the main server struct, routing setup, middleware for CORS and authentication,
// and integration with various AI API handlers (OpenAI, Claude, Gemini).
// The server supports hot-reloading of clients and configuration.
package api
import (
@@ -5,6 +9,11 @@ import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/luispater/CLIProxyAPI/internal/api/handlers"
"github.com/luispater/CLIProxyAPI/internal/api/handlers/claude"
"github.com/luispater/CLIProxyAPI/internal/api/handlers/gemini"
"github.com/luispater/CLIProxyAPI/internal/api/handlers/gemini/cli"
"github.com/luispater/CLIProxyAPI/internal/api/handlers/openai"
"github.com/luispater/CLIProxyAPI/internal/client"
"github.com/luispater/CLIProxyAPI/internal/config"
log "github.com/sirupsen/logrus"
@@ -17,7 +26,7 @@ import (
type Server struct {
engine *gin.Engine
server *http.Server
handlers *APIHandlers
handlers *handlers.APIHandlers
cfg *config.Config
}
@@ -29,9 +38,6 @@ func NewServer(cfg *config.Config, cliClients []*client.Client) *Server {
gin.SetMode(gin.ReleaseMode)
}
// Create handlers
handlers := NewAPIHandlers(cliClients, cfg)
// Create gin engine
engine := gin.New()
@@ -43,7 +49,7 @@ func NewServer(cfg *config.Config, cliClients []*client.Client) *Server {
// Create server instance
s := &Server{
engine: engine,
handlers: handlers,
handlers: handlers.NewAPIHandlers(cliClients, cfg),
cfg: cfg,
}
@@ -62,22 +68,27 @@ func NewServer(cfg *config.Config, cliClients []*client.Client) *Server {
// 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.NewOpenAIAPIHandlers(s.handlers)
geminiHandlers := gemini.NewGeminiAPIHandlers(s.handlers)
geminiCLIHandlers := cli.NewGeminiCLIAPIHandlers(s.handlers)
claudeCodeHandlers := claude.NewClaudeCodeAPIHandlers(s.handlers)
// OpenAI compatible API routes
v1 := s.engine.Group("/v1")
v1.Use(AuthMiddleware(s.cfg))
{
v1.GET("/models", s.handlers.Models)
v1.POST("/chat/completions", s.handlers.ChatCompletions)
v1.POST("/messages", s.handlers.ClaudeMessages)
v1.GET("/models", openaiHandlers.Models)
v1.POST("/chat/completions", openaiHandlers.ChatCompletions)
v1.POST("/messages", claudeCodeHandlers.ClaudeMessages)
}
// Gemini compatible API routes
v1beta := s.engine.Group("/v1beta")
v1beta.Use(AuthMiddleware(s.cfg))
{
v1beta.GET("/models", s.handlers.GeminiModels)
v1beta.POST("/models/:action", s.handlers.GeminiHandler)
v1beta.GET("/models/:action", s.handlers.GeminiGetHandler)
v1beta.GET("/models", geminiHandlers.GeminiModels)
v1beta.POST("/models/:action", geminiHandlers.GeminiHandler)
v1beta.GET("/models/:action", geminiHandlers.GeminiGetHandler)
}
// Root endpoint
@@ -91,7 +102,7 @@ func (s *Server) setupRoutes() {
},
})
})
s.engine.POST("/v1internal:method", s.handlers.CLIHandler)
s.engine.POST("/v1internal:method", geminiCLIHandlers.CLIHandler)
}
@@ -150,7 +161,7 @@ func (s *Server) UpdateClients(clients []*client.Client, cfg *config.Config) {
// using API keys. If no API keys are configured, it allows all requests.
func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
if len(cfg.ApiKeys) == 0 {
if len(cfg.APIKeys) == 0 {
c.Next()
return
}
@@ -181,9 +192,9 @@ func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
// Find the API key in the in-memory list
var foundKey string
for i := range cfg.ApiKeys {
if cfg.ApiKeys[i] == apiKey || cfg.ApiKeys[i] == authHeaderGoogle || cfg.ApiKeys[i] == authHeaderAnthropic || cfg.ApiKeys[i] == apiKeyQuery {
foundKey = cfg.ApiKeys[i]
for i := range cfg.APIKeys {
if cfg.APIKeys[i] == apiKey || cfg.APIKeys[i] == authHeaderGoogle || cfg.APIKeys[i] == authHeaderAnthropic || cfg.APIKeys[i] == apiKeyQuery {
foundKey = cfg.APIKeys[i]
break
}
}