feat: add Claude and Codex effort controls#943
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds reasoning-effort metadata for Claude and Codex providers, validates effort in server-side SDK query flows, and threads effort state through chat provider state, composer submission, and the composer dropdown UI. It also updates the agent API docs and request handling. ChangesProvider Effort Selection
Agent request wiring and cleanup
Sequence Diagram(s)sequenceDiagram
participant User
participant ChatComposer
participant useChatProviderState
participant useChatComposerState
participant ClaudeSDK
participant CodexSDK
User->>ChatComposer: select effort
ChatComposer->>useChatProviderState: onSelectEffort(effort)
useChatProviderState->>useChatProviderState: setStoredProviderEffort(provider, effort)
User->>ChatComposer: submit message
ChatComposer->>useChatComposerState: handleSubmit()
useChatComposerState->>ClaudeSDK: sendMessage(options.effort)
useChatComposerState->>CodexSDK: sendMessage(options.effort)
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/modules/providers/list/codex/codex-models.provider.ts (1)
94-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
effort.valuescan end up empty after filtering, buteffortobject is still assigned.The outer guard (
Array.isArray(...) && length > 0) only checks the rawsupported_reasoning_levelsarray. If every entry lacks a valideffortstring, the inner.filter(Boolean)produces an emptyvaluesarray, yeteffortis still set to{ default, values: [] }instead ofundefined. Downstream,ChatInterface.tsx'scomposerEffortOptionsonly falls back toFALLBACK_COMPOSER_EFFORT_OPTIONSwheneffort.values.lengthis falsy, but since this object exists withvalues: [], the checkmodelOption?.effort?.values?.lengthis0(falsy) so it does fall back — meaning the UI would offer fallback effort options that may not actually be valid for this specific model, and the backend's per-model effort validation would then reject them.🐛 Proposed fix to avoid assigning empty effort
const mapCodexModel = (model: CodexCachedModel): ProviderModelOption => ({ value: model.slug as string, label: readOptionalString(model.display_name) ?? (model.slug as string), description: readOptionalString(model.description), - effort: Array.isArray(model.supported_reasoning_levels) && model.supported_reasoning_levels.length > 0 - ? { - default: readOptionalString(model.default_reasoning_level) ?? undefined, - values: model.supported_reasoning_levels - .map((level) => { - const value = readOptionalString(level?.effort); - if (!value) { - return null; - } - - return { - value, - description: readOptionalString(level?.description), - }; - }) - .filter((level): level is NonNullable<typeof level> => Boolean(level)), - } - : undefined, + effort: ((): ProviderModelOption['effort'] => { + const values = Array.isArray(model.supported_reasoning_levels) + ? model.supported_reasoning_levels + .map((level) => { + const value = readOptionalString(level?.effort); + return value ? { value, description: readOptionalString(level?.description) } : null; + }) + .filter((level): level is NonNullable<typeof level> => Boolean(level)) + : []; + + return values.length > 0 + ? { default: readOptionalString(model.default_reasoning_level) ?? undefined, values } + : undefined; + })(), });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/list/codex/codex-models.provider.ts` around lines 94 - 116, The `mapCodexModel` helper is still assigning an `effort` object even when `supported_reasoning_levels` maps down to no valid entries after filtering. Update `mapCodexModel` in `codex-models.provider.ts` so it only sets `effort` when the derived `values` array is non-empty, otherwise return `undefined` for `effort`. Keep the existing `readOptionalString` mapping for `default` and each level, but add the final empty-array guard around the constructed `effort` object so `ProviderModelOption` never exposes an empty effort list.
🧹 Nitpick comments (4)
server/claude-sdk.js (1)
44-53: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueAdd optional chaining on
OPTIONSlookup.If
modelsDefinitionis a truthy object without anOPTIONSarray (e.g. a malformed catalog returned by the provider models service),modelsDefinition.OPTIONS.find(...)throws. This is currently caught by the outer try/catch inqueryClaudeSDK, so it degrades to a failed query rather than a crash, but a defensive?.would avoid an unnecessary user-facing failure for malformed catalog data.🛡️ Suggested defensive tweak
function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_FALLBACK_MODELS) { - const selectedModel = modelsDefinition.OPTIONS + const selectedModel = modelsDefinition?.OPTIONS .find((option) => option.value === model) || null;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/claude-sdk.js` around lines 44 - 53, The model lookup in resolveClaudeEffort assumes modelsDefinition.OPTIONS always exists, which can throw for malformed catalog data. Update the OPTIONS access in resolveClaudeEffort to use optional chaining before calling find so a missing or invalid OPTIONS array safely falls back to null/undefined. Keep the effort validation logic unchanged and make sure the fix is localized to resolveClaudeEffort.src/components/chat/view/ChatInterface.tsx (1)
19-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winEffort fallback values duplicated across files.
FALLBACK_COMPOSER_EFFORT_OPTIONSrepeats the same literal['low', 'medium', 'high', 'xhigh', 'max']/['low', 'medium', 'high', 'xhigh']lists asFALLBACK_EFFORT_VALUESinuseChatProviderState.ts. Keeping two (likely three, counting backend fallback definitions) copies of the same effort tiers risks drift if a tier is ever added/removed. Consider extracting a single shared constant.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/chat/view/ChatInterface.tsx` around lines 19 - 22, The fallback effort tier lists are duplicated in ChatInterface and the chat provider state, so update FALLBACK_COMPOSER_EFFORT_OPTIONS to use a shared source of truth instead of hardcoded literals. Extract the common effort tiers into one shared constant/module and reference it from both ChatInterface and useChatProviderState so the values stay aligned if tiers change.src/components/chat/hooks/useChatProviderState.ts (1)
309-334: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant localStorage read duplicates
currentEffort.
reconcileStoredEffortre-readslocalStorage.getItem(storageKey)instead of just using thecurrentEffortparameter that was passed in. SincecurrentEffort(the React state) and the localStorage value are kept in sync bysetStoredProviderEffortand by this same reconciliation effect, the two should always match — the separatestoredEffortlookup appears to just duplicatecurrentEffort. Consider simplifying to operate oncurrentEffortalone.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/chat/hooks/useChatProviderState.ts` around lines 309 - 334, The reconcileStoredEffort callback is redundantly reading from localStorage even though currentEffort already represents the same persisted state. Update reconcileStoredEffort in useChatProviderState to operate on currentEffort alone, removing the extra storageKey/localStorage.getItem path and keeping the allowedValues checks and DEFAULT_EFFORT_VALUE fallback behavior intact.src/components/chat/hooks/useChatComposerState.ts (1)
166-176: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDirect localStorage read for effort duplicates the reactive prop.
getLatestProviderEffortre-reads${provider}-effortfromsafeLocalStorageinstead of usingclaudeEffort/codexEffortdirectly, even though those props are already part ofhandleSubmit's dependency array and should be current at submit time (sincesetStoredProviderEffortupdates both state and localStorage synchronously). This adds an extra access path that must stay consistent with the hook's own state, increasing the surface for drift without a clear functional benefit.Also applies to: 748-753
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/chat/hooks/useChatComposerState.ts` around lines 166 - 176, `getLatestProviderEffort` is redundantly reading `${provider}-effort` from `safeLocalStorage` instead of using the already reactive `claudeEffort` and `codexEffort` values available in `handleSubmit`. Update the `handleSubmit`/effort resolution path to rely on the hook state props directly, keeping the existing fallback behavior for non-Claude/Codex providers, and remove the extra localStorage access so the logic stays aligned with the dependency-tracked state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/chat/hooks/useChatProviderState.ts`:
- Around line 301-307: The fallback logic in getAllowedEffortValues is too broad
because it uses FALLBACK_EFFORT_VALUES whenever option?.effort?.values is
missing, even for cataloged models that simply don’t support effort; change it
to return an empty list when getModelOption finds a model but it has no effort
config, and only use the provider fallback when the model itself is unknown.
Update reconcileStoredEffort behavior accordingly so unsupported models reset to
'default', and apply the same correction to composerEffortOptions in
ChatInterface.tsx.
In `@src/components/chat/view/ChatInterface.tsx`:
- Around line 245-260: The composer effort fallback in ChatInterface should only
be used when the current model is not found in providerModelCatalog, not when a
найден modelOption simply lacks effort support. Update composerEffortOptions so
it returns modelOption.effort.values when present, returns an empty list when
modelOption exists without effort, and keeps the provider fallback only for the
missing-catalog case; mirror the same behavior in getAllowedEffortValues in
useChatProviderState to keep UI and state consistent.
In `@src/components/chat/view/subcomponents/ChatComposer.tsx`:
- Around line 211-233: The Escape key handler in ChatComposer’s useEffect is
allowing the event to reach ChatInterface’s global capture listener, which can
abort an active session unintentionally. Update the local handleKeyDown logic
for the effort dropdown to stop propagation when Escape is pressed before
closing the menu, or otherwise gate the global abort path so it ignores Escape
while the dropdown is open. Keep the fix scoped to the ChatComposer dropdown
behavior and the related ChatInterface abort handling.
---
Outside diff comments:
In `@server/modules/providers/list/codex/codex-models.provider.ts`:
- Around line 94-116: The `mapCodexModel` helper is still assigning an `effort`
object even when `supported_reasoning_levels` maps down to no valid entries
after filtering. Update `mapCodexModel` in `codex-models.provider.ts` so it only
sets `effort` when the derived `values` array is non-empty, otherwise return
`undefined` for `effort`. Keep the existing `readOptionalString` mapping for
`default` and each level, but add the final empty-array guard around the
constructed `effort` object so `ProviderModelOption` never exposes an empty
effort list.
---
Nitpick comments:
In `@server/claude-sdk.js`:
- Around line 44-53: The model lookup in resolveClaudeEffort assumes
modelsDefinition.OPTIONS always exists, which can throw for malformed catalog
data. Update the OPTIONS access in resolveClaudeEffort to use optional chaining
before calling find so a missing or invalid OPTIONS array safely falls back to
null/undefined. Keep the effort validation logic unchanged and make sure the fix
is localized to resolveClaudeEffort.
In `@src/components/chat/hooks/useChatComposerState.ts`:
- Around line 166-176: `getLatestProviderEffort` is redundantly reading
`${provider}-effort` from `safeLocalStorage` instead of using the already
reactive `claudeEffort` and `codexEffort` values available in `handleSubmit`.
Update the `handleSubmit`/effort resolution path to rely on the hook state props
directly, keeping the existing fallback behavior for non-Claude/Codex providers,
and remove the extra localStorage access so the logic stays aligned with the
dependency-tracked state.
In `@src/components/chat/hooks/useChatProviderState.ts`:
- Around line 309-334: The reconcileStoredEffort callback is redundantly reading
from localStorage even though currentEffort already represents the same
persisted state. Update reconcileStoredEffort in useChatProviderState to operate
on currentEffort alone, removing the extra storageKey/localStorage.getItem path
and keeping the allowedValues checks and DEFAULT_EFFORT_VALUE fallback behavior
intact.
In `@src/components/chat/view/ChatInterface.tsx`:
- Around line 19-22: The fallback effort tier lists are duplicated in
ChatInterface and the chat provider state, so update
FALLBACK_COMPOSER_EFFORT_OPTIONS to use a shared source of truth instead of
hardcoded literals. Extract the common effort tiers into one shared
constant/module and reference it from both ChatInterface and
useChatProviderState so the values stay aligned if tiers change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d969bedc-daa9-4414-b99b-51627438edda
📒 Files selected for processing (12)
server/claude-sdk.jsserver/modules/providers/list/claude/claude-models.provider.tsserver/modules/providers/list/codex/codex-models.provider.tsserver/modules/providers/services/provider-models.service.tsserver/openai-codex.jsserver/shared/types.tssrc/components/chat/hooks/useChatComposerState.tssrc/components/chat/hooks/useChatProviderState.tssrc/components/chat/view/ChatInterface.tsxsrc/components/chat/view/subcomponents/ChatComposer.tsxsrc/components/chat/view/subcomponents/CommandResultModal.tsxsrc/types/app.ts
💤 Files with no reviewable changes (1)
- src/components/chat/view/subcomponents/CommandResultModal.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/chat/hooks/useChatProviderState.ts`:
- Around line 557-569: The current effort value in useChatProviderState is still
read directly from providerEfforts, which can expose a stale selection after a
model change before the reconciliation effect runs. Update currentProviderEffort
in useChatProviderState to derive the reconciled value with
reconcileStoredEffort(provider, providerModels[provider], ...) using the same
effort options source as currentProviderEffortOptions, and keep the existing
effect only for persisting/clearing invalid stored values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 53d99807-7551-437d-a21b-33c78a46f5e0
📒 Files selected for processing (8)
server/claude-sdk.jsserver/modules/providers/list/codex/codex-models.provider.tsserver/modules/providers/services/provider-capabilities.service.tssrc/components/chat/constants/providerEffort.tssrc/components/chat/hooks/useChatComposerState.tssrc/components/chat/hooks/useChatProviderState.tssrc/components/chat/view/ChatInterface.tsxsrc/components/chat/view/subcomponents/ChatComposer.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- src/components/chat/view/subcomponents/ChatComposer.tsx
- src/components/chat/view/ChatInterface.tsx
- server/modules/providers/list/codex/codex-models.provider.ts
- server/claude-sdk.js
* feat: add Claude and Codex effort controls * refactor: generic provider effort handling * fix: reconcile provider effort after model changes * fix: pass effort through external agent api * feat: add effort support for opencode * chore: update gpt fallback models * fix: use portal for showing effort dropdown --------- Co-authored-by: Haileyesus <118998054+blackmammoth@users.noreply.github.com>
* upstream/main: feat(redesign): skills and MCP action controls in settings (siteboon#942) Update localized README docs chore(release): v1.36.0 feat: add Claude and Codex effort controls (siteboon#943) fix desktop release asset upload chore(release): v1.35.1 fix: remove obsolete semantic helper release jobs Feat/design improvements and minor bug fixes (siteboon#939) Clarify desktop companion in README Point desktop downloads to CloudCLI
* upstream/main: feat(redesign): skills and MCP action controls in settings (siteboon#942) Update localized README docs chore(release): v1.36.0 feat: add Claude and Codex effort controls (siteboon#943) fix desktop release asset upload chore(release): v1.35.1 fix: remove obsolete semantic helper release jobs Feat/design improvements and minor bug fixes (siteboon#939) Clarify desktop companion in README Point desktop downloads to CloudCLI
Summary
This PR adds effort controls for both Claude and Codex and wires the selected value all the way from the chat composer to each provider SDK.
The change includes:
effortdefinitionseffortinchat.sendand applying it in the backend:effortoptionmodelReasoningEffortWhy
Users already know these controls by the provider-native effort names, so the UI should expose them directly instead of normalizing them into a custom abstraction.
This also fixes an important Codex failure mode: when the model cache lacked effort metadata, the app could silently drop the UI selection and Codex would fall back to the CLI config value instead.
User Impact
After this change:
Defaultmeans “do not force an override”; the provider keeps its own default behaviorSummary by CodeRabbit
POST /api/agentwith an optionaleffortparameter for effort-capable providers.effortparameter and supported providers.