provider: a held All gives each caller its own providers, Keys and Headers included — 463462cd held All for a request and gave callers a slices.Clone of it, which shares each provider's slices and maps: SetKeyOn, RemoveKey and RenameKey change p.Keys[i] of what Find returns, so a key switched while a GUI GET or Codex's /models held the list changed every other caller's copy before it was saved. All now clones each provider (Was, Keys, Fallback, Models, Headers, AccountProxies, AccountModels, AccountCaps, Contexts, MaxConcurrency, ZhipuTeam; Account, the sign-in's runtime, stays shared). TestHeldAllUnshared.

This commit is contained in:
yetone committed 2026-10-04 12:15:40 +08:00
1 parent cb7f442785
commit cb2e3bcca4
2 files changed
+55 -1

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
package provider
import "testing"
// A held list is shared by the request's callers: one that changes a key
// of its copy (SetKeyOn through Find) leaves the others' as they were.
func TestHeldAllUnshared(t *testing.T) {
n := 3
p := Provider{ID: "a", Keys: []KeyAccount{{Name: "k"}}, Headers: map[string]string{"h": "1"},
Models: []string{"m"}, AccountModels: map[string][]string{"x": {"m"}}, MaxConcurrency: &n}
q := p.clone()
q.Keys[0].Name = "changed"
q.Headers["h"] = "2"
q.Models[0] = "n"
q.AccountModels["x"][0] = "n"
*q.MaxConcurrency = 9
if p.Keys[0].Name != "k" || p.Headers["h"] != "1" || p.Models[0] != "m" || p.AccountModels["x"][0] != "m" || n != 3 {
t.Fatalf("clone shares with the original: %+v", p)
}
}
+35 -1
View File
@@ -11,6 +11,7 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"net"
"os"
"path/filepath"
@@ -317,7 +318,40 @@ func store(f file) error {
// item or a CLI's among them, and Codex's /models asked for it seven times
// over, past the 5 s Codex waits for the list (#746).
func All() []Provider {
return slices.Clone(heldOf("all", allProviders)) // callers change theirs
held := heldOf("all", allProviders)
out := make([]Provider, len(held))
for i, p := range held {
out[i] = p.clone() // callers change theirs, Keys[i] and Headers included
}
return out
}
// clone is p with its settings unshared: slices, maps and pointers copied.
func (p Provider) clone() Provider {
p.Was = slices.Clone(p.Was)
p.Keys = slices.Clone(p.Keys)
p.Fallback = slices.Clone(p.Fallback)
p.Models = slices.Clone(p.Models)
p.Headers = maps.Clone(p.Headers)
p.AccountProxies = maps.Clone(p.AccountProxies)
p.AccountCaps = maps.Clone(p.AccountCaps)
p.Contexts = maps.Clone(p.Contexts)
if p.AccountModels != nil {
m := make(map[string][]string, len(p.AccountModels))
for k, v := range p.AccountModels {
m[k] = slices.Clone(v)
}
p.AccountModels = m
}
if p.MaxConcurrency != nil {
v := *p.MaxConcurrency
p.MaxConcurrency = &v
}
if p.ZhipuTeam != nil {
v := *p.ZhipuTeam
p.ZhipuTeam = &v
}
return p // Account, the sign-in's runtime, stays shared
}
// AllBuilt counts the times All built the list anew, for tests.