Skip to content

feat: edit managed-model context windows in Settings - #1221

Merged
agegr merged 8 commits into
agegr:mainfrom
22zyfeng-collab:feat/model-context-window
Oct 15, 2026
Merged

agegr merged 8 commits into
agegr:mainfrom
22zyfeng-collab:feat/model-context-window

Conversation

@22zyfeng-collab

Copy link
Copy Markdown
Contributor

Summary

Add context-window inputs to Settings → Models for managed providers, including account/OAuth models such as ChatGPT Plus/Pro. Custom models already expose this setting; managed models currently require editing models.json by hand.

  • Use Pi's native providers[provider].modelOverrides[modelId].contextWindow; no SDK patches or new dependencies.
  • Keep these edits in the Models panel's existing draft and shared Save flow, rather than introducing a second writer that could overwrite custom-model edits.
  • Show the SDK-resolved budget, validate positive safe-integer token counts on both client and server, and retain invalid/failed-save drafts.
  • Clearing the input or choosing Default removes only the context override, preserving credentials, other overrides and custom models.
  • Keep override-only entries under their managed provider instead of duplicating them in the custom-provider sidebar.
  • Include English, Simplified Chinese and Traditional Chinese strings, accessible labels/errors, and a narrow-screen layout.

Application semantics

This changes Pi's context accounting, not the upstream provider's actual capacity. New session runtimes read the saved value. Loaded sessions need a Pi Web restart: an integration check confirmed that Pi 1.2's /reload retains their old model metadata, so the help text deliberately does not promise that /reload applies this edit.

Verification

  • 92 targeted tests passed: context helper/component, ModelsConfig helpers, models-config HTTP handlers/store (including real SDK resolution), enabled-models runtime/edit engine and i18n registry.
  • npm run lint passed.
  • Production webpack build and TypeScript checks passed; the existing dynamic-import warning in the session export route remains.
  • Ran checkModelContext against the packed production build at 1280px and 390px: edit, shared Save, reopen, invalid draft, server refusal/retry, Default, preservation of unrelated configuration, and no duplicate provider row.
  • Additional isolated HTTP/browser integration with fixture-only credentials: real UI → models.json → newly created session contextUsage.contextWindow changed 272000 → 500000 → 272000, without changing auth.json or sending inference requests.

Existing test limitations

The full suite was attempted on Windows/Node 24 and did not finish cleanly. Unrelated MCP component/transport failures also reproduce on an untouched v0.12.1 worktree (53 failures among 62 tests in components/McpConfig.test.mjs and app/api/mcp/test/route.test.mjs). These baseline issues are not changed by this PR; I am not claiming a green full suite.

The E2E runner blocks service workers so its API route fixtures also intercept requests in production mode.

22zyfeng-collab and others added 6 commits October 13, 2026 20:59
The context-window normalizer refused any value in models.json that was
not a positive safe integer, on the client and in the route. pi itself
accepts any positive number (400000.5 loads and resolves), so one
hand-edited override blocked every later Save, even an edit to an
unrelated custom provider.

parseContextWindow() now keeps the field's rule for what the user types
(text must be a whole token count) and only pi's own check for a number
already in the file. A value pi refuses still stops the save, since pi
drops the whole models.json on a schema error, and the message names
the model's provider/id so the field can be found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each managed model row carried a 180px block: a repeated "Context
window (tokens)" label, an input, a Default button and a "Currently: N"
line, which took rows from 43.5px to 78px (117.5px on a phone, where
the block wrapped under the name) and nearly doubled OpenRouter's
393-row list.

The row now holds one compact numeric text field between the name and
the switch. Its placeholder is the window pi resolves for the model,
so a blank field reads as "no override" without a Default button or a
current-value line; clearing it removes the override as before. The
model's name stays in its aria-label, a title says the unit, and the
single note above the list says what the fields are and that loaded
sessions need a restart. An invalid draft keeps its red border, and the
refused Save names the model in the footer. Measured in headless
Chrome, rows are 43.5px at 1280px and 390px, as on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e loading

An entry holding only modelOverrides is hidden from the custom
providers once /api/auth/providers names its managed provider. Until
that request answers both lists are empty, so the entry was listed as a
custom provider (with its "+ Model" row) for as long as the request
took, then vanished.

The entry now waits for the lists to answer before it can be listed as
custom; a failed request still lists it, as before this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/model-context.ts and components/ModelContextWindow.tsx were missing
from the File Map, and the models.md entry under Topic Notes did not list
them, so a change to either would not lead to the note that explains
the overrides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e loading

The previous commit kept an overrides-only entry out of the custom
provider list until /api/auth/providers answers, but the detail pane
still opened it. With no remembered selection the panel selects the
first models.json key, and a user who only set an override has exactly
that entry first: for as long as the provider lists took, the pane
showed a custom-provider form for openai-codex (Delete, Provider name,
Base URL) beside a list that did not show it, then jumped to the
subscription.

The pane now opens the custom form only for an entry the list shows as
custom, so it stays on its empty state until the lists answer and the
existing redirect moves it to the managed provider. A failed request
still lists and opens the entry as custom, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agegr

agegr commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Thanks @22zyfeng-collab! I checked this on a merge with main first: pi 1.2.0 really applies modelOverrides[id].contextWindow (in a scratch agent dir openai-codex/gpt-5.5 resolves to 500000 instead of 272000, and a new AgentSession reports that window), the field goes through the shared Save without clobbering custom-model edits, and you're right that /reload keeps the old value, so the restart note is accurate.

The one thing I wanted changed was the weight of each row: the field took every model row from 43.5 to 78 px (117.5 px on a phone), so OpenRouter's 393 rows grew from about 17.5k to 31k px. I pushed five commits on top of your branch so it can go in:

  • 7e5d7df fix(models): let a context override pi accepts through a save. pi accepts any positive contextWindow (400000.5 loads), but the normalizer refused any non-integer already in models.json, so one hand-edited value blocked every later Save, even for an unrelated custom provider. What you type in the field still has to be a whole token count; a value already in the file only needs to pass pi's own check, and values pi refuses are still refused, named by provider/id. A new route test returns 400 on your head and 200 now.
  • 20d5faa refactor(models): reduce the context-window editor to one input per row. Each managed row now has one compact field. Its placeholder is the window pi resolves for that model, and blank means no override. The label, Default button and "Currently" line are gone (the aria-label names the model), the note above the list is one line on desktop, and the unused strings are removed in all three locales. Rows are 43.5px at 1280px and 390px, the same as main. The unit tests, e2e/model-context.mjs and docs/agents/models.md are updated.
  • 1f7bb16, 8ee706c keep an overrides-only entry out of the custom list and form while loading: until /api/auth/providers answered, such an entry still flashed in the custom-provider list, and on a fresh browser the detail pane could open it as a custom-provider form.
  • 7bc6fa3 docs: lib/model-context.ts and components/ModelContextWindow.tsx are in AGENTS.md's File Map and the models.md file list.

Checked on a merge with main: tsc, lint and the full unit suite pass, your e2e check passes in headless Chrome at 1280x800 and 390x844, and a real save round trip against next dev with a scratch agent dir writes, keeps and removes the override as expected.

agegr and others added 2 commits October 15, 2026 15:50
…ntexts

serviceWorkers: "block" makes reading navigator.serviceWorker throw a
SecurityError in production builds, so PwaRegistration's register call
became a page error and the width check failed. The routes need no
block: sw.js leaves every /api/ request to the network, where page.route
sees it, as checkModelDiscovery's routes already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agegr
agegr merged commit 0e78bbe into agegr:main Oct 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants