Merge branch 'v7' into dev

This commit is contained in:
Luis Pater
2026-05-10 02:33:42 +08:00
324 changed files with 3634 additions and 1148 deletions
+16 -7
View File
@@ -13,7 +13,7 @@ import (
"strings"
"syscall"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"gopkg.in/yaml.v3"
@@ -37,6 +37,9 @@ type Config struct {
// TLS config controls HTTPS server settings.
TLS TLSConfig `yaml:"tls" json:"tls"`
// Home config enables the Redis-based control plane integration.
Home HomeConfig `yaml:"home" json:"-"`
// RemoteManagement nests management-related options under 'remote-management'.
RemoteManagement RemoteManagement `yaml:"remote-management" json:"-"`
@@ -224,12 +227,6 @@ type RoutingConfig struct {
// Supported values: "round-robin" (default), "fill-first".
Strategy string `yaml:"strategy,omitempty" json:"strategy,omitempty"`
// ClaudeCodeSessionAffinity enables session-sticky routing for Claude Code clients.
// When enabled, requests with the same session ID (extracted from metadata.user_id)
// are routed to the same auth credential when available.
// Deprecated: Use SessionAffinity instead for universal session support.
ClaudeCodeSessionAffinity bool `yaml:"claude-code-session-affinity,omitempty" json:"claude-code-session-affinity,omitempty"`
// SessionAffinity enables universal session-sticky routing for all clients.
// Session IDs are extracted from multiple sources:
// metadata.user_id (Claude Code session format), X-Session-ID, Session_id (Codex),
@@ -401,6 +398,9 @@ type ClaudeKey struct {
// ExcludedModels lists model IDs that should be excluded for this provider.
ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"`
// DisableCooling disables auth/model cooldown scheduling for this credential when true.
DisableCooling bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"`
// Cloak configures request cloaking for non-Claude-Code clients.
Cloak *CloakConfig `yaml:"cloak,omitempty" json:"cloak,omitempty"`
@@ -456,6 +456,9 @@ type CodexKey struct {
// ExcludedModels lists model IDs that should be excluded for this provider.
ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"`
// DisableCooling disables auth/model cooldown scheduling for this credential when true.
DisableCooling bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"`
}
func (k CodexKey) GetAPIKey() string { return k.APIKey }
@@ -500,6 +503,9 @@ type GeminiKey struct {
// ExcludedModels lists model IDs that should be excluded for this provider.
ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"`
// DisableCooling disables auth/model cooldown scheduling for this credential when true.
DisableCooling bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"`
}
func (k GeminiKey) GetAPIKey() string { return k.APIKey }
@@ -544,6 +550,9 @@ type OpenAICompatibility struct {
// Headers optionally adds extra HTTP headers for requests sent to this provider.
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
// DisableCooling disables auth/model cooldown scheduling for this provider when true.
DisableCooling bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"`
}
// OpenAICompatibilityAPIKey represents an API key configuration with optional proxy setting.
+9
View File
@@ -0,0 +1,9 @@
package config
// HomeConfig configures the optional "home" control plane integration over Redis protocol.
type HomeConfig struct {
Enabled bool `yaml:"enabled" json:"enabled"`
Host string `yaml:"host" json:"-"`
Port int `yaml:"port" json:"-"`
Password string `yaml:"password" json:"-"`
}
+89
View File
@@ -0,0 +1,89 @@
package config
import (
"fmt"
"strings"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"gopkg.in/yaml.v3"
)
// ParseConfigBytes parses a YAML configuration payload into Config and applies the same
// in-memory normalizations as LoadConfigOptional, without persisting any changes to disk.
func ParseConfigBytes(data []byte) (*Config, error) {
if len(data) == 0 {
return nil, fmt.Errorf("config payload is empty")
}
var cfg Config
// Keep defaults aligned with LoadConfigOptional.
cfg.Host = "" // Default empty: binds to all interfaces (IPv4 + IPv6)
cfg.LoggingToFile = false
cfg.LogsMaxTotalSizeMB = 0
cfg.ErrorLogsMaxFiles = 10
cfg.UsageStatisticsEnabled = false
cfg.RedisUsageQueueRetentionSeconds = 60
cfg.DisableCooling = false
cfg.DisableImageGeneration = DisableImageGenerationOff
cfg.Pprof.Enable = false
cfg.Pprof.Addr = DefaultPprofAddr
cfg.AmpCode.RestrictManagementToLocalhost = false // Default to false: API key auth is sufficient
cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config payload: %w", err)
}
// Hash remote management key if plaintext is detected (nested), but do NOT persist.
if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) {
hashed, errHash := bcrypt.GenerateFromPassword([]byte(cfg.RemoteManagement.SecretKey), bcrypt.DefaultCost)
if errHash != nil {
return nil, fmt.Errorf("hash remote management key: %w", errHash)
}
cfg.RemoteManagement.SecretKey = string(hashed)
}
cfg.RemoteManagement.PanelGitHubRepository = strings.TrimSpace(cfg.RemoteManagement.PanelGitHubRepository)
if cfg.RemoteManagement.PanelGitHubRepository == "" {
cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository
}
cfg.Pprof.Addr = strings.TrimSpace(cfg.Pprof.Addr)
if cfg.Pprof.Addr == "" {
cfg.Pprof.Addr = DefaultPprofAddr
}
if cfg.LogsMaxTotalSizeMB < 0 {
cfg.LogsMaxTotalSizeMB = 0
}
if cfg.ErrorLogsMaxFiles < 0 {
cfg.ErrorLogsMaxFiles = 10
}
if cfg.RedisUsageQueueRetentionSeconds <= 0 {
cfg.RedisUsageQueueRetentionSeconds = 60
} else if cfg.RedisUsageQueueRetentionSeconds > 3600 {
log.WithField("value", cfg.RedisUsageQueueRetentionSeconds).Warn("redis-usage-queue-retention-seconds too large; clamping to 3600")
cfg.RedisUsageQueueRetentionSeconds = 3600
}
if cfg.MaxRetryCredentials < 0 {
cfg.MaxRetryCredentials = 0
}
// Apply the same sanitization pipeline.
cfg.SanitizeGeminiKeys()
cfg.SanitizeVertexCompatKeys()
cfg.SanitizeCodexKeys()
cfg.SanitizeCodexHeaderDefaults()
cfg.SanitizeClaudeHeaderDefaults()
cfg.SanitizeClaudeKeys()
cfg.SanitizeOpenAICompatibility()
cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels)
cfg.SanitizeOAuthModelAlias()
cfg.SanitizePayloadRules()
return &cfg, nil
}