From f9e747feafc6533e79eb972ca480088d0e86cfd7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:02:11 +0000 Subject: [PATCH 1/2] Write context window and max tokens into Pi's models.json on configure Co-Authored-By: john --- internal/harnesses/pi.go | 36 ++++++++++++++++++++++++++++++++--- internal/harnesses/pi_test.go | 29 ++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/internal/harnesses/pi.go b/internal/harnesses/pi.go index f10bfcf..cbcf031 100644 --- a/internal/harnesses/pi.go +++ b/internal/harnesses/pi.go @@ -46,7 +46,9 @@ type piProviderConfig struct { } type piModelConfig struct { - ID string `json:"id"` + ID string `json:"id"` + ContextWindow int `json:"contextWindow"` + MaxTokens int `json:"maxTokens"` } type PiHarness struct { @@ -402,7 +404,7 @@ func (p *PiHarness) configureMerge(opts ConfigureOptions) error { "X-Title": "Pi", }, "models": []any{ - map[string]any{"id": opts.Model}, + p.modelConfig(opts.Model), }, }, }, @@ -445,7 +447,7 @@ func (p *PiHarness) configureOverwrite(opts ConfigureOptions) error { "X-Title": "Pi", }, Models: []piModelConfig{ - {ID: opts.Model}, + p.modelConfig(opts.Model), }, }, }, @@ -468,6 +470,34 @@ func (p *PiHarness) configureOverwrite(opts ConfigureOptions) error { return nil } +// modelConfig describes model in models.json with the capacities the model +// list reports, since Pi caps a model without them at 128k tokens. When the +// list cannot be fetched, the launch defaults stand in. +func (p *PiHarness) modelConfig(model string) piModelConfig { + config := piModelConfig{ + ID: model, + ContextWindow: piCatalogContextWindow, + MaxTokens: piCatalogMaxTokens, + } + + ctx, cancel := context.WithTimeout(context.Background(), piCatalogTimeout) + defer cancel() + + catalog, err := p.fetchCatalog(ctx) + if err != nil { + return config + } + + index := slices.IndexFunc(catalog.Models, func(m piCatalogModel) bool { return m.ID == model }) + if index < 0 { + return config + } + config.ContextWindow = catalog.Models[index].ContextWindow + config.MaxTokens = catalog.Models[index].MaxTokens + + return config +} + func (p *PiHarness) modelsPath() string { return filepath.Join(p.configDir, "models.json") } diff --git a/internal/harnesses/pi_test.go b/internal/harnesses/pi_test.go index 7fb400c..08490e2 100644 --- a/internal/harnesses/pi_test.go +++ b/internal/harnesses/pi_test.go @@ -1,6 +1,8 @@ package harnesses import ( + "net/http" + "net/http/httptest" "os" "path/filepath" "testing" @@ -10,9 +12,23 @@ import ( "github.com/stretchr/testify/require" ) +// newPiModelsServer is a router whose model list is body. +func newPiModelsServer(t *testing.T, status int, body string) *httptest.Server { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(status) + _, _ = w.Write([]byte(body)) + })) + t.Cleanup(server.Close) + + return server +} + func TestPiHarnessRoundTrip(t *testing.T) { + server := newPiModelsServer(t, http.StatusOK, `{"data": [ + {"id": "anthropic/claude-fable-5", "context_window": 1000000, "max_output_tokens": 64000} + ]}`) config := config.Config{ - RouterBaseURL: "https://router.requesty.ai", + RouterBaseURL: server.URL, APIKey: "my-api-key", } @@ -55,7 +71,7 @@ func TestPiHarnessRoundTrip(t *testing.T) { "other": {"name": "Other provider"}, "requesty": { "name": "Requesty", - "baseUrl": "https://router.requesty.ai", + "baseUrl": "`+server.URL+`", "api": "anthropic-messages", "apiKey": "my-api-key", "headers": { @@ -63,7 +79,7 @@ func TestPiHarnessRoundTrip(t *testing.T) { "X-Title": "Pi" }, "models": [ - {"id": "anthropic/claude-fable-5"} + {"id": "anthropic/claude-fable-5", "contextWindow": 1000000, "maxTokens": 64000} ] } } @@ -80,8 +96,9 @@ func TestPiHarnessRoundTrip(t *testing.T) { } func TestPiHarnessConfigureCreatesMissingConfig(t *testing.T) { + server := newPiModelsServer(t, http.StatusInternalServerError, "") config := config.Config{ - RouterBaseURL: "https://router.requesty.ai", + RouterBaseURL: server.URL, APIKey: "my-api-key", } configDir := t.TempDir() @@ -99,7 +116,7 @@ func TestPiHarnessConfigureCreatesMissingConfig(t *testing.T) { "providers": { "requesty": { "name": "Requesty", - "baseUrl": "https://router.requesty.ai", + "baseUrl": "`+server.URL+`", "api": "anthropic-messages", "apiKey": "my-api-key", "headers": { @@ -107,7 +124,7 @@ func TestPiHarnessConfigureCreatesMissingConfig(t *testing.T) { "X-Title": "Pi" }, "models": [ - {"id": "anthropic/claude-fable-5"} + {"id": "anthropic/claude-fable-5", "contextWindow": 200000, "maxTokens": 8192} ] } } From dfe9c5f96825b0529237a93073e90f214c6a23ff Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:13:00 +0000 Subject: [PATCH 2/2] Take Pi's context window from the picked model instead of refetching Co-Authored-By: john --- internal/harnesses/harnesses.go | 8 +++- internal/harnesses/pi.go | 40 +++++++------------ internal/harnesses/pi_test.go | 29 ++++---------- .../pages/requesty/dashboard/integrations.go | 7 +++- 4 files changed, 33 insertions(+), 51 deletions(-) diff --git a/internal/harnesses/harnesses.go b/internal/harnesses/harnesses.go index d637b00..3417abf 100644 --- a/internal/harnesses/harnesses.go +++ b/internal/harnesses/harnesses.go @@ -13,8 +13,12 @@ type Status struct { } type ConfigureOptions struct { - Model string - Overwrite bool + Model string + // ContextWindow and MaxOutputTokens are what the model list reports for + // Model, zero when it does not say. + ContextWindow int + MaxOutputTokens int + Overwrite bool } type LaunchOptions struct { diff --git a/internal/harnesses/pi.go b/internal/harnesses/pi.go index cbcf031..ecd672a 100644 --- a/internal/harnesses/pi.go +++ b/internal/harnesses/pi.go @@ -404,7 +404,7 @@ func (p *PiHarness) configureMerge(opts ConfigureOptions) error { "X-Title": "Pi", }, "models": []any{ - p.modelConfig(opts.Model), + piModelConfigFrom(opts), }, }, }, @@ -447,7 +447,7 @@ func (p *PiHarness) configureOverwrite(opts ConfigureOptions) error { "X-Title": "Pi", }, Models: []piModelConfig{ - p.modelConfig(opts.Model), + piModelConfigFrom(opts), }, }, }, @@ -470,32 +470,20 @@ func (p *PiHarness) configureOverwrite(opts ConfigureOptions) error { return nil } -// modelConfig describes model in models.json with the capacities the model -// list reports, since Pi caps a model without them at 128k tokens. When the -// list cannot be fetched, the launch defaults stand in. -func (p *PiHarness) modelConfig(model string) piModelConfig { - config := piModelConfig{ - ID: model, - ContextWindow: piCatalogContextWindow, - MaxTokens: piCatalogMaxTokens, - } - - ctx, cancel := context.WithTimeout(context.Background(), piCatalogTimeout) - defer cancel() - - catalog, err := p.fetchCatalog(ctx) - if err != nil { - return config - } +// piModelConfigFrom describes the configured model with its capacities, since +// Pi caps a model without them at 128k tokens. +func piModelConfigFrom(opts ConfigureOptions) piModelConfig { + entry := piCatalogModelFrom(client.Model{ + ID: opts.Model, + ContextWindow: opts.ContextWindow, + MaxOutputTokens: opts.MaxOutputTokens, + }) - index := slices.IndexFunc(catalog.Models, func(m piCatalogModel) bool { return m.ID == model }) - if index < 0 { - return config + return piModelConfig{ + ID: entry.ID, + ContextWindow: entry.ContextWindow, + MaxTokens: entry.MaxTokens, } - config.ContextWindow = catalog.Models[index].ContextWindow - config.MaxTokens = catalog.Models[index].MaxTokens - - return config } func (p *PiHarness) modelsPath() string { diff --git a/internal/harnesses/pi_test.go b/internal/harnesses/pi_test.go index 08490e2..8a8405b 100644 --- a/internal/harnesses/pi_test.go +++ b/internal/harnesses/pi_test.go @@ -1,8 +1,6 @@ package harnesses import ( - "net/http" - "net/http/httptest" "os" "path/filepath" "testing" @@ -12,23 +10,9 @@ import ( "github.com/stretchr/testify/require" ) -// newPiModelsServer is a router whose model list is body. -func newPiModelsServer(t *testing.T, status int, body string) *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(status) - _, _ = w.Write([]byte(body)) - })) - t.Cleanup(server.Close) - - return server -} - func TestPiHarnessRoundTrip(t *testing.T) { - server := newPiModelsServer(t, http.StatusOK, `{"data": [ - {"id": "anthropic/claude-fable-5", "context_window": 1000000, "max_output_tokens": 64000} - ]}`) config := config.Config{ - RouterBaseURL: server.URL, + RouterBaseURL: "https://router.requesty.ai", APIKey: "my-api-key", } @@ -55,7 +39,9 @@ func TestPiHarnessRoundTrip(t *testing.T) { // Configure the machine. err = harness.Configure(ConfigureOptions{ - Model: "anthropic/claude-fable-5", + Model: "anthropic/claude-fable-5", + ContextWindow: 1_000_000, + MaxOutputTokens: 64_000, }) require.NoError(t, err) @@ -71,7 +57,7 @@ func TestPiHarnessRoundTrip(t *testing.T) { "other": {"name": "Other provider"}, "requesty": { "name": "Requesty", - "baseUrl": "`+server.URL+`", + "baseUrl": "https://router.requesty.ai", "api": "anthropic-messages", "apiKey": "my-api-key", "headers": { @@ -96,9 +82,8 @@ func TestPiHarnessRoundTrip(t *testing.T) { } func TestPiHarnessConfigureCreatesMissingConfig(t *testing.T) { - server := newPiModelsServer(t, http.StatusInternalServerError, "") config := config.Config{ - RouterBaseURL: server.URL, + RouterBaseURL: "https://router.requesty.ai", APIKey: "my-api-key", } configDir := t.TempDir() @@ -116,7 +101,7 @@ func TestPiHarnessConfigureCreatesMissingConfig(t *testing.T) { "providers": { "requesty": { "name": "Requesty", - "baseUrl": "`+server.URL+`", + "baseUrl": "https://router.requesty.ai", "api": "anthropic-messages", "apiKey": "my-api-key", "headers": { diff --git a/internal/tui/pages/requesty/dashboard/integrations.go b/internal/tui/pages/requesty/dashboard/integrations.go index 80b17c8..f8fe0bc 100644 --- a/internal/tui/pages/requesty/dashboard/integrations.go +++ b/internal/tui/pages/requesty/dashboard/integrations.go @@ -254,7 +254,10 @@ func (m integrationState) updateModelStep(msg tea.KeyPressMsg) (integrationState if m.wizard.modelsErr == nil && len(models) > 0 && m.cursor < len(m.items) { - m.wizard.options.Model = models[m.wizard.modelCursor].ID + model := models[m.wizard.modelCursor] + m.wizard.options.Model = model.ID + m.wizard.options.ContextWindow = model.ContextWindow + m.wizard.options.MaxOutputTokens = model.MaxOutputTokens m.wizard.step = integrationModeWizardStep m.wizard.modeCursor = 0 m.wizard.configureErr = nil @@ -292,6 +295,8 @@ func (m integrationState) updateModeStep(msg tea.KeyPressMsg) (integrationState, case "esc": m.wizard.step = integrationModelWizardStep m.wizard.options.Model = "" + m.wizard.options.ContextWindow = 0 + m.wizard.options.MaxOutputTokens = 0 m.wizard.modeCursor = 0 m.wizard.configureErr = nil case "up", "k":