vibe-proxy/backend/internal/config/weight.go
2026-08-24 00:10:41 +02:00

153 lines
5 KiB
Go

package config
import (
"fmt"
"github.com/router-for-me/CLIProxyAPI/v7/internal/credentialweight"
"gopkg.in/yaml.v3"
)
// MaxCredentialWeight is the largest positive credential routing weight.
const MaxCredentialWeight = int(credentialweight.Max)
// ValidateCredentialWeight validates one optional config credential weight.
func ValidateCredentialWeight(weight *int) error {
if weight == nil {
return nil
}
_, errNormalize := credentialweight.Normalize(int64(*weight))
return errNormalize
}
func validateCredentialWeightYAML(data []byte) error {
var document yaml.Node
if errUnmarshal := yaml.Unmarshal(data, &document); errUnmarshal != nil {
return nil
}
if len(document.Content) == 0 {
return nil
}
root := document.Content[0]
families := map[string]struct{}{
"gemini-api-key": {}, "interactions-api-key": {}, "claude-api-key": {},
"vertex-api-key": {}, "codex-api-key": {}, "xai-api-key": {},
}
for index := 0; root != nil && root.Kind == yaml.MappingNode && index+1 < len(root.Content); index += 2 {
name := root.Content[index].Value
value := root.Content[index+1]
if _, ok := families[name]; ok {
if errValidate := validateWeightSequenceNode(value, name); errValidate != nil {
return errValidate
}
continue
}
if name == "openai-compatibility" {
if errValidate := validateOpenAICompatibilityWeightNodes(value); errValidate != nil {
return errValidate
}
}
}
return nil
}
func validateWeightSequenceNode(sequence *yaml.Node, path string) error {
if sequence == nil || sequence.Kind != yaml.SequenceNode {
return nil
}
for index, item := range sequence.Content {
if errValidate := validateWeightMappingNode(item, fmt.Sprintf("%s[%d]", path, index)); errValidate != nil {
return errValidate
}
}
return nil
}
func validateWeightMappingNode(mapping *yaml.Node, path string) error {
if mapping == nil || mapping.Kind != yaml.MappingNode {
return nil
}
for index := 0; index+1 < len(mapping.Content); index += 2 {
if mapping.Content[index].Value != "weight" {
continue
}
value := mapping.Content[index+1]
if value.Kind != yaml.ScalarNode || value.Tag != "!!int" {
return fmt.Errorf("%s.weight: weight must be an integer", path)
}
var weight int64
if errDecode := value.Decode(&weight); errDecode != nil {
return fmt.Errorf("%s.weight: weight must be an integer", path)
}
if _, errNormalize := credentialweight.Normalize(weight); errNormalize != nil {
return fmt.Errorf("%s.weight: %w", path, errNormalize)
}
}
return nil
}
func validateOpenAICompatibilityWeightNodes(sequence *yaml.Node) error {
if sequence == nil || sequence.Kind != yaml.SequenceNode {
return nil
}
for providerIndex, provider := range sequence.Content {
if provider == nil || provider.Kind != yaml.MappingNode {
continue
}
for index := 0; index+1 < len(provider.Content); index += 2 {
if provider.Content[index].Value != "api-key-entries" {
continue
}
path := fmt.Sprintf("openai-compatibility[%d].api-key-entries", providerIndex)
if errValidate := validateWeightSequenceNode(provider.Content[index+1], path); errValidate != nil {
return errValidate
}
}
}
return nil
}
// ValidateCredentialWeights validates weights for every API-key family.
func (cfg *Config) ValidateCredentialWeights() error {
if cfg == nil {
return nil
}
for index := range cfg.GeminiKey {
if errValidate := ValidateCredentialWeight(cfg.GeminiKey[index].Weight); errValidate != nil {
return fmt.Errorf("gemini-api-key[%d].weight: %w", index, errValidate)
}
}
for index := range cfg.InteractionsKey {
if errValidate := ValidateCredentialWeight(cfg.InteractionsKey[index].Weight); errValidate != nil {
return fmt.Errorf("interactions-api-key[%d].weight: %w", index, errValidate)
}
}
for index := range cfg.ClaudeKey {
if errValidate := ValidateCredentialWeight(cfg.ClaudeKey[index].Weight); errValidate != nil {
return fmt.Errorf("claude-api-key[%d].weight: %w", index, errValidate)
}
}
for index := range cfg.VertexCompatAPIKey {
if errValidate := ValidateCredentialWeight(cfg.VertexCompatAPIKey[index].Weight); errValidate != nil {
return fmt.Errorf("vertex-api-key[%d].weight: %w", index, errValidate)
}
}
for index := range cfg.CodexKey {
if errValidate := ValidateCredentialWeight(cfg.CodexKey[index].Weight); errValidate != nil {
return fmt.Errorf("codex-api-key[%d].weight: %w", index, errValidate)
}
}
for index := range cfg.XAIKey {
if errValidate := ValidateCredentialWeight(cfg.XAIKey[index].Weight); errValidate != nil {
return fmt.Errorf("xai-api-key[%d].weight: %w", index, errValidate)
}
}
for providerIndex := range cfg.OpenAICompatibility {
for keyIndex := range cfg.OpenAICompatibility[providerIndex].APIKeyEntries {
weight := cfg.OpenAICompatibility[providerIndex].APIKeyEntries[keyIndex].Weight
if errValidate := ValidateCredentialWeight(weight); errValidate != nil {
return fmt.Errorf("openai-compatibility[%d].api-key-entries[%d].weight: %w", providerIndex, keyIndex, errValidate)
}
}
}
return nil
}