mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-21 16:40:22 +00:00
- 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.
123 lines
4.0 KiB
Go
123 lines
4.0 KiB
Go
// Package handlers provides core API handler functionality for the CLI Proxy API server.
|
|
// It includes common types, client management, load balancing, and error handling
|
|
// shared across all API endpoint handlers (OpenAI, Claude, Gemini).
|
|
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/luispater/CLIProxyAPI/internal/client"
|
|
"github.com/luispater/CLIProxyAPI/internal/config"
|
|
log "github.com/sirupsen/logrus"
|
|
"sync"
|
|
)
|
|
|
|
// ErrorResponse represents a standard error response format for the API.
|
|
// It contains a single ErrorDetail field.
|
|
type ErrorResponse struct {
|
|
Error ErrorDetail `json:"error"`
|
|
}
|
|
|
|
// ErrorDetail provides specific information about an error that occurred.
|
|
// It includes a human-readable message, an error type, and an optional error code.
|
|
type ErrorDetail struct {
|
|
// A human-readable message providing more details about the error.
|
|
Message string `json:"message"`
|
|
// The type of error that occurred (e.g., "invalid_request_error").
|
|
Type string `json:"type"`
|
|
// A short code identifying the error, if applicable.
|
|
Code string `json:"code,omitempty"`
|
|
}
|
|
|
|
// APIHandlers contains the handlers for API endpoints.
|
|
// It holds a pool of clients to interact with the backend service.
|
|
type APIHandlers struct {
|
|
CliClients []*client.Client
|
|
Cfg *config.Config
|
|
Mutex *sync.Mutex
|
|
LastUsedClientIndex int
|
|
}
|
|
|
|
// NewAPIHandlers creates a new API handlers instance.
|
|
// It takes a slice of clients and a debug flag as input.
|
|
func NewAPIHandlers(cliClients []*client.Client, cfg *config.Config) *APIHandlers {
|
|
return &APIHandlers{
|
|
CliClients: cliClients,
|
|
Cfg: cfg,
|
|
Mutex: &sync.Mutex{},
|
|
LastUsedClientIndex: 0,
|
|
}
|
|
}
|
|
|
|
// UpdateClients updates the handlers' client list and configuration
|
|
func (h *APIHandlers) UpdateClients(clients []*client.Client, cfg *config.Config) {
|
|
h.CliClients = clients
|
|
h.Cfg = cfg
|
|
}
|
|
|
|
// GetClient returns an available client from the pool using round-robin load balancing.
|
|
// It checks for quota limits and tries to find an unlocked client for immediate use.
|
|
// The modelName parameter is used to check quota status for specific models.
|
|
func (h *APIHandlers) GetClient(modelName string, isGenerateContent ...bool) (*client.Client, *client.ErrorMessage) {
|
|
if len(h.CliClients) == 0 {
|
|
return nil, &client.ErrorMessage{StatusCode: 500, Error: fmt.Errorf("no clients available")}
|
|
}
|
|
|
|
var cliClient *client.Client
|
|
|
|
// Lock the mutex to update the last used client index
|
|
h.Mutex.Lock()
|
|
startIndex := h.LastUsedClientIndex
|
|
if (len(isGenerateContent) > 0 && isGenerateContent[0]) || len(isGenerateContent) == 0 {
|
|
currentIndex := (startIndex + 1) % len(h.CliClients)
|
|
h.LastUsedClientIndex = currentIndex
|
|
}
|
|
h.Mutex.Unlock()
|
|
|
|
// Reorder the client to start from the last used index
|
|
reorderedClients := make([]*client.Client, 0)
|
|
for i := 0; i < len(h.CliClients); i++ {
|
|
cliClient = h.CliClients[(startIndex+1+i)%len(h.CliClients)]
|
|
if cliClient.IsModelQuotaExceeded(modelName) {
|
|
log.Debugf("Model %s is quota exceeded for account %s, project id: %s", modelName, cliClient.GetEmail(), cliClient.GetProjectID())
|
|
cliClient = nil
|
|
continue
|
|
}
|
|
reorderedClients = append(reorderedClients, cliClient)
|
|
}
|
|
|
|
if len(reorderedClients) == 0 {
|
|
return nil, &client.ErrorMessage{StatusCode: 429, Error: fmt.Errorf(`{"error":{"code":429,"message":"All the models of '%s' are quota exceeded","status":"RESOURCE_EXHAUSTED"}}`, modelName)}
|
|
}
|
|
|
|
locked := false
|
|
for i := 0; i < len(reorderedClients); i++ {
|
|
cliClient = reorderedClients[i]
|
|
if cliClient.RequestMutex.TryLock() {
|
|
locked = true
|
|
break
|
|
}
|
|
}
|
|
if !locked {
|
|
cliClient = h.CliClients[0]
|
|
cliClient.RequestMutex.Lock()
|
|
}
|
|
|
|
return cliClient, nil
|
|
}
|
|
|
|
// GetAlt extracts the 'alt' parameter from the request query string.
|
|
// It checks both 'alt' and '$alt' parameters and returns the appropriate value.
|
|
func (h *APIHandlers) GetAlt(c *gin.Context) string {
|
|
var alt string
|
|
var hasAlt bool
|
|
alt, hasAlt = c.GetQuery("alt")
|
|
if !hasAlt {
|
|
alt, _ = c.GetQuery("$alt")
|
|
}
|
|
if alt == "sse" {
|
|
return ""
|
|
}
|
|
return alt
|
|
}
|