groups: a routing group whose member's reasoning levels nothing knows offers its other members' levels, and Pi's thinking field offers and shows what Pi runs the model at (Moody-Sin, #597: 路由组设了推理强度最高是否正确应用?) — Pi on group/deepseek (腾讯DeepSeek) at magpie's 最高 listed off alone in its /thinking, and sent no reasoning_effort: Pi clamps defaultThinkingLevel max to the off it offers, so the group's members were asked at the vendor's default, not max.
The group offered no levels: its levels were what every member has, and a member whose levels magpie doesn't know (a Token Plan's deepseek-v4-pro-202606, which models.dev doesn't list) has none, so the intersection was empty and models.json had the group "reasoning": false. A model a provider was given that its vendor's models.dev list leaves out (TokenHub's deepseek-v4-pro, its list holding the Hy models alone) also had none in the catalog, where the gateway (Provider.Efforts) fits efforts to DeepSeek's low, high, max. Magpie's Pi field offered all seven levels whatever the model. - provider.Exposed: a picked model the vendor's list doesn't have takes the levels Provider.Known gives it (knownElsewhere), not none. - groupEntries: a member levelsUnknown (no levels, no account list, its vendor and maker don't list it, models.dev knows no model of its id — catalog.Knows) doesn't narrow the group's levels; the gateway sends it the effort as asked (fitEffort). A member listed with none still narrows them. - Pi/OmO thinking field: for a model of magpie's, Options are the levels Pi offers for the entry magpie writes (piSupported, as pi-ai's getSupportedThinkingLevels), and Get shows a level it doesn't offer as Pi's clamp of it (piClamp). Now Pi at max on that group sends reasoning_effort "max", relayed as max to both members (DeepSeek's levels have max; the unknown one is sent it as asked). New tests: TestGroupLevelsWithUnknownMember (provider), TestPiGroupThinkingIsWhatPiRuns and TestPiClamp (agent); each fails without the change (group entry "reasoning": false; catalog levels []; field offering all seven). TestMoveCursorEfforts gives its model levels, since Pi is offered off alone for one without. go vet, nogui/linux/windows builds and the full test suite pass but the known flake TestFXInState.
This commit is contained in:
1 parent
7547dfb3c9
commit
4160f464aa
8 files changed
+338
-8
No files matched your search
@@ -611,6 +611,8 @@ func piLike(at place, id, name, dir string) *Agent {
|
||||
get := func(k string) (string, bool) { return edit.GetJSON(path, k) }
|
||||
set := func(kvs ...edit.KV) error { return edit.SetJSON(path, kvs...) }
|
||||
pair := pairSet(set, "defaultProvider", "defaultModel")
|
||||
// the model a new session starts on, as the model field shows it
|
||||
startup := func() string { return piStartup(path, pairGet(get, "defaultProvider", "defaultModel")()) }
|
||||
writeMagpie := func() error {
|
||||
return edit.SetJSON(modelsPath, edit.KV{Path: "providers." + magpieID, Value: magpieProviderJSONAt("pi", id, at.gw())})
|
||||
}
|
||||
@@ -630,7 +632,7 @@ func piLike(at place, id, name, dir string) *Agent {
|
||||
{
|
||||
Key: "model", Label: "model",
|
||||
// what a new session starts on, which enabledModels decides
|
||||
Get: func() string { return piStartup(path, pairGet(get, "defaultProvider", "defaultModel")()) },
|
||||
Get: startup,
|
||||
Set: func(v string) error {
|
||||
if v == "" {
|
||||
if err := edit.DelJSON(path, "defaultProvider", "defaultModel"); err != nil {
|
||||
@@ -663,8 +665,20 @@ func piLike(at place, id, name, dir string) *Agent {
|
||||
// here replaced model fields a person had edited, such as a
|
||||
// contextWindow. Picking the model, and the catalog sync, still
|
||||
// refresh the provider.
|
||||
//
|
||||
// For a model of magpie's, the levels are those Pi offers for
|
||||
// it, from the entry magpie writes, and a level it doesn't
|
||||
// offer is shown as the one Pi runs it at: magpie showed max
|
||||
// for a group Pi offered off alone for, so ran without
|
||||
// reasoning (#597).
|
||||
Key: "effort", Label: "thinking",
|
||||
Get: func() string { v, _ := get("defaultThinkingLevel"); return v },
|
||||
Get: func() string {
|
||||
v, _ := get("defaultThinkingLevel")
|
||||
if offered := piOffered(id, startup()); v != "" && offered != nil {
|
||||
return piClamp(v, offered)
|
||||
}
|
||||
return v
|
||||
},
|
||||
Set: func(v string) error {
|
||||
cur, _ := get("defaultThinkingLevel")
|
||||
if v == cur {
|
||||
@@ -675,8 +689,11 @@ func piLike(at place, id, name, dir string) *Agent {
|
||||
}
|
||||
return set(edit.KV{Path: "defaultThinkingLevel", Value: v})
|
||||
},
|
||||
Options: func(map[string]string) []Option {
|
||||
return static("off", "minimal", "low", "medium", "high", "xhigh", "max")
|
||||
Options: func(cur map[string]string) []Option {
|
||||
if offered := piOffered(id, cur["model"]); offered != nil {
|
||||
return static(offered...)
|
||||
}
|
||||
return static(piLevels...)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -111,6 +111,11 @@ func TestMoveCursorEfforts(t *testing.T) {
|
||||
if err := provider.Save(provider.Provider{ID: "curs", Name: "Curs", Chat: "https://curs.example/v1", Key: "k", Models: []string{"grok-4.7"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Pi is offered the levels its entry gives the model (#597): a model
|
||||
// with none would be offered off alone, and low not moved
|
||||
if err := provider.SetModelEfforts("curs/grok-4.7", []string{"low", "high"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
base := func(id string) (string, string, bool) {
|
||||
if id == "grok-4.7-low" || id == "gone-low" {
|
||||
return strings.TrimSuffix(id, "-low"), "low", true
|
||||
|
||||
@@ -107,3 +107,67 @@ func piStartup(settings, def string) string {
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
// piOffered are the thinking levels Pi offers for model, a provider/model
|
||||
// as the model field shows it, when it is one of magpie's: what Pi's
|
||||
// /thinking lists for the entry magpie writes for it in models.json. nil
|
||||
// for any other model, whose levels are Pi's own business.
|
||||
func piOffered(agentID, model string) []string {
|
||||
ref, ok := strings.CutPrefix(model, magpieID+"/")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
for _, m := range magpieModels(agentID) {
|
||||
if m.ID == ref {
|
||||
return piSupported(piModelJSON(m, "", true))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// piSupported are the thinking levels Pi offers for a models.json entry, as
|
||||
// pi-ai's getSupportedThinkingLevels reads it: off alone for a model not
|
||||
// marked reasoning; else each of its levels not mapped to null, xhigh and
|
||||
// max only where they are mapped.
|
||||
func piSupported(e map[string]any) []string {
|
||||
if r, _ := e["reasoning"].(bool); !r {
|
||||
return []string{"off"}
|
||||
}
|
||||
levels, _ := e["thinkingLevelMap"].(map[string]any)
|
||||
var out []string
|
||||
for _, l := range piLevels {
|
||||
v, set := levels[l]
|
||||
if set && v == nil || !set && (l == "xhigh" || l == "max") {
|
||||
continue
|
||||
}
|
||||
out = append(out, l)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// piClamp is the level Pi runs a model at when it is set to level, as
|
||||
// pi-ai's clampThinkingLevel picks it among the levels it offers: level
|
||||
// itself, else the nearest above it, else the nearest below.
|
||||
func piClamp(level string, offered []string) string {
|
||||
if slices.Contains(offered, level) {
|
||||
return level
|
||||
}
|
||||
at := slices.Index(piLevels, level)
|
||||
if at < 0 {
|
||||
if len(offered) > 0 {
|
||||
return offered[0]
|
||||
}
|
||||
return "off"
|
||||
}
|
||||
for _, l := range piLevels[at:] {
|
||||
if slices.Contains(offered, l) {
|
||||
return l
|
||||
}
|
||||
}
|
||||
for i := at - 1; i >= 0; i-- {
|
||||
if slices.Contains(offered, piLevels[i]) {
|
||||
return piLevels[i]
|
||||
}
|
||||
}
|
||||
return "off"
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/yetone/magpie/internal/catalog"
|
||||
"github.com/yetone/magpie/internal/provider"
|
||||
)
|
||||
|
||||
// #597: Pi on a routing group of DeepSeek V4 Pro (TokenHub's, and the Token
|
||||
// Plan's deepseek-v4-pro-202606 that nothing magpie reads knows) was
|
||||
// written the group without reasoning, so its /thinking offered off alone,
|
||||
// while magpie's thinking field offered every level and showed max — which
|
||||
// Pi clamped to off, sending no reasoning_effort. The group now has its
|
||||
// known member's levels, Pi's entry offers them, and magpie's field offers
|
||||
// what Pi offers for the model and shows the level Pi runs it at.
|
||||
func TestPiGroupThinkingIsWhatPiRuns(t *testing.T) {
|
||||
home := syncHome(t)
|
||||
if err := os.WriteFile(catalog.CachePath(), []byte(`{
|
||||
"deepseek": {"models": {"deepseek-v4-pro": {"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","reasoning":true,
|
||||
"reasoning_options":[{"type":"toggle"},{"type":"effort","values":["low","high","max"]}],"limit":{"context":1000000}}}}
|
||||
}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
catalog.Reset()
|
||||
for _, p := range []provider.Provider{
|
||||
{ID: "th", Name: "TokenHub", Key: "k", Chat: "http://127.0.0.1:1/v1", Catalog: "tencent-tokenhub", Models: []string{"deepseek-v4-pro"}},
|
||||
{ID: "tp", Name: "Token Plan", Key: "k", Chat: "http://127.0.0.1:1/v1", Models: []string{"deepseek-v4-pro-202606"}},
|
||||
} {
|
||||
if err := provider.Save(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := provider.SaveGroup(provider.Group{ID: "deepseek", Name: "腾讯DeepSeek", Members: []string{"tp/deepseek-v4-pro-202606", "th/deepseek-v4-pro"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := provider.SaveGroup(provider.Group{ID: "plan", Name: "Plan", Members: []string{"tp/deepseek-v4-pro-202606"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a := pi(home)
|
||||
if err := a.Field("model").Set("magpie/group/deepseek"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := a.Field("effort").Set("max"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var file map[string]any
|
||||
if err := json.Unmarshal([]byte(readFile(filepath.Join(home, ".pi", "agent", "models.json"))), &file); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var entry map[string]any
|
||||
for _, raw := range file["providers"].(map[string]any)["magpie"].(map[string]any)["models"].([]any) {
|
||||
if m := raw.(map[string]any); m["id"] == "group/deepseek" {
|
||||
entry = m
|
||||
}
|
||||
}
|
||||
if entry == nil || entry["reasoning"] != true {
|
||||
t.Fatalf("group entry: %v", entry)
|
||||
}
|
||||
if got := piSupported(entry); !reflect.DeepEqual(got, []string{"low", "high", "max"}) {
|
||||
t.Fatalf("Pi offers %v for the group: %v", got, entry["thinkingLevelMap"])
|
||||
}
|
||||
|
||||
values := func(os []Option) []string {
|
||||
var out []string
|
||||
for _, o := range os {
|
||||
out = append(out, o.Value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
effort := a.Field("effort")
|
||||
if got := values(effort.Options(map[string]string{"model": "magpie/group/deepseek"})); !reflect.DeepEqual(got, []string{"low", "high", "max"}) {
|
||||
t.Fatalf("offered %v", got)
|
||||
}
|
||||
if got := effort.Get(); got != "max" {
|
||||
t.Fatalf("shown %q, want max", got)
|
||||
}
|
||||
|
||||
// on a group Pi can't reason with, max is shown as the off Pi runs at
|
||||
if err := a.Field("model").Set("magpie/group/plan"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := values(effort.Options(map[string]string{"model": "magpie/group/plan"})); !reflect.DeepEqual(got, []string{"off"}) {
|
||||
t.Fatalf("plan: offered %v", got)
|
||||
}
|
||||
if got := effort.Get(); got != "off" {
|
||||
t.Fatalf("plan: shown %q, want off", got)
|
||||
}
|
||||
// a model of Pi's own keeps every level
|
||||
if got := values(effort.Options(map[string]string{"model": "anthropic/claude-x"})); !reflect.DeepEqual(got, piLevels) {
|
||||
t.Fatalf("own model: offered %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Pi's clamp: the level itself, else the nearest above, else below.
|
||||
func TestPiClamp(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
level string
|
||||
offered []string
|
||||
want string
|
||||
}{
|
||||
{"max", []string{"off"}, "off"},
|
||||
{"max", []string{"low", "high", "max"}, "max"},
|
||||
{"medium", []string{"low", "high", "max"}, "high"},
|
||||
{"xhigh", []string{"off", "low", "medium", "high"}, "high"},
|
||||
{"minimal", []string{"low", "high"}, "low"},
|
||||
} {
|
||||
if got := piClamp(c.level, c.offered); got != c.want {
|
||||
t.Errorf("%s of %v: %s, want %s", c.level, c.offered, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -485,6 +485,12 @@ func Thinks(id string) bool {
|
||||
return thinks[bareID(id)]
|
||||
}
|
||||
|
||||
// Knows reports whether models.dev lists a model of this id at all, under
|
||||
// any provider, as ContextOf and EffortsOf match it.
|
||||
func Knows(id string) bool {
|
||||
return ContextOf(id) > 0 || len(EffortsOf(id)) > 0 || Thinks(id)
|
||||
}
|
||||
|
||||
// SeesImages reports whether models.dev says a model of this id takes
|
||||
// images, as most of the providers it lists serving it do; for a vendor it
|
||||
// doesn't list, serving a model it knows from others ("z-ai/glm-5.3" is
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/yetone/magpie/internal/catalog"
|
||||
"github.com/yetone/magpie/internal/settings"
|
||||
)
|
||||
|
||||
@@ -497,7 +498,8 @@ func membersIn(entries []Entry, all []Group, g Group) []Member {
|
||||
// as the user named it, answering for its first member when an agent asks
|
||||
// what the model can do, and offering only the reasoning levels every
|
||||
// member has — but for those fixed at an effort of their own, which take
|
||||
// whatever the agent asks. With every member fixed, the group offers the
|
||||
// whatever the agent asks, and those whose levels nothing magpie reads
|
||||
// knows (levelsUnknown), which are sent it as asked. With every member fixed, the group offers the
|
||||
// levels they are fixed at, so that an agent still asks it to reason.
|
||||
// A group that names its own levels (Group.Levels) offers those, and so
|
||||
// does a group in it for its models.
|
||||
@@ -527,9 +529,11 @@ func groupEntries(entries []Entry) []Entry {
|
||||
var efforts []string
|
||||
images, thinks, ctx, output := false, false, 0, 0
|
||||
var imageInput *bool
|
||||
unknown := false
|
||||
for _, x := range entries {
|
||||
if x.Provider.ID == m.Provider.ID && x.Model == m.Model {
|
||||
efforts, images, thinks, ctx, output, imageInput = x.Efforts, x.Images, x.Reasoning, x.Context, x.Output, x.ImageInput
|
||||
unknown = levelsUnknown(x)
|
||||
}
|
||||
}
|
||||
e.Reasoning = e.Reasoning && thinks
|
||||
@@ -554,6 +558,14 @@ func groupEntries(entries []Entry) []Entry {
|
||||
fixed = append(fixed, m.Effort)
|
||||
}
|
||||
continue
|
||||
} else if unknown {
|
||||
// nothing magpie reads says which levels it takes, or that
|
||||
// it takes none: the gateway sends it the effort asked as
|
||||
// it is (fitEffort), so it doesn't take the others' away —
|
||||
// a Token Plan's deepseek-v4-pro-202606 beside a TokenHub
|
||||
// deepseek-v4-pro left the group none, and Pi only off
|
||||
// (#597)
|
||||
continue
|
||||
}
|
||||
ultra = ultra || slices.Contains(efforts, "ultra")
|
||||
if !levelled {
|
||||
@@ -586,6 +598,23 @@ func groupEntries(entries []Entry) []Entry {
|
||||
return out
|
||||
}
|
||||
|
||||
// levelsUnknown reports whether nothing magpie reads says which reasoning
|
||||
// levels the model of x takes, or that it takes none: it has none in the
|
||||
// catalog, no account's list gives it, its vendor and its maker don't list
|
||||
// it, and models.dev lists no model of its id at all.
|
||||
func levelsUnknown(x Entry) bool {
|
||||
if len(x.Efforts) > 0 || x.Provider.Account != nil && x.Provider.Account.models != nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := catalog.ListedBy(x.Provider.Catalogs(), x.Model); ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := catalog.ListedBy(makerCatalogs(), x.Model); ok {
|
||||
return false
|
||||
}
|
||||
return !catalog.Knows(x.Model)
|
||||
}
|
||||
|
||||
// SaveGroup adds or replaces a group of the user's. Changing one magpie
|
||||
// found makes it the user's.
|
||||
func SaveGroup(g Group) error {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/yetone/magpie/internal/catalog"
|
||||
)
|
||||
|
||||
// #597: a routing group of DeepSeek V4 Pro on Tencent's TokenHub and on its
|
||||
// Token Plan (deepseek-v4-pro-202606, which nothing magpie reads knows)
|
||||
// offered no reasoning levels, so Pi listed off alone for it and ran it
|
||||
// without reasoning while magpie showed max. A model a provider serves that
|
||||
// its vendor's list leaves out takes the levels the gateway fits an effort
|
||||
// to (Provider.Efforts), and a member whose levels nothing knows takes
|
||||
// whatever the agent asks rather than taking the others' away. A member
|
||||
// known to have none still does.
|
||||
func TestGroupLevelsWithUnknownMember(t *testing.T) {
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
t.Setenv("USERPROFILE", t.TempDir())
|
||||
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
||||
cache := t.TempDir()
|
||||
t.Setenv("XDG_CACHE_HOME", cache)
|
||||
os.MkdirAll(filepath.Join(cache, "magpie"), 0o755)
|
||||
if err := os.WriteFile(catalog.CachePath(), []byte(`{
|
||||
"deepseek": {"models": {"deepseek-v4-pro": {"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","reasoning":true,
|
||||
"reasoning_options":[{"type":"toggle"},{"type":"effort","values":["low","high","max"]}],"limit":{"context":1000000}}}},
|
||||
"tencent-tokenhub": {"models": {"hy3": {"id":"hy3","reasoning":true,"reasoning_options":[{"type":"effort","values":["none","high"]}]}}},
|
||||
"plaincat": {"models": {"plain-chat": {"id":"plain-chat","limit":{"context":128000}}}}
|
||||
}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
catalog.Reset()
|
||||
t.Cleanup(catalog.Reset)
|
||||
for _, p := range []Provider{
|
||||
// TokenHub's models.dev list has its Hy models alone
|
||||
{ID: "th", Name: "TokenHub", Key: "k", Chat: "http://127.0.0.1:1/v1", Catalog: "tencent-tokenhub", Models: []string{"deepseek-v4-pro"}},
|
||||
{ID: "tp", Name: "Token Plan", Key: "k", Chat: "http://127.0.0.1:1/v1", Models: []string{"deepseek-v4-pro-202606"}},
|
||||
{ID: "pl", Name: "Plain", Key: "k", Chat: "http://127.0.0.1:1/v1", Catalog: "plaincat", Models: []string{"plain-chat"}},
|
||||
} {
|
||||
if err := Save(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
want := []string{"low", "high", "max"}
|
||||
entry := func(id string) Entry {
|
||||
t.Helper()
|
||||
e, ok := EntryOf(id)
|
||||
if !ok {
|
||||
t.Fatalf("%s not listed", id)
|
||||
}
|
||||
return e
|
||||
}
|
||||
// the catalog and the gateway agree on the model's levels
|
||||
p, _ := Find("th")
|
||||
if got := p.Efforts("deepseek-v4-pro"); !slices.Equal(got, want) {
|
||||
t.Fatalf("gateway's levels: %v", got)
|
||||
}
|
||||
if e := entry("th/deepseek-v4-pro"); !slices.Equal(e.Efforts, want) {
|
||||
t.Fatalf("catalog's levels: %v", e.Efforts)
|
||||
}
|
||||
for name, members := range map[string][]string{
|
||||
"ds": {"tp/deepseek-v4-pro-202606", "th/deepseek-v4-pro"},
|
||||
"plain": {"th/deepseek-v4-pro", "pl/plain-chat"},
|
||||
"tp": {"tp/deepseek-v4-pro-202606"},
|
||||
} {
|
||||
if err := SaveGroup(Group{Name: name, Members: members}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if e := entry("group/ds"); !slices.Equal(e.Efforts, want) || !e.Reasoning {
|
||||
t.Fatalf("ds: levels %v, reasoning %v", e.Efforts, e.Reasoning)
|
||||
}
|
||||
// a member listed without levels still takes them away, and a group
|
||||
// of models nothing knows offers none
|
||||
if e := entry("group/plain"); len(e.Efforts) != 0 {
|
||||
t.Fatalf("plain: levels %v", e.Efforts)
|
||||
}
|
||||
if e := entry("group/tp"); len(e.Efforts) != 0 {
|
||||
t.Fatalf("tp: levels %v", e.Efforts)
|
||||
}
|
||||
}
|
||||
@@ -551,7 +551,10 @@ func (p Provider) Exposed() []catalog.Model {
|
||||
if m, ok := byID[id]; ok {
|
||||
out = append(out, m)
|
||||
} else {
|
||||
out = append(out, catalog.Model{ID: id, Name: id, Provider: p.firstCatalog()})
|
||||
// with the levels the gateway fits an effort to (Known),
|
||||
// not the none effortsOf takes a vendor's word for: the
|
||||
// vendor's list doesn't have it, so it gave no word (#597)
|
||||
out = append(out, catalog.Model{ID: id, Name: id, Provider: p.firstCatalog(), Efforts: p.knownElsewhere(id)})
|
||||
}
|
||||
}
|
||||
return out
|
||||
@@ -617,8 +620,13 @@ func (p Provider) Known(model string) []string {
|
||||
return []string{l}
|
||||
}
|
||||
}
|
||||
// one of the vendor's own its list leaves out (a preview) or typed in:
|
||||
// the vendor's word on it, before the others'
|
||||
return p.knownElsewhere(model)
|
||||
}
|
||||
|
||||
// knownElsewhere are the reasoning levels of a model the provider's list
|
||||
// doesn't have — one of the vendor's own its list leaves out (a preview),
|
||||
// or typed in: the vendor's word on it, before the others'.
|
||||
func (p Provider) knownElsewhere(model string) []string {
|
||||
if e, ok := catalog.ListedBy(p.Catalogs(), model); ok {
|
||||
return e
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user