fix(agent): preserve Zed reply limits (#857)

Keep Zed's known input and output limits independent when building its
combined token budget. A 64K prompt limit no longer truncates a 128K reply.
Preserve the existing fallback reply cap when the input limit is unknown.

Cover larger reply limits, manual settings and synchronization, and
assert the prompt room left after Zed reserves its reply allowance.
This commit is contained in:
Zhizhen He authored and GitHub committed 2026-10-05 12:42:46 +08:00
1 parent b78a1ce0b2
commit 69a7744a9f
2 files changed
+62 -8

No files matched your search

+5 -2
View File
@@ -139,8 +139,11 @@ func zedProviderJSON() map[string]any {
"chat_completions": true,
},
}
if output := maxTokens(m); output > 0 {
output = min(output, context)
if output := m.Output; output > 0 {
if m.Context == 0 {
// Keep the fallback reply cap when the prompt limit is unknown.
output = min(output, context)
}
entry["max_output_tokens"] = output
entry["max_tokens"] = context + output
}
+57 -6
View File
@@ -153,9 +153,12 @@ func TestZedWindowHoldsPromptAndReply(t *testing.T) {
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}}}}}`)
"gpt-5-pro":{"id":"gpt-5-pro","limit":{"context":400000,"input":128000,"output":272000}},
"plain":{"id":"plain","name":"Plain","limit":{"context":64000}},
"unknown":{"id":"unknown","limit":{"output":256000}}}},
"zai":{"models":{"glm-4.6":{"id":"glm-4.6","limit":{"context":204800,"output":131072}}}}}`)
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 {
if err := provider.Save(provider.Provider{ID: "relay", Name: "Relay", Key: "k", Chat: "http://127.0.0.1:1/v1", Models: []string{"gpt-5", "gpt-5-pro", "plain", "unknown", "glm-4.6"}}); err != nil {
t.Fatal(err)
}
a := zedAt(filepath.Join(home, "zed"))
@@ -181,13 +184,61 @@ func TestZedWindowHoldsPromptAndReply(t *testing.T) {
}
got[m.Name] = s
}
for name, want := range map[string]string{"relay/gpt-5": "400000 128000 prompt 272000", "relay/plain": "64000"} {
for name, want := range map[string]string{
"relay/gpt-5": "400000 128000 prompt 272000",
"relay/gpt-5-pro": "400000 272000 prompt 128000",
"relay/plain": "64000",
"relay/unknown": "256000 128000 prompt 128000",
"relay/glm-4.6": "335872 131072 prompt 204800",
} {
if got[name] != want {
t.Errorf("%s: max_tokens, max_output_tokens = %q, want %q (all %v)", name, got[name], want, got)
}
}
}
func TestZedTokenLimitOverrides(t *testing.T) {
home := syncHome(t)
credential := zedCredential
t.Cleanup(func() { zedCredential = credential })
zedCredential = func(string) error { return nil }
writeFile(t, catalog.CachePath(), `{"zai":{"models":{"glm-4.6":{"id":"glm-4.6","limit":{"context":204800,"output":128000}}}}}`)
catalog.Reset()
p, err := provider.Find("relay")
if err != nil {
t.Fatal(err)
}
if err := provider.SetContext(*p, "glm-4.6", 64000); err != nil {
t.Fatal(err)
}
a := zedAt(filepath.Join(home, "zed"))
if err := a.Field("model").Set("magpie/relay/glm-4.6"); err != nil {
t.Fatal(err)
}
check := func(output int) {
t.Helper()
var m struct {
Max int `json:"max_tokens"`
Output int `json:"max_output_tokens"`
}
raw, _ := edit.GetJSON(a.Path, zedProvider+".available_models.0")
if err := json.Unmarshal([]byte(raw), &m); err != nil {
t.Fatal(err)
}
if m.Output != output || m.Max-m.Output != 64000 {
t.Errorf("max_tokens=%d, max_output_tokens=%d: want output %d and prompt 64000", m.Max, m.Output, output)
}
}
check(128000)
if err := provider.SetModelOutput("relay/glm-4.6", 256000); err != nil {
t.Fatal(err)
}
if err := a.Sync(); err != nil {
t.Fatal(err)
}
check(256000)
}
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}}}}}`)
@@ -201,9 +252,9 @@ func TestZedRestoresProviderAndModelLimits(t *testing.T) {
if err := a.Field("model").Set("magpie/relay/glm-4.6"); err != nil {
t.Fatal(err)
}
// 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"} {
// The independent reply limit keeps its allowance beside the prompt's
// 204800 tokens, even when the reply may be longer than the prompt.
for key, want := range map[string]string{"max_tokens": "504800", "max_output_tokens": "300000", "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)
}