From 0865c405fb0599b7f134ef0c2c547b2da020a7ac Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Fri, 2 Oct 2026 05:11:46 +0100 Subject: [PATCH] feat(pi): emit model context and output limits when set The pi adapter writes contextWindow and maxTokens to models.json only when the profile sets them. Pi keeps its own defaults for every other model. Tests cover both cases. --- internal/adapters/pi/pi.go | 11 +++++++++++ internal/adapters/pi/pi_test.go | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/internal/adapters/pi/pi.go b/internal/adapters/pi/pi.go index 87466f3..ae4e6c0 100644 --- a/internal/adapters/pi/pi.go +++ b/internal/adapters/pi/pi.go @@ -116,6 +116,17 @@ func piModelsJSON(b *canonical.Bundle) ([]byte, error) { if m.Alias != "" { entry["name"] = m.Alias } + // Emit limits only when the profile sets them explicitly. Pi applies + // its own defaults (contextWindow 128000, maxTokens 16384) to a model + // definition that omits them, so writing ContextLimit()/OutputLimit() + // unconditionally would push canonical's defaults onto every model + // that never asked for them. + if m.Context > 0 { + entry["contextWindow"] = m.Context + } + if m.Output > 0 { + entry["maxTokens"] = m.Output + } modelEntries = append(modelEntries, entry) } if len(modelEntries) == 0 && b.Profile.Gateway.DefaultModel != "" { diff --git a/internal/adapters/pi/pi_test.go b/internal/adapters/pi/pi_test.go index b9f7124..ad3296b 100644 --- a/internal/adapters/pi/pi_test.go +++ b/internal/adapters/pi/pi_test.go @@ -56,6 +56,41 @@ func TestPiRender(t *testing.T) { first := modelList[0].(map[string]any) assert.Equal(t, "claude-sonnet-4-6", first["id"]) assert.Equal(t, "Sonnet", first["name"]) + // A model with no explicit limits must carry none, so pi keeps applying + // its own defaults rather than inheriting canonical's. + assert.NotContains(t, first, "contextWindow") + assert.NotContains(t, first, "maxTokens") +} + +func TestPiRenderEmitsExplicitModelLimits(t *testing.T) { + home := t.TempDir() + ad := New(WithHome(home)) + b := &canonical.Bundle{ + Profile: canonical.Profile{ + Gateway: canonical.Gateway{URL: "https://gw", Token: "dummy"}, + Models: []canonical.Model{ + {ID: "local/current-model", Alias: "Local", Context: 1048576, Output: 32768}, + {ID: "bare/model"}, + }, + }, + } + fs, err := ad.Render(b) + require.NoError(t, err) + + seen := map[string]adapter.File{} + fs.ForEach(func(f adapter.File) { seen[f.Dest] = f }) + + var models map[string]any + require.NoError(t, json.Unmarshal(seen[filepath.Join(home, ".pi", "agent", "models.json")].Content, &models)) + list := models["providers"].(map[string]any)["hs-gw"].(map[string]any)["models"].([]any) + + withLimits := list[0].(map[string]any) + assert.InDelta(t, float64(1048576), withLimits["contextWindow"], 0) + assert.InDelta(t, float64(32768), withLimits["maxTokens"], 0) + + withoutLimits := list[1].(map[string]any) + assert.NotContains(t, withoutLimits, "contextWindow") + assert.NotContains(t, withoutLimits, "maxTokens") } func TestPiRenderMergesExistingSettings(t *testing.T) {