agent: Zed's max_tokens is the window a prompt and its reply share, so a prompt keeps the room magpie's catalog gives it (#850) — Zed's openai_compatible max_tokens is the whole window: it keeps max_output_tokens of it for the reply and lets the prompt fill only the rest before it compacts (its thread's input_token_capacity = max_tokens - max_output_tokens). magpie wrote its catalog's Context there, which is what a prompt may hold, so Zed took the reply out of it: glm-4.6 (204800 / 131072) left a prompt 73728, deepseek-chat (128000 / 128000) none at all, gpt-5 (input 272000 / 128000) 144000 of its 400000 window. max_tokens is now Context + max_output_tokens: 335872, 256000, 400000 (OpenAI's own), and a model with no output limit still gets its Context alone. In a sandbox HOME with the real models.dev catalog, setting Zed to magpie wrote glm-4.6 335872/131072, gpt-5 400000/128000, deepseek-chat 256000/128000, claude-sonnet-4-5 264000/64000, kimi-k2 229376/98304. TestZedWindowHoldsPromptAndReply (gpt-5 400000 with a 272000 prompt; a model without an output limit) and TestZedRestoresProviderAndModelLimits fail on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn, TestUserPath and TestClaudeSignedOut timed out under load and pass on their own).

This commit is contained in:
yetone committed 2026-10-05 11:05:49 +08:00
1 parent 969e54f23b
commit 12cba74ab3
2 files changed
+61 -2

No files matched your search

+9 -1
View File
@@ -125,6 +125,12 @@ func zedProviderJSON() map[string]any {
if context == 0 {
context = 128000 // Zed requires a context window for every custom model.
}
// Zed's max_tokens is the window a prompt and its reply share: it
// keeps max_output_tokens of it for the reply and lets the prompt
// fill the rest before it compacts. Context is what a prompt may
// hold, so the window is it and the reply together (#850); written
// as Context alone, a model whose reply may be as long as its
// prompt (glm-4.6) left Zed no room for a prompt at all.
entry := map[string]any{
"name": m.ID, "display_name": m.Name, "max_tokens": context,
"capabilities": map[string]any{
@@ -134,7 +140,9 @@ func zedProviderJSON() map[string]any {
},
}
if output := maxTokens(m); output > 0 {
entry["max_output_tokens"] = min(output, context)
output = min(output, context)
entry["max_output_tokens"] = output
entry["max_tokens"] = context + output
}
models = append(models, entry)
}
+52 -1
View File
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
@@ -139,6 +140,54 @@ func TestZedFreshAndOwn(t *testing.T) {
}
}
// Zed's max_tokens is the window a prompt and its reply share, and it lets
// a prompt fill max_tokens - max_output_tokens before it compacts (its
// thread's input_token_capacity). That room is the prompt magpie's catalog
// says the model takes (#850): GPT-5's input 272000 with its 128000 reply,
// a 400000 window as OpenAI gives it, and a model with no output limit
// gets its context alone.
func TestZedWindowHoldsPromptAndReply(t *testing.T) {
home := syncHome(t)
credential := zedCredential
t.Cleanup(func() { zedCredential = credential; catalog.Reset() })
zedCredential = func(string) error { return nil }
writeFile(t, catalog.CachePath(), `{"openai":{"models":{
"gpt-5":{"id":"gpt-5","name":"GPT-5","limit":{"context":400000,"input":272000,"output":128000}},
"plain":{"id":"plain","name":"Plain","limit":{"context":64000}}}}}`)
catalog.Reset()
if err := provider.Save(provider.Provider{ID: "relay", Name: "Relay", Key: "k", Chat: "http://127.0.0.1:1/v1", Models: []string{"gpt-5", "plain"}}); err != nil {
t.Fatal(err)
}
a := zedAt(filepath.Join(home, "zed"))
if err := a.Field("model").Set("magpie/relay/gpt-5"); err != nil {
t.Fatal(err)
}
var p struct {
Models []struct {
Name string `json:"name"`
Max int `json:"max_tokens"`
Output *int `json:"max_output_tokens"`
} `json:"available_models"`
}
raw, _ := edit.GetJSON(a.Path, zedProvider)
if err := json.Unmarshal([]byte(raw), &p); err != nil {
t.Fatal(err)
}
got := map[string]string{}
for _, m := range p.Models {
s := strconv.Itoa(m.Max)
if m.Output != nil {
s += " " + strconv.Itoa(*m.Output) + " prompt " + strconv.Itoa(m.Max-*m.Output)
}
got[m.Name] = s
}
for name, want := range map[string]string{"relay/gpt-5": "400000 128000 prompt 272000", "relay/plain": "64000"} {
if got[name] != want {
t.Errorf("%s: max_tokens, max_output_tokens = %q, want %q (all %v)", name, got[name], want, got)
}
}
}
func TestZedRestoresProviderAndModelLimits(t *testing.T) {
home := syncHome(t)
writeFile(t, catalog.CachePath(), `{"zai":{"models":{"glm-4.6":{"id":"glm-4.6","name":"GLM","modalities":{"input":["text","image"]},"limit":{"context":204800,"output":300000}}}}}`)
@@ -152,7 +201,9 @@ func TestZedRestoresProviderAndModelLimits(t *testing.T) {
if err := a.Field("model").Set("magpie/relay/glm-4.6"); err != nil {
t.Fatal(err)
}
for key, want := range map[string]string{"max_tokens": "204800", "max_output_tokens": "204800", "capabilities.images": "true"} {
// Zed keeps max_output_tokens of max_tokens for the reply: the prompt's
// 204800 stays (#850), not 204800 - 204800 = 0
for key, want := range map[string]string{"max_tokens": "409600", "max_output_tokens": "204800", "capabilities.images": "true"} {
if got, _ := edit.GetJSON(a.Path, zedProvider+".available_models.0."+key); got != want {
t.Errorf("%s = %q, want %q", key, got, want)
}