mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
111 lines
3.1 KiB
Go
111 lines
3.1 KiB
Go
package synthesizer
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff"
|
|
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
|
)
|
|
|
|
// StableIDGenerator generates stable, deterministic IDs for auth entries.
|
|
// It uses SHA256 hashing with collision handling via counters.
|
|
// It is not safe for concurrent use.
|
|
type StableIDGenerator struct {
|
|
counters map[string]int
|
|
}
|
|
|
|
// NewStableIDGenerator creates a new StableIDGenerator instance.
|
|
func NewStableIDGenerator() *StableIDGenerator {
|
|
return &StableIDGenerator{counters: make(map[string]int)}
|
|
}
|
|
|
|
// Next generates a stable ID based on the kind and parts.
|
|
// Returns the full ID (kind:hash) and the short hash portion.
|
|
func (g *StableIDGenerator) Next(kind string, parts ...string) (string, string) {
|
|
if g == nil {
|
|
return kind + ":000000000000", "000000000000"
|
|
}
|
|
hasher := sha256.New()
|
|
hasher.Write([]byte(kind))
|
|
for _, part := range parts {
|
|
trimmed := strings.TrimSpace(part)
|
|
hasher.Write([]byte{0})
|
|
hasher.Write([]byte(trimmed))
|
|
}
|
|
digest := hex.EncodeToString(hasher.Sum(nil))
|
|
if len(digest) < 12 {
|
|
digest = fmt.Sprintf("%012s", digest)
|
|
}
|
|
short := digest[:12]
|
|
key := kind + ":" + short
|
|
index := g.counters[key]
|
|
g.counters[key] = index + 1
|
|
if index > 0 {
|
|
short = fmt.Sprintf("%s-%d", short, index)
|
|
}
|
|
return fmt.Sprintf("%s:%s", kind, short), short
|
|
}
|
|
|
|
// ApplyAuthExcludedModelsMeta applies excluded models metadata to an auth entry.
|
|
// It computes a hash of excluded models and sets the auth_kind attribute.
|
|
func ApplyAuthExcludedModelsMeta(auth *coreauth.Auth, cfg *config.Config, perKey []string, authKind string) {
|
|
if auth == nil || cfg == nil {
|
|
return
|
|
}
|
|
authKindKey := strings.ToLower(strings.TrimSpace(authKind))
|
|
seen := make(map[string]struct{})
|
|
add := func(list []string) {
|
|
for _, entry := range list {
|
|
if trimmed := strings.TrimSpace(entry); trimmed != "" {
|
|
key := strings.ToLower(trimmed)
|
|
if _, exists := seen[key]; exists {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
if authKindKey == "apikey" {
|
|
add(perKey)
|
|
} else if cfg.OAuthExcludedModels != nil {
|
|
providerKey := strings.ToLower(strings.TrimSpace(auth.Provider))
|
|
add(cfg.OAuthExcludedModels[providerKey])
|
|
}
|
|
combined := make([]string, 0, len(seen))
|
|
for k := range seen {
|
|
combined = append(combined, k)
|
|
}
|
|
sort.Strings(combined)
|
|
hash := diff.ComputeExcludedModelsHash(combined)
|
|
if auth.Attributes == nil {
|
|
auth.Attributes = make(map[string]string)
|
|
}
|
|
if hash != "" {
|
|
auth.Attributes["excluded_models_hash"] = hash
|
|
}
|
|
if authKind != "" {
|
|
auth.Attributes["auth_kind"] = authKind
|
|
}
|
|
}
|
|
|
|
// addConfigHeadersToAttrs adds header configuration to auth attributes.
|
|
// Headers are prefixed with "header:" in the attributes map.
|
|
func addConfigHeadersToAttrs(headers map[string]string, attrs map[string]string) {
|
|
if len(headers) == 0 || attrs == nil {
|
|
return
|
|
}
|
|
for hk, hv := range headers {
|
|
key := strings.TrimSpace(hk)
|
|
val := strings.TrimSpace(hv)
|
|
if key == "" || val == "" {
|
|
continue
|
|
}
|
|
attrs["header:"+key] = val
|
|
}
|
|
}
|