zcode: what you choose in ZCode for magpie stays — the provider turned off stays off, its rule stays where you moved it, and a rule you removed isn't put back on the next sync. gateway: a model that always thinks (GLM-5.3 answers 1210 to thinking turned off) is asked again with thinking left to it, rather than failing. Asked on Discord

This commit is contained in:
yetone committed 2026-09-26 18:45:49 +08:00
1 parent 9b06c30790
commit 5dbd28c39e
5 files changed
+150 -7

No files matched your search

+24 -7
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"os"
"path/filepath"
"slices"
"github.com/yetone/magpie/internal/edit"
"github.com/yetone/magpie/internal/gateway"
@@ -64,10 +65,14 @@ func zcode(home string) *Agent {
if !wired() {
return nil
}
if err := zcodeRules(rules, true); err != nil {
return err
// a ZCode that keeps its providers as rules, with magpie's taken
// out there, had it removed in ZCode: it isn't put back
if _, err := os.Stat(rules); err != nil || zcodeRuled(rules) {
if err := zcodeRules(rules, true); err != nil {
return err
}
}
return edit.SetJSON(path, edit.KV{Path: key, Value: zcodeProviderJSON()})
return edit.SetJSON(path, edit.KV{Path: key, Value: zcodeProviderJSON(path)})
},
Fields: []Field{{
Key: "provider", Label: "provider",
@@ -84,7 +89,7 @@ func zcode(home string) *Agent {
if v == "" {
return edit.DelJSON(path, key)
}
return edit.SetJSON(path, edit.KV{Path: key, Value: zcodeProviderJSON()})
return edit.SetJSON(path, edit.KV{Path: key, Value: zcodeProviderJSON(path)})
},
Options: func(map[string]string) []Option {
return []Option{{Value: magpieID, Label: "magpie", Icon: "magpie", Note: "every magpie model in ZCode's picker"}}
@@ -93,7 +98,9 @@ func zcode(home string) *Agent {
}
}
func zcodeProviderJSON() any {
// zcodeProviderJSON is magpie's provider in config.json at path; one
// turned off in ZCode stays off.
func zcodeProviderJSON(path string) any {
ms := map[string]any{}
for _, m := range magpieModels("zcode") {
window := m.Context
@@ -107,7 +114,11 @@ func zcodeProviderJSON() any {
ms[m.ID] = map[string]any{"name": m.Name, "limit": map[string]any{"context": window},
"modalities": map[string]any{"input": in, "output": []string{"text"}}}
}
return map[string]any{"name": "magpie", "kind": "anthropic", "enabled": true, "source": "custom",
on := true
if v, ok := edit.GetJSON(path, "provider."+magpieID+".enabled"); ok && v == "false" {
on = false
}
return map[string]any{"name": "magpie", "kind": "anthropic", "enabled": on, "source": "custom",
"options": map[string]any{"apiKey": gateway.Token, "baseURL": gateway.URL()}, "models": ms}
}
@@ -161,9 +172,11 @@ func zcodeRules(path string, on bool) error {
var old map[string]any
var providers []any
at := -1 // where magpie's was, which it keeps
for _, r := range zcodeList(pcr, "providerRules") {
if mine(r) {
old, _ = r.(map[string]any)
at = len(providers)
continue
}
providers = append(providers, r)
@@ -213,7 +226,11 @@ func zcodeRules(path string, on bool) error {
if e, ok := old["enabled"].(bool); ok {
rule["enabled"] = e
}
providers = append(providers, rule)
if at >= 0 {
providers = slices.Insert(providers, at, any(rule))
} else {
providers = append(providers, rule)
}
}
pcr["providerRules"] = zcodeNonNil(providers)
mcr["providerModelRules"] = zcodeNonNil(models)
+59
View File
@@ -132,3 +132,62 @@ func TestZCode(t *testing.T) {
t.Fatal("sync added the provider")
}
}
// What the user did in ZCode stays: magpie turned off stays off, in both
// files; its rule stays where it is among theirs; and a rule they removed
// isn't put back when magpie syncs.
func TestZCodeKeepsUserChoices(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
if err := provider.Save(provider.Provider{ID: "deepseek", Name: "DeepSeek", Chat: "https://api.deepseek.com/v1", Key: "k", Models: []string{"pro"}}); err != nil {
t.Fatal(err)
}
path := filepath.Join(home, ".zcode", "v2", "config.json")
rules := filepath.Join(home, ".zcode", "v2", "provider_config.json")
os.MkdirAll(filepath.Dir(path), 0o755)
os.WriteFile(path, []byte(`{"provider":{"magpie":{"name":"magpie","kind":"anthropic","enabled":false}}}`), 0o644)
os.WriteFile(rules, []byte(`{"schemaVersion":1,"config":{"providerConfigRules":{"providerRules":[
{"providerId":"a","config":{}},{"providerId":"magpie","enabled":false,"config":{}},{"providerId":"b","config":{}}]}}}`), 0o600)
a := zcode(home)
if err := a.Sync(); err != nil {
t.Fatal(err)
}
ids := func() (out []string, on []any) {
var d struct {
Config struct {
ProviderConfigRules struct {
ProviderRules []map[string]any `json:"providerRules"`
} `json:"providerConfigRules"`
} `json:"config"`
}
b, _ := os.ReadFile(rules)
json.Unmarshal(b, &d)
for _, r := range d.Config.ProviderConfigRules.ProviderRules {
out = append(out, r["providerId"].(string))
on = append(on, r["enabled"])
}
return out, on
}
if got, on := ids(); len(got) != 3 || got[1] != "magpie" || on[1] != false {
t.Fatalf("rules %v %v", got, on)
}
var c struct {
Provider map[string]map[string]any `json:"provider"`
}
b, _ := os.ReadFile(path)
json.Unmarshal(b, &c)
if c.Provider["magpie"]["enabled"] != false || c.Provider["magpie"]["models"] == nil {
t.Fatalf("config.json: %s", b)
}
// removed in ZCode: not put back
os.WriteFile(rules, []byte(`{"schemaVersion":1,"config":{"providerConfigRules":{"providerRules":[{"providerId":"a","config":{}}]}}}`), 0o600)
if err := a.Sync(); err != nil {
t.Fatal(err)
}
if got, _ := ids(); len(got) != 1 {
t.Fatalf("magpie put back: %v", got)
}
}
+19
View File
@@ -154,6 +154,25 @@ func thinkingOffUnlessAsked(body []byte) []byte {
return withFields(body, map[string]any{"thinking": map[string]any{"type": "disabled"}})
}
// alwaysThinks is a vendor refusing to turn a model's thinking off: Z.ai's
// GLM-5.3 answers 1210, "…always engages in thinking…".
var alwaysThinks = regexp.MustCompile(`(?i)always engages in thinking|thinking (?:can ?not|can't) be (?:disabled|turned off)`)
// withoutThinkingOff is body with its thinking left to the model, when it
// says thinking is off; false when it doesn't.
func withoutThinkingOff(body []byte) ([]byte, bool) {
var q map[string]json.RawMessage
var th struct {
Type string `json:"type"`
}
if json.Unmarshal(body, &q) != nil || json.Unmarshal(q["thinking"], &th) != nil || th.Type != "disabled" {
return nil, false
}
delete(q, "thinking")
out, err := json.Marshal(q)
return out, err == nil
}
// buildAnthropic renders a request for an Anthropic-style upstream.
// claudeVersion finds the family's version in a Claude model id however a
// relay spells it: claude-opus-4-6, claude-opus-5, anthropic.claude-sonnet-4.6-v1.
+12
View File
@@ -755,6 +755,18 @@ func (s *Server) passthrough(w http.ResponseWriter, r *http.Request, p provider.
if err != nil {
return writeError(w, proto, 502, p.Name+": "+err.Error()), err.Error(), true
}
if proto == provider.Anthropic && res.StatusCode == http.StatusBadRequest {
// a model that always thinks refuses thinking turned off: asked
// again with it left to the model
b, _ := io.ReadAll(io.LimitReader(res.Body, 1<<20))
res.Body.Close()
res.Body = io.NopCloser(bytes.NewReader(b))
if nb, ok := withoutThinkingOff(body); ok && alwaysThinks.Match(b) {
if res, err = s.forward(r.Context(), p, proto, pathOf(proto), p.Prepare(nb), r.Header); err != nil {
return writeError(w, proto, 502, p.Name+": "+err.Error()), err.Error(), true
}
}
}
defer res.Body.Close()
if res.StatusCode >= 400 {
b, _ := io.ReadAll(io.LimitReader(res.Body, 1<<20))
+36
View File
@@ -29,11 +29,23 @@ type fake struct {
reply string // SSE body
ctype string // "" means text/event-stream; "none" sends no header at all
code int
// refuse, when set, answers a request it gives a code for with it
refuse func(body []byte) (code int, reply string)
calls int
}
func (f *fake) ServeHTTP(w http.ResponseWriter, r *http.Request) {
f.got, _ = io.ReadAll(r.Body)
f.path, f.head = r.URL.Path, r.Header
f.calls++
if f.refuse != nil {
if code, reply := f.refuse(f.got); code != 0 {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
io.WriteString(w, reply)
return
}
}
ct := f.ctype
if ct == "" {
ct = "text/event-stream"
@@ -479,6 +491,30 @@ func TestAnthropicPassthroughTurnsThinkingOffUnlessAsked(t *testing.T) {
}
}
// GLM-5.3 always thinks and refuses thinking turned off (Z.ai's 1210):
// the request magpie turned it off for is asked again with it left to the
// model, once
func TestAnthropicPassthroughModelThatAlwaysThinks(t *testing.T) {
f := &fake{t: t, ctype: "application/json", reply: `{"id":"msg","type":"message","content":[]}`}
f.refuse = func(b []byte) (int, string) {
if bytes.Contains(b, []byte(`"disabled"`)) {
return 400, `{"type":"error","error":{"type":"1210","message":"GLM-5.3 always engages in thinking; use low, high, or max"}}`
}
return 0, ""
}
setup(t, provider.Anthropic, f)
code, body := post(t, "/v1/messages", `{"model":"m1","max_tokens":5,"messages":[{"role":"user","content":"title?"}]}`)
if code != 200 || f.calls != 2 || bytes.Contains(f.got, []byte("thinking")) || !bytes.Contains(f.got, []byte(`"title?"`)) {
t.Fatalf("%d %s after %d calls, last sent %s", code, body, f.calls, f.got)
}
// another 400 is the agent's to see, not asked again
f.calls = 0
f.refuse = func([]byte) (int, string) { return 400, `{"type":"error","error":{"message":"bad request"}}` }
if code, _ := post(t, "/v1/messages", `{"model":"m1","max_tokens":5,"messages":[]}`); code != 400 || f.calls != 1 {
t.Errorf("%d after %d calls", code, f.calls)
}
}
// effort without thinking asks for no reasoning on a Chat upstream
func TestAnthropicEffortWithoutThinking(t *testing.T) {
f := &fake{t: t, reply: sse(`data: {"id":"c1","choices":[{"delta":{"content":"T"},"finish_reason":"stop"}]}`, `data: [DONE]`)}