feat(batch): add provider id and reuse policy

This commit is contained in:
phamnazage-jpg
2026-05-22 14:33:29 +08:00
parent 6420efbef1
commit 60bf8f0fd1
4 changed files with 291 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
package batch
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"net/url"
"path"
"strings"
)
func NormalizeProviderID(baseURL string) string {
parsedURL, err := url.Parse(strings.TrimSpace(baseURL))
if err != nil {
return ""
}
if parsedURL.Scheme == "" || parsedURL.Host == "" {
return ""
}
normalizedHost := normalizeProviderHost(parsedURL.Hostname())
normalizedPath := path.Clean(strings.TrimSpace(parsedURL.EscapedPath()))
if normalizedPath == "." {
normalizedPath = ""
}
normalizedURL := parsedURL.Scheme + "://" + strings.ToLower(parsedURL.Hostname()) + normalizedPath
digest := sha256.Sum256([]byte(normalizedURL))
hash := hex.EncodeToString(digest[:])
if len(hash) > 8 {
hash = hash[len(hash)-8:]
}
if normalizedHost == "" {
normalizedHost = "provider"
}
return fmt.Sprintf("%s-%s", normalizedHost, hash)
}
func normalizeProviderHost(host string) string {
host = strings.TrimSpace(strings.ToLower(host))
if host == "" {
return ""
}
parts := strings.Split(host, ".")
filtered := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
filtered = append(filtered, part)
}
if len(filtered) > 1 {
filtered = filtered[:len(filtered)-1]
}
if len(filtered) == 0 {
return strings.ReplaceAll(host, ".", "-")
}
return strings.Join(filtered, "-")
}

View File

@@ -0,0 +1,20 @@
package batch
import "testing"
func TestNormalizeProviderID(t *testing.T) {
t.Parallel()
first := NormalizeProviderID("https://api.deepseek.com/v1")
second := NormalizeProviderID("https://api.deepseek.com/proxy/v1")
if first == second {
t.Fatalf("NormalizeProviderID() = %q for both URLs, want distinct ids", first)
}
if first == "" || second == "" {
t.Fatalf("NormalizeProviderID() returned empty ids: %q %q", first, second)
}
if got := first[:12]; got != "api-deepseek" {
t.Fatalf("NormalizeProviderID() prefix = %q, want api-deepseek*", first)
}
}

View File

@@ -0,0 +1,95 @@
package batch
import (
"strings"
"sub2api-cn-relay-manager/internal/probe"
)
type ReuseInput struct {
ProviderID string
CanonicalModelFamilies []string
MatchedAccountID int64
MatchedAccountState MatchedAccountState
ExistingProviderID string
ExistingAccessStatus AccessStatus
ExistingCanonicalFamilys []string
}
type ReuseDecision struct {
ProvisionReused bool
ReusedFromProviderID string
ReusedFromAccountID int64
MatchedAccountState MatchedAccountState
AccountResolution AccountResolution
FamilyCovered bool
}
func DecideReuse(input ReuseInput) ReuseDecision {
decision := ReuseDecision{
MatchedAccountState: input.MatchedAccountState,
AccountResolution: AccountResolutionCreated,
FamilyCovered: canonicalFamiliesCovered(input.CanonicalModelFamilies, input.ExistingCanonicalFamilys),
}
if input.MatchedAccountState == "" {
decision.MatchedAccountState = MatchedAccountStateNone
}
if !sameProvider(input.ProviderID, input.ExistingProviderID) || !decision.FamilyCovered {
return decision
}
if input.ExistingAccessStatus == AccessStatusBroken || input.MatchedAccountState == MatchedAccountStateBroken {
decision.AccountResolution = AccountResolutionReplaced
return decision
}
switch input.MatchedAccountState {
case MatchedAccountStateDisabled, MatchedAccountStateDeprecated:
decision.ProvisionReused = true
decision.ReusedFromProviderID = strings.TrimSpace(input.ExistingProviderID)
decision.ReusedFromAccountID = input.MatchedAccountID
decision.AccountResolution = AccountResolutionReactivated
return decision
case MatchedAccountStateActive, MatchedAccountStateNone, "":
decision.ProvisionReused = true
decision.ReusedFromProviderID = strings.TrimSpace(input.ExistingProviderID)
decision.ReusedFromAccountID = input.MatchedAccountID
decision.AccountResolution = AccountResolutionReused
return decision
default:
return decision
}
}
func canonicalFamiliesCovered(requested []string, existing []string) bool {
if len(requested) == 0 || len(existing) == 0 {
return false
}
existingSet := make(map[string]struct{}, len(existing))
for _, family := range existing {
canonical := probe.CanonicalModelFamily(family)
if canonical == "" {
continue
}
existingSet[canonical] = struct{}{}
}
for _, family := range requested {
canonical := probe.CanonicalModelFamily(family)
if canonical == "" {
return false
}
if _, ok := existingSet[canonical]; !ok {
return false
}
}
return true
}
func sameProvider(left, right string) bool {
return strings.TrimSpace(left) != "" && strings.TrimSpace(left) == strings.TrimSpace(right)
}

