mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 04:10:51 +08:00
feat(config): add per-key model blacklist for providers
This commit is contained in:
@@ -62,6 +62,8 @@ ws-auth: false
|
|||||||
# headers:
|
# headers:
|
||||||
# X-Custom-Header: "custom-value"
|
# X-Custom-Header: "custom-value"
|
||||||
# proxy-url: "socks5://proxy.example.com:1080"
|
# proxy-url: "socks5://proxy.example.com:1080"
|
||||||
|
# model-blacklist:
|
||||||
|
# - "gemini-2.0-pro-exp" # exclude specific models from this provider
|
||||||
# - api-key: "AIzaSy...02"
|
# - api-key: "AIzaSy...02"
|
||||||
|
|
||||||
# API keys for official Generative Language API (legacy compatibility)
|
# API keys for official Generative Language API (legacy compatibility)
|
||||||
@@ -76,6 +78,8 @@ ws-auth: false
|
|||||||
# headers:
|
# headers:
|
||||||
# X-Custom-Header: "custom-value"
|
# X-Custom-Header: "custom-value"
|
||||||
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
# model-blacklist:
|
||||||
|
# - "gpt-5" # exclude specific models from this provider
|
||||||
|
|
||||||
# Claude API keys
|
# Claude API keys
|
||||||
#claude-api-key:
|
#claude-api-key:
|
||||||
@@ -88,6 +92,8 @@ ws-auth: false
|
|||||||
# models:
|
# models:
|
||||||
# - name: "claude-3-5-sonnet-20241022" # upstream model name
|
# - name: "claude-3-5-sonnet-20241022" # upstream model name
|
||||||
# alias: "claude-sonnet-latest" # client alias mapped to the upstream model
|
# alias: "claude-sonnet-latest" # client alias mapped to the upstream model
|
||||||
|
# model-blacklist:
|
||||||
|
# - "claude-3-5-sonnet-20241022" # exclude specific models from this provider
|
||||||
|
|
||||||
# OpenAI compatibility providers
|
# OpenAI compatibility providers
|
||||||
#openai-compatibility:
|
#openai-compatibility:
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ type ClaudeKey struct {
|
|||||||
|
|
||||||
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
||||||
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
||||||
|
|
||||||
|
// ModelBlacklist lists model IDs that should be excluded for this provider.
|
||||||
|
ModelBlacklist []string `yaml:"model-blacklist,omitempty" json:"model-blacklist,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClaudeModel describes a mapping between an alias and the actual upstream model name.
|
// ClaudeModel describes a mapping between an alias and the actual upstream model name.
|
||||||
@@ -183,6 +186,9 @@ type CodexKey struct {
|
|||||||
|
|
||||||
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
||||||
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
||||||
|
|
||||||
|
// ModelBlacklist lists model IDs that should be excluded for this provider.
|
||||||
|
ModelBlacklist []string `yaml:"model-blacklist,omitempty" json:"model-blacklist,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeminiKey represents the configuration for a Gemini API key,
|
// GeminiKey represents the configuration for a Gemini API key,
|
||||||
@@ -199,6 +205,9 @@ type GeminiKey struct {
|
|||||||
|
|
||||||
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
||||||
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
||||||
|
|
||||||
|
// ModelBlacklist lists model IDs that should be excluded for this provider.
|
||||||
|
ModelBlacklist []string `yaml:"model-blacklist,omitempty" json:"model-blacklist,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAICompatibility represents the configuration for OpenAI API compatibility
|
// OpenAICompatibility represents the configuration for OpenAI API compatibility
|
||||||
|
|||||||
@@ -450,6 +450,28 @@ func computeClaudeModelsHash(models []config.ClaudeModel) string {
|
|||||||
return hex.EncodeToString(sum[:])
|
return hex.EncodeToString(sum[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func computeModelBlacklistHash(blacklist []string) string {
|
||||||
|
if len(blacklist) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
normalized := make([]string, 0, len(blacklist))
|
||||||
|
for _, entry := range blacklist {
|
||||||
|
if trimmed := strings.TrimSpace(entry); trimmed != "" {
|
||||||
|
normalized = append(normalized, strings.ToLower(trimmed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(normalized) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
sort.Strings(normalized)
|
||||||
|
data, err := json.Marshal(normalized)
|
||||||
|
if err != nil || len(data) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
sum := sha256.Sum256(data)
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
|
||||||
// SetClients sets the file-based clients.
|
// SetClients sets the file-based clients.
|
||||||
// SetClients removed
|
// SetClients removed
|
||||||
// SetAPIKeyClients removed
|
// SetAPIKeyClients removed
|
||||||
@@ -838,6 +860,9 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if base != "" {
|
if base != "" {
|
||||||
attrs["base_url"] = base
|
attrs["base_url"] = base
|
||||||
}
|
}
|
||||||
|
if hash := computeModelBlacklistHash(entry.ModelBlacklist); hash != "" {
|
||||||
|
attrs["model_blacklist_hash"] = hash
|
||||||
|
}
|
||||||
addConfigHeadersToAttrs(entry.Headers, attrs)
|
addConfigHeadersToAttrs(entry.Headers, attrs)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
@@ -870,6 +895,9 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if hash := computeClaudeModelsHash(ck.Models); hash != "" {
|
if hash := computeClaudeModelsHash(ck.Models); hash != "" {
|
||||||
attrs["models_hash"] = hash
|
attrs["models_hash"] = hash
|
||||||
}
|
}
|
||||||
|
if hash := computeModelBlacklistHash(ck.ModelBlacklist); hash != "" {
|
||||||
|
attrs["model_blacklist_hash"] = hash
|
||||||
|
}
|
||||||
addConfigHeadersToAttrs(ck.Headers, attrs)
|
addConfigHeadersToAttrs(ck.Headers, attrs)
|
||||||
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
@@ -899,6 +927,9 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if ck.BaseURL != "" {
|
if ck.BaseURL != "" {
|
||||||
attrs["base_url"] = ck.BaseURL
|
attrs["base_url"] = ck.BaseURL
|
||||||
}
|
}
|
||||||
|
if hash := computeModelBlacklistHash(ck.ModelBlacklist); hash != "" {
|
||||||
|
attrs["model_blacklist_hash"] = hash
|
||||||
|
}
|
||||||
addConfigHeadersToAttrs(ck.Headers, attrs)
|
addConfigHeadersToAttrs(ck.Headers, attrs)
|
||||||
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
|
|||||||
@@ -640,6 +640,9 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
switch provider {
|
switch provider {
|
||||||
case "gemini":
|
case "gemini":
|
||||||
models = registry.GetGeminiModels()
|
models = registry.GetGeminiModels()
|
||||||
|
if entry := s.resolveConfigGeminiKey(a); entry != nil {
|
||||||
|
models = applyModelBlacklist(models, entry.ModelBlacklist)
|
||||||
|
}
|
||||||
case "vertex":
|
case "vertex":
|
||||||
// Vertex AI Gemini supports the same model identifiers as Gemini.
|
// Vertex AI Gemini supports the same model identifiers as Gemini.
|
||||||
models = registry.GetGeminiVertexModels()
|
models = registry.GetGeminiVertexModels()
|
||||||
@@ -653,11 +656,17 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
cancel()
|
cancel()
|
||||||
case "claude":
|
case "claude":
|
||||||
models = registry.GetClaudeModels()
|
models = registry.GetClaudeModels()
|
||||||
if entry := s.resolveConfigClaudeKey(a); entry != nil && len(entry.Models) > 0 {
|
if entry := s.resolveConfigClaudeKey(a); entry != nil {
|
||||||
models = buildClaudeConfigModels(entry)
|
if len(entry.Models) > 0 {
|
||||||
|
models = buildClaudeConfigModels(entry)
|
||||||
|
}
|
||||||
|
models = applyModelBlacklist(models, entry.ModelBlacklist)
|
||||||
}
|
}
|
||||||
case "codex":
|
case "codex":
|
||||||
models = registry.GetOpenAIModels()
|
models = registry.GetOpenAIModels()
|
||||||
|
if entry := s.resolveConfigCodexKey(a); entry != nil {
|
||||||
|
models = applyModelBlacklist(models, entry.ModelBlacklist)
|
||||||
|
}
|
||||||
case "qwen":
|
case "qwen":
|
||||||
models = registry.GetQwenModels()
|
models = registry.GetQwenModels()
|
||||||
case "iflow":
|
case "iflow":
|
||||||
@@ -749,7 +758,10 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
key = strings.ToLower(strings.TrimSpace(a.Provider))
|
key = strings.ToLower(strings.TrimSpace(a.Provider))
|
||||||
}
|
}
|
||||||
GlobalModelRegistry().RegisterClient(a.ID, key, models)
|
GlobalModelRegistry().RegisterClient(a.ID, key, models)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GlobalModelRegistry().UnregisterClient(a.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) resolveConfigClaudeKey(auth *coreauth.Auth) *config.ClaudeKey {
|
func (s *Service) resolveConfigClaudeKey(auth *coreauth.Auth) *config.ClaudeKey {
|
||||||
@@ -791,6 +803,84 @@ func (s *Service) resolveConfigClaudeKey(auth *coreauth.Auth) *config.ClaudeKey
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) resolveConfigGeminiKey(auth *coreauth.Auth) *config.GeminiKey {
|
||||||
|
if auth == nil || s.cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var attrKey, attrBase string
|
||||||
|
if auth.Attributes != nil {
|
||||||
|
attrKey = strings.TrimSpace(auth.Attributes["api_key"])
|
||||||
|
attrBase = strings.TrimSpace(auth.Attributes["base_url"])
|
||||||
|
}
|
||||||
|
for i := range s.cfg.GeminiKey {
|
||||||
|
entry := &s.cfg.GeminiKey[i]
|
||||||
|
cfgKey := strings.TrimSpace(entry.APIKey)
|
||||||
|
cfgBase := strings.TrimSpace(entry.BaseURL)
|
||||||
|
if attrKey != "" && strings.EqualFold(cfgKey, attrKey) {
|
||||||
|
if cfgBase == "" || strings.EqualFold(cfgBase, attrBase) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if attrKey == "" && attrBase != "" && strings.EqualFold(cfgBase, attrBase) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) resolveConfigCodexKey(auth *coreauth.Auth) *config.CodexKey {
|
||||||
|
if auth == nil || s.cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var attrKey, attrBase string
|
||||||
|
if auth.Attributes != nil {
|
||||||
|
attrKey = strings.TrimSpace(auth.Attributes["api_key"])
|
||||||
|
attrBase = strings.TrimSpace(auth.Attributes["base_url"])
|
||||||
|
}
|
||||||
|
for i := range s.cfg.CodexKey {
|
||||||
|
entry := &s.cfg.CodexKey[i]
|
||||||
|
cfgKey := strings.TrimSpace(entry.APIKey)
|
||||||
|
cfgBase := strings.TrimSpace(entry.BaseURL)
|
||||||
|
if attrKey != "" && strings.EqualFold(cfgKey, attrKey) {
|
||||||
|
if cfgBase == "" || strings.EqualFold(cfgBase, attrBase) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if attrKey == "" && attrBase != "" && strings.EqualFold(cfgBase, attrBase) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyModelBlacklist(models []*ModelInfo, blacklist []string) []*ModelInfo {
|
||||||
|
if len(models) == 0 || len(blacklist) == 0 {
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
blocked := make(map[string]struct{}, len(blacklist))
|
||||||
|
for _, item := range blacklist {
|
||||||
|
if trimmed := strings.TrimSpace(item); trimmed != "" {
|
||||||
|
blocked[strings.ToLower(trimmed)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(blocked) == 0 {
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
filtered := make([]*ModelInfo, 0, len(models))
|
||||||
|
for _, model := range models {
|
||||||
|
if model == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, blockedModel := blocked[strings.ToLower(strings.TrimSpace(model.ID))]; blockedModel {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered = append(filtered, model)
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
func buildClaudeConfigModels(entry *config.ClaudeKey) []*ModelInfo {
|
func buildClaudeConfigModels(entry *config.ClaudeKey) []*ModelInfo {
|
||||||
if entry == nil || len(entry.Models) == 0 {
|
if entry == nil || len(entry.Models) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user