* fix(model-picker): eliminate O(n²) catalog rebuild lag in /model
getModelOptions() ran an O(n²) optionMatchesModel loop (catalog scan per
option) plus an O(n²) duplicate-apiName filter, costing ~43ms per call on
catalogs with hundreds of models (e.g. Fireworks' ~280 entries). The
picker also rebuilt the full options list on every keystroke via
isGenuineSwitchProfileValue, so arrow-key navigation lagged badly.
- hoist catalog lookup out of the per-option loop (hasOptionValue)
- precompute duplicate apiNames into a Set
- short-circuit isGenuineSwitchProfileValue for non-switch values
getModelOptions(): 43.5ms -> 2.0ms on a 277-entry catalog
* perf(model-options): share route-catalog context across getModelOptions checks
getRouteCatalogModelOption re-resolved the active route, fetched the
catalog entries, and rebuilt the duplicate-apiName set on every call —
getModelOptions() invoked it up to 3x per build (env custom model, each
scoped additional option, active custom model + fallback), so large
catalogs (Fireworks ~280 entries) paid O(n) context rebuilds repeatedly.
Build the RouteCatalogContext lazily once per options build and pass it
through findRouteCatalogOption/hasOptionValue. Behavior unchanged; the
catalog-miss path measures 6.1ms -> 4.1ms on a 277-entry catalog.
* test(model-picker): add regression tests for catalog dedup and switch-profile guard
* test(model-picker): gate process-wide mocks in regression tests
* test(model-picker): reuse mocked modelOptions instance in switch-profile test
* test(model-picker): prevent mock leakage
* test(model-picker): drop dead env overrides overwritten by catalog dedup helper
OPENAI_BASE_URL and OPENAI_MODEL assigned in the scoped-cache test were
immediately overwritten by getRouteCatalogModelOptions, so they never
affected the exercised path. Remove the dead assignments; keep
OPENAI_API_KEY to preserve the helper's auth path.
* test(model-picker): assert getModelOptions skipped for ordinary ids
isGenuineSwitchProfileValue short-circuits on the switch-profile prefix,
skipping the getModelOptions() rebuild for ordinary model ids. Track the
gated getModelOptions binding ModelPicker captures (opt-in call-through
mock in importFreshModelPicker) and assert it is never invoked for
non-prefixed ids, alongside the existing false-result assertions.
* test(model-picker): address review feedback on spies, fetch bounds, and alias dedup
* feat: add /set-context-window and /clear-context-window commands
Add session-scoped context window overrides for OpenAI-compatible models
that fall back to the default 128k context window.
- /set-context-window [model] <tokens>: set override for current model
- /clear-context-window [model]: clear override(s)
- Overrides are in-memory, per-session, and die with the process
- Auto-compact and all context display commands respect the override
- Minimum 32k tokens to avoid auto-compact floor paradox
* fix: tighten parsing, fix minimum floor, add tests
- Reject extra arguments in /set-context-window (require exactly 1 or 2 tokens)
- Reject non-integer tokens like '64000foo' with strict regex check
- Fix MIN_CONTEXT_WINDOW_OVERRIDE from 32k to 33k to match documented floor
- Add 8 tests for session override set/get/clear/normalization/precedence
* fix: test isolation, integer validation, env override precedence test
- Add clearSessionContextWindowOverride to beforeEach/afterEach for test isolation
- Add Number.isInteger check to setSessionContextWindowOverride
- Split regression test into env override and unknown model fallback cases
- Add test for fractional values (64000.5)
* fix: provider-qualified model isolation, session cleanup, model resolution
- Store full normalized model name (lowercase only, no prefix stripping)
- Lookup tries full name first, then stripped prefix fallback
- Provider-qualified names (zai-org/glm-5.2) no longer collide with unqualified (glm-5.2)
- Clear overrides in clearSessionCaches() so /clear resets state
- Resolve active model via getMainLoopModel() instead of raw context.options.mainLoopModel
- Add tests for known model precedence, provider isolation, session isolation
* fix: use session-active model, clear both exact and stripped keys
- Revert to context.options.mainLoopModel for session-active model (not global getMainLoopModel)
- clearSessionContextWindowOverride now deletes both exact and stripped-prefix keys
- Add regression test: clearing openai/gpt-4o also clears gpt-4o fallback
* fix: store both exact and stripped keys on write for symmetric lookup
- setSessionContextWindowOverride now stores both normalized and stripped-prefix keys
- Reading openai/gpt-4o via gpt-4o now works on first query (no fallback needed)
- Update test: provider-qualified writes now create both keys (symmetric with clear)
* test: canonicalize session context overrides and fix mixed-order alias tests
* test: verify CLAUDE_CODE_MAX_CONTEXT_TOKENS precedence over session overrides