provider: an account's "only through routing groups" holds, and one removed stays removed — All() merged a signed-in account's picks without Unlisted, so provider listed claude no saved but claude/* stayed in /v1/models; and any save of an account rebuilt its entry without Hidden, so a removed one came back; a save now keeps it removed, and Add it back (and magpie provider add <account>, which made claude-2) goes through ShowAccount, keeping its picks; and magpie provider set <id> k=v… changes a provider with add's pairs (the v0.1.77 notes named it, it didn't exist)

This commit is contained in:
yetone committed 2026-09-26 01:38:37 +08:00
1 parent ac352a7755
commit c330e20231
5 files changed
+74 -4

No files matched your search

+1 -1
View File
@@ -1260,7 +1260,7 @@ function renderExcluded() {
r.lastChild.append(el("b", "", t("{agent} is signed in, but stays out of this list. ", { agent: x.agentName })), t(x.why));
if (x.provider) {
const back = el("button", "link", t("Add it back"));
back.onclick = () => providerAction("save", { id: x.provider }, t("{name} added back", { name: x.agentName }));
back.onclick = () => providerAction("show", { id: x.provider }, t("{name} added back", { name: x.agentName }));
r.lastChild.append(" ", back);
}
box.append(r);
+6
View File
@@ -342,6 +342,12 @@ func providerRoutes(mux *http.ServeMux, w Windows, gw *gateway.Server) {
}
in := req.Provider
switch r.PathValue("action") {
case "show":
// a signed-in account the user removed, back with its picks
if err := provider.ShowAccount(in.ID); err != nil {
fail(rw, err)
return
}
case "save":
// a preset needs nothing but the key; a saved provider keeps
// its key when the form left it blank
+22
View File
@@ -133,12 +133,34 @@ func TestAccountsAreProviders(t *testing.T) {
if x := Excluded(); len(x) != 1 || x[0].Provider != "codex" || x[0].Agent != "codex" {
t.Fatalf("excluded: %+v", x)
}
// saving its picks again (anything that saves it) keeps it removed
if err := Save(Provider{ID: "codex", Models: []string{"gpt-5.5"}}); err != nil {
t.Fatal(err)
}
if _, ok := find(All(), "codex"); ok || len(Excluded()) != 1 {
t.Fatal("a save brought a removed codex back")
}
if err := ShowAccount("codex"); err != nil {
t.Fatal(err)
}
if codex, ok = find(All(), "codex"); !ok || len(codex.Models) != 1 || codex.Account == nil || len(Excluded()) != 0 {
t.Fatalf("after add back: %+v", codex)
}
// only through routing groups holds for an account too
codex.Unlisted = true
if err := Save(codex); err != nil {
t.Fatal(err)
}
if codex, _ = find(All(), "codex"); !codex.Unlisted {
t.Fatal("unlisted lost on an account")
}
for _, e := range Catalog() {
if e.Provider.ID == "codex" {
t.Fatalf("unlisted account in the catalog: %s", e.ID)
}
}
codex.Unlisted = false
Save(codex)
// signed out: gone, and a stale picks entry is not a provider
Save(Provider{ID: "copilot", Models: []string{"gpt-5.5"}})
+14 -3
View File
@@ -189,7 +189,8 @@ func All() []Provider {
if _, taken := find(out, a.ID); taken || picks[a.ID].Hidden {
continue
}
a.Models, a.Fallback, a.Routing, a.Affinity = picks[a.ID].Models, picks[a.ID].Fallback, picks[a.ID].Routing, picks[a.ID].Affinity
pk := picks[a.ID]
a.Models, a.Unlisted, a.Fallback, a.Routing, a.Affinity = pk.Models, pk.Unlisted, pk.Fallback, pk.Routing, pk.Affinity
out = append(out, a)
}
return out
@@ -256,8 +257,9 @@ func Save(p Provider) error {
}
if a, ok := find(Accounts(), p.ID); ok {
// an account keeps only the user's model picks; the rest is the
// agent's own sign-in. Saving it again brings a removed one back.
p = Provider{ID: a.ID, Models: p.Models, Unlisted: p.Unlisted, Fallback: p.Fallback, Routing: p.Routing, Affinity: p.Affinity}
// agent's own sign-in. One the user removed stays removed: only
// ShowAccount brings it back.
p = Provider{ID: a.ID, Models: p.Models, Unlisted: p.Unlisted, Fallback: p.Fallback, Routing: p.Routing, Affinity: p.Affinity, Hidden: hiddenAccount(a.ID)}
} else {
if slices.Contains(accountIDs, p.ID) && !stored(p.ID) {
// taken, it would hide that subscription once signed in
@@ -333,6 +335,15 @@ func stored(id string) bool {
return false
}
func hiddenAccount(id string) bool {
for _, p := range load().Providers {
if p.ID == id {
return p.Hidden
}
}
return false
}
// ShowAccount brings back the signed-in account of an agent the user had
// removed from magpie.
func ShowAccount(id string) error {
+31
View File
@@ -26,6 +26,7 @@ const providerUsage = `usage:
magpie provider add <preset> <key> add a preset vendor e.g. magpie provider add deepseek sk-…
again, it adds another (deepseek-2); k=v pairs too: id, name, header.X-Foo
magpie provider add <name> k=v… add a custom vendor k: url, anthropic, responses, key, models, catalog, icon, header.X-Foo, balance, balance.path, models.url
magpie provider set <id> k=v… change a provider's settings, with the same k=v pairs as add
magpie provider key <id> <key> change the API key
magpie provider icon <id> <file|name> give a custom provider a picture (PNG, JPEG, SVG…) or a built-in icon
magpie provider fallback <id> <provider/model>… where requests go when it's out of quota or down (none clears)
@@ -38,6 +39,7 @@ const providerUsage = `usage:
magpie provider add "Own Claude" anthropic=https://gw.example.com key=sk-… catalog=anthropic
magpie provider add "My Relay" url=https://relay.example.com/v1 key=sk-… header.X-Org-Id=acme
magpie provider add anthropic sk-… id=anthropic-ws2 name="Anthropic WS2" header.anthropic-workspace-id=wrkspc_…
magpie provider set my-relay models.url=https://relay.example.com/api/models catalog=
magpie provider add "My Relay" url=https://relay.example.com/v1 key=sk-… balance=https://relay.example.com/api/usage/token balance.path='$data.total_available / 500000'`
// providers: `magpie providers`
@@ -191,6 +193,28 @@ func providerCmd(args []string) error {
switch verb {
case "add":
return addProvider(rest)
case "set":
// the same k=v pairs as add, on a provider already here
if len(rest) < 2 {
return fmt.Errorf("magpie provider set <id> k=v…\n\n%s", providerUsage)
}
p, err := provider.Find(rest[0])
if err != nil {
return err
}
for _, kv := range rest[1:] {
if k, _, _ := strings.Cut(kv, "="); strings.EqualFold(k, "id") {
return fmt.Errorf("a provider's id can't change; groups and agents name it by it")
}
}
if err := applyPairs(p, rest[1:]); err != nil {
return err
}
if err := provider.Save(*p); err != nil {
return err
}
fmt.Println(green.Render("✓"), "saved", p.Name, muted.Render("("+p.ID+")"))
return nil
case "key":
if len(rest) != 2 {
return fmt.Errorf("magpie provider key <id> <key>")
@@ -362,6 +386,13 @@ func addProvider(rest []string) error {
if len(rest) == 0 {
return fmt.Errorf("magpie provider add <preset> <key> or magpie provider add <name> k=v…\n\n%s", providerUsage)
}
if len(rest) == 1 && slices.ContainsFunc(provider.Excluded(), func(x provider.Exclusion) bool { return x.Provider == strings.ToLower(rest[0]) }) {
// a signed-in account the user removed comes back with its picks
if err := provider.ShowAccount(strings.ToLower(rest[0])); err != nil {
return err
}
return announce(strings.ToLower(rest[0]))
}
var p provider.Provider
if pr, err := provider.FromPreset(strings.ToLower(rest[0])); err == nil {
p = pr