Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
262
backend/sdk/cliproxy/auth/api_key_model_capabilities.go
Normal file
262
backend/sdk/cliproxy/auth/api_key_model_capabilities.go
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/modelconfig"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
const resolvedAPIKeyModelInfoMetadataKey = "cliproxy.resolved_api_key_model_info"
|
||||
|
||||
type apiKeyModelCapabilityRoute struct {
|
||||
upstreamModel string
|
||||
modelInfo *registry.ModelInfo
|
||||
}
|
||||
|
||||
type apiKeyModelCapabilityTable map[string]map[string][]apiKeyModelCapabilityRoute
|
||||
|
||||
type apiKeyModelRoutingSnapshot struct {
|
||||
config *internalconfig.Config
|
||||
aliases apiKeyModelAliasTable
|
||||
capabilities apiKeyModelCapabilityTable
|
||||
}
|
||||
|
||||
func isConfiguredModelRoutingAuth(auth *Auth) bool {
|
||||
if auth != nil && auth.AuthKind() == AuthKindAPIKey {
|
||||
return true
|
||||
}
|
||||
if auth == nil || auth.AuthSourceKind() != AuthSourceConfig || auth.Attributes == nil {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(auth.Attributes["compat_name"]) != ""
|
||||
}
|
||||
|
||||
func (m *Manager) loadAPIKeyModelRouting() *apiKeyModelRoutingSnapshot {
|
||||
if m == nil {
|
||||
return &apiKeyModelRoutingSnapshot{config: &internalconfig.Config{}}
|
||||
}
|
||||
snapshot, _ := m.apiKeyModelRouting.Load().(*apiKeyModelRoutingSnapshot)
|
||||
if snapshot == nil {
|
||||
return &apiKeyModelRoutingSnapshot{config: &internalconfig.Config{}}
|
||||
}
|
||||
return snapshot
|
||||
}
|
||||
|
||||
// ResolvedAPIKeyModelInfo returns the exact configured model definition bound to
|
||||
// this API-key execution attempt.
|
||||
func ResolvedAPIKeyModelInfo(req cliproxyexecutor.Request) (*registry.ModelInfo, bool) {
|
||||
modelInfo, ok := req.Metadata[resolvedAPIKeyModelInfoMetadataKey].(*registry.ModelInfo)
|
||||
if !ok || modelInfo == nil {
|
||||
return nil, false
|
||||
}
|
||||
return modelInfo, true
|
||||
}
|
||||
|
||||
// CodexAPIKeyModelIsCompat reports whether the selected codex-api-key model has
|
||||
// is-compat enabled. When true and codex.optimize-multi-agent-v2 is also true,
|
||||
// Codex MultiAgentV2 agent_message items are converted into portable Responses
|
||||
// message/user input for third-party Responses-compatible endpoints.
|
||||
func CodexAPIKeyModelIsCompat(cfg *internalconfig.Config, auth *Auth, model string) bool {
|
||||
if cfg == nil || auth == nil || !strings.EqualFold(strings.TrimSpace(auth.Provider), "codex") {
|
||||
return false
|
||||
}
|
||||
entry := resolveCodexAPIKeyConfig(cfg, auth)
|
||||
if entry == nil || len(entry.Models) == 0 {
|
||||
return false
|
||||
}
|
||||
requested := strings.TrimSpace(model)
|
||||
if requested == "" {
|
||||
return false
|
||||
}
|
||||
baseModel := strings.TrimSpace(thinking.ParseSuffix(requested).ModelName)
|
||||
if baseModel == "" {
|
||||
baseModel = requested
|
||||
}
|
||||
for i := range entry.Models {
|
||||
name := strings.TrimSpace(entry.Models[i].Name)
|
||||
alias := strings.TrimSpace(entry.Models[i].Alias)
|
||||
if name == "" {
|
||||
name = alias
|
||||
}
|
||||
if alias == "" {
|
||||
alias = name
|
||||
}
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(name, requested) || strings.EqualFold(name, baseModel) ||
|
||||
strings.EqualFold(alias, requested) || strings.EqualFold(alias, baseModel) {
|
||||
return entry.Models[i].IsCompat
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Manager) attachResolvedAPIKeyModelInfo(req cliproxyexecutor.Request, auth *Auth, routeModel, upstreamModel string) cliproxyexecutor.Request {
|
||||
return attachResolvedAPIKeyModelInfo(m.loadAPIKeyModelRouting(), req, auth, routeModel, upstreamModel)
|
||||
}
|
||||
|
||||
func attachResolvedAPIKeyModelInfo(routing *apiKeyModelRoutingSnapshot, req cliproxyexecutor.Request, auth *Auth, routeModel, upstreamModel string) cliproxyexecutor.Request {
|
||||
modelInfo, ok := lookupAPIKeyModelCapability(routing, auth, routeModel, upstreamModel)
|
||||
if !ok {
|
||||
return req
|
||||
}
|
||||
metadata := make(map[string]any, len(req.Metadata)+1)
|
||||
maps.Copy(metadata, req.Metadata)
|
||||
metadata[resolvedAPIKeyModelInfoMetadataKey] = modelInfo
|
||||
req.Metadata = metadata
|
||||
return req
|
||||
}
|
||||
|
||||
func lookupAPIKeyModelCapability(routing *apiKeyModelRoutingSnapshot, auth *Auth, routeModel, upstreamModel string) (*registry.ModelInfo, bool) {
|
||||
if !isConfiguredModelRoutingAuth(auth) || routing == nil {
|
||||
return nil, false
|
||||
}
|
||||
byRoute := routing.capabilities[strings.TrimSpace(auth.ID)]
|
||||
if len(byRoute) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
requestedModel := rewriteModelForAuth(strings.TrimSpace(routeModel), auth)
|
||||
_, candidates := modelAliasLookupCandidates(requestedModel)
|
||||
routes := make([]apiKeyModelCapabilityRoute, 0)
|
||||
for _, candidate := range candidates {
|
||||
routes = append(routes, byRoute[strings.ToLower(strings.TrimSpace(candidate))]...)
|
||||
}
|
||||
selected := strings.TrimSpace(upstreamModel)
|
||||
for _, route := range routes {
|
||||
if strings.EqualFold(strings.TrimSpace(route.upstreamModel), selected) {
|
||||
return route.modelInfo, route.modelInfo != nil
|
||||
}
|
||||
}
|
||||
for _, route := range routes {
|
||||
if configuredUpstreamFallbackMatches(route.upstreamModel, selected) {
|
||||
return route.modelInfo, route.modelInfo != nil
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func configuredUpstreamFallbackMatches(configured, selected string) bool {
|
||||
configuredResult := thinking.ParseSuffix(strings.TrimSpace(configured))
|
||||
if configuredResult.HasSuffix {
|
||||
return false
|
||||
}
|
||||
selectedResult := thinking.ParseSuffix(strings.TrimSpace(selected))
|
||||
return strings.EqualFold(strings.TrimSpace(configuredResult.ModelName), strings.TrimSpace(selectedResult.ModelName))
|
||||
}
|
||||
|
||||
func compileAPIKeyModelCapabilitiesForAuth(cfg *internalconfig.Config, auth *Auth) map[string][]apiKeyModelCapabilityRoute {
|
||||
if cfg == nil || !isConfiguredModelRoutingAuth(auth) {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string][]apiKeyModelCapabilityRoute)
|
||||
switch strings.ToLower(strings.TrimSpace(auth.Provider)) {
|
||||
case "gemini":
|
||||
if entry := resolveGeminiAPIKeyConfig(cfg, auth); entry != nil {
|
||||
compileConfiguredModelCapabilities(out, entry.Models, "gemini")
|
||||
}
|
||||
case "gemini-interactions":
|
||||
if entry := resolveInteractionsAPIKeyConfig(cfg, auth); entry != nil {
|
||||
compileConfiguredModelCapabilities(out, entry.Models, "interactions")
|
||||
}
|
||||
case "claude":
|
||||
if entry := resolveClaudeAPIKeyConfig(cfg, auth); entry != nil {
|
||||
compileConfiguredModelCapabilities(out, entry.Models, "claude")
|
||||
}
|
||||
case "codex":
|
||||
if entry := resolveCodexAPIKeyConfig(cfg, auth); entry != nil {
|
||||
compileConfiguredModelCapabilities(out, entry.Models, "codex")
|
||||
}
|
||||
case "xai":
|
||||
if entry := resolveXAIAPIKeyConfig(cfg, auth); entry != nil {
|
||||
compileConfiguredModelCapabilities(out, entry.Models, "xai")
|
||||
}
|
||||
case "vertex":
|
||||
if entry := resolveVertexAPIKeyConfig(cfg, auth); entry != nil {
|
||||
compileConfiguredModelCapabilities(out, entry.Models, "gemini")
|
||||
}
|
||||
default:
|
||||
providerKey, compatName := "", ""
|
||||
if auth.Attributes != nil {
|
||||
providerKey = strings.TrimSpace(auth.Attributes["provider_key"])
|
||||
compatName = strings.TrimSpace(auth.Attributes["compat_name"])
|
||||
}
|
||||
if entry := resolveOpenAICompatConfigForAuth(cfg, auth, providerKey, compatName); entry != nil {
|
||||
compileOpenAICompatibleModelCapabilities(out, entry.Models)
|
||||
}
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func compileConfiguredModelCapabilities[T interface {
|
||||
GetName() string
|
||||
GetAlias() string
|
||||
GetThinking() *registry.ThinkingSupport
|
||||
}](out map[string][]apiKeyModelCapabilityRoute, models []T, modelType string) {
|
||||
for i := range models {
|
||||
isCompat := false
|
||||
if compatModel, okCompat := any(models[i]).(interface{ GetIsCompat() bool }); okCompat {
|
||||
isCompat = compatModel.GetIsCompat()
|
||||
}
|
||||
addConfiguredModelCapability(out, models[i].GetName(), models[i].GetAlias(), modelType, models[i].GetThinking(), isCompat)
|
||||
}
|
||||
}
|
||||
|
||||
func compileOpenAICompatibleModelCapabilities(out map[string][]apiKeyModelCapabilityRoute, models []internalconfig.OpenAICompatibilityModel) {
|
||||
for i := range models {
|
||||
support := models[i].Thinking
|
||||
if support == nil && !models[i].Image {
|
||||
support = ®istry.ThinkingSupport{Levels: []string{"low", "medium", "high"}}
|
||||
}
|
||||
addConfiguredModelCapability(out, models[i].Name, models[i].Alias, "openai-compatibility", support, models[i].IsCompat)
|
||||
}
|
||||
}
|
||||
|
||||
func addConfiguredModelCapability(out map[string][]apiKeyModelCapabilityRoute, name, alias, modelType string, support *registry.ThinkingSupport, isCompat bool) {
|
||||
name = strings.TrimSpace(name)
|
||||
alias = strings.TrimSpace(alias)
|
||||
if name == "" {
|
||||
name = alias
|
||||
}
|
||||
if alias == "" {
|
||||
alias = name
|
||||
}
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
modelInfo := modelconfig.ResolveModelInfo(name, modelType, support)
|
||||
modelInfo.IsCompat = isCompat
|
||||
route := apiKeyModelCapabilityRoute{upstreamModel: name, modelInfo: modelInfo}
|
||||
seenKeys := make(map[string]struct{})
|
||||
for _, routeModel := range []string{alias, name} {
|
||||
_, candidates := modelAliasLookupCandidates(routeModel)
|
||||
for _, candidate := range candidates {
|
||||
key := strings.ToLower(strings.TrimSpace(candidate))
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := seenKeys[key]; exists {
|
||||
continue
|
||||
}
|
||||
seenKeys[key] = struct{}{}
|
||||
duplicate := false
|
||||
for _, existing := range out[key] {
|
||||
if strings.EqualFold(existing.upstreamModel, route.upstreamModel) {
|
||||
duplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !duplicate {
|
||||
out[key] = append(out[key], route)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue