diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 82186179..870f6868 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -176,6 +176,12 @@ type Agent struct { // names, never taken for one of magpie's models nor moved by what // matches the picker (RenameRefs moves the names in it). SplitSuffix func(v string) (model, suffix string, one bool) + // Prefixed, when set, is what each of magpie's values in the agent's + // fields starts with ("magpie/", its provider there): a value without + // it is a model of one of the agent's own providers, never magpie's, + // even when magpie has a provider of the same name (OpenHanako's own + // deepseek/deepseek-v4-pro, #835). + Prefixed string // detect, when set, says whether the agent is here in place of looking // for its files and binary: a distro's, probed once. detect func() bool diff --git a/internal/agent/applied.go b/internal/agent/applied.go index e81bfa05..220472d2 100644 --- a/internal/agent/applied.go +++ b/internal/agent/applied.go @@ -207,7 +207,7 @@ func magpieValue(a *Agent, f Field, v string, vals map[string]string) bool { return false } v, _, one := a.split(v) - if !one { + if !one || a.Prefixed != "" && !strings.HasPrefix(v, a.Prefixed) { return false } if isMagpie(v) { diff --git a/internal/agent/hanako.go b/internal/agent/hanako.go index 8895b2b4..d94de326 100644 --- a/internal/agent/hanako.go +++ b/internal/agent/hanako.go @@ -91,6 +91,9 @@ func hanako(home string) *Agent { // what its provider client sends, though the key names it first UA: []string{"hanaagent"}, Dir: dir, Path: path, + // its own providers' models are provider/id as magpie's are: one + // is magpie's only on magpie's provider + Prefixed: magpieID + "/", Sync: func() error { cur, ok := hanakoCurrent(dir) if !ok || hanakoSame(cur, hanakoProvider()) { diff --git a/internal/agent/hanako_test.go b/internal/agent/hanako_test.go index d32535d2..75767df4 100644 --- a/internal/agent/hanako_test.go +++ b/internal/agent/hanako_test.go @@ -461,3 +461,41 @@ func TestHanakoKey(t *testing.T) { t.Fatal(k) } } + +// One of OpenHanako's own providers named as one of magpie's (its own +// DeepSeek, and magpie's deepseek): its model isn't magpie's, so the row +// isn't connected on it, and Disconnect from magpie puts it back and is +// done, rather than leaving the row connected on the model it went back to +// (Hu9956, #835). +func TestHanakoOwnProviderNamedAsMagpies(t *testing.T) { + home, dir := hanakoTestHome(t) + catalogPath := filepath.Join(dir, "provider-catalog.json") + // the user's own relay provider, which magpie has one of too + hanakoWrite(t, catalogPath, `{"catalogVersion":2,"providers":{"relay":{"base_url":"https://x/v1","api":"openai-completions","api_key":"k","models":["glm-4.6"]}}}`, 0o600) + hanakoWrite(t, filepath.Join(dir, "user", "preferences.json"), `{"primaryAgent":"hana"}`, 0o644) + cfg := filepath.Join(dir, "agents", "hana", "config.yaml") + hanakoWrite(t, cfg, "models:\n chat:\n id: glm-4.6\n provider: relay\n", 0o644) + + a := hanako(home) + if a.Wired() { + t.Fatal("its own relay/glm-4.6 reads as magpie's") + } + if err := a.Connect(); err != nil { + t.Fatal(err) + } + if !a.Wired() { + t.Fatalf("not wired after connect: %v", a.Values()) + } + if err := a.Disconnect(); err != nil { + t.Fatal(err) + } + if chat, raw := hanakoChatOf(t, cfg); chat["id"] != "glm-4.6" || chat["provider"] != "relay" { + t.Fatalf("disconnected config:\n%s", raw) + } + if _, ok := hanakoCurrent(dir); ok { + t.Fatal("magpie's provider is still there") + } + if a.Wired() { + t.Fatalf("still connected after Disconnect: %v", a.Values()) + } +}