feat(models): clamp-on-write for pinned thinking levels + DEFAULT_THINKING_ORDER in the core barrel
Fix 1 — clamp, never reject (owner ruling, interlude 2026-09-22): a model swap with a pinned thinking level must JUST WORK. The owner hit the write-side 400 live (Claude Fable 5.1/xhigh → GLM 5.3 flash: "z-ai/glm-5.3-flash does not support thinking level xhigh"). The supported-level verdicts in validateChatSettings and validateModelsSettings are DEAD — the WRITE clamps the pin to the new model's nearest supported level (pi's clampThinkingLevel, the same translation piEngineForSpec applies at runtime) and stores the clamped result; the server is the truth — the record shows the clamped level after write. New pure helpers in model-catalog.ts, applied by ALL THREE write routes (section, defaults, per-chat — one verdict seam, so every surface that used the old 400 gets the clamp uniformly): - clampModelsLevels: every slot's level clamps against the SLOT'S OWN model (the parser already requires a model on each slot write). - clampChatLevel: the body's own model, else the record's resolved strong slot (the chain's fallback); no model anywhere → rides unchecked (runtime clamps at engine build; the turn gate refuses the model-less chat anyway). Fix 2 — DEFAULT_THINKING_ORDER moves from config.ts to core/settings (core barrel, AUTH_KEYS precedent — one source of truth, no drift). The UI imports it to auto-populate a thinking level on FRESH model picks; the ordering has no other client role. Legacy unset records keep the runtime defaultThinkingLevel fallback at engine build untouched. Import sites updated: agent/models.ts, tests. Verified live on 3004: the owner's exact case stores max (not 400); a bare chat-level write clamps to low against the strong slot's glm; unknown provider/model still 400s with the provider list. Tests: the level-verdict suites became clamp suites (the owner's case pinned verbatim: xhigh→max, minimal→low, off→low, supported pass-through, level-less slots ride); the machine-aware expectBareLevelVerdict chain is retired — clampChatLevel takes the strong slot as an explicit param, so the tests are pure again. Gates: typecheck, lint, 839/839, build.
This commit is contained in:
@@ -69,6 +69,29 @@ export interface RoleModelOverride {
|
||||
export const MODEL_SLOTS = ["strong", "cheap", "vision"] as const;
|
||||
export type ModelSlotName = (typeof MODEL_SLOTS)[number];
|
||||
|
||||
/** The DEFAULT thinking level for UNSET levels only (owner, 2026-09-22):
|
||||
* a slot/override/chat that names a model but no thinking level resolves
|
||||
* to the FIRST entry in this order that the model supports
|
||||
* (getSupportedThinkingLevels); a non-reasoning model resolves "off"
|
||||
* regardless (pi reports exactly ["off"]). EXPLICITLY set levels never
|
||||
* consult this list — they clamp nearest via pi's native
|
||||
* clampThinkingLevel on model switches.
|
||||
*
|
||||
* Client surface (interlude, 2026-09-22): this constant lives in the CORE
|
||||
* BARREL — the UI imports it to auto-populate a thinking level on a FRESH
|
||||
* model pick (one source of truth, AUTH_KEYS precedent; per-model
|
||||
* supportedLevels already reach the client via the catalog). The ordering
|
||||
* has NO other client role: legacy unset records keep the runtime
|
||||
* defaultThinkingLevel fallback at engine build. */
|
||||
export const DEFAULT_THINKING_ORDER = [
|
||||
"medium",
|
||||
"low",
|
||||
"high",
|
||||
"minimal",
|
||||
"xhigh",
|
||||
"max",
|
||||
] as const satisfies readonly ThinkingLevel[];
|
||||
|
||||
/** The machine-readable NOT-CONFIGURED refusal envelope (owner, 2026-09-22):
|
||||
* every per-need gate — the turn gate in chat-handler, a job spawn, a
|
||||
* notes import — refuses with this exact shape. HTTP callers get 503 +
|
||||
|
||||
Reference in New Issue
Block a user