| author | |
| committer | |
| log | aeddb5759425de2f9fd589843a2392280d6c89ef |
| tree | 06bfd311491d5e28460a957e78f4e5d55ea8fdb3 |
| parent | f2253269ddf80446425b06629ed5dd8809a2bc1e |
- 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 behavior3 files changed, 63 insertions(+), 26 deletions(-)
apps/web/src/components/ChatView.tsx+13-11| ... | ... | @@ -2,17 +2,18 @@ import { |
| 2 | 2 | type ApprovalRequestId, |
| 3 | 3 | CURSOR_REASONING_OPTIONS, |
| 4 | 4 | DEFAULT_MODEL, |
| 5 | DEFAULT_REASONING, | |
| 6 | 5 | EDITORS, |
| 7 | 6 | type EditorId, |
| 8 | 7 | type KeybindingCommand, |
| 8 | type CodexReasoningEffort, | |
| 9 | 9 | type CursorReasoningOption, |
| 10 | 10 | type MessageId, |
| 11 | 11 | getDefaultModel, |
| 12 | getDefaultReasoningEffort, | |
| 12 | 13 | getCursorModelCapabilities, |
| 13 | 14 | getCursorModelFamilyOptions, |
| 14 | 15 | getModelOptions, |
| 15 | getReasoningOptions, | |
| 16 | getReasoningEffortOptions, | |
| 16 | 17 | type ProjectId, |
| 17 | 18 | type ProjectEntry, |
| 18 | 19 | type ProjectScript, |
| ... | ... | @@ -20,7 +21,6 @@ import { |
| 20 | 21 | parseCursorModelSelection, |
| 21 | 22 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, |
| 22 | 23 | PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, |
| 23 | type ReasoningEffort, | |
| 24 | 24 | type ResolvedKeybindingsConfig, |
| 25 | 25 | type ProviderApprovalDecision, |
| 26 | 26 | type ServerProviderStatus, |
| ... | ... | @@ -683,8 +683,8 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 683 | 683 | DEFAULT_MODEL, |
| 684 | 684 | ); |
| 685 | 685 | 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); | |
| 688 | 688 | const supportsReasoningEffort = reasoningOptions.length > 0; |
| 689 | 689 | const selectedCursorModel = useMemo( |
| 690 | 690 | () => (selectedProvider === "cursor" ? parseCursorModelSelection(selectedModel) : null), |
| ... | ... | @@ -2386,8 +2386,8 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 2386 | 2386 | [onProviderModelSelect, selectedModel, selectedProvider], |
| 2387 | 2387 | ); |
| 2388 | 2388 | const onEffortSelect = useCallback( |
| 2389 | (effort: ReasoningEffort) => { | |
| 2390 | setComposerDraftEffort(threadId, effort); | |
| 2389 | (effort: CodexReasoningEffort) => { | |
| 2390 | setSelectedEffort(effort); | |
| 2391 | 2391 | scheduleComposerFocus(); |
| 2392 | 2392 | }, |
| 2393 | 2393 | [scheduleComposerFocus, setComposerDraftEffort, threadId], |
| ... | ... | @@ -4132,10 +4132,12 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: { |
| 4132 | 4132 | }); |
| 4133 | 4133 | |
| 4134 | 4134 | const 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; | |
| 4138 | 4138 | }) { |
| 4139 | const defaultReasoningEffort = getDefaultReasoningEffort("codex"); | |
| 4140 | ||
| 4139 | 4141 | return ( |
| 4140 | 4142 | <Select |
| 4141 | 4143 | value={props.effort} |
| ... | ... | @@ -4148,7 +4150,7 @@ const ReasoningEffortPicker = memo(function ReasoningEffortPicker(props: { |
| 4148 | 4150 | {props.options.map((effort) => ( |
| 4149 | 4151 | <SelectItem key={effort} value={effort}> |
| 4150 | 4152 | {effort} |
| 4151 | {effort === DEFAULT_REASONING ? " (default)" : ""} | |
| 4153 | {effort === defaultReasoningEffort ? " (default)" : ""} | |
| 4152 | 4154 | </SelectItem> |
| 4153 | 4155 | ))} |
| 4154 | 4156 | </SelectPopup> |
packages/contracts/src/model.test.ts+27-5| ... | ... | @@ -7,12 +7,14 @@ import { |
| 7 | 7 | DEFAULT_MODEL_BY_PROVIDER, |
| 8 | 8 | MODEL_OPTIONS, |
| 9 | 9 | MODEL_OPTIONS_BY_PROVIDER, |
| 10 | REASONING_EFFORT_OPTIONS_BY_PROVIDER, | |
| 11 | DEFAULT_REASONING_EFFORT_BY_PROVIDER, | |
| 10 | 12 | getDefaultModel, |
| 13 | getDefaultReasoningEffort, | |
| 11 | 14 | getCursorModelFamilyOptions, |
| 12 | 15 | getModelOptions, |
| 13 | getReasoningOptions, | |
| 16 | getReasoningEffortOptions, | |
| 14 | 17 | normalizeModelSlug, |
| 15 | REASONING_OPTIONS, | |
| 16 | 18 | parseCursorModelSelection, |
| 17 | 19 | resolveCursorModelFromSelection, |
| 18 | 20 | resolveModelSlug, |
| ... | ... | @@ -148,12 +150,32 @@ describe("cursor model selection", () => { |
| 148 | 150 | }); |
| 149 | 151 | }); |
| 150 | 152 | |
| 151 | describe("getReasoningOptions", () => { | |
| 153 | describe("getReasoningEffortOptions", () => { | |
| 152 | 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 | }); |
| 155 | 159 | |
| 156 | 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 | ||
| 169 | describe("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 | 491 | return resolveModelSlug(model, provider); |
| 492 | 492 | } |
| 493 | 493 | |
| 494 | export const REASONING_OPTIONS_BY_PROVIDER = { | |
| 495 | codex: ["xhigh", "high", "medium", "low"], | |
| 494 | const CODEX_REASONING_EFFORT_OPTIONS = ["xhigh", "high", "medium", "low"] as const; | |
| 495 | export type CodexReasoningEffort = (typeof CODEX_REASONING_EFFORT_OPTIONS)[number]; | |
| 496 | ||
| 497 | export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = { | |
| 498 | codex: CODEX_REASONING_EFFORT_OPTIONS, | |
| 496 | 499 | claudeCode: [], |
| 497 | } as const satisfies Record<ProviderKind, readonly string[]>; | |
| 500 | cursor: [], | |
| 501 | } as const satisfies Record<ProviderKind, readonly CodexReasoningEffort[]>; | |
| 498 | 502 | |
| 499 | // Backward compatibility for existing Codex-only call sites. | |
| 500 | export const REASONING_OPTIONS = REASONING_OPTIONS_BY_PROVIDER.codex; | |
| 501 | export type ReasoningEffort = (typeof REASONING_OPTIONS_BY_PROVIDER.codex)[number]; | |
| 502 | export const DEFAULT_REASONING: ReasoningEffort = "high"; | |
| 503 | export const DEFAULT_REASONING_EFFORT_BY_PROVIDER = { | |
| 504 | codex: "high", | |
| 505 | claudeCode: null, | |
| 506 | cursor: null, | |
| 507 | } as const satisfies Record<ProviderKind, CodexReasoningEffort | null>; | |
| 508 | ||
| 509 | export function getReasoningEffortOptions( | |
| 510 | provider: ProviderKind = "codex", | |
| 511 | ): ReadonlyArray<CodexReasoningEffort> { | |
| 512 | return REASONING_EFFORT_OPTIONS_BY_PROVIDER[provider]; | |
| 513 | } | |
| 503 | 514 | |
| 504 | export function getReasoningOptions( | |
| 515 | export function getDefaultReasoningEffort(provider: "codex"): CodexReasoningEffort; | |
| 516 | export function getDefaultReasoningEffort(provider: ProviderKind): CodexReasoningEffort | null; | |
| 517 | export function getDefaultReasoningEffort( | |
| 505 | 518 | 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]; | |
| 508 | 521 | } |