Add projects

This commit is contained in:
Alois 2026-08-24 00:10:41 +02:00
commit 8b607dd700
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
1802 changed files with 503346 additions and 2 deletions

View file

@ -0,0 +1,125 @@
package modelconfig
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
)
// ComputeOpenAICompatModelsHash returns a stable hash for OpenAI-compatible models.
func ComputeOpenAICompatModelsHash(models []config.OpenAICompatibilityModel) string {
keys := modelRoutingKeys(func(out func(key string)) {
for _, model := range models {
name := strings.TrimSpace(model.Name)
alias := strings.TrimSpace(model.Alias)
if name == "" && alias == "" {
continue
}
out(strings.ToLower(name) + "|" + strings.ToLower(alias) + "|" + strings.TrimSpace(model.DisplayName) + "|" + fmt.Sprintf("image=%t", model.Image) + "|" + fmt.Sprintf("force-mapping=%t", model.ForceMapping) + "|" + fmt.Sprintf("is-compat=%t", model.IsCompat) + "|input=" + strings.Join(normalizeModalities(model.InputModalities), ",") + "|output=" + strings.Join(normalizeModalities(model.OutputModalities), ",") + thinkingHashSuffix(model.Thinking))
}
})
return hashJoined(keys)
}
// ComputeVertexCompatModelsHash returns a stable hash for Vertex-compatible models.
func ComputeVertexCompatModelsHash(models []config.VertexCompatModel) string {
keys := modelRoutingKeys(func(out func(key string)) {
for _, model := range models {
name := strings.TrimSpace(model.Name)
alias := strings.TrimSpace(model.Alias)
if name == "" && alias == "" {
continue
}
out(strings.ToLower(name) + "|" + strings.ToLower(alias) + "|" + strings.TrimSpace(model.DisplayName) + "|" + fmt.Sprintf("force-mapping=%t", model.ForceMapping) + thinkingHashSuffix(model.Thinking))
}
})
return hashJoined(keys)
}
// ComputeClaudeModelsHash returns a stable hash for Claude model aliases.
func ComputeClaudeModelsHash(models []config.ClaudeModel) string {
keys := modelRoutingKeys(func(out func(key string)) {
for _, model := range models {
name := strings.TrimSpace(model.Name)
alias := strings.TrimSpace(model.Alias)
if name == "" && alias == "" {
continue
}
out(strings.ToLower(name) + "|" + strings.ToLower(alias) + "|" + strings.TrimSpace(model.DisplayName) + "|" + fmt.Sprintf("force-mapping=%t", model.ForceMapping) + "|" + fmt.Sprintf("is-compat=%t", model.IsCompat) + thinkingHashSuffix(model.Thinking))
}
})
return hashJoined(keys)
}
// ComputeCodexModelsHash returns a stable hash for Codex model aliases.
func ComputeCodexModelsHash(models []config.CodexModel) string {
keys := modelRoutingKeys(func(out func(key string)) {
for _, model := range models {
name := strings.TrimSpace(model.Name)
alias := strings.TrimSpace(model.Alias)
if name == "" && alias == "" {
continue
}
out(strings.ToLower(name) + "|" + strings.ToLower(alias) + "|" + strings.TrimSpace(model.DisplayName) + "|" + fmt.Sprintf("force-mapping=%t", model.ForceMapping) + "|" + fmt.Sprintf("is-compat=%t", model.IsCompat) + thinkingHashSuffix(model.Thinking))
}
})
return hashJoined(keys)
}
// ComputeGeminiModelsHash returns a stable hash for Gemini model aliases.
func ComputeGeminiModelsHash(models []config.GeminiModel) string {
keys := modelRoutingKeys(func(out func(key string)) {
for _, model := range models {
name := strings.TrimSpace(model.Name)
alias := strings.TrimSpace(model.Alias)
if name == "" && alias == "" {
continue
}
out(strings.ToLower(name) + "|" + strings.ToLower(alias) + "|" + strings.TrimSpace(model.DisplayName) + "|" + fmt.Sprintf("force-mapping=%t", model.ForceMapping) + "|" + fmt.Sprintf("is-compat=%t", model.IsCompat) + thinkingHashSuffix(model.Thinking))
}
})
return hashJoined(keys)
}
func normalizeModalities(raw []string) []string {
seen := make(map[string]struct{}, len(raw))
out := make([]string, 0, len(raw))
for _, value := range raw {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
continue
}
if _, exists := seen[value]; exists {
continue
}
seen[value] = struct{}{}
out = append(out, value)
}
return out
}
func thinkingHashSuffix(support *registry.ThinkingSupport) string {
data, _ := json.Marshal(support)
return "|thinking=" + string(data)
}
func modelRoutingKeys(collect func(out func(key string))) []string {
keys := make([]string, 0)
collect(func(key string) {
keys = append(keys, key)
})
return keys
}
func hashJoined(keys []string) string {
if len(keys) == 0 {
return ""
}
sum := sha256.Sum256([]byte(strings.Join(keys, "\n")))
return hex.EncodeToString(sum[:])
}

