agents: each magpie model in T3 Code has a Reasoning pick of the levels it takes (KevinXC on Discord: 加进去的那些模型没有任何 reason 和 thinking 的设置,导致现在所有的模型都默认是 medium) — magpie's custom models in T3 Code's magpie instance were a slug and a name, and T3 shows a custom model with no capabilities with no options and sends Claude Code no effort. T3 Code's custom model entry takes capabilities.optionDescriptors (packages/contracts model.ts CustomModelEntry), which replace the driver's default in its picker, and its Claude adapter hands the effort chosen to the Agent SDK as it is for a custom model (no effortMap), which Claude Code sends to the gateway. Each model now gets an "effort" select labelled Reasoning, as T3's own Claude models have it, of the levels it takes that Claude Code can send (low, medium, high, xhigh, max, within claudeEffortsFor for Claude's own models), medium chosen first, else the lowest; a model with none (or only none/minimal) gets no pick. TestT3CodeEfforts is new, and t3Format now decodes a custom model's capabilities strictly as T3's schema has them (one default per select). Not run against a live T3 Code. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
This commit is contained in:
1 parent
38b4a9d984
commit
12cc2089e4
2 files changed
+128
-5
No files matched your search
@@ -9,7 +9,8 @@ package agent
|
||||
//
|
||||
// {"providerInstances":{"<id>":{"driver":"claudeAgent","displayName":…,
|
||||
// "enabled":true,"environment":[{"name":…,"value":…,"sensitive":false}],
|
||||
// "config":{"customModels":[{"slug":…,"name":…}]}}}}
|
||||
// "config":{"customModels":[{"slug":…,"name":…,"capabilities":
|
||||
// {"optionDescriptors":[{"id":"effort","type":"select",…}]}}]}}}}
|
||||
//
|
||||
// A claudeAgent instance runs Claude Code (Anthropic's Agent SDK, the
|
||||
// user's ~/.claude settings read as Claude Code reads them) with the
|
||||
@@ -18,7 +19,8 @@ package agent
|
||||
// own, "magpie": Claude Code pointed at the gateway (ANTHROPIC_BASE_URL and
|
||||
// the token Claude Code is routed with, so its requests are Claude Code's
|
||||
// to the gateway, tiers and all), every magpie model one of its custom
|
||||
// models, a 1M one marked [1m] as magpie marks it for Claude Code. It is
|
||||
// models, a 1M one marked [1m] as magpie marks it for Claude Code, with
|
||||
// a Reasoning pick of the levels it takes (t3Capabilities). It is
|
||||
// magpie's alone: the user's own instances, their custom models and T3's
|
||||
// other keys stay as they are, and off takes only it out. A binary path or
|
||||
// Claude home the user gave T3's own Claude is carried over, so it runs the
|
||||
@@ -107,7 +109,11 @@ func t3InstanceJSON(path, gw string) map[string]any {
|
||||
mark := claude1MFor("t3code")
|
||||
models := []map[string]any{}
|
||||
for _, m := range magpieModels("t3code") {
|
||||
models = append(models, map[string]any{"slug": mark(m.ID), "name": m.Name})
|
||||
e := map[string]any{"slug": mark(m.ID), "name": m.Name}
|
||||
if c := t3Capabilities(m.ID, m.Efforts); c != nil {
|
||||
e["capabilities"] = c
|
||||
}
|
||||
models = append(models, e)
|
||||
}
|
||||
config := map[string]any{"customModels": models}
|
||||
for _, k := range []string{"binaryPath", "homePath"} {
|
||||
@@ -130,3 +136,36 @@ func t3InstanceJSON(path, gw string) map[string]any {
|
||||
"config": config,
|
||||
}
|
||||
}
|
||||
|
||||
// t3EffortLabels are T3 Code's names for Claude Code's levels, as its own
|
||||
// Claude models show them.
|
||||
var t3EffortLabels = map[string]string{"low": "Low", "medium": "Medium", "high": "High", "xhigh": "Extra High", "max": "Max"}
|
||||
|
||||
// t3Capabilities is the pick T3 Code shows beside a custom model: without
|
||||
// one it shows none and sends no effort, so every magpie model ran at
|
||||
// whatever Claude Code took (KevinXC on Discord). It is Reasoning, the
|
||||
// levels of Claude Code's the model takes, medium chosen first as on T3's
|
||||
// own Claude models (else the lowest); T3 hands the level to Claude Code
|
||||
// as it is, which sends it to the gateway. nil for a model with no level
|
||||
// Claude Code can send.
|
||||
func t3Capabilities(id string, efforts []string) map[string]any {
|
||||
var options []map[string]any
|
||||
for _, l := range claudeEffortsFor(id) {
|
||||
if contains(efforts, l) {
|
||||
options = append(options, map[string]any{"id": l, "label": t3EffortLabels[l]})
|
||||
}
|
||||
}
|
||||
if len(options) == 0 {
|
||||
return nil
|
||||
}
|
||||
def := options[0]
|
||||
for _, o := range options {
|
||||
if o["id"] == "medium" {
|
||||
def = o
|
||||
}
|
||||
}
|
||||
def["isDefault"] = true
|
||||
return map[string]any{"optionDescriptors": []map[string]any{
|
||||
{"id": "effort", "label": "Reasoning", "type": "select", "options": options},
|
||||
}}
|
||||
}
|
||||
@@ -46,8 +46,20 @@ type t3Envelope struct {
|
||||
BinaryPath string `json:"binaryPath"`
|
||||
HomePath string `json:"homePath"`
|
||||
CustomModels []struct {
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
Capabilities *struct {
|
||||
OptionDescriptors []struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"`
|
||||
Options []struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
IsDefault *bool `json:"isDefault"`
|
||||
} `json:"options"`
|
||||
} `json:"optionDescriptors"`
|
||||
} `json:"capabilities"`
|
||||
} `json:"customModels"`
|
||||
} `json:"config"`
|
||||
}
|
||||
@@ -78,6 +90,23 @@ func t3Format(t *testing.T, raw []byte) t3Envelope {
|
||||
if m.Slug == "" || m.Name == "" {
|
||||
t.Errorf("custom model: %s", raw)
|
||||
}
|
||||
if m.Capabilities == nil {
|
||||
continue
|
||||
}
|
||||
for _, d := range m.Capabilities.OptionDescriptors {
|
||||
defaults := 0
|
||||
for _, o := range d.Options {
|
||||
if o.ID == "" || o.Label == "" {
|
||||
t.Errorf("option: %s", raw)
|
||||
}
|
||||
if o.IsDefault != nil && *o.IsDefault {
|
||||
defaults++
|
||||
}
|
||||
}
|
||||
if d.ID == "" || d.Label == "" || d.Type != "select" || len(d.Options) == 0 || defaults != 1 {
|
||||
t.Errorf("descriptor: %s", raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -230,3 +259,58 @@ func TestT3CodeHomeAndInstance(t *testing.T) {
|
||||
t.Error("the user's Claude instance is gone")
|
||||
}
|
||||
}
|
||||
|
||||
// Each magpie model in T3 Code has a Reasoning pick of the levels it takes
|
||||
// that Claude Code can send, medium chosen first, else the lowest (KevinXC
|
||||
// on Discord: every model ran at medium with no way to change it); none
|
||||
// for a model with no such level.
|
||||
func TestT3CodeEfforts(t *testing.T) {
|
||||
type opt struct {
|
||||
id string
|
||||
def bool
|
||||
}
|
||||
pick := func(id string, efforts []string) []opt {
|
||||
c := t3Capabilities(id, efforts)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
raw, _ := json.Marshal(c)
|
||||
var caps struct {
|
||||
OptionDescriptors []struct {
|
||||
ID, Label, Type string
|
||||
Options []struct {
|
||||
ID, Label string
|
||||
IsDefault bool
|
||||
}
|
||||
}
|
||||
}
|
||||
json.Unmarshal(raw, &caps)
|
||||
if len(caps.OptionDescriptors) != 1 || caps.OptionDescriptors[0].ID != "effort" || caps.OptionDescriptors[0].Type != "select" {
|
||||
t.Fatalf("%s: %s", id, raw)
|
||||
}
|
||||
var out []opt
|
||||
for _, o := range caps.OptionDescriptors[0].Options {
|
||||
if o.Label == "" {
|
||||
t.Errorf("%s: no label: %s", id, raw)
|
||||
}
|
||||
out = append(out, opt{o.ID, o.IsDefault})
|
||||
}
|
||||
return out
|
||||
}
|
||||
for _, c := range []struct {
|
||||
id string
|
||||
efforts []string
|
||||
want []opt
|
||||
}{
|
||||
{"codex/gpt-6", []string{"none", "minimal", "low", "medium", "high", "xhigh"}, []opt{{"low", false}, {"medium", true}, {"high", false}, {"xhigh", false}}},
|
||||
{"ds/deepseek-v4", []string{"high", "max"}, []opt{{"high", true}, {"max", false}}},
|
||||
{"claude/claude-opus-4-6", []string{"low", "medium", "high", "xhigh", "max"}, []opt{{"low", false}, {"medium", true}, {"high", false}, {"max", false}}},
|
||||
{"claude/claude-haiku-4-5", []string{"low", "medium", "high"}, nil},
|
||||
{"glm/glm-4.6", []string{"none", "minimal"}, nil},
|
||||
{"glm/glm-4.6", nil, nil},
|
||||
} {
|
||||
if got := pick(c.id, c.efforts); !slices.Equal(got, c.want) {
|
||||
t.Errorf("%s %v: %v, want %v", c.id, c.efforts, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user