2 Commits
Author SHA1 Message Date
keyarvsfandGitHub 248424ffe3 fix(model-picker): eliminate O(n²) catalog rebuild lag in /model (#2078)
* 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
2026-08-06 20:22:29 +08:00
keyarrandGitHub 5a7969785a feat: add /set-context-window and /clear-context-window commands (#1810)
* 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
2026-06-30 08:24:50 +08:00