View file

@ -0,0 +1,55 @@
package modelconfig
import (
"strings"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
)
// ResolveModelInfo returns a private capability snapshot for a configured model.
// Static capabilities come from the suffix-free upstream name, while explicit
// configuration takes precedence.
func ResolveModelInfo(name, modelType string, support *registry.ThinkingSupport) *registry.ModelInfo {
trimmedName := strings.TrimSpace(name)
baseName := strings.TrimSpace(thinking.ParseSuffix(trimmedName).ModelName)
info := registry.LookupStaticModelInfo(baseName)
if info == nil {
info = &registry.ModelInfo{}
}
info.ID = trimmedName
info.Type = strings.TrimSpace(modelType)
if support != nil {
info.Thinking = NormalizeThinkingSupport(support)
}
info.UserDefined = false
return info
}
// NormalizeThinkingSupport clones and normalizes configured reasoning levels.
func NormalizeThinkingSupport(raw *registry.ThinkingSupport) *registry.ThinkingSupport {
if raw == nil {
return nil
}
normalized := *raw
normalized.Levels = nil
seen := make(map[string]struct{}, len(raw.Levels))
for _, value := range raw.Levels {
level := strings.ToLower(strings.TrimSpace(value))
if level == "" {
continue
}
switch level {
case "none":
normalized.ZeroAllowed = true
case "auto":
normalized.DynamicAllowed = true
}
if _, exists := seen[level]; exists {
continue
}
seen[level] = struct{}{}
normalized.Levels = append(normalized.Levels, level)
}
return &normalized
}

View file

@ -0,0 +1,61 @@
package modelconfig
import (
"testing"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
)
func TestResolveModelInfoUsesSuffixFreeStaticCapabilities(t *testing.T) {
info := ResolveModelInfo("claude-opus-4-6(high)", "claude", nil)
if info == nil || info.Thinking == nil {
t.Fatalf("ResolveModelInfo() = %+v, want inherited thinking support", info)
}
if info.ID != "claude-opus-4-6(high)" {
t.Fatalf("model ID = %q, want configured upstream name", info.ID)
}
if info.UserDefined {
t.Fatal("resolved capability snapshot must not be user-defined")
}
}
func TestResolveModelInfoExplicitThinkingOverridesAndClones(t *testing.T) {
support := &registry.ThinkingSupport{Levels: []string{" XHIGH ", "xhigh", " High "}}
info := ResolveModelInfo("custom-model", "codex", support)
if info == nil || info.Thinking == nil {
t.Fatalf("ResolveModelInfo() = %+v, want explicit thinking support", info)
}
if got := info.Thinking.Levels; len(got) != 2 || got[0] != "xhigh" || got[1] != "high" {
t.Fatalf("normalized levels = %v, want [xhigh high]", got)
}
support.Levels[0] = "low"
if info.Thinking.Levels[0] != "xhigh" {
t.Fatal("resolved thinking support shares mutable config storage")
}
}
func TestNormalizeThinkingSupportDerivesSpecialLevelFlags(t *testing.T) {
support := NormalizeThinkingSupport(&registry.ThinkingSupport{Levels: []string{"low", "none", "auto"}})
if support == nil {
t.Fatal("NormalizeThinkingSupport() = nil")
}
if !support.ZeroAllowed {
t.Fatal("none level did not enable ZeroAllowed")
}
if !support.DynamicAllowed {
t.Fatal("auto level did not enable DynamicAllowed")
}
}
func TestResolveModelInfoUnknownModelKeepsMissingCapability(t *testing.T) {
info := ResolveModelInfo("unknown-configured-model", "claude", nil)
if info == nil {
t.Fatal("ResolveModelInfo() = nil")
}
if info.Thinking != nil {
t.Fatalf("unknown model thinking = %+v, want nil", info.Thinking)
}
if info.UserDefined {
t.Fatal("unknown configured model must use its exact bound capability")
}
}