From e72cab7102d73f22bb2a66670c077c0c00f006a1 Mon Sep 17 00:00:00 2001 From: Justin Carper Date: Wed, 24 Jun 2026 11:30:45 -0500 Subject: [PATCH] fix(variants): normalize enum keys and drop 'none' for provider parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cursor GPT models expose a 'none' reasoning value (reasoning OFF) and label the top tier 'extra-high'. Standard opencode providers (models.dev) use 'xhigh' for that tier and treat 'none' as the implicit default, not a selectable level. The cycler therefore showed 'none' and 'extra-high' as variant keys, breaking parity with how the same models appear under other providers. Skip 'none' in the enum reasoning branch — it's the model default (no variant selected). Alias 'extra-high' to 'xhigh' for the variant KEY so the picker label matches the standard; the param VALUE sent to Cursor's API stays 'extra-high' (wire format unchanged). The collision-prefix guard now uses the display key. Tests: extra-high→xhigh rename, none dropped, realistic GPT shape (none+extra-high+fast composition), and an extra-high/xhigh collision guard. --- src/model-variants.ts | 16 ++++++-- test/model-variants.test.ts | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/model-variants.ts b/src/model-variants.ts index 3198dfb..1f696ce 100644 --- a/src/model-variants.ts +++ b/src/model-variants.ts @@ -88,9 +88,19 @@ export function buildModelVariants(item: ModelListItem): Record { }); }); + it("renames extra-high to xhigh in the variant key but keeps the wire value", () => { + // Cursor labels the top reasoning tier "extra-high"; the opencode standard + // (models.dev) calls it "xhigh". The variant KEY normalizes to xhigh so + // the cycler is consistent across providers; the param VALUE sent to + // Cursor's API stays "extra-high". + const variants = buildModelVariants( + model([{ id: "reasoning", values: [{ value: "low" }, { value: "extra-high" }] }]), + ); + expect(variants).toEqual({ + low: { params: { reasoning: "low" } }, + xhigh: { params: { reasoning: "extra-high" } }, + }); + }); + + it("drops the 'none' reasoning value (it is the model default)", () => { + // `none` = reasoning OFF, which is what you get by selecting no variant. + // Surfacing it as a selectable entry is meaningless, so it is skipped. + const variants = buildModelVariants( + model([ + { id: "reasoning", values: [{ value: "none" }, { value: "low" }, { value: "high" }] }, + ]), + ); + expect(variants).toEqual({ + low: { params: { reasoning: "low" } }, + high: { params: { reasoning: "high" } }, + }); + }); + + it("composes none-drop and extra-high rename for the real GPT shape", () => { + // gpt-5.5 / gpt-5.4 catalog: reasoning=[none,low,medium,high,extra-high] + // + fast. Expect: low, medium, high, xhigh (no none, extra-high→xhigh), + // each effort variant bakes fast OFF, plus a standalone fast opt-in. + const variants = buildModelVariants( + model([ + { + id: "reasoning", + values: [ + { value: "none" }, + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "extra-high" }, + ], + }, + { id: "fast", values: [{ value: "false" }, { value: "true" }] }, + ]), + ); + expect(variants).toEqual({ + low: { params: { reasoning: "low", fast: "false" } }, + medium: { params: { reasoning: "medium", fast: "false" } }, + high: { params: { reasoning: "high", fast: "false" } }, + xhigh: { params: { reasoning: "extra-high", fast: "false" } }, + fast: { params: { fast: "true" } }, + }); + }); + + it("prefixes the display key on a collision involving extra-high", () => { + // Defensive: if two reasoning params both resolve to the xhigh display key + // (one via the real "xhigh" value, one via "extra-high"→xhigh), the second + // is prefixed with its param id. No current catalog model hits this, but + // the guard must hold. + const variants = buildModelVariants( + model([ + { id: "effort", values: [{ value: "xhigh" }] }, + { id: "reasoning", values: [{ value: "extra-high" }] }, + ]), + ); + expect(variants).toEqual({ + xhigh: { params: { effort: "xhigh" } }, + "reasoning-xhigh": { params: { reasoning: "extra-high" } }, + }); + }); + it("surfaces a non-reasoning boolean (fast) as an opt-in toggle; ignores enum context", () => { // `fast` is Cursor's fast-tier toggle. It is not a reasoning level, but the // user must be able to opt INTO it from the picker (default is off, sent