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 {...@@ -2,17 +2,18 @@ import {
2 type ApprovalRequestId,2 type ApprovalRequestId,
3 CURSOR_REASONING_OPTIONS,3 CURSOR_REASONING_OPTIONS,
4 DEFAULT_MODEL,4 DEFAULT_MODEL,
5 DEFAULT_REASONING,
6 EDITORS,5 EDITORS,
7 type EditorId,6 type EditorId,
8 type KeybindingCommand,7 type KeybindingCommand,
8 type CodexReasoningEffort,
9 type CursorReasoningOption,9 type CursorReasoningOption,
10 type MessageId,10 type MessageId,
11 getDefaultModel,11 getDefaultModel,
12 getDefaultReasoningEffort,
12 getCursorModelCapabilities,13 getCursorModelCapabilities,
13 getCursorModelFamilyOptions,14 getCursorModelFamilyOptions,
14 getModelOptions,15 getModelOptions,
15 getReasoningOptions,16 getReasoningEffortOptions,
16 type ProjectId,17 type ProjectId,
17 type ProjectEntry,18 type ProjectEntry,
18 type ProjectScript,19 type ProjectScript,
...@@ -20,7 +21,6 @@ import {...@@ -20,7 +21,6 @@ import {
20 parseCursorModelSelection,21 parseCursorModelSelection,
21 PROVIDER_SEND_TURN_MAX_ATTACHMENTS,22 PROVIDER_SEND_TURN_MAX_ATTACHMENTS,
22 PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,23 PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,
23 type ReasoningEffort,
24 type ResolvedKeybindingsConfig,24 type ResolvedKeybindingsConfig,
25 type ProviderApprovalDecision,25 type ProviderApprovalDecision,
26 type ServerProviderStatus,26 type ServerProviderStatus,
...@@ -683,8 +683,8 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -683,8 +683,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
683 DEFAULT_MODEL,683 DEFAULT_MODEL,
684 );684 );
685 const selectedModel = resolveModelSlugForProvider(selectedProvider, composerDraft.model ?? baseThreadModel);685 const selectedModel = resolveModelSlugForProvider(selectedProvider, composerDraft.model ?? baseThreadModel);
686 const selectedEffort = composerDraft.effort ?? DEFAULT_REASONING;686 const selectedEffort = composerDraft.effort ?? getDefaultReasoningEffort(selectedProvider);
687 const reasoningOptions = getReasoningOptions(selectedProvider);687 const reasoningOptions = getReasoningEffortOptions(selectedProvider);
688 const supportsReasoningEffort = reasoningOptions.length > 0;688 const supportsReasoningEffort = reasoningOptions.length > 0;
689 const selectedCursorModel = useMemo(689 const selectedCursorModel = useMemo(
690 () => (selectedProvider === "cursor" ? parseCursorModelSelection(selectedModel) : null),690 () => (selectedProvider === "cursor" ? parseCursorModelSelection(selectedModel) : null),
...@@ -2386,8 +2386,8 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -2386,8 +2386,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
2386 [onProviderModelSelect, selectedModel, selectedProvider],2386 [onProviderModelSelect, selectedModel, selectedProvider],
2387 );2387 );
2388 const onEffortSelect = useCallback(2388 const onEffortSelect = useCallback(
2389 (effort: ReasoningEffort) => {2389 (effort: CodexReasoningEffort) => {
2390 setComposerDraftEffort(threadId, effort);2390 setSelectedEffort(effort);
2391 scheduleComposerFocus();2391 scheduleComposerFocus();
2392 },2392 },
2393 [scheduleComposerFocus, setComposerDraftEffort, threadId],2393 [scheduleComposerFocus, setComposerDraftEffort, threadId],
...@@ -4132,10 +4132,12 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {...@@ -4132,10 +4132,12 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
4132});4132});
41334133
4134const ReasoningEffortPicker = memo(function ReasoningEffortPicker(props: {4134const ReasoningEffortPicker = memo(function ReasoningEffortPicker(props: {
4135 effort: ReasoningEffort;4135 effort: CodexReasoningEffort;
4136 options: ReadonlyArray<ReasoningEffort>;4136 options: ReadonlyArray<CodexReasoningEffort>;
4137 onEffortChange: (effort: ReasoningEffort) => void;4137 onEffortChange: (effort: CodexReasoningEffort) => void;
4138}) {4138}) {
4139 const defaultReasoningEffort = getDefaultReasoningEffort("codex");
4140
4139 return (4141 return (
4140 <Select4142 <Select
4141 value={props.effort}4143 value={props.effort}
...@@ -4148,7 +4150,7 @@ const ReasoningEffortPicker = memo(function ReasoningEffortPicker(props: {...@@ -4148,7 +4150,7 @@ const ReasoningEffortPicker = memo(function ReasoningEffortPicker(props: {
4148 {props.options.map((effort) => (4150 {props.options.map((effort) => (
4149 <SelectItem key={effort} value={effort}>4151 <SelectItem key={effort} value={effort}>
4150 {effort}4152 {effort}
4151 {effort === DEFAULT_REASONING ? " (default)" : ""}4153 {effort === defaultReasoningEffort ? " (default)" : ""}
4152 </SelectItem>4154 </SelectItem>
4153 ))}4155 ))}
4154 </SelectPopup>4156 </SelectPopup>
packages/contracts/src/model.test.ts+27-5
...@@ -7,12 +7,14 @@ import {...@@ -7,12 +7,14 @@ import {
7 DEFAULT_MODEL_BY_PROVIDER,7 DEFAULT_MODEL_BY_PROVIDER,
8 MODEL_OPTIONS,8 MODEL_OPTIONS,
9 MODEL_OPTIONS_BY_PROVIDER,9 MODEL_OPTIONS_BY_PROVIDER,
10 REASONING_EFFORT_OPTIONS_BY_PROVIDER,
11 DEFAULT_REASONING_EFFORT_BY_PROVIDER,
10 getDefaultModel,12 getDefaultModel,
13 getDefaultReasoningEffort,
11 getCursorModelFamilyOptions,14 getCursorModelFamilyOptions,
12 getModelOptions,15 getModelOptions,
13 getReasoningOptions,16 getReasoningEffortOptions,
14 normalizeModelSlug,17 normalizeModelSlug,
15 REASONING_OPTIONS,
16 parseCursorModelSelection,18 parseCursorModelSelection,
17 resolveCursorModelFromSelection,19 resolveCursorModelFromSelection,
18 resolveModelSlug,20 resolveModelSlug,
...@@ -148,12 +150,32 @@ describe("cursor model selection", () => {...@@ -148,12 +150,32 @@ describe("cursor model selection", () => {
148 });150 });
149});151});
150152
151describe("getReasoningOptions", () => {153describe("getReasoningEffortOptions", () => {
152 it("returns codex reasoning options for codex", () => {154 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 );
154 });158 });
155159
156 it("returns no reasoning options for claudeCode", () => {160 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 );
158 });180 });
159});181});
packages/contracts/src/model.ts+23-10
...@@ -491,18 +491,31 @@ export function resolveModelSlugForProvider(...@@ -491,18 +491,31 @@ export function resolveModelSlugForProvider(
491 return resolveModelSlug(model, provider);491 return resolveModelSlug(model, provider);
492}492}
493493
494export const REASONING_OPTIONS_BY_PROVIDER = {494const CODEX_REASONING_EFFORT_OPTIONS = ["xhigh", "high", "medium", "low"] as const;
495 codex: ["xhigh", "high", "medium", "low"],495export type CodexReasoningEffort = (typeof CODEX_REASONING_EFFORT_OPTIONS)[number];
496
497export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = {
498 codex: CODEX_REASONING_EFFORT_OPTIONS,
496 claudeCode: [],499 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.503export const DEFAULT_REASONING_EFFORT_BY_PROVIDER = {
500export const REASONING_OPTIONS = REASONING_OPTIONS_BY_PROVIDER.codex;504 codex: "high",
501export type ReasoningEffort = (typeof REASONING_OPTIONS_BY_PROVIDER.codex)[number];505 claudeCode: null,
502export const DEFAULT_REASONING: ReasoningEffort = "high";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(
505 provider: ProviderKind = "codex",518 provider: ProviderKind = "codex",
506): ReadonlyArray<ReasoningEffort> {519): CodexReasoningEffort | null {
507 return REASONING_OPTIONS_BY_PROVIDER[provider] as ReadonlyArray<ReasoningEffort>;520 return DEFAULT_REASONING_EFFORT_BY_PROVIDER[provider];
508}521}