From ab46bb36f763e54cbe6b9d1d52151a80278e0982 Mon Sep 17 00:00:00 2001 From: Derpedyea Date: Mon, 26 Oct 2026 14:28:21 -0400 Subject: [PATCH 1/3] feat(web): effort icon is a live gauge of the selected reasoning level Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/chat/TraitsPicker.tsx | 26 +++++++++++-- packages/client-runtime/src/modelTraits.ts | 38 +++++++++++++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index 68580c39a1e3e..c9ed5319436bc 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -50,6 +50,26 @@ import { useComposerMenuState } from "./useComposerMenuState"; type ProviderOptions = ReadonlyArray; +function TraitsTriggerIcon({ level, size }: { level: number | null; size: ComposerControlSize }) { + if (level === null) return ; + return ( + + ); +} + /** * All the picker reads from the prompt while rendering. Passing this instead of * the prompt keeps the picker from re-rendering on every keystroke. @@ -555,7 +575,7 @@ export const TraitsPicker = memo(function TraitsPicker({ return null; } - const { label: triggerLabel } = buildTraitsTriggerDisplay({ + const { label: triggerLabel, reasoningLevel } = buildTraitsTriggerDisplay({ provider, descriptors, primarySelectDescriptorId: primarySelectDescriptor?.id ?? null, @@ -600,7 +620,7 @@ export const TraitsPicker = memo(function TraitsPicker({ size === "xs" ? "gap-1" : "gap-1.5", )} > - + {triggerLabel} @@ -608,7 +628,7 @@ export const TraitsPicker = memo(function TraitsPicker({ ) : ( <> - + {triggerLabel} diff --git a/packages/client-runtime/src/modelTraits.ts b/packages/client-runtime/src/modelTraits.ts index 198d09e3a13c6..0037cbd8167cc 100644 --- a/packages/client-runtime/src/modelTraits.ts +++ b/packages/client-runtime/src/modelTraits.ts @@ -110,10 +110,11 @@ export function buildTraitsTriggerDisplay(input: { ultrathinkPromptControlled: boolean; modelSelection?: ModelSelection | null; reportedModelSelection?: ModelSelection | null | undefined; -}): { label: string } { +}): { label: string; reasoningLevel: number | null } { let fastModeFallbackLabel: string | null = null; let speedLabel: string | null = null; let reasoningLabelIndex = -1; + let reasoningLevel: number | null = null; const labels: Array = []; for (const descriptor of input.descriptors) { if (descriptor.id === "fastMode" && descriptor.type === "boolean") { @@ -162,13 +163,22 @@ export function buildTraitsTriggerDisplay(input: { REASONING_SELECT_DESCRIPTOR_IDS.has(descriptor.id) ) { reasoningLabelIndex = labels.length; + reasoningLevel = getReasoningLevel( + input.ultrathinkPromptControlled && descriptor.id === input.primarySelectDescriptorId + ? descriptor.promptInjectedValues?.[0] + : getProviderOptionCurrentValue( + descriptor, + input.modelSelection, + input.reportedModelSelection, + ), + ); } labels.push(label); } } if (labels.length === 0 && fastModeFallbackLabel !== null) { - return { label: fastModeFallbackLabel }; + return { label: fastModeFallbackLabel, reasoningLevel }; } if (speedLabel) { if (reasoningLabelIndex >= 0) { @@ -177,5 +187,27 @@ export function buildTraitsTriggerDisplay(input: { labels.push(speedLabel); } } - return { label: labels.join(" · ") }; + return { label: labels.join(" · "), reasoningLevel }; +} + +// One scale for every provider so the same effort reads the same on any model. +const REASONING_LEVEL_RANKS: Readonly> = { + none: 0, + off: 0, + minimal: 1, + low: 2, + medium: 3, + high: 4, + xhigh: 5, + // Claude runs Ultracode at xhigh effort. + ultracode: 5, + max: 6, + ultra: 7, + ultrathink: 7, +}; +const MAX_REASONING_LEVEL_RANK = 7; + +function getReasoningLevel(value: string | boolean | undefined): number | null { + const rank = typeof value === "string" ? REASONING_LEVEL_RANKS[value.toLowerCase()] : undefined; + return rank === undefined ? null : rank / MAX_REASONING_LEVEL_RANK; } From 05268f68880c4a4638e17b7fad607e5eb13718f2 Mon Sep 17 00:00:00 2001 From: Derpedyea Date: Mon, 26 Oct 2026 14:39:23 -0400 Subject: [PATCH 2/3] fix(web): effort gauge reads thinking-off as empty and leaves ultrathink unscaled Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/chat/TraitsPicker.tsx | 4 +- .../client-runtime/src/modelTraits.test.ts | 53 +++++++++++++++++++ packages/client-runtime/src/modelTraits.ts | 46 ++++++++-------- 3 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 packages/client-runtime/src/modelTraits.test.ts diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index c9ed5319436bc..abfc3d8b62aea 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -64,7 +64,9 @@ function TraitsTriggerIcon({ level, size }: { level: number | null; size: Compos data-composer-control-icon > - + {level > 0 && ( + + )} ); diff --git a/packages/client-runtime/src/modelTraits.test.ts b/packages/client-runtime/src/modelTraits.test.ts new file mode 100644 index 0000000000000..88bdcac88daa2 --- /dev/null +++ b/packages/client-runtime/src/modelTraits.test.ts @@ -0,0 +1,53 @@ +import { ProviderDriverKind, type ProviderOptionDescriptor } from "@t3tools/contracts"; +import { describe, expect, it } from "vite-plus/test"; +import { buildTraitsTriggerDisplay } from "./modelTraits.ts"; + +function effort(values: ReadonlyArray, currentValue: string): ProviderOptionDescriptor { + return { + id: "effort", + label: "Reasoning", + type: "select", + options: values.map((id) => ({ id, label: id })), + currentValue, + }; +} + +function reasoningLevel(descriptors: ReadonlyArray) { + return buildTraitsTriggerDisplay({ + provider: ProviderDriverKind.make("cursor"), + descriptors, + primarySelectDescriptorId: "effort", + ultrathinkPromptControlled: false, + }).reasoningLevel; +} + +describe("buildTraitsTriggerDisplay reasoningLevel", () => { + it("reads an effort the same however the provider orders or trims its list", () => { + const claude = reasoningLevel([effort(["low", "medium", "high", "xhigh", "max"], "high")]); + const grok = reasoningLevel([effort(["xhigh", "high", "medium", "low"], "high")]); + const glm = reasoningLevel([effort(["high", "max"], "high")]); + + expect(claude).toBeGreaterThan(0); + expect(grok).toBe(claude); + expect(glm).toBe(claude); + expect(reasoningLevel([effort(["high", "max"], "max")])).toBeGreaterThan(claude ?? 1); + }); + + it("has no reading for efforts outside the shared scale", () => { + for (const value of ["ultrathink", "constructor", "deep"]) { + expect(reasoningLevel([effort(["low", value], value)])).toBeNull(); + } + }); + + it("reads zero when thinking is switched off", () => { + const thinking = (currentValue: boolean): ProviderOptionDescriptor => ({ + id: "thinking", + label: "Thinking", + type: "boolean", + currentValue, + }); + + expect(reasoningLevel([effort(["low", "high"], "high"), thinking(false)])).toBe(0); + expect(reasoningLevel([thinking(true)])).toBeNull(); + }); +}); diff --git a/packages/client-runtime/src/modelTraits.ts b/packages/client-runtime/src/modelTraits.ts index 0037cbd8167cc..fd26ec12ed5e4 100644 --- a/packages/client-runtime/src/modelTraits.ts +++ b/packages/client-runtime/src/modelTraits.ts @@ -115,8 +115,12 @@ export function buildTraitsTriggerDisplay(input: { let speedLabel: string | null = null; let reasoningLabelIndex = -1; let reasoningLevel: number | null = null; + let thinkingOff = false; const labels: Array = []; for (const descriptor of input.descriptors) { + if (descriptor.id === "thinking" && descriptor.type === "boolean") { + thinkingOff = descriptor.currentValue === false; + } if (descriptor.id === "fastMode" && descriptor.type === "boolean") { speedLabel = descriptor.currentValue === true ? "Fast" : null; fastModeFallbackLabel = speedLabel ?? "Normal"; @@ -164,19 +168,18 @@ export function buildTraitsTriggerDisplay(input: { ) { reasoningLabelIndex = labels.length; reasoningLevel = getReasoningLevel( - input.ultrathinkPromptControlled && descriptor.id === input.primarySelectDescriptorId - ? descriptor.promptInjectedValues?.[0] - : getProviderOptionCurrentValue( - descriptor, - input.modelSelection, - input.reportedModelSelection, - ), + getProviderOptionCurrentValue( + descriptor, + input.modelSelection, + input.reportedModelSelection, + ), ); } labels.push(label); } } + if (thinkingOff) reasoningLevel = 0; if (labels.length === 0 && fastModeFallbackLabel !== null) { return { label: fastModeFallbackLabel, reasoningLevel }; } @@ -191,23 +194,24 @@ export function buildTraitsTriggerDisplay(input: { } // One scale for every provider so the same effort reads the same on any model. -const REASONING_LEVEL_RANKS: Readonly> = { - none: 0, - off: 0, - minimal: 1, - low: 2, - medium: 3, - high: 4, - xhigh: 5, +// Ultrathink is absent: it leaves the effort sent to Claude unchanged. +const REASONING_LEVEL_RANKS: ReadonlyMap = new Map([ + ["none", 0], + ["off", 0], + ["minimal", 1], + ["low", 2], + ["medium", 3], + ["high", 4], + ["xhigh", 5], // Claude runs Ultracode at xhigh effort. - ultracode: 5, - max: 6, - ultra: 7, - ultrathink: 7, -}; + ["ultracode", 5], + ["max", 6], + ["ultra", 7], +]); const MAX_REASONING_LEVEL_RANK = 7; function getReasoningLevel(value: string | boolean | undefined): number | null { - const rank = typeof value === "string" ? REASONING_LEVEL_RANKS[value.toLowerCase()] : undefined; + const rank = + typeof value === "string" ? REASONING_LEVEL_RANKS.get(value.toLowerCase()) : undefined; return rank === undefined ? null : rank / MAX_REASONING_LEVEL_RANK; } From 0237b30df19ceac9e1b173d7290bb945d7614c7a Mon Sep 17 00:00:00 2001 From: Derpedyea Date: Tue, 27 Oct 2026 00:10:17 -0400 Subject: [PATCH 3/3] test(client-runtime): keep only the gauge tests that catch regressions Co-Authored-By: Claude Opus 5.5 --- .../client-runtime/src/modelTraits.test.ts | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/packages/client-runtime/src/modelTraits.test.ts b/packages/client-runtime/src/modelTraits.test.ts index 88bdcac88daa2..1bb60634a4df2 100644 --- a/packages/client-runtime/src/modelTraits.test.ts +++ b/packages/client-runtime/src/modelTraits.test.ts @@ -2,17 +2,24 @@ import { ProviderDriverKind, type ProviderOptionDescriptor } from "@t3tools/cont import { describe, expect, it } from "vite-plus/test"; import { buildTraitsTriggerDisplay } from "./modelTraits.ts"; -function effort(values: ReadonlyArray, currentValue: string): ProviderOptionDescriptor { - return { - id: "effort", - label: "Reasoning", - type: "select", - options: values.map((id) => ({ id, label: id })), - currentValue, - }; -} - -function reasoningLevel(descriptors: ReadonlyArray) { +function highEffortLevel(efforts: ReadonlyArray, thinking?: boolean) { + const descriptors: Array = [ + { + id: "effort", + label: "Reasoning", + type: "select", + options: efforts.map((id) => ({ id, label: id })), + currentValue: "high", + }, + ]; + if (thinking !== undefined) { + descriptors.push({ + id: "thinking", + label: "Thinking", + type: "boolean", + currentValue: thinking, + }); + } return buildTraitsTriggerDisplay({ provider: ProviderDriverKind.make("cursor"), descriptors, @@ -22,32 +29,14 @@ function reasoningLevel(descriptors: ReadonlyArray) { } describe("buildTraitsTriggerDisplay reasoningLevel", () => { - it("reads an effort the same however the provider orders or trims its list", () => { - const claude = reasoningLevel([effort(["low", "medium", "high", "xhigh", "max"], "high")]); - const grok = reasoningLevel([effort(["xhigh", "high", "medium", "low"], "high")]); - const glm = reasoningLevel([effort(["high", "max"], "high")]); - + it("reads High the same however the provider orders or trims its efforts", () => { + const claude = highEffortLevel(["low", "medium", "high", "xhigh", "max"]); expect(claude).toBeGreaterThan(0); - expect(grok).toBe(claude); - expect(glm).toBe(claude); - expect(reasoningLevel([effort(["high", "max"], "max")])).toBeGreaterThan(claude ?? 1); - }); - - it("has no reading for efforts outside the shared scale", () => { - for (const value of ["ultrathink", "constructor", "deep"]) { - expect(reasoningLevel([effort(["low", value], value)])).toBeNull(); - } + expect(highEffortLevel(["xhigh", "high", "medium", "low"])).toBe(claude); + expect(highEffortLevel(["high", "max"])).toBe(claude); }); it("reads zero when thinking is switched off", () => { - const thinking = (currentValue: boolean): ProviderOptionDescriptor => ({ - id: "thinking", - label: "Thinking", - type: "boolean", - currentValue, - }); - - expect(reasoningLevel([effort(["low", "high"], "high"), thinking(false)])).toBe(0); - expect(reasoningLevel([thinking(true)])).toBeNull(); + expect(highEffortLevel(["low", "high"], false)).toBe(0); }); });