View File

@@ -0,0 +1,113 @@
package batch
import "testing"
func TestDecideReuse(t *testing.T) {
t.Parallel()
t.Run("active provider with covered family is reused", func(t *testing.T) {
t.Parallel()
decision := DecideReuse(ReuseInput{
ProviderID: "api-deepseek-12345678",
CanonicalModelFamilies: []string{"kimi-k2.6"},
MatchedAccountID: 101,
MatchedAccountState: MatchedAccountStateActive,
ExistingProviderID: "api-deepseek-12345678",
ExistingAccessStatus: AccessStatusActive,
ExistingCanonicalFamilys: []string{"kimi 2.6"},
})
if !decision.ProvisionReused {
t.Fatal("ProvisionReused = false, want true")
}
if decision.AccountResolution != AccountResolutionReused {
t.Fatalf("AccountResolution = %q, want %q", decision.AccountResolution, AccountResolutionReused)
}
if decision.MatchedAccountState != MatchedAccountStateActive {
t.Fatalf("MatchedAccountState = %q, want %q", decision.MatchedAccountState, MatchedAccountStateActive)
}
})
t.Run("disabled or deprecated account is reactivated", func(t *testing.T) {
t.Parallel()
for _, state := range []MatchedAccountState{MatchedAccountStateDisabled, MatchedAccountStateDeprecated} {
state := state
t.Run(string(state), func(t *testing.T) {
t.Parallel()
decision := DecideReuse(ReuseInput{
ProviderID: "api-kimi-12345678",
CanonicalModelFamilies: []string{"kimi-k2.6"},
MatchedAccountID: 202,
MatchedAccountState: state,
ExistingProviderID: "api-kimi-12345678",
ExistingAccessStatus: AccessStatusActive,
ExistingCanonicalFamilys: []string{"kimi-2.6"},
})
if !decision.ProvisionReused {
t.Fatal("ProvisionReused = false, want true")
}
if decision.AccountResolution != AccountResolutionReactivated {
t.Fatalf("AccountResolution = %q, want %q", decision.AccountResolution, AccountResolutionReactivated)
}
})
}
})
t.Run("broken provider or account is replaced", func(t *testing.T) {
t.Parallel()
brokenProvider := DecideReuse(ReuseInput{
ProviderID: "api-deepseek-12345678",
CanonicalModelFamilies: []string{"deepseek-v4-pro"},
MatchedAccountState: MatchedAccountStateActive,
ExistingProviderID: "api-deepseek-12345678",
ExistingAccessStatus: AccessStatusBroken,
ExistingCanonicalFamilys: []string{"deepseek-v4-pro"},
})
if brokenProvider.ProvisionReused {
t.Fatal("ProvisionReused = true, want false for broken provider")
}
if brokenProvider.AccountResolution != AccountResolutionReplaced {
t.Fatalf("AccountResolution = %q, want %q", brokenProvider.AccountResolution, AccountResolutionReplaced)
}
brokenAccount := DecideReuse(ReuseInput{
ProviderID: "api-deepseek-12345678",
CanonicalModelFamilies: []string{"deepseek-v4-pro"},
MatchedAccountState: MatchedAccountStateBroken,
ExistingProviderID: "api-deepseek-12345678",
ExistingAccessStatus: AccessStatusActive,
ExistingCanonicalFamilys: []string{"deepseek-v4-pro"},
})
if brokenAccount.ProvisionReused {
t.Fatal("ProvisionReused = true, want false for broken account")
}
if brokenAccount.AccountResolution != AccountResolutionReplaced {
t.Fatalf("AccountResolution = %q, want %q", brokenAccount.AccountResolution, AccountResolutionReplaced)
}
})
t.Run("same family different alias counts as covered", func(t *testing.T) {
t.Parallel()
decision := DecideReuse(ReuseInput{
ProviderID: "api-kimi-12345678",
CanonicalModelFamilies: []string{"kimi-k2.6"},
MatchedAccountState: MatchedAccountStateActive,
ExistingProviderID: "api-kimi-12345678",
ExistingAccessStatus: AccessStatusActive,
ExistingCanonicalFamilys: []string{"kimi 2.6"},
})
if !decision.ProvisionReused {
t.Fatal("ProvisionReused = false, want true")
}
if !decision.FamilyCovered {
t.Fatal("FamilyCovered = false, want true")
}
})
}