Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
288
backend/internal/util/provider.go
Normal file
288
backend/internal/util/provider.go
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// Package util provides utility functions used across the CLIProxyAPI application.
|
||||
// These functions handle common tasks such as determining AI service providers
|
||||
// from model names and managing HTTP proxies.
|
||||
package util
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const openAICompatibleProviderPrefix = "openai-compatible-"
|
||||
|
||||
// OpenAICompatibleProviderKey returns the internal provider key for an OpenAI-compatible provider.
|
||||
func OpenAICompatibleProviderKey(name string) string {
|
||||
name = strings.ToLower(strings.TrimSpace(name))
|
||||
if name == "" || name == "openai-compatibility" || strings.HasPrefix(name, openAICompatibleProviderPrefix) {
|
||||
if name == "" {
|
||||
return "openai-compatibility"
|
||||
}
|
||||
return name
|
||||
}
|
||||
return openAICompatibleProviderPrefix + name
|
||||
}
|
||||
|
||||
// GetProviderName determines all AI service providers capable of serving a registered model.
|
||||
// It first queries the global model registry to retrieve the providers backing the supplied model name.
|
||||
// When the model has not been registered yet, it falls back to legacy string heuristics to infer
|
||||
// potential providers.
|
||||
//
|
||||
// Supported providers include (but are not limited to):
|
||||
// - "gemini" for Google's Gemini family
|
||||
// - "codex" for OpenAI GPT-compatible providers
|
||||
// - "claude" for Anthropic models
|
||||
// - "openai-compatibility" for external OpenAI-compatible providers
|
||||
//
|
||||
// Parameters:
|
||||
// - modelName: The name of the model to identify providers for.
|
||||
// - cfg: The application configuration containing OpenAI compatibility settings.
|
||||
//
|
||||
// Returns:
|
||||
// - []string: All provider identifiers capable of serving the model, ordered by preference.
|
||||
func GetProviderName(modelName string) []string {
|
||||
if modelName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
providers := make([]string, 0, 4)
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
appendProvider := func(name string) {
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
if _, exists := seen[name]; exists {
|
||||
return
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
providers = append(providers, name)
|
||||
}
|
||||
|
||||
for _, provider := range registry.GetGlobalRegistry().GetModelProviders(modelName) {
|
||||
appendProvider(provider)
|
||||
}
|
||||
|
||||
if len(providers) > 0 {
|
||||
return providers
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
// ResolveAutoModel resolves the "auto" model name to an actual available model.
|
||||
// It uses an empty handler type to get any available model from the registry.
|
||||
//
|
||||
// Parameters:
|
||||
// - modelName: The model name to check (should be "auto")
|
||||
//
|
||||
// Returns:
|
||||
// - string: The resolved model name, or the original if not "auto" or resolution fails
|
||||
func ResolveAutoModel(modelName string) string {
|
||||
if modelName != "auto" {
|
||||
return modelName
|
||||
}
|
||||
|
||||
// Use empty string as handler type to get any available model
|
||||
firstModel, err := registry.GetGlobalRegistry().GetFirstAvailableModel("")
|
||||
if err != nil {
|
||||
log.Warnf("Failed to resolve 'auto' model: %v, falling back to original model name", err)
|
||||
return modelName
|
||||
}
|
||||
|
||||
log.Infof("Resolved 'auto' model to: %s", firstModel)
|
||||
return firstModel
|
||||
}
|
||||
|
||||
// IsOpenAICompatibilityAlias checks if the given model name is an alias
|
||||
// configured for OpenAI compatibility routing.
|
||||
//
|
||||
// Parameters:
|
||||
// - modelName: The model name to check
|
||||
// - cfg: The application configuration containing OpenAI compatibility settings
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if the model name is an OpenAI compatibility alias, false otherwise
|
||||
func IsOpenAICompatibilityAlias(modelName string, cfg *config.Config) bool {
|
||||
if cfg == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, compat := range cfg.OpenAICompatibility {
|
||||
if compat.Disabled {
|
||||
continue
|
||||
}
|
||||
for _, model := range compat.Models {
|
||||
if model.Alias == modelName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetOpenAICompatibilityConfig returns the OpenAI compatibility configuration
|
||||
// and model details for the given alias.
|
||||
//
|
||||
// Parameters:
|
||||
// - alias: The model alias to find configuration for
|
||||
// - cfg: The application configuration containing OpenAI compatibility settings
|
||||
//
|
||||
// Returns:
|
||||
// - *config.OpenAICompatibility: The matching compatibility configuration, or nil if not found
|
||||
// - *config.OpenAICompatibilityModel: The matching model configuration, or nil if not found
|
||||
func GetOpenAICompatibilityConfig(alias string, cfg *config.Config) (*config.OpenAICompatibility, *config.OpenAICompatibilityModel) {
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, compat := range cfg.OpenAICompatibility {
|
||||
if compat.Disabled {
|
||||
continue
|
||||
}
|
||||
for _, model := range compat.Models {
|
||||
if model.Alias == alias {
|
||||
return &compat, &model
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// InArray checks if a string exists in a slice of strings.
|
||||
// It iterates through the slice and returns true if the target string is found,
|
||||
// otherwise it returns false.
|
||||
//
|
||||
// Parameters:
|
||||
// - hystack: The slice of strings to search in
|
||||
// - needle: The string to search for
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if the string is found, false otherwise
|
||||
func InArray(hystack []string, needle string) bool {
|
||||
for _, item := range hystack {
|
||||
if needle == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HideAPIKey obscures an API key for logging purposes, showing only the first and last few characters.
|
||||
//
|
||||
// Parameters:
|
||||
// - apiKey: The API key to hide.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The obscured API key.
|
||||
func HideAPIKey(apiKey string) string {
|
||||
if len(apiKey) > 8 {
|
||||
return apiKey[:4] + "..." + apiKey[len(apiKey)-4:]
|
||||
} else if len(apiKey) > 4 {
|
||||
return apiKey[:2] + "..." + apiKey[len(apiKey)-2:]
|
||||
} else if len(apiKey) > 2 {
|
||||
return apiKey[:1] + "..." + apiKey[len(apiKey)-1:]
|
||||
}
|
||||
return apiKey
|
||||
}
|
||||
|
||||
// maskAuthorizationHeader masks the Authorization header value while preserving the auth type prefix.
|
||||
// Common formats: "Bearer <token>", "Basic <credentials>", "ApiKey <key>", etc.
|
||||
// It preserves the prefix (e.g., "Bearer ") and only masks the token/credential part.
|
||||
//
|
||||
// Parameters:
|
||||
// - value: The Authorization header value
|
||||
//
|
||||
// Returns:
|
||||
// - string: The masked Authorization value with prefix preserved
|
||||
func MaskAuthorizationHeader(value string) string {
|
||||
parts := strings.SplitN(strings.TrimSpace(value), " ", 2)
|
||||
if len(parts) < 2 {
|
||||
return HideAPIKey(value)
|
||||
}
|
||||
return parts[0] + " " + HideAPIKey(parts[1])
|
||||
}
|
||||
|
||||
// MaskSensitiveHeaderValue masks sensitive header values while preserving expected formats.
|
||||
//
|
||||
// Behavior by header key (case-insensitive):
|
||||
// - "Authorization": Preserve the auth type prefix (e.g., "Bearer ") and mask only the credential part.
|
||||
// - Headers containing "api-key": Mask the entire value using HideAPIKey.
|
||||
// - Others: Return the original value unchanged.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The HTTP header name to inspect (case-insensitive matching).
|
||||
// - value: The header value to mask when sensitive.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The masked value according to the header type; unchanged if not sensitive.
|
||||
func MaskSensitiveHeaderValue(key, value string) string {
|
||||
lowerKey := strings.ToLower(strings.TrimSpace(key))
|
||||
switch {
|
||||
case strings.Contains(lowerKey, "authorization"):
|
||||
return MaskAuthorizationHeader(value)
|
||||
case strings.Contains(lowerKey, "api-key"),
|
||||
strings.Contains(lowerKey, "apikey"),
|
||||
strings.Contains(lowerKey, "token"),
|
||||
strings.Contains(lowerKey, "secret"):
|
||||
return HideAPIKey(value)
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// MaskSensitiveQuery masks sensitive query parameters, e.g. auth_token, within the raw query string.
|
||||
func MaskSensitiveQuery(raw string) string {
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(raw, "&")
|
||||
changed := false
|
||||
for i, part := range parts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
keyPart := part
|
||||
valuePart := ""
|
||||
if idx := strings.Index(part, "="); idx >= 0 {
|
||||
keyPart = part[:idx]
|
||||
valuePart = part[idx+1:]
|
||||
}
|
||||
decodedKey, err := url.QueryUnescape(keyPart)
|
||||
if err != nil {
|
||||
decodedKey = keyPart
|
||||
}
|
||||
if !shouldMaskQueryParam(decodedKey) {
|
||||
continue
|
||||
}
|
||||
decodedValue, err := url.QueryUnescape(valuePart)
|
||||
if err != nil {
|
||||
decodedValue = valuePart
|
||||
}
|
||||
masked := HideAPIKey(strings.TrimSpace(decodedValue))
|
||||
parts[i] = keyPart + "=" + url.QueryEscape(masked)
|
||||
changed = true
|
||||
}
|
||||
if !changed {
|
||||
return raw
|
||||
}
|
||||
return strings.Join(parts, "&")
|
||||
}
|
||||
|
||||
func shouldMaskQueryParam(key string) bool {
|
||||
key = strings.ToLower(strings.TrimSpace(key))
|
||||
if key == "" {
|
||||
return false
|
||||
}
|
||||
key = strings.TrimSuffix(key, "[]")
|
||||
if key == "key" || strings.Contains(key, "api-key") || strings.Contains(key, "apikey") || strings.Contains(key, "api_key") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(key, "token") || strings.Contains(key, "secret") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Loading…
Reference in a new issue