From cb2e3bcca4634f7fb31a1702607b796d2999a67d Mon Sep 17 00:00:00 2001 From: yetone Date: Sun, 4 Oct 2026 12:12:07 +0800 Subject: [PATCH] =?UTF-8?q?provider:=20a=20held=20All=20gives=20each=20cal?= =?UTF-8?q?ler=20its=20own=20providers,=20Keys=20and=20Headers=20included?= =?UTF-8?q?=20=E2=80=94=20463462cd=20held=20All=20for=20a=20request=20and?= =?UTF-8?q?=20gave=20callers=20a=20slices.Clone=20of=20it,=20which=20share?= =?UTF-8?q?s=20each=20provider's=20slices=20and=20maps:=20SetKeyOn,=20Remo?= =?UTF-8?q?veKey=20and=20RenameKey=20change=20p.Keys[i]=20of=20what=20Find?= =?UTF-8?q?=20returns,=20so=20a=20key=20switched=20while=20a=20GUI=20GET?= =?UTF-8?q?=20or=20Codex's=20/models=20held=20the=20list=20changed=20every?= =?UTF-8?q?=20other=20caller's=20copy=20before=20it=20was=20saved.=20All?= =?UTF-8?q?=20now=20clones=20each=20provider=20(Was,=20Keys,=20Fallback,?= =?UTF-8?q?=20Models,=20Headers,=20AccountProxies,=20AccountModels,=20Acco?= =?UTF-8?q?untCaps,=20Contexts,=20MaxConcurrency,=20ZhipuTeam;=20Account,?= =?UTF-8?q?=20the=20sign-in's=20runtime,=20stays=20shared).=20TestHeldAllU?= =?UTF-8?q?nshared.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/provider/clone_test.go | 20 ++++++++++++++++++ internal/provider/provider.go | 36 ++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 internal/provider/clone_test.go 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.