Files
yetone 1bc0ed404a minimax-code: magpie's models have one slash in the whole ref, so SubAgents take them (#1387, worksyang)
MiniMax Code 3.1.0 checks a SubAgent's model against /^[^/\s]+\/[^/\s]+$/
(uQn in @minimax-ai/code's chunk-2XPMNZVR.js), and magpie wrote its models
under custom_provider.magpie by catalog id, so
custom_provider:magpie/bb-codex/gpt-6.1-sol was refused
(AGENT_CONFIG_INVALID).

- The writer keys each model by its catalog id with "~" for each slash
  (bb-codex~gpt-6.1-sol, provider.FlatID), when exactly one served id
  spells that way (provider.Flattened); else it keeps the slashed id.
- The gateway takes such an id back to that one provider's model or group
  in validateModel and unprefixed (unflat / provider.Unflat): only an id
  with "~", no slash, that resolves as nothing else; two ids spelled alike
  are neither. Never a bare-name first hit. Key limits, middleware and
  usage see the catalog id, so a key held to bb-codex/* is refused
  other~gpt-6.1-sol on every route, count_tokens too.
- A config an older magpie wrote moves on the next sync: the user's keys on
  a model go to its new key, defaultModel, defaultLightModel and
  memory.captureModel/consolidationModel follow, and the slashed key stays
  with enabled: false, so a session begun on it still resumes (checked
  with mcode exec --continue). Get still reads magpie/<catalog id>, and
  Disconnect still puts back the stashed default and variant.

Other agents: no installed agent bundle here (cline, command-code,
qwen-code, deepseek, pi, omp, codebuff, openclaw) has this regex; native
agents were not checked.

Verified:
- go test -tags nogui ./internal/agent ./internal/gateway ./internal/provider
  (temp HOME), gofmt -l, go vet darwin/linux/windows.
- New TestMiniMaxCodeOneSlash uses the reporter's regex and ids;
  TestFlatModelIDsReachTheirProvider has two providers serving
  gpt-6.1-sol. Breaking unflat, mcodeKey, the key carry-over, the legacy
  enabled:false and the ref rewrite each turned the tests red.
- Real run: built magpie serve in a temp HOME on 47812, two fake upstreams;
  `magpie mcode magpie/other/gpt-6.1-sol` wrote other~gpt-6.1-sol; real
  mcode 0.6.5 exec reached other's upstream with gpt-6.1-sol (usage:
  other/gpt-6.1-sol); its agent loader accepted an agent.md with
  custom_provider:magpie/other~gpt-6.1-sol and refused the two-slash one;
  `magpie mcode default` put minimax/MiniMax-M2.7 back.
2026-10-09 14:08:07 +08:00

69 lines
2.0 KiB
Go

package provider
import "strings"
// FlatSep stands for each slash of a catalog id in its flat spelling.
//
// MiniMax Code takes a SubAgent's model only as "<provider>/<model>" with
// one slash in the whole (/^[^/\s]+\/[^/\s]+$/, #1387), and its provider
// for magpie is "custom_provider:magpie", so a model under it can't hold a
// slash: "bb-codex/gpt-6.1-sol" is written there as "bb-codex~gpt-6.1-sol",
// and the gateway takes that back to the catalog id (Unflat). A tilde is in
// no id a provider was seen to serve but OpenRouter's "~anthropic/…"
// latest-aliases, which flatten to "openrouter~~anthropic~…", still one
// entry's.
const FlatSep = "~"
// FlatID is a catalog id with no slash in it.
func FlatID(id string) string { return strings.ReplaceAll(id, "/", FlatSep) }
// Unflat is the served id (a provider's model or a routing group) whose
// flat spelling is id, when exactly one has it. An id with a slash, or
// with no tilde, is never a flat one.
func Unflat(id string) (string, bool) {
if !strings.Contains(id, FlatSep) || strings.Contains(id, "/") {
return "", false
}
return unflatIn(Served(), id)
}
func unflatIn(entries []Entry, id string) (string, bool) {
found := ""
for _, e := range entries {
if e.ID == found || FlatID(e.ID) != id {
continue
}
if found != "" {
return "", false // two ids flatten alike: neither is meant
}
found = e.ID
}
return found, found != ""
}
// Flattened maps each served id that can be spelled flat, and taken back
// by Unflat, to that spelling; an id two served ids flatten to alike is
// left out, as is one already without a slash.
func Flattened() map[string]string {
by := map[string]map[string]bool{}
for _, e := range Served() {
f := FlatID(e.ID)
if f == e.ID {
continue
}
if by[f] == nil {
by[f] = map[string]bool{}
}
by[f][e.ID] = true
}
out := map[string]string{}
for f, ids := range by {
if len(ids) == 1 {
for id := range ids {
out[id] = f
}
}
}
return out
}