Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
286
backend/sdk/cliproxy/auth/conductor_lifecycle.go
Normal file
286
backend/sdk/cliproxy/auth/conductor_lifecycle.go
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
)
|
||||
|
||||
// SetRetryConfig updates additional credential retry rounds, the per-round credential limit, and the cooldown wait interval.
|
||||
func (m *Manager) SetRetryConfig(retry int, maxRetryInterval time.Duration, maxRetryCredentials int) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
if retry < 0 {
|
||||
retry = 0
|
||||
}
|
||||
if maxRetryCredentials < 0 {
|
||||
maxRetryCredentials = 0
|
||||
}
|
||||
if maxRetryInterval < 0 {
|
||||
maxRetryInterval = 0
|
||||
}
|
||||
m.requestRetry.Store(int32(retry))
|
||||
m.maxRetryCredentials.Store(int32(maxRetryCredentials))
|
||||
m.maxRetryInterval.Store(maxRetryInterval.Nanoseconds())
|
||||
}
|
||||
|
||||
// RegisterExecutor registers a provider executor with the manager.
|
||||
func (m *Manager) RegisterExecutor(executor ProviderExecutor) {
|
||||
if executor == nil {
|
||||
return
|
||||
}
|
||||
provider := strings.TrimSpace(executor.Identifier())
|
||||
if provider == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var replaced ProviderExecutor
|
||||
m.mu.Lock()
|
||||
replaced = m.executors[provider]
|
||||
m.executors[provider] = executor
|
||||
m.mu.Unlock()
|
||||
|
||||
if replaced == nil || replaced == executor {
|
||||
return
|
||||
}
|
||||
if closer, ok := replaced.(ExecutionSessionCloser); ok && closer != nil {
|
||||
closer.CloseExecutionSession(CloseAllExecutionSessionsID)
|
||||
}
|
||||
}
|
||||
|
||||
// UnregisterExecutor removes the executor associated with the provider key.
|
||||
func (m *Manager) UnregisterExecutor(provider string) {
|
||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||
if provider == "" {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
delete(m.executors, provider)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Register inserts a new auth entry into the manager.
|
||||
func (m *Manager) Register(ctx context.Context, auth *Auth) (*Auth, error) {
|
||||
if auth == nil {
|
||||
return nil, nil
|
||||
}
|
||||
NormalizeCredentialMetadata(auth.Metadata)
|
||||
if errWeight := ValidateAuthWeight(auth); errWeight != nil {
|
||||
return nil, fmt.Errorf("register auth: %w", errWeight)
|
||||
}
|
||||
if auth.ID == "" {
|
||||
auth.ID = uuid.NewString()
|
||||
}
|
||||
now := time.Now()
|
||||
cooldownStateChanged := normalizeModelStates(auth)
|
||||
if m.cooldownDisabledForAuth(auth) || auth.Disabled || auth.Status == StatusDisabled {
|
||||
cooldownStateChanged = clearCooldownStateForAuth(auth, now) || cooldownStateChanged
|
||||
}
|
||||
auth.EnsureIndex()
|
||||
authClone := auth.Clone()
|
||||
m.mu.Lock()
|
||||
m.auths[auth.ID] = authClone
|
||||
m.mu.Unlock()
|
||||
if !shouldDeferAPIKeyModelAliasRebuild(ctx) {
|
||||
m.rebuildAPIKeyModelAliasFromRuntimeConfig()
|
||||
}
|
||||
if m.scheduler != nil {
|
||||
m.scheduler.upsertAuth(authClone)
|
||||
}
|
||||
m.queueRefreshReschedule(auth.ID)
|
||||
_ = m.persist(ctx, auth)
|
||||
m.hook.OnAuthRegistered(ctx, auth.Clone())
|
||||
if cooldownStateChanged {
|
||||
m.persistCooldownStates(ctx)
|
||||
}
|
||||
return auth.Clone(), nil
|
||||
}
|
||||
|
||||
// Update replaces an existing auth entry and notifies hooks.
|
||||
func (m *Manager) Update(ctx context.Context, auth *Auth) (*Auth, error) {
|
||||
if auth == nil || auth.ID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
NormalizeCredentialMetadata(auth.Metadata)
|
||||
if errWeight := ValidateAuthWeight(auth); errWeight != nil {
|
||||
return nil, fmt.Errorf("update auth: %w", errWeight)
|
||||
}
|
||||
m.mu.Lock()
|
||||
existing, ok := m.auths[auth.ID]
|
||||
if !ok || existing == nil {
|
||||
m.mu.Unlock()
|
||||
return nil, nil
|
||||
}
|
||||
if !auth.indexAssigned && auth.Index == "" {
|
||||
auth.Index = existing.Index
|
||||
auth.indexAssigned = existing.indexAssigned
|
||||
}
|
||||
auth.Success = existing.Success
|
||||
auth.Failed = existing.Failed
|
||||
auth.recentRequests = existing.recentRequests
|
||||
if !existing.Disabled && existing.Status != StatusDisabled && !auth.Disabled && auth.Status != StatusDisabled {
|
||||
if len(auth.ModelStates) == 0 && len(existing.ModelStates) > 0 {
|
||||
auth.ModelStates = existing.ModelStates
|
||||
}
|
||||
if existing.Quota.Exceeded && existing.Quota.Reason == "credential_quota" && existing.Quota.NextRecoverAt.After(time.Now()) {
|
||||
auth.Unavailable = existing.Unavailable
|
||||
auth.NextRetryAfter = existing.NextRetryAfter
|
||||
auth.Quota = existing.Quota
|
||||
if auth.Status == StatusActive {
|
||||
auth.Status = existing.Status
|
||||
}
|
||||
}
|
||||
}
|
||||
now := time.Now()
|
||||
cooldownStateChanged := normalizeModelStates(auth)
|
||||
if m.cooldownDisabledForAuth(auth) || auth.Disabled || auth.Status == StatusDisabled {
|
||||
cooldownStateChanged = clearCooldownStateForAuth(auth, now) || cooldownStateChanged
|
||||
}
|
||||
auth.EnsureIndex()
|
||||
authClone := auth.Clone()
|
||||
m.auths[auth.ID] = authClone
|
||||
m.mu.Unlock()
|
||||
if !shouldDeferAPIKeyModelAliasRebuild(ctx) {
|
||||
m.rebuildAPIKeyModelAliasFromRuntimeConfig()
|
||||
}
|
||||
if m.scheduler != nil {
|
||||
m.scheduler.upsertAuth(authClone)
|
||||
}
|
||||
m.queueRefreshReschedule(auth.ID)
|
||||
_ = m.persist(ctx, auth)
|
||||
m.hook.OnAuthUpdated(ctx, auth.Clone())
|
||||
if cooldownStateChanged {
|
||||
m.persistCooldownStates(ctx)
|
||||
}
|
||||
return auth.Clone(), nil
|
||||
}
|
||||
|
||||
// Remove deletes an auth from runtime state without persisting.
|
||||
// Disk and token-store deletion must be handled by the caller.
|
||||
func (m *Manager) Remove(ctx context.Context, id string) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
return
|
||||
}
|
||||
_ = ctx
|
||||
|
||||
m.mu.Lock()
|
||||
existing := m.auths[id]
|
||||
if existing == nil {
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
provider := strings.TrimSpace(existing.Provider)
|
||||
delete(m.auths, id)
|
||||
if m.modelPoolOffsets != nil {
|
||||
delete(m.modelPoolOffsets, id)
|
||||
}
|
||||
for sessionID, sessionAuths := range m.homeRuntimeAuths {
|
||||
if sessionAuths == nil {
|
||||
continue
|
||||
}
|
||||
delete(sessionAuths, id)
|
||||
if len(sessionAuths) == 0 {
|
||||
delete(m.homeRuntimeAuths, sessionID)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
if !shouldDeferAPIKeyModelAliasRebuild(ctx) {
|
||||
m.rebuildAPIKeyModelAliasFromRuntimeConfig()
|
||||
}
|
||||
if m.scheduler != nil {
|
||||
m.scheduler.removeAuth(id)
|
||||
}
|
||||
m.queueRefreshUnschedule(id)
|
||||
m.invalidateSessionAffinity(id)
|
||||
|
||||
if provider != "" {
|
||||
if exec, ok := m.Executor(provider); ok && exec != nil {
|
||||
if closer, okCloser := exec.(ExecutionSessionCloser); okCloser {
|
||||
closer.CloseExecutionSession(CloseAllExecutionSessionsID)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.persistCooldownStates(ctx)
|
||||
}
|
||||
|
||||
func (m *Manager) invalidateSessionAffinity(authID string) {
|
||||
if m == nil || authID == "" {
|
||||
return
|
||||
}
|
||||
if invalidator, ok := m.selector.(interface{ InvalidateAuth(string) }); ok && invalidator != nil {
|
||||
invalidator.InvalidateAuth(authID)
|
||||
}
|
||||
}
|
||||
|
||||
// Load resets manager state from the backing store.
|
||||
func (m *Manager) Load(ctx context.Context) error {
|
||||
m.mu.Lock()
|
||||
if m.store == nil {
|
||||
m.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
items, err := m.store.List(ctx)
|
||||
if err != nil {
|
||||
m.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
m.auths = make(map[string]*Auth, len(items))
|
||||
for _, auth := range items {
|
||||
if auth == nil || auth.ID == "" {
|
||||
continue
|
||||
}
|
||||
NormalizeCredentialMetadata(auth.Metadata)
|
||||
if errWeight := ValidateAuthWeight(auth); errWeight != nil {
|
||||
continue
|
||||
}
|
||||
auth.EnsureIndex()
|
||||
m.auths[auth.ID] = auth.Clone()
|
||||
}
|
||||
cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config)
|
||||
if cfg == nil {
|
||||
cfg = &internalconfig.Config{}
|
||||
}
|
||||
m.rebuildAPIKeyModelAliasLocked(cfg)
|
||||
m.mu.Unlock()
|
||||
m.syncScheduler()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) persist(ctx context.Context, auth *Auth) error {
|
||||
if m.store == nil || auth == nil {
|
||||
return nil
|
||||
}
|
||||
if errWeight := ValidateAuthWeight(auth); errWeight != nil {
|
||||
return fmt.Errorf("persist auth: %w", errWeight)
|
||||
}
|
||||
if shouldSkipPersist(ctx) {
|
||||
return nil
|
||||
}
|
||||
if IsConfigAPIKeyAuth(auth) {
|
||||
return nil
|
||||
}
|
||||
if auth.Attributes != nil {
|
||||
if v := strings.ToLower(strings.TrimSpace(auth.Attributes["runtime_only"])); v == "true" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if IsPluginVirtualAuth(auth) {
|
||||
return nil
|
||||
}
|
||||
// Skip persistence when metadata is absent (e.g., runtime-only auths).
|
||||
if auth.Metadata == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := m.store.Save(ctx, auth)
|
||||
return err
|
||||
}
|
||||
Loading…
Reference in a new issue