Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
type AutoConvertLongText,
type CompletionSound,
type DefaultInitialTaskMode,
type DefaultReasoningEffort,
type DiffOpenMode,
type SendMessagesWith,
useSettingsStore,
Expand Down Expand Up @@ -77,6 +78,7 @@ export function GeneralSettings() {
completionVolume,
autoConvertLongText,
defaultInitialTaskMode,
defaultReasoningEffort,
diffOpenMode,
sendMessagesWith,
hedgehogMode,
Expand All @@ -87,6 +89,7 @@ export function GeneralSettings() {
setCompletionVolume,
setAutoConvertLongText,
setDefaultInitialTaskMode,
setDefaultReasoningEffort,
setDiffOpenMode,
setSendMessagesWith,
setHedgehogMode,
Expand Down Expand Up @@ -190,6 +193,18 @@ export function GeneralSettings() {
[defaultInitialTaskMode, setDefaultInitialTaskMode],
);

const handleDefaultReasoningEffortChange = useCallback(
(value: DefaultReasoningEffort) => {
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
setting_name: "default_reasoning_effort",
new_value: value,
old_value: defaultReasoningEffort,
});
setDefaultReasoningEffort(value);
},
[defaultReasoningEffort, setDefaultReasoningEffort],
);

const handleSendMessagesWithChange = useCallback(
(value: SendMessagesWith) => {
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
Expand Down Expand Up @@ -386,6 +401,29 @@ export function GeneralSettings() {
</Select.Root>
</SettingRow>

<SettingRow
label="Default effort level"
description="Choose the default reasoning effort for new tasks, or remember your last-used level"
>
<Select.Root
value={defaultReasoningEffort}
onValueChange={(value) =>
handleDefaultReasoningEffortChange(value as DefaultReasoningEffort)
}
size="1"
>
<Select.Trigger className="min-w-[100px]" />
<Select.Content>
<Select.Item value="last_used">Last used</Select.Item>
<Select.Item value="low">Low</Select.Item>
<Select.Item value="medium">Medium</Select.Item>
<Select.Item value="high">High</Select.Item>
<Select.Item value="xhigh">Extra High</Select.Item>
<Select.Item value="max">Max</Select.Item>
</Select.Content>
</Select.Root>
</SettingRow>

<SettingRow
label="Send messages with"
description="Choose which key combination sends messages. Use Shift+Enter for new lines"
Expand Down
13 changes: 13 additions & 0 deletions apps/code/src/renderer/features/settings/stores/settingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ export type DefaultRunMode = "local" | "cloud" | "last_used";
export type LocalWorkspaceMode = "worktree" | "local";
export type AgentAdapter = "claude" | "codex";
export type DefaultInitialTaskMode = "plan" | "last_used";
export type DefaultReasoningEffort =
| "low"
| "medium"
| "high"
| "xhigh"
| "max"
| "last_used";

export type SendMessagesWith = "enter" | "cmd+enter";
export type AutoConvertLongText = "off" | "1000" | "2500" | "5000" | "10000";
Expand Down Expand Up @@ -56,6 +63,7 @@ interface SettingsStore {
lastUsedEnvironments: Record<string, string>;
defaultInitialTaskMode: DefaultInitialTaskMode;
lastUsedInitialTaskMode: ExecutionMode;
defaultReasoningEffort: DefaultReasoningEffort;
setDefaultRunMode: (mode: DefaultRunMode) => void;
setLastUsedRunMode: (mode: "local" | "cloud") => void;
setLastUsedLocalWorkspaceMode: (mode: LocalWorkspaceMode) => void;
Expand All @@ -71,6 +79,7 @@ interface SettingsStore {
getLastUsedEnvironment: (repoPath: string) => string | null;
setDefaultInitialTaskMode: (mode: DefaultInitialTaskMode) => void;
setLastUsedInitialTaskMode: (mode: ExecutionMode) => void;
setDefaultReasoningEffort: (effort: DefaultReasoningEffort) => void;

// Notifications
desktopNotifications: boolean;
Expand Down Expand Up @@ -140,6 +149,7 @@ export const useSettingsStore = create<SettingsStore>()(
lastUsedEnvironments: {},
defaultInitialTaskMode: "plan",
lastUsedInitialTaskMode: "plan",
defaultReasoningEffort: "last_used",
setDefaultRunMode: (mode) => set({ defaultRunMode: mode }),
setLastUsedRunMode: (mode) => set({ lastUsedRunMode: mode }),
setLastUsedLocalWorkspaceMode: (mode) =>
Expand Down Expand Up @@ -167,6 +177,8 @@ export const useSettingsStore = create<SettingsStore>()(
set({ defaultInitialTaskMode: mode }),
setLastUsedInitialTaskMode: (mode) =>
set({ lastUsedInitialTaskMode: mode }),
setDefaultReasoningEffort: (effort) =>
set({ defaultReasoningEffort: effort }),

// Notifications
desktopNotifications: true,
Expand Down Expand Up @@ -264,6 +276,7 @@ export const useSettingsStore = create<SettingsStore>()(
lastUsedEnvironments: state.lastUsedEnvironments,
defaultInitialTaskMode: state.defaultInitialTaskMode,
lastUsedInitialTaskMode: state.lastUsedInitialTaskMode,
defaultReasoningEffort: state.defaultReasoningEffort,

// Notifications
desktopNotifications: state.desktopNotifications,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,45 @@ function flattenValues(
);
}

const EFFORT_RANK: Record<string, number> = {
low: 0,
medium: 1,
high: 2,
xhigh: 3,
max: 4,
};

/**
* Clamp a desired effort to the nearest level the current model supports.
* Falls back to the highest supported level when the desired level has no
* known rank (e.g. unrecognized value from older settings).
*/
function clampEffortToAvailable(
desired: string,
available: string[],
): string | null {
if (available.length === 0) return null;
if (available.includes(desired)) return desired;

const desiredRank = EFFORT_RANK[desired];
if (desiredRank === undefined) {
return available[available.length - 1];
}

const ranked = available
.map((value) => ({ value, rank: EFFORT_RANK[value] }))
.filter((entry): entry is { value: string; rank: number } =>
Number.isFinite(entry.rank),
);
if (ranked.length === 0) return available[0];

return ranked.reduce((closest, entry) =>
Math.abs(entry.rank - desiredRank) < Math.abs(closest.rank - desiredRank)
? entry
: closest,
).value;
}

/**
* Fetches config options (models, modes, effort levels) for the task input
* page via a lightweight tRPC query. No agent session is created.
Expand Down Expand Up @@ -70,6 +109,7 @@ export function usePreviewConfig(
const {
defaultInitialTaskMode,
lastUsedInitialTaskMode,
defaultReasoningEffort,
lastUsedReasoningEffort,
} = useSettingsStore.getState();

Expand Down Expand Up @@ -121,13 +161,26 @@ export function usePreviewConfig(
options?: Array<{ value: string }>;
}>,
);
if (
lastUsedReasoningEffort &&
validValues.includes(lastUsedReasoningEffort)
) {
if (defaultReasoningEffort === "last_used") {
if (
lastUsedReasoningEffort &&
validValues.includes(lastUsedReasoningEffort)
) {
return {
...opt,
currentValue: lastUsedReasoningEffort,
} as SessionConfigOption;
}
return opt;
}
const clamped = clampEffortToAvailable(
defaultReasoningEffort,
validValues,
);
if (clamped) {
return {
...opt,
currentValue: lastUsedReasoningEffort,
currentValue: clamped,
} as SessionConfigOption;
}
return opt;
Expand Down Expand Up @@ -168,26 +221,34 @@ export function usePreviewConfig(
? "reasoning_effort"
: "effort";

const { lastUsedReasoningEffort } = useSettingsStore.getState();
const { lastUsedReasoningEffort, defaultReasoningEffort } =
useSettingsStore.getState();
const isValidEffort = (effort: unknown): effort is string =>
typeof effort === "string" &&
!!effortOpts?.some((e) => e.value === effort);
const resolveEffortFallback = (): string => {
if (
defaultReasoningEffort !== "last_used" &&
isValidEffort(defaultReasoningEffort)
) {
return defaultReasoningEffort;
}
return isValidEffort(lastUsedReasoningEffort)
? lastUsedReasoningEffort
: "high";
};
if (effortOpts && existingIdx >= 0) {
const currentEffort = updated[existingIdx].currentValue;
const nextEffort = isValidEffort(currentEffort)
? currentEffort
: isValidEffort(lastUsedReasoningEffort)
? lastUsedReasoningEffort
: "high";
: resolveEffortFallback();
updated[existingIdx] = {
...updated[existingIdx],
currentValue: nextEffort,
options: effortOpts,
} as SessionConfigOption;
} else if (effortOpts && existingIdx === -1) {
const nextEffort = isValidEffort(lastUsedReasoningEffort)
? lastUsedReasoningEffort
: "high";
const nextEffort = resolveEffortFallback();
updated = [
...updated,
{
Expand Down
Loading