mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-30 09:18:12 +00:00
refactor(pr): narrow Codex cache fix scope
Remove the experimental auth-affinity routing changes from this PR so it stays focused on the validated Codex continuity fix. This keeps the prompt-cache repair while avoiding unrelated routing-policy concerns such as provider/model affinity scope, lifecycle cleanup, and hard-pin fallback semantics.
This commit is contained in:
@@ -46,7 +46,6 @@ type ErrorDetail struct {
|
||||
}
|
||||
|
||||
const idempotencyKeyMetadataKey = "idempotency_key"
|
||||
const authAffinityMetadataKey = "auth_affinity_key"
|
||||
|
||||
const (
|
||||
defaultStreamingKeepAliveSeconds = 0
|
||||
@@ -190,11 +189,9 @@ func requestExecutionMetadata(ctx context.Context) map[string]any {
|
||||
// Idempotency-Key is an optional client-supplied header used to correlate retries.
|
||||
// It is forwarded as execution metadata; when absent we generate a UUID.
|
||||
key := ""
|
||||
explicitIdempotencyKey := ""
|
||||
if ctx != nil {
|
||||
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil {
|
||||
explicitIdempotencyKey = strings.TrimSpace(ginCtx.GetHeader("Idempotency-Key"))
|
||||
key = explicitIdempotencyKey
|
||||
key = strings.TrimSpace(ginCtx.GetHeader("Idempotency-Key"))
|
||||
}
|
||||
}
|
||||
if key == "" {
|
||||
@@ -210,34 +207,10 @@ func requestExecutionMetadata(ctx context.Context) map[string]any {
|
||||
}
|
||||
if executionSessionID := executionSessionIDFromContext(ctx); executionSessionID != "" {
|
||||
meta[coreexecutor.ExecutionSessionMetadataKey] = executionSessionID
|
||||
} else if ctx != nil {
|
||||
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil {
|
||||
if apiKey, exists := ginCtx.Get("apiKey"); exists && apiKey != nil {
|
||||
if principal := stablePrincipalMetadataKey(apiKey); principal != "" {
|
||||
meta[authAffinityMetadataKey] = principal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
func stablePrincipalMetadataKey(raw any) string {
|
||||
var keyStr string
|
||||
switch v := raw.(type) {
|
||||
case string:
|
||||
keyStr = v
|
||||
case fmt.Stringer:
|
||||
keyStr = v.String()
|
||||
default:
|
||||
keyStr = fmt.Sprintf("%v", raw)
|
||||
}
|
||||
if trimmed := strings.TrimSpace(keyStr); trimmed != "" {
|
||||
return "principal:" + trimmed
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func pinnedAuthIDFromContext(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
|
||||
@@ -128,15 +128,13 @@ func (NoopHook) OnResult(context.Context, Result) {}
|
||||
|
||||
// Manager orchestrates auth lifecycle, selection, execution, and persistence.
|
||||
type Manager struct {
|
||||
store Store
|
||||
executors map[string]ProviderExecutor
|
||||
selector Selector
|
||||
hook Hook
|
||||
mu sync.RWMutex
|
||||
auths map[string]*Auth
|
||||
scheduler *authScheduler
|
||||
affinityMu sync.RWMutex
|
||||
affinity map[string]string
|
||||
store Store
|
||||
executors map[string]ProviderExecutor
|
||||
selector Selector
|
||||
hook Hook
|
||||
mu sync.RWMutex
|
||||
auths map[string]*Auth
|
||||
scheduler *authScheduler
|
||||
// providerOffsets tracks per-model provider rotation state for multi-provider routing.
|
||||
providerOffsets map[string]int
|
||||
|
||||
@@ -181,7 +179,6 @@ func NewManager(store Store, selector Selector, hook Hook) *Manager {
|
||||
selector: selector,
|
||||
hook: hook,
|
||||
auths: make(map[string]*Auth),
|
||||
affinity: make(map[string]string),
|
||||
providerOffsets: make(map[string]int),
|
||||
modelPoolOffsets: make(map[string]int),
|
||||
refreshSemaphore: make(chan struct{}, refreshMaxConcurrency),
|
||||
@@ -1132,7 +1129,6 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req
|
||||
continue
|
||||
}
|
||||
m.MarkResult(execCtx, result)
|
||||
m.persistAuthAffinity(entry, opts, auth.ID, provider, req.Model)
|
||||
return resp, nil
|
||||
}
|
||||
if authErr != nil {
|
||||
@@ -1211,7 +1207,6 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string,
|
||||
continue
|
||||
}
|
||||
m.MarkResult(execCtx, result)
|
||||
m.persistAuthAffinity(entry, opts, auth.ID, provider, req.Model)
|
||||
return resp, nil
|
||||
}
|
||||
if authErr != nil {
|
||||
@@ -1282,7 +1277,6 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string
|
||||
lastErr = errStream
|
||||
continue
|
||||
}
|
||||
m.persistAuthAffinity(entry, opts, auth.ID, provider, req.Model)
|
||||
return streamResult, nil
|
||||
}
|
||||
}
|
||||
@@ -2228,93 +2222,6 @@ func (m *Manager) CloseExecutionSession(sessionID string) {
|
||||
}
|
||||
}
|
||||
|
||||
func authAffinityKeyFromMetadata(meta map[string]any) string {
|
||||
if len(meta) == 0 {
|
||||
return ""
|
||||
}
|
||||
raw, ok := meta["auth_affinity_key"]
|
||||
if !ok || raw == nil {
|
||||
return ""
|
||||
}
|
||||
switch val := raw.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(val)
|
||||
case []byte:
|
||||
return strings.TrimSpace(string(val))
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func scopedAuthAffinityKey(provider, key string) string {
|
||||
provider = strings.TrimSpace(strings.ToLower(provider))
|
||||
key = strings.TrimSpace(key)
|
||||
if provider == "" || key == "" {
|
||||
return ""
|
||||
}
|
||||
return provider + "|" + key
|
||||
}
|
||||
|
||||
func (m *Manager) AuthAffinity(provider, key string) string {
|
||||
key = scopedAuthAffinityKey(provider, key)
|
||||
if m == nil || key == "" {
|
||||
return ""
|
||||
}
|
||||
m.affinityMu.RLock()
|
||||
defer m.affinityMu.RUnlock()
|
||||
return strings.TrimSpace(m.affinity[key])
|
||||
}
|
||||
|
||||
func (m *Manager) applyAuthAffinity(provider string, opts *cliproxyexecutor.Options) {
|
||||
if m == nil || opts == nil || pinnedAuthIDFromMetadata(opts.Metadata) != "" {
|
||||
return
|
||||
}
|
||||
if affinityKey := authAffinityKeyFromMetadata(opts.Metadata); affinityKey != "" {
|
||||
if affinityAuthID := m.AuthAffinity(provider, affinityKey); affinityAuthID != "" {
|
||||
if opts.Metadata == nil {
|
||||
opts.Metadata = make(map[string]any)
|
||||
}
|
||||
opts.Metadata[cliproxyexecutor.PinnedAuthMetadataKey] = affinityAuthID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) persistAuthAffinity(entry *log.Entry, opts cliproxyexecutor.Options, authID, provider, model string) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
if affinityKey := authAffinityKeyFromMetadata(opts.Metadata); affinityKey != "" {
|
||||
m.SetAuthAffinity(provider, affinityKey, authID)
|
||||
if entry != nil && log.IsLevelEnabled(log.DebugLevel) {
|
||||
entry.Debugf("auth affinity pinned auth_id=%s provider=%s model=%s", authID, provider, model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) SetAuthAffinity(provider, key, authID string) {
|
||||
key = scopedAuthAffinityKey(provider, key)
|
||||
authID = strings.TrimSpace(authID)
|
||||
if m == nil || key == "" || authID == "" {
|
||||
return
|
||||
}
|
||||
m.affinityMu.Lock()
|
||||
if m.affinity == nil {
|
||||
m.affinity = make(map[string]string)
|
||||
}
|
||||
m.affinity[key] = authID
|
||||
m.affinityMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *Manager) ClearAuthAffinity(provider, key string) {
|
||||
key = scopedAuthAffinityKey(provider, key)
|
||||
if m == nil || key == "" {
|
||||
return
|
||||
}
|
||||
m.affinityMu.Lock()
|
||||
delete(m.affinity, key)
|
||||
m.affinityMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *Manager) useSchedulerFastPath() bool {
|
||||
if m == nil || m.scheduler == nil {
|
||||
return false
|
||||
@@ -2398,7 +2305,6 @@ func (m *Manager) pickNextLegacy(ctx context.Context, provider, model string, op
|
||||
}
|
||||
|
||||
func (m *Manager) pickNext(ctx context.Context, provider, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, error) {
|
||||
m.applyAuthAffinity(provider, &opts)
|
||||
if !m.useSchedulerFastPath() {
|
||||
return m.pickNextLegacy(ctx, provider, model, opts, tried)
|
||||
}
|
||||
@@ -2513,18 +2419,6 @@ func (m *Manager) pickNextMixedLegacy(ctx context.Context, providers []string, m
|
||||
}
|
||||
|
||||
func (m *Manager) pickNextMixed(ctx context.Context, providers []string, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, string, error) {
|
||||
if pinnedAuthIDFromMetadata(opts.Metadata) == "" {
|
||||
for _, provider := range providers {
|
||||
providerKey := strings.TrimSpace(strings.ToLower(provider))
|
||||
if providerKey == "" {
|
||||
continue
|
||||
}
|
||||
m.applyAuthAffinity(providerKey, &opts)
|
||||
if pinnedAuthIDFromMetadata(opts.Metadata) != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !m.useSchedulerFastPath() {
|
||||
return m.pickNextMixedLegacy(ctx, providers, model, opts, tried)
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
type affinityTestExecutor struct{ id string }
|
||||
|
||||
func (e affinityTestExecutor) Identifier() string { return e.id }
|
||||
|
||||
func (e affinityTestExecutor) Execute(context.Context, *Auth, cliproxyexecutor.Request, cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||
return cliproxyexecutor.Response{}, nil
|
||||
}
|
||||
|
||||
func (e affinityTestExecutor) ExecuteStream(context.Context, *Auth, cliproxyexecutor.Request, cliproxyexecutor.Options) (*cliproxyexecutor.StreamResult, error) {
|
||||
ch := make(chan cliproxyexecutor.StreamChunk)
|
||||
close(ch)
|
||||
return &cliproxyexecutor.StreamResult{Chunks: ch}, nil
|
||||
}
|
||||
|
||||
func (e affinityTestExecutor) Refresh(_ context.Context, auth *Auth) (*Auth, error) { return auth, nil }
|
||||
|
||||
func (e affinityTestExecutor) CountTokens(context.Context, *Auth, cliproxyexecutor.Request, cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||
return cliproxyexecutor.Response{}, nil
|
||||
}
|
||||
|
||||
func (e affinityTestExecutor) HttpRequest(context.Context, *Auth, *http.Request) (*http.Response, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestManagerPickNextMixedUsesAuthAffinity(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := NewManager(nil, &RoundRobinSelector{}, nil)
|
||||
manager.executors["codex"] = affinityTestExecutor{id: "codex"}
|
||||
reg := registry.GetGlobalRegistry()
|
||||
reg.RegisterClient("codex-a", "codex", []*registry.ModelInfo{{ID: "gpt-5.4"}})
|
||||
reg.RegisterClient("codex-b", "codex", []*registry.ModelInfo{{ID: "gpt-5.4"}})
|
||||
t.Cleanup(func() {
|
||||
reg.UnregisterClient("codex-a")
|
||||
reg.UnregisterClient("codex-b")
|
||||
})
|
||||
if _, errRegister := manager.Register(context.Background(), &Auth{ID: "codex-a", Provider: "codex"}); errRegister != nil {
|
||||
t.Fatalf("Register(codex-a) error = %v", errRegister)
|
||||
}
|
||||
if _, errRegister := manager.Register(context.Background(), &Auth{ID: "codex-b", Provider: "codex"}); errRegister != nil {
|
||||
t.Fatalf("Register(codex-b) error = %v", errRegister)
|
||||
}
|
||||
|
||||
manager.SetAuthAffinity("codex", "idem-1", "codex-b")
|
||||
opts := cliproxyexecutor.Options{Metadata: map[string]any{"auth_affinity_key": "idem-1"}}
|
||||
|
||||
got, _, provider, errPick := manager.pickNextMixed(context.Background(), []string{"codex"}, "gpt-5.4", opts, map[string]struct{}{})
|
||||
if errPick != nil {
|
||||
t.Fatalf("pickNextMixed() error = %v", errPick)
|
||||
}
|
||||
if provider != "codex" {
|
||||
t.Fatalf("provider = %q, want %q", provider, "codex")
|
||||
}
|
||||
if got == nil || got.ID != "codex-b" {
|
||||
t.Fatalf("auth.ID = %v, want codex-b", got)
|
||||
}
|
||||
if pinned := pinnedAuthIDFromMetadata(opts.Metadata); pinned != "codex-b" {
|
||||
t.Fatalf("pinned auth metadata = %q, want %q", pinned, "codex-b")
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerAuthAffinityRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := NewManager(nil, nil, nil)
|
||||
manager.SetAuthAffinity("codex", "idem-2", "auth-1")
|
||||
if got := manager.AuthAffinity("codex", "idem-2"); got != "auth-1" {
|
||||
t.Fatalf("AuthAffinity = %q, want %q", got, "auth-1")
|
||||
}
|
||||
manager.ClearAuthAffinity("codex", "idem-2")
|
||||
if got := manager.AuthAffinity("codex", "idem-2"); got != "" {
|
||||
t.Fatalf("AuthAffinity after clear = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerAuthAffinityScopedByProvider(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := NewManager(nil, nil, nil)
|
||||
manager.SetAuthAffinity("codex", "shared-key", "codex-auth")
|
||||
manager.SetAuthAffinity("gemini", "shared-key", "gemini-auth")
|
||||
|
||||
if got := manager.AuthAffinity("codex", "shared-key"); got != "codex-auth" {
|
||||
t.Fatalf("codex affinity = %q, want %q", got, "codex-auth")
|
||||
}
|
||||
if got := manager.AuthAffinity("gemini", "shared-key"); got != "gemini-auth" {
|
||||
t.Fatalf("gemini affinity = %q, want %q", got, "gemini-auth")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user