authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-07-30 02:15:48-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-30 02:15:48-07:00
log9146ed2f49a094aa8afee7d3a66987e2a82aaaf6
tree1998debaf5e0b1e6fd9a96d702bbba344419ece1
parentf0f16e4f6e8e923dcb17fc1c25d4fb1e442f80a5
signature Signed by PGP key B5690EEEBB952194

fix(web): show Codex fast mode as a bolt (#4947)


2 files changed, 30 insertions(+), 24 deletions(-)

apps/web/src/components/chat/TraitsPicker.test.ts+20-12
...@@ -35,39 +35,48 @@ const CONTEXT_WINDOW = selectDescriptor(...@@ -35,39 +35,48 @@ const CONTEXT_WINDOW = selectDescriptor(
3535
36const CODEX = ProviderDriverKind.make("codex");36const CODEX = ProviderDriverKind.make("codex");
3737
38function display(descriptors: ReadonlyArray<ProviderOptionDescriptor>, fastModeEnabled: boolean) {38function display(descriptors: ReadonlyArray<ProviderOptionDescriptor>) {
39 return buildTraitsTriggerDisplay({39 return buildTraitsTriggerDisplay({
40 provider: CODEX,40 provider: CODEX,
41 descriptors,41 descriptors,
42 primarySelectDescriptorId: "reasoningEffort",42 primarySelectDescriptorId: "reasoningEffort",
43 ultrathinkPromptControlled: false,43 ultrathinkPromptControlled: false,
44 fastModeEnabled,
45 });44 });
46}45}
4746
48describe("buildTraitsTriggerDisplay", () => {47describe("buildTraitsTriggerDisplay", () => {
49 it("omits fast mode from the label entirely when it is off", () => {48 it("omits fast mode from the label entirely when it is off", () => {
50 expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW], false)).toEqual({49 expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW])).toEqual({
51 label: "High · 1M",50 label: "High · 1M",
52 showFastModeIcon: false,51 showFastModeIcon: false,
53 });52 });
54 });53 });
5554
56 it("shows the bolt instead of a text label when fast mode is on", () => {55 it("shows the bolt instead of a text label when fast mode is on", () => {
57 expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW], true)).toEqual({56 expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW])).toEqual({
58 label: "High · 1M",57 label: "High · 1M",
59 showFastModeIcon: true,58 showFastModeIcon: true,
60 });59 });
61 });60 });
6261
63 it("omits Codex's default Standard tier beside reasoning", () => {62 it("renders Codex's Standard and Fast service tiers as fast mode", () => {
64 const serviceTier = selectDescriptor(63 const serviceTier = selectDescriptor(
65 "serviceTier",64 "serviceTier",
66 [{ id: "default", label: "Standard", isDefault: true }],65 [
66 { id: "default", label: "Standard", isDefault: true },
67 { id: "priority", label: "Fast" },
68 ],
67 "default",69 "default",
68 );70 );
6971
70 expect(display([EFFORT, serviceTier], false).label).toBe("High");72 expect(display([EFFORT, serviceTier])).toEqual({
73 label: "High",
74 showFastModeIcon: false,
75 });
76 expect(display([EFFORT, { ...serviceTier, currentValue: "priority" }])).toEqual({
77 label: "High",
78 showFastModeIcon: true,
79 });
71 });80 });
7281
73 it("keeps non-fastMode booleans as text labels", () => {82 it("keeps non-fastMode booleans as text labels", () => {
...@@ -77,18 +86,18 @@ describe("buildTraitsTriggerDisplay", () => {...@@ -77,18 +86,18 @@ describe("buildTraitsTriggerDisplay", () => {
77 type: "boolean",86 type: "boolean",
78 currentValue: true,87 currentValue: true,
79 };88 };
80 expect(display([EFFORT, thinking], false)).toEqual({89 expect(display([EFFORT, thinking])).toEqual({
81 label: "High · Thinking On",90 label: "High · Thinking On",
82 showFastModeIcon: false,91 showFastModeIcon: false,
83 });92 });
84 });93 });
8594
86 it("falls back to a text label when fast mode is the only trait", () => {95 it("falls back to a text label when fast mode is the only trait", () => {
87 expect(display([fastModeDescriptor(true)], true)).toEqual({96 expect(display([fastModeDescriptor(true)])).toEqual({
88 label: "Fast",97 label: "Fast",
89 showFastModeIcon: false,98 showFastModeIcon: false,
90 });99 });
91 expect(display([fastModeDescriptor(false)], false)).toEqual({100 expect(display([fastModeDescriptor(false)])).toEqual({
92 label: "Normal",101 label: "Normal",
93 showFastModeIcon: false,102 showFastModeIcon: false,
94 });103 });
...@@ -107,7 +116,7 @@ describe("buildTraitsTriggerDisplay", () => {...@@ -107,7 +116,7 @@ describe("buildTraitsTriggerDisplay", () => {
107 { id: "high", label: "High" },116 { id: "high", label: "High" },
108 ],117 ],
109 };118 };
110 expect(display([unresolved], false)).toEqual({ label: "", showFastModeIcon: false });119 expect(display([unresolved])).toEqual({ label: "", showFastModeIcon: false });
111 });120 });
112121
113 it("still renders the prompt-controlled ultrathink label alongside the bolt", () => {122 it("still renders the prompt-controlled ultrathink label alongside the bolt", () => {
...@@ -117,7 +126,6 @@ describe("buildTraitsTriggerDisplay", () => {...@@ -117,7 +126,6 @@ describe("buildTraitsTriggerDisplay", () => {
117 descriptors: [EFFORT, fastModeDescriptor(true)],126 descriptors: [EFFORT, fastModeDescriptor(true)],
118 primarySelectDescriptorId: "reasoningEffort",127 primarySelectDescriptorId: "reasoningEffort",
119 ultrathinkPromptControlled: true,128 ultrathinkPromptControlled: true,
120 fastModeEnabled: true,
121 }),129 }),
122 ).toEqual({ label: "Ultrathink", showFastModeIcon: true });130 ).toEqual({ label: "Ultrathink", showFastModeIcon: true });
123 });131 });
apps/web/src/components/chat/TraitsPicker.tsx+10-12
...@@ -134,8 +134,6 @@ function getSelectedTraits(...@@ -134,8 +134,6 @@ function getSelectedTraits(
134 : getDescriptorStringValue(primarySelectDescriptor)) ?? null;134 : getDescriptorStringValue(primarySelectDescriptor)) ?? null;
135 const thinkingEnabled =135 const thinkingEnabled =
136 typeof thinkingDescriptor?.currentValue === "boolean" ? thinkingDescriptor.currentValue : null;136 typeof thinkingDescriptor?.currentValue === "boolean" ? thinkingDescriptor.currentValue : null;
137 const fastModeEnabled =
138 typeof fastModeDescriptor?.currentValue === "boolean" ? fastModeDescriptor.currentValue : false;
139 const contextWindow = getDescriptorStringValue(contextWindowDescriptor);137 const contextWindow = getDescriptorStringValue(contextWindowDescriptor);
140 const selectedAgent = getDescriptorStringValue(agentDescriptor);138 const selectedAgent = getDescriptorStringValue(agentDescriptor);
141 const selectedAgentLabel = agentDescriptor139 const selectedAgentLabel = agentDescriptor
...@@ -154,7 +152,6 @@ function getSelectedTraits(...@@ -154,7 +152,6 @@ function getSelectedTraits(
154 thinkingDescriptor,152 thinkingDescriptor,
155 effort,153 effort,
156 thinkingEnabled,154 thinkingEnabled,
157 fastModeEnabled,
158 contextWindow,155 contextWindow,
159 ultrathinkPromptControlled,156 ultrathinkPromptControlled,
160 ultrathinkInBodyText,157 ultrathinkInBodyText,
...@@ -392,24 +389,26 @@ export function buildTraitsTriggerDisplay(input: {...@@ -392,24 +389,26 @@ export function buildTraitsTriggerDisplay(input: {
392 descriptors: ReadonlyArray<ProviderOptionDescriptor>;389 descriptors: ReadonlyArray<ProviderOptionDescriptor>;
393 primarySelectDescriptorId: string | null;390 primarySelectDescriptorId: string | null;
394 ultrathinkPromptControlled: boolean;391 ultrathinkPromptControlled: boolean;
395 fastModeEnabled: boolean;
396}): { label: string; showFastModeIcon: boolean } {392}): { label: string; showFastModeIcon: boolean } {
397 let hasFastMode = false;393 let hasFastMode = false;
394 let fastModeEnabled = false;
398 const labels: Array<string> = [];395 const labels: Array<string> = [];
399 for (const descriptor of input.descriptors) {396 for (const descriptor of input.descriptors) {
400 if (descriptor.id === "fastMode" && descriptor.type === "boolean") {397 if (descriptor.id === "fastMode" && descriptor.type === "boolean") {
401 hasFastMode = true;398 hasFastMode = true;
399 fastModeEnabled = descriptor.currentValue === true;
402 continue;400 continue;
403 }401 }
404 if (402 if (
405 input.provider === "codex" &&403 input.provider === "codex" &&
406 descriptor.id === "serviceTier" &&404 descriptor.id === "serviceTier" &&
407 descriptor.type === "select" &&405 descriptor.type === "select"
408 input.descriptors.some(({ id }) => id === "reasoningEffort")
409 ) {406 ) {
410 const currentValue = getProviderOptionCurrentValue(descriptor);407 const currentValue = getProviderOptionCurrentValue(descriptor);
411 const currentOption = descriptor.options.find((option) => option.id === currentValue);408 const fastTier = descriptor.options.find(({ label }) => label === "Fast");
412 if (currentOption?.id === "default" && currentOption.isDefault === true) {409 if (fastTier && (currentValue === "default" || currentValue === fastTier.id)) {
410 hasFastMode = true;
411 fastModeEnabled = currentValue === fastTier.id;
413 continue;412 continue;
414 }413 }
415 }414 }
...@@ -428,9 +427,9 @@ export function buildTraitsTriggerDisplay(input: {...@@ -428,9 +427,9 @@ export function buildTraitsTriggerDisplay(input: {
428 // off an empty label list alone would also catch descriptors that resolved to427 // off an empty label list alone would also catch descriptors that resolved to
429 // no label at all, printing a bogus "Normal" for a model without fast mode.428 // no label at all, printing a bogus "Normal" for a model without fast mode.
430 if (labels.length === 0 && hasFastMode) {429 if (labels.length === 0 && hasFastMode) {
431 return { label: input.fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false };430 return { label: fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false };
432 }431 }
433 return { label: labels.join(" · "), showFastModeIcon: input.fastModeEnabled };432 return { label: labels.join(" · "), showFastModeIcon: fastModeEnabled };
434}433}
435434
436export const TraitsPicker = memo(function TraitsPicker({435export const TraitsPicker = memo(function TraitsPicker({
...@@ -447,7 +446,7 @@ export const TraitsPicker = memo(function TraitsPicker({...@@ -447,7 +446,7 @@ export const TraitsPicker = memo(function TraitsPicker({
447 ...persistence446 ...persistence
448}: TraitsMenuContentProps & TraitsPersistence) {447}: TraitsMenuContentProps & TraitsPersistence) {
449 const [isMenuOpen, setIsMenuOpen] = useState(false);448 const [isMenuOpen, setIsMenuOpen] = useState(false);
450 const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled, fastModeEnabled } =449 const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled } =
451 getTraitsSectionVisibility({450 getTraitsSectionVisibility({
452 provider,451 provider,
453 models,452 models,
...@@ -474,7 +473,6 @@ export const TraitsPicker = memo(function TraitsPicker({...@@ -474,7 +473,6 @@ export const TraitsPicker = memo(function TraitsPicker({
474 descriptors,473 descriptors,
475 primarySelectDescriptorId: primarySelectDescriptor?.id ?? null,474 primarySelectDescriptorId: primarySelectDescriptor?.id ?? null,
476 ultrathinkPromptControlled,475 ultrathinkPromptControlled,
477 fastModeEnabled,
478 });476 });
479 const fastModeIcon = showFastModeIcon ? (477 const fastModeIcon = showFastModeIcon ? (
480 <>478 <>