From b60cfa6787b0b23bac91ce92454f853efae9be2b Mon Sep 17 00:00:00 2001 From: cyxer000 <48980829+cyxer000@users.noreply.github.com> Date: Wed, 30 Sep 2026 22:58:08 +0800 Subject: [PATCH] agents: omp, Pi, Cline and dsh are no longer told a model writes more than its window holds (#338) models.dev lists some models' output above their context (deepseek-chat: 384000 out, 128000 in), and magpie handed that on as maxTokens. The limit is now kept within the contextWindow written beside it; a model whose window isn't known keeps its output as before. Follow-up to the review of --- internal/agent/agents.go | 2 +- internal/agent/cline.go | 2 +- internal/agent/dsh.go | 2 +- internal/agent/omp.go | 2 +- internal/agent/route.go | 11 +++++++++++ internal/agent/sync_test.go | 24 ++++++++++++++++++++++++ 6 files changed, 39 insertions(+), 4 deletions(-) diff --git a/internal/agent/agents.go b/internal/agent/agents.go index 4d4212e1..8c0e1d86 100644 --- a/internal/agent/agents.go +++ b/internal/agent/agents.go @@ -292,7 +292,7 @@ func magpieProviderJSONAt(shape, catalog, gw string) any { // without it Pi caps every reply at 16384 tokens, a model // that can write 128K included if m.Output > 0 { - e["maxTokens"] = m.Output + e["maxTokens"] = maxTokens(m) } ms = append(ms, e) } diff --git a/internal/agent/cline.go b/internal/agent/cline.go index 34f1198f..96dd3115 100644 --- a/internal/agent/cline.go +++ b/internal/agent/cline.go @@ -375,7 +375,7 @@ func clineModels(model string) map[string]any { e["contextWindow"] = m.Context } if m.Output > 0 { - e["maxTokens"] = m.Output + e["maxTokens"] = maxTokens(m) } ms[m.ID] = e } diff --git a/internal/agent/dsh.go b/internal/agent/dsh.go index c2cf81e3..1fa9f7d2 100644 --- a/internal/agent/dsh.go +++ b/internal/agent/dsh.go @@ -405,7 +405,7 @@ func dshProviderLines(modern bool, effort string) []string { lines = append(lines, fmt.Sprintf(" contextWindow: %d", m.Context)) } if m.Output > 0 { - lines = append(lines, fmt.Sprintf(" maxTokens: %d", m.Output)) + lines = append(lines, fmt.Sprintf(" maxTokens: %d", maxTokens(m))) } if m.Images { lines = append(lines, " inputModalities: [text, image]") diff --git a/internal/agent/omp.go b/internal/agent/omp.go index f1ba8a1f..a18032bc 100644 --- a/internal/agent/omp.go +++ b/internal/agent/omp.go @@ -184,7 +184,7 @@ type ompProviderEntry struct { func ompProvider() ompProviderEntry { ms := []ompModel{} for _, m := range magpieModels("omp") { - e := ompModel{ID: m.ID, Name: m.Name, Context: m.Context, MaxTokens: m.Output} + e := ompModel{ID: m.ID, Name: m.Name, Context: m.Context, MaxTokens: maxTokens(m)} mode := "effort" switch { case slices.Contains(m.APIs, string(provider.Responses)): diff --git a/internal/agent/route.go b/internal/agent/route.go index b205e8d4..2f947328 100644 --- a/internal/agent/route.go +++ b/internal/agent/route.go @@ -86,6 +86,17 @@ func magpieModels(agent string) []catalog.Model { return out } +// maxTokens is the output limit an agent is handed for m, kept within the +// context window it is handed with it: models.dev lists some models' output +// above their window (deepseek-chat's 384000 against 128000). An unknown +// window leaves the output as it is. +func maxTokens(m catalog.Model) int { + if m.Context > 0 && m.Output > m.Context { + return m.Context + } + return m.Output +} + // group tags every option with a group name. func group(name string, opts []Option) []Option { for i := range opts { diff --git a/internal/agent/sync_test.go b/internal/agent/sync_test.go index 1a50ef55..a90c1651 100644 --- a/internal/agent/sync_test.go +++ b/internal/agent/sync_test.go @@ -13,6 +13,7 @@ import ( "github.com/yetone/magpie/internal/edit" "github.com/yetone/magpie/internal/provider" "github.com/yetone/magpie/internal/settings" + "gopkg.in/yaml.v3" ) // syncHome is a sandbox home with a models.dev catalog that knows glm-4.6's @@ -103,6 +104,29 @@ func TestPiModelsCarryMaxTokens(t *testing.T) { } } +// An output limit above the model's window (models.dev lists deepseek-chat's +// 384000 against 128000 of context) is cut to the window for every agent +// magpie hands maxTokens; one whose window isn't known keeps its output. +func TestMaxTokensWithinContextWindow(t *testing.T) { + syncHome(t) + check := func(limit, want string) { + t.Helper() + os.WriteFile(catalog.CachePath(), []byte(`{"zai":{"models":{"glm-4.6":{"id":"glm-4.6","name":"GLM-4.6","limit":{`+limit+`}}}}}`), 0o644) + catalog.Reset() + pi, _ := json.Marshal(magpieProviderJSON("pi")) + cline, _ := json.Marshal(clineModels("")) + omp, _ := yaml.Marshal(ompProvider()) + dsh := strings.Join(dshProviderLines(true, ""), "\n") + if !strings.Contains(string(pi), `"maxTokens":`+want) || !strings.Contains(string(cline), `"maxTokens":`+want) || + !strings.Contains(string(omp), "maxTokens: "+want) || !strings.Contains(dsh, "maxTokens: "+want) { + t.Fatalf("want maxTokens %s:\npi %s\ncline %s\nomp %s\ndsh %s", want, pi, cline, omp, dsh) + } + } + check(`"context":128000,"output":384000`, "128000") + check(`"output":384000`, "384000") + check(`"context":204800,"output":131072`, "131072") +} + // A provider added after a magpie model was picked reaches the lists agents // keep of magpie's models; a file magpie wrote nothing into stays as it is. func TestSyncCatalogRewritesAgentLists(t *testing.T) {