authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-01 01:59:22-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
logaeddb5759425de2f9fd589843a2392280d6c89ef
tree06bfd311491d5e28460a957e78f4e5d55ea8fdb3
parentf2253269ddf80446425b06629ed5dd8809a2bc1e

Scope reasoning effort defaults and options by provider

- Replace global reasoning constants with provider-scoped effort options/defaults - Update ChatView to use Codex-specific reasoning effort types and default lookup - Expand model contract tests for provider-specific reasoning effort behavior

3 files changed, 63 insertions(+), 26 deletions(-)

apps/web/src/components/ChatView.tsx+13-11
......@@ -2,17 +2,18 @@ import {
22 type ApprovalRequestId,
33 CURSOR_REASONING_OPTIONS,
44 DEFAULT_MODEL,
5 DEFAULT_REASONING,
65 EDITORS,
76 type EditorId,
87 type KeybindingCommand,
8 type CodexReasoningEffort,
99 type CursorReasoningOption,
1010 type MessageId,
1111 getDefaultModel,
12 getDefaultReasoningEffort,
1213 getCursorModelCapabilities,
1314 getCursorModelFamilyOptions,
1415 getModelOptions,
15 getReasoningOptions,
16 getReasoningEffortOptions,
1617 type ProjectId,
1718 type ProjectEntry,
1819 type ProjectScript,
......@@ -20,7 +21,6 @@ import {
2021 parseCursorModelSelection,
2122 PROVIDER_SEND_TURN_MAX_ATTACHMENTS,
2223 PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,
23 type ReasoningEffort,
2424 type ResolvedKeybindingsConfig,
2525 type ProviderApprovalDecision,
2626 type ServerProviderStatus,
......@@ -683,8 +683,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
683683 DEFAULT_MODEL,
684684 );
685685 const selectedModel = resolveModelSlugForProvider(selectedProvider, composerDraft.model ?? baseThreadModel);
686 const selectedEffort = composerDraft.effort ?? DEFAULT_REASONING;
687 const reasoningOptions = getReasoningOptions(selectedProvider);
686 const selectedEffort = composerDraft.effort ?? getDefaultReasoningEffort(selectedProvider);
687 const reasoningOptions = getReasoningEffortOptions(selectedProvider);
688688 const supportsReasoningEffort = reasoningOptions.length > 0;
689689 const selectedCursorModel = useMemo(
690690 () => (selectedProvider === "cursor" ? parseCursorModelSelection(selectedModel) : null),
......@@ -2386,8 +2386,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
23862386 [onProviderModelSelect, selectedModel, selectedProvider],
23872387 );
23882388 const onEffortSelect = useCallback(
2389 (effort: ReasoningEffort) => {
2390 setComposerDraftEffort(threadId, effort);
2389 (effort: CodexReasoningEffort) => {
2390 setSelectedEffort(effort);
23912391 scheduleComposerFocus();
23922392 },
23932393 [scheduleComposerFocus, setComposerDraftEffort, threadId],
......@@ -4132,10 +4132,12 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
41324132});
41334133
41344134const ReasoningEffortPicker = memo(function ReasoningEffortPicker(props: {
4135 effort: ReasoningEffort;
4136 options: ReadonlyArray<ReasoningEffort>;
4137 onEffortChange: (effort: ReasoningEffort) => void;
4135 effort: CodexReasoningEffort;
4136 options: ReadonlyArray<CodexReasoningEffort>;
4137 onEffortChange: (effort: CodexReasoningEffort) => void;
41384138}) {
4139 const defaultReasoningEffort = getDefaultReasoningEffort("codex");
4140
41394141 return (
41404142 <Select
41414143 value={props.effort}
......@@ -4148,7 +4150,7 @@ const ReasoningEffortPicker = memo(function ReasoningEffortPicker(props: {
41484150 {props.options.map((effort) => (
41494151 <SelectItem key={effort} value={effort}>
41504152 {effort}
4151 {effort === DEFAULT_REASONING ? " (default)" : ""}
4153 {effort === defaultReasoningEffort ? " (default)" : ""}
41524154 </SelectItem>
41534155 ))}
41544156 </SelectPopup>
packages/contracts/src/model.test.ts+27-5
......@@ -7,12 +7,14 @@ import {
77 DEFAULT_MODEL_BY_PROVIDER,
88 MODEL_OPTIONS,
99 MODEL_OPTIONS_BY_PROVIDER,
10 REASONING_EFFORT_OPTIONS_BY_PROVIDER,
11 DEFAULT_REASONING_EFFORT_BY_PROVIDER,
1012 getDefaultModel,
13 getDefaultReasoningEffort,
1114 getCursorModelFamilyOptions,
1215 getModelOptions,
13 getReasoningOptions,
16 getReasoningEffortOptions,
1417 normalizeModelSlug,
15 REASONING_OPTIONS,
1618 parseCursorModelSelection,
1719 resolveCursorModelFromSelection,
1820 resolveModelSlug,
......@@ -148,12 +150,32 @@ describe("cursor model selection", () => {
148150 });
149151});
150152
151describe("getReasoningOptions", () => {
153describe("getReasoningEffortOptions", () => {
152154 it("returns codex reasoning options for codex", () => {
153 expect(getReasoningOptions("codex")).toEqual(REASONING_OPTIONS);
155 expect(getReasoningEffortOptions("codex")).toEqual(
156 REASONING_EFFORT_OPTIONS_BY_PROVIDER.codex,
157 );
154158 });
155159
156160 it("returns no reasoning options for claudeCode", () => {
157 expect(getReasoningOptions("claudeCode")).toEqual([]);
161 expect(getReasoningEffortOptions("claudeCode")).toEqual([]);
162 });
163
164 it("returns no reasoning options for cursor", () => {
165 expect(getReasoningEffortOptions("cursor")).toEqual([]);
166 });
167});
168
169describe("getDefaultReasoningEffort", () => {
170 it("returns provider-scoped defaults", () => {
171 expect(getDefaultReasoningEffort("codex")).toBe(
172 DEFAULT_REASONING_EFFORT_BY_PROVIDER.codex,
173 );
174 expect(getDefaultReasoningEffort("claudeCode")).toBe(
175 DEFAULT_REASONING_EFFORT_BY_PROVIDER.claudeCode,
176 );
177 expect(getDefaultReasoningEffort("cursor")).toBe(
178 DEFAULT_REASONING_EFFORT_BY_PROVIDER.cursor,
179 );
158180 });
159181});
packages/contracts/src/model.ts+23-10
......@@ -491,18 +491,31 @@ export function resolveModelSlugForProvider(
491491 return resolveModelSlug(model, provider);
492492}
493493
494export const REASONING_OPTIONS_BY_PROVIDER = {
495 codex: ["xhigh", "high", "medium", "low"],
494const CODEX_REASONING_EFFORT_OPTIONS = ["xhigh", "high", "medium", "low"] as const;
495export type CodexReasoningEffort = (typeof CODEX_REASONING_EFFORT_OPTIONS)[number];
496
497export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = {
498 codex: CODEX_REASONING_EFFORT_OPTIONS,
496499 claudeCode: [],
497} as const satisfies Record<ProviderKind, readonly string[]>;
500 cursor: [],
501} as const satisfies Record<ProviderKind, readonly CodexReasoningEffort[]>;
498502
499// Backward compatibility for existing Codex-only call sites.
500export const REASONING_OPTIONS = REASONING_OPTIONS_BY_PROVIDER.codex;
501export type ReasoningEffort = (typeof REASONING_OPTIONS_BY_PROVIDER.codex)[number];
502export const DEFAULT_REASONING: ReasoningEffort = "high";
503export const DEFAULT_REASONING_EFFORT_BY_PROVIDER = {
504 codex: "high",
505 claudeCode: null,
506 cursor: null,
507} as const satisfies Record<ProviderKind, CodexReasoningEffort | null>;
508
509export function getReasoningEffortOptions(
510 provider: ProviderKind = "codex",
511): ReadonlyArray<CodexReasoningEffort> {
512 return REASONING_EFFORT_OPTIONS_BY_PROVIDER[provider];
513}
503514
504export function getReasoningOptions(
515export function getDefaultReasoningEffort(provider: "codex"): CodexReasoningEffort;
516export function getDefaultReasoningEffort(provider: ProviderKind): CodexReasoningEffort | null;
517export function getDefaultReasoningEffort(
505518 provider: ProviderKind = "codex",
506): ReadonlyArray<ReasoningEffort> {
507 return REASONING_OPTIONS_BY_PROVIDER[provider] as ReadonlyArray<ReasoningEffort>;
519): CodexReasoningEffort | null {
520 return DEFAULT_REASONING_EFFORT_BY_PROVIDER[provider];
508521}