// Package claude provides HTTP handlers for Claude API code-related functionality. // This package implements Claude-compatible streaming chat completions with sophisticated // client rotation and quota management systems to ensure high availability and optimal // resource utilization across multiple backend clients. It handles request translation // between Claude API format and the underlying Gemini backend, providing seamless // API compatibility while maintaining robust error handling and connection management. package claude import ( "bufio" "context" "encoding/json" "fmt" "net/http" "time" "github.com/gin-gonic/gin" . "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" ) // ClaudeCodeAPIHandler contains the handlers for Claude API endpoints. // It holds a pool of clients to interact with the backend service. type ClaudeCodeAPIHandler struct { *handlers.BaseAPIHandler } // NewClaudeCodeAPIHandler creates a new Claude API handlers instance. // It takes an BaseAPIHandler instance as input and returns a ClaudeCodeAPIHandler. // // Parameters: // - apiHandlers: The base API handler instance. // // Returns: // - *ClaudeCodeAPIHandler: A new Claude code API handler instance. func NewClaudeCodeAPIHandler(apiHandlers *handlers.BaseAPIHandler) *ClaudeCodeAPIHandler { return &ClaudeCodeAPIHandler{ BaseAPIHandler: apiHandlers, } } // HandlerType returns the identifier for this handler implementation. func (h *ClaudeCodeAPIHandler) HandlerType() string { return Claude } // Models returns a list of models supported by this handler. func (h *ClaudeCodeAPIHandler) Models() []map[string]any { // Get dynamic models from the global registry modelRegistry := registry.GetGlobalRegistry() return modelRegistry.GetAvailableModels("claude") } // ClaudeMessages handles Claude-compatible streaming chat completions. // This function implements a sophisticated client rotation and quota management system // to ensure high availability and optimal resource utilization across multiple backend clients. // // Parameters: // - c: The Gin context for the request. func (h *ClaudeCodeAPIHandler) ClaudeMessages(c *gin.Context) { // Extract raw JSON data from the incoming request rawJSON, err := c.GetRawData() // If data retrieval fails, return a 400 Bad Request error. if err != nil { c.JSON(http.StatusBadRequest, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: fmt.Sprintf("Invalid request: %v", err), Type: "invalid_request_error", }, }) return } // Check if the client requested a streaming response. streamResult := gjson.GetBytes(rawJSON, "stream") if !streamResult.Exists() || streamResult.Type == gjson.False { h.handleNonStreamingResponse(c, rawJSON) } else { h.handleStreamingResponse(c, rawJSON) } } // ClaudeMessages handles Claude-compatible streaming chat completions. // This function implements a sophisticated client rotation and quota management system // to ensure high availability and optimal resource utilization across multiple backend clients. // // Parameters: // - c: The Gin context for the request. func (h *ClaudeCodeAPIHandler) ClaudeCountTokens(c *gin.Context) { // Extract raw JSON data from the incoming request rawJSON, err := c.GetRawData() // If data retrieval fails, return a 400 Bad Request error. if err != nil { c.JSON(http.StatusBadRequest, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: fmt.Sprintf("Invalid request: %v", err), Type: "invalid_request_error", }, }) return } c.Header("Content-Type", "application/json") alt := h.GetAlt(c) cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background()) modelName := gjson.GetBytes(rawJSON, "model").String() resp, errMsg := h.ExecuteCountWithAuthManager(cliCtx, h.HandlerType(), modelName, rawJSON, alt) if errMsg != nil { h.WriteErrorResponse(c, errMsg) cliCancel(errMsg.Error) return } _, _ = c.Writer.Write(resp) cliCancel() } // ClaudeModels handles the Claude models listing endpoint. // It returns a JSON response containing available Claude models and their specifications. // // Parameters: // - c: The Gin context for the request. func (h *ClaudeCodeAPIHandler) ClaudeModels(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "data": h.Models(), }) } // handleNonStreamingResponse handles non-streaming content generation requests for Claude models. // This function processes the request synchronously and returns the complete generated // response in a single API call. It supports various generation parameters and // response formats. // // Parameters: // - c: The Gin context for the request // - modelName: The name of the Gemini model to use for content generation // - rawJSON: The raw JSON request body containing generation parameters and content func (h *ClaudeCodeAPIHandler) handleNonStreamingResponse(c *gin.Context, rawJSON []byte) { c.Header("Content-Type", "application/json") alt := h.GetAlt(c) cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background()) modelName := gjson.GetBytes(rawJSON, "model").String() resp, errMsg := h.ExecuteWithAuthManager(cliCtx, h.HandlerType(), modelName, rawJSON, alt) if errMsg != nil { h.WriteErrorResponse(c, errMsg) cliCancel(errMsg.Error) return } _, _ = c.Writer.Write(resp) cliCancel() } // handleStreamingResponse streams Claude-compatible responses backed by Gemini. // It sets up SSE, selects a backend client with rotation/quota logic, // forwards chunks, and translates them to Claude CLI format. // // Parameters: // - c: The Gin context for the request. // - rawJSON: The raw JSON request body. func (h *ClaudeCodeAPIHandler) handleStreamingResponse(c *gin.Context, rawJSON []byte) { // Set up Server-Sent Events (SSE) headers for streaming response // These headers are essential for maintaining a persistent connection // and enabling real-time streaming of chat completions c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") c.Header("Access-Control-Allow-Origin", "*") // Get the http.Flusher interface to manually flush the response. // This is crucial for streaming as it allows immediate sending of data chunks flusher, ok := c.Writer.(http.Flusher) if !ok { c.JSON(http.StatusInternalServerError, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: "Streaming not supported", Type: "server_error", }, }) return } modelName := gjson.GetBytes(rawJSON, "model").String() // Create a cancellable context for the backend client request // This allows proper cleanup and cancellation of ongoing requests cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background()) dataChan, errChan := h.ExecuteStreamWithAuthManager(cliCtx, h.HandlerType(), modelName, rawJSON, "") h.forwardClaudeStream(c, flusher, func(err error) { cliCancel(err) }, dataChan, errChan) return } func (h *ClaudeCodeAPIHandler) forwardClaudeStream(c *gin.Context, flusher http.Flusher, cancel func(error), data <-chan []byte, errs <-chan *interfaces.ErrorMessage) { // v6.1: Intelligent Buffered Streamer strategy // Enhanced buffering with larger buffer size (16KB) and longer flush interval (120ms). // Smart flush only when buffer is sufficiently filled (≥50%), dramatically reducing // flush frequency from ~12.5Hz to ~5-8Hz while maintaining low latency. writer := bufio.NewWriterSize(c.Writer, 16*1024) // 4KB → 16KB ticker := time.NewTicker(120 * time.Millisecond) // 80ms → 120ms defer ticker.Stop() var chunkIdx int for { select { case <-c.Request.Context().Done(): // Context cancelled, flush any remaining data before exit _ = writer.Flush() cancel(c.Request.Context().Err()) return case <-ticker.C: // Smart flush: only flush when buffer has sufficient data (≥50% full) // This reduces flush frequency while ensuring data flows naturally buffered := writer.Buffered() if buffered >= 8*1024 { // At least 8KB (50% of 16KB buffer) if err := writer.Flush(); err != nil { // Error flushing, cancel and return cancel(err) return } flusher.Flush() // Also flush the underlying http.ResponseWriter } case chunk, ok := <-data: if !ok { // Stream ended, flush remaining data _ = writer.Flush() cancel(nil) return } // Forward the complete SSE event block directly (already formatted by the translator). // The translator returns a complete SSE-compliant event block, including event:, data:, and separators. // The handler just needs to forward it without reassembly. if len(chunk) > 0 { _, _ = writer.Write(chunk) } chunkIdx++ case errMsg, ok := <-errs: if !ok { continue } if errMsg != nil { // An error occurred: emit as a proper SSE error event errorBytes, _ := json.Marshal(h.toClaudeError(errMsg)) _, _ = writer.WriteString("event: error\n") _, _ = writer.WriteString("data: ") _, _ = writer.Write(errorBytes) _, _ = writer.WriteString("\n\n") _ = writer.Flush() } var execErr error if errMsg != nil { execErr = errMsg.Error } cancel(execErr) return } } } type claudeErrorDetail struct { Type string `json:"type"` Message string `json:"message"` } type claudeErrorResponse struct { Type string `json:"type"` Error claudeErrorDetail `json:"error"` } func (h *ClaudeCodeAPIHandler) toClaudeError(msg *interfaces.ErrorMessage) claudeErrorResponse { return claudeErrorResponse{ Type: "error", Error: claudeErrorDetail{ Type: "api_error", Message: msg.Error.Error(), }, } }