diff --git a/internal/provider/clone_test.go b/internal/provider/clone_test.go new file mode 100644 index 00000000..a8267f9f --- /dev/null +++ b/internal/provider/clone_test.go @@ -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) + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 28016c94..0ae0bf24 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -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.