Improved the /v1/models endpoint
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/luispater/CLIProxyAPI/internal/api/handlers"
|
||||
. "github.com/luispater/CLIProxyAPI/internal/constant"
|
||||
"github.com/luispater/CLIProxyAPI/internal/interfaces"
|
||||
"github.com/luispater/CLIProxyAPI/internal/registry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
@@ -47,7 +48,9 @@ func (h *ClaudeCodeAPIHandler) HandlerType() string {
|
||||
|
||||
// Models returns a list of models supported by this handler.
|
||||
func (h *ClaudeCodeAPIHandler) Models() []map[string]any {
|
||||
return make([]map[string]any, 0)
|
||||
// Get dynamic models from the global registry
|
||||
modelRegistry := registry.GetGlobalRegistry()
|
||||
return modelRegistry.GetAvailableModels("claude")
|
||||
}
|
||||
|
||||
// ClaudeMessages handles Claude-compatible streaming chat completions.
|
||||
@@ -79,6 +82,17 @@ func (h *ClaudeCodeAPIHandler) ClaudeMessages(c *gin.Context) {
|
||||
h.handleStreamingResponse(c, rawJSON)
|
||||
}
|
||||
|
||||
// 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(),
|
||||
})
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/luispater/CLIProxyAPI/internal/api/handlers"
|
||||
. "github.com/luispater/CLIProxyAPI/internal/constant"
|
||||
"github.com/luispater/CLIProxyAPI/internal/interfaces"
|
||||
"github.com/luispater/CLIProxyAPI/internal/registry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -40,62 +41,9 @@ func (h *GeminiAPIHandler) HandlerType() string {
|
||||
|
||||
// Models returns the Gemini-compatible model metadata supported by this handler.
|
||||
func (h *GeminiAPIHandler) Models() []map[string]any {
|
||||
return []map[string]any{
|
||||
{
|
||||
"name": "models/gemini-2.5-flash",
|
||||
"version": "001",
|
||||
"displayName": "Gemini 2.5 Flash",
|
||||
"description": "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
|
||||
"inputTokenLimit": 1048576,
|
||||
"outputTokenLimit": 65536,
|
||||
"supportedGenerationMethods": []string{
|
||||
"generateContent",
|
||||
"countTokens",
|
||||
"createCachedContent",
|
||||
"batchGenerateContent",
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64,
|
||||
"maxTemperature": 2,
|
||||
"thinking": true,
|
||||
},
|
||||
{
|
||||
"name": "models/gemini-2.5-pro",
|
||||
"version": "2.5",
|
||||
"displayName": "Gemini 2.5 Pro",
|
||||
"description": "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
|
||||
"inputTokenLimit": 1048576,
|
||||
"outputTokenLimit": 65536,
|
||||
"supportedGenerationMethods": []string{
|
||||
"generateContent",
|
||||
"countTokens",
|
||||
"createCachedContent",
|
||||
"batchGenerateContent",
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64,
|
||||
"maxTemperature": 2,
|
||||
"thinking": true,
|
||||
},
|
||||
{
|
||||
"name": "gpt-5",
|
||||
"version": "001",
|
||||
"displayName": "GPT 5",
|
||||
"description": "Stable version of GPT 5, The best model for coding and agentic tasks across domains.",
|
||||
"inputTokenLimit": 400000,
|
||||
"outputTokenLimit": 128000,
|
||||
"supportedGenerationMethods": []string{
|
||||
"generateContent",
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64,
|
||||
"maxTemperature": 2,
|
||||
"thinking": true,
|
||||
},
|
||||
}
|
||||
// Get dynamic models from the global registry
|
||||
modelRegistry := registry.GetGlobalRegistry()
|
||||
return modelRegistry.GetAvailableModels("gemini")
|
||||
}
|
||||
|
||||
// GeminiModels handles the Gemini models listing endpoint.
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/luispater/CLIProxyAPI/internal/api/handlers"
|
||||
. "github.com/luispater/CLIProxyAPI/internal/constant"
|
||||
"github.com/luispater/CLIProxyAPI/internal/interfaces"
|
||||
"github.com/luispater/CLIProxyAPI/internal/registry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
@@ -47,90 +48,18 @@ func (h *OpenAIAPIHandler) HandlerType() string {
|
||||
|
||||
// Models returns the OpenAI-compatible model metadata supported by this handler.
|
||||
func (h *OpenAIAPIHandler) Models() []map[string]any {
|
||||
return []map[string]any{
|
||||
{
|
||||
"id": "gemini-2.5-pro",
|
||||
"object": "model",
|
||||
"version": "2.5",
|
||||
"name": "Gemini 2.5 Pro",
|
||||
"description": "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
|
||||
"context_length": 1_048_576,
|
||||
"max_completion_tokens": 65_536,
|
||||
"supported_parameters": []string{
|
||||
"tools",
|
||||
"temperature",
|
||||
"top_p",
|
||||
"top_k",
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64,
|
||||
"maxTemperature": 2,
|
||||
"thinking": true,
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.5-flash",
|
||||
"object": "model",
|
||||
"version": "001",
|
||||
"name": "Gemini 2.5 Flash",
|
||||
"description": "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
|
||||
"context_length": 1_048_576,
|
||||
"max_completion_tokens": 65_536,
|
||||
"supported_parameters": []string{
|
||||
"tools",
|
||||
"temperature",
|
||||
"top_p",
|
||||
"top_k",
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64,
|
||||
"maxTemperature": 2,
|
||||
"thinking": true,
|
||||
},
|
||||
{
|
||||
"id": "gpt-5",
|
||||
"object": "model",
|
||||
"version": "gpt-5-2025-08-07",
|
||||
"name": "GPT 5",
|
||||
"description": "Stable version of GPT 5, The best model for coding and agentic tasks across domains.",
|
||||
"context_length": 400_000,
|
||||
"max_completion_tokens": 128_000,
|
||||
"supported_parameters": []string{
|
||||
"tools",
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64,
|
||||
"maxTemperature": 2,
|
||||
"thinking": true,
|
||||
},
|
||||
{
|
||||
"id": "claude-opus-4-1-20250805",
|
||||
"object": "model",
|
||||
"version": "claude-opus-4-1-20250805",
|
||||
"name": "Claude Opus 4.1",
|
||||
"description": "Anthropic's most capable model.",
|
||||
"context_length": 200_000,
|
||||
"max_completion_tokens": 32_000,
|
||||
"supported_parameters": []string{
|
||||
"tools",
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64,
|
||||
"maxTemperature": 2,
|
||||
"thinking": true,
|
||||
},
|
||||
}
|
||||
// Get dynamic models from the global registry
|
||||
modelRegistry := registry.GetGlobalRegistry()
|
||||
return modelRegistry.GetAvailableModels("openai")
|
||||
}
|
||||
|
||||
// OpenAIModels handles the /v1/models endpoint.
|
||||
// It returns a hardcoded list of available AI models with their capabilities
|
||||
// It returns a list of available AI models with their capabilities
|
||||
// and specifications in OpenAI-compatible format.
|
||||
func (h *OpenAIAPIHandler) OpenAIModels(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": h.Models(),
|
||||
"object": "list",
|
||||
"data": h.Models(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ func (s *Server) setupRoutes() {
|
||||
v1 := s.engine.Group("/v1")
|
||||
v1.Use(AuthMiddleware(s.cfg))
|
||||
{
|
||||
v1.GET("/models", openaiHandlers.OpenAIModels)
|
||||
v1.GET("/models", s.unifiedModelsHandler(openaiHandlers, claudeCodeHandlers))
|
||||
v1.POST("/chat/completions", openaiHandlers.ChatCompletions)
|
||||
v1.POST("/messages", claudeCodeHandlers.ClaudeMessages)
|
||||
}
|
||||
@@ -130,6 +130,25 @@ func (s *Server) setupRoutes() {
|
||||
s.engine.POST("/v1internal:method", geminiCLIHandlers.CLIHandler)
|
||||
}
|
||||
|
||||
// unifiedModelsHandler creates a unified handler for the /v1/models endpoint
|
||||
// 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 {
|
||||
return func(c *gin.Context) {
|
||||
userAgent := c.GetHeader("User-Agent")
|
||||
|
||||
// Route to Claude handler if User-Agent starts with "claude-cli"
|
||||
if strings.HasPrefix(userAgent, "claude-cli") {
|
||||
log.Debugf("Routing /v1/models to Claude handler for User-Agent: %s", userAgent)
|
||||
claudeHandler.ClaudeModels(c)
|
||||
} else {
|
||||
log.Debugf("Routing /v1/models to OpenAI handler for User-Agent: %s", userAgent)
|
||||
openaiHandler.OpenAIModels(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start begins listening for and serving HTTP requests.
|
||||
// It's a blocking call and will only return on an unrecoverable error.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user