Skip to content

Commit e4e49a6

Browse files
committed
Add effort level control for Claude sessions
1 parent 91443cc commit e4e49a6

File tree

9 files changed

+50
-13
lines changed

9 files changed

+50
-13
lines changed

apps/code/src/main/services/agent/schemas.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const startSessionInput = z.object({
4646
adapter: z.enum(["claude", "codex"]).optional(),
4747
additionalDirectories: z.array(z.string()).optional(),
4848
customInstructions: z.string().max(2000).optional(),
49+
effort: z.enum(["low", "medium", "high", "max"]).optional(),
4950
});
5051

5152
export type StartSessionInput = z.infer<typeof startSessionInput>;

apps/code/src/main/services/agent/service.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ interface SessionConfig {
200200
permissionMode?: string;
201201
/** Custom instructions injected into the system prompt */
202202
customInstructions?: string;
203+
/** Effort level for Claude sessions */
204+
effort?: "low" | "medium" | "high" | "max";
203205
}
204206

205207
interface ManagedSession {
@@ -632,6 +634,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
632634
additionalDirectories,
633635
permissionMode,
634636
customInstructions,
637+
effort,
635638
} = config;
636639

637640
// Preview sessions don't need a real repo — use a temp directory
@@ -783,6 +786,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
783786
...(additionalDirectories?.length && {
784787
additionalDirectories,
785788
}),
789+
...(effort && { effort }),
786790
plugins,
787791
},
788792
},
@@ -811,6 +815,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
811815
claudeCode: {
812816
options: {
813817
...(additionalDirectories?.length && { additionalDirectories }),
818+
...(effort && { effort }),
814819
plugins,
815820
},
816821
},
@@ -1551,6 +1556,7 @@ For git operations while detached:
15511556
"permissionMode" in params ? params.permissionMode : undefined,
15521557
customInstructions:
15531558
"customInstructions" in params ? params.customInstructions : undefined,
1559+
effort: "effort" in params ? params.effort : undefined,
15541560
};
15551561
}
15561562

apps/code/src/renderer/features/message-editor/components/EditorToolbar.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ModelSelector } from "@features/sessions/components/ModelSelector";
2-
import { ReasoningLevelSelector } from "@features/sessions/components/ReasoningLevelSelector";
32
import { Paperclip } from "@phosphor-icons/react";
43
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
54
import { useRef } from "react";
@@ -45,7 +44,7 @@ export function EditorToolbar({
4544
};
4645

4746
return (
48-
<Flex align="center" gap="1">
47+
<Flex align="center" gap="3">
4948
<input
5049
ref={fileInputRef}
5150
type="file"
@@ -68,14 +67,7 @@ export function EditorToolbar({
6867
</IconButton>
6968
</Tooltip>
7069
{!hideSelectors && (
71-
<>
72-
<ModelSelector
73-
taskId={taskId}
74-
adapter={adapter}
75-
disabled={disabled}
76-
/>
77-
<ReasoningLevelSelector taskId={taskId} disabled={disabled} />
78-
</>
70+
<ModelSelector taskId={taskId} adapter={adapter} disabled={disabled} />
7971
)}
8072
</Flex>
8173
);

apps/code/src/renderer/features/sessions/components/ReasoningLevelSelector.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Select, Text } from "@radix-ui/themes";
22
import { getSessionService } from "../service/service";
33
import {
44
flattenSelectOptions,
5+
useAdapterForTask,
56
useSessionForTask,
67
useThoughtLevelConfigOptionForTask,
78
} from "../stores/sessionStore";
@@ -17,6 +18,7 @@ export function ReasoningLevelSelector({
1718
}: ReasoningLevelSelectorProps) {
1819
const session = useSessionForTask(taskId);
1920
const thoughtOption = useThoughtLevelConfigOptionForTask(taskId);
21+
const adapter = useAdapterForTask(taskId);
2022

2123
if (!thoughtOption) {
2224
return null;
@@ -57,7 +59,7 @@ export function ReasoningLevelSelector({
5759
}}
5860
>
5961
<Text size="1" style={{ fontFamily: "var(--font-mono)" }}>
60-
Reasoning: {activeLabel}
62+
{adapter === "codex" ? "Reasoning" : "Effort"}: {activeLabel}
6163
</Text>
6264
</Select.Trigger>
6365
<Select.Content position="popper" sideOffset={4}>

apps/code/src/renderer/features/sessions/service/service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ export class SessionService {
537537
permissionMode: executionMode,
538538
adapter,
539539
customInstructions: startCustomInstructions || undefined,
540+
effort: reasoningLevel as "low" | "medium" | "high" | "max" | undefined,
540541
});
541542

542543
const session = this.createBaseSession(taskRun.id, taskId, taskTitle);

apps/code/src/renderer/features/task-detail/components/TaskInputEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export const TaskInputEditor = forwardRef<
210210
</Flex>
211211

212212
<Flex justify="between" align="center" px="3" pb="3">
213-
<Flex align="center" gap="1">
213+
<Flex align="center" gap="3">
214214
<EditorToolbar
215215
disabled={isCreatingTask}
216216
adapter={adapter}

packages/agent/src/adapters/claude/claude-agent.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
558558
const sdkModelId = toSdkModelId(params.value);
559559
await this.session.query.setModel(sdkModelId);
560560
this.session.modelId = params.value;
561+
} else if (params.configId === "effort") {
562+
this.session.effort = params.value as "low" | "medium" | "high" | "max";
561563
}
562564

563565
this.session.configOptions = this.session.configOptions.map((o) =>
@@ -623,6 +625,12 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
623625

624626
const meta = params._meta as NewSessionMeta | undefined;
625627
const taskId = meta?.persistence?.taskId;
628+
const effort = meta?.claudeCode?.options?.effort as
629+
| "low"
630+
| "medium"
631+
| "high"
632+
| "max"
633+
| undefined;
626634

627635
// We want to create a new session id unless it is resume,
628636
// but not resume + forkSession.
@@ -673,6 +681,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
673681
onModeChange: this.createOnModeChange(),
674682
onProcessSpawned: this.options?.onProcessSpawned,
675683
onProcessExited: this.options?.onProcessExited,
684+
effort,
676685
});
677686

678687
// Use the same abort controller that buildSessionOptions gave to the query
@@ -693,6 +702,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
693702
cachedReadTokens: 0,
694703
cachedWriteTokens: 0,
695704
},
705+
effort,
696706
configOptions: [],
697707
promptRunning: false,
698708
pendingMessages: new Map(),
@@ -790,7 +800,11 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
790800
),
791801
};
792802

793-
const configOptions = this.buildConfigOptions(permissionMode, modelOptions);
803+
const configOptions = this.buildConfigOptions(
804+
permissionMode,
805+
modelOptions,
806+
effort ?? "high",
807+
);
794808
session.configOptions = configOptions;
795809

796810
if (!creationOpts.skipBackgroundFetches) {
@@ -844,6 +858,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
844858
currentModelId: string;
845859
options: SessionConfigSelectOption[];
846860
},
861+
currentEffort: "low" | "medium" | "high" | "max" = "high",
847862
): SessionConfigOption[] {
848863
const modeOptions = getAvailableModes().map((mode) => ({
849864
value: mode.id,
@@ -871,6 +886,20 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
871886
category: "model" as SessionConfigOptionCategory,
872887
description: "Choose which model Claude should use",
873888
},
889+
{
890+
id: "effort",
891+
name: "Effort",
892+
type: "select",
893+
currentValue: currentEffort,
894+
options: [
895+
{ value: "low", name: "Low" },
896+
{ value: "medium", name: "Medium" },
897+
{ value: "high", name: "High" },
898+
{ value: "max", name: "Max" },
899+
],
900+
category: "thought_level" as SessionConfigOptionCategory,
901+
description: "Controls how much effort Claude puts into its response",
902+
},
874903
];
875904
}
876905

packages/agent/src/adapters/claude/session/options.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface BuildOptionsParams {
4343
onModeChange?: OnModeChange;
4444
onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
4545
onProcessExited?: (pid: number) => void;
46+
effort?: "low" | "medium" | "high" | "max";
4647
}
4748

4849
const BRANCH_NAMING_INSTRUCTIONS = `
@@ -295,6 +296,10 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
295296
options.additionalDirectories = params.additionalDirectories;
296297
}
297298

299+
if (params.effort) {
300+
options.effort = params.effort;
301+
}
302+
298303
clearStatsigCache();
299304
return options;
300305
}

packages/agent/src/adapters/claude/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export type Session = BaseSession & {
4646
taskRunId?: string;
4747
lastPlanFilePath?: string;
4848
lastPlanContent?: string;
49+
effort?: "low" | "medium" | "high" | "max";
4950
configOptions: SessionConfigOption[];
5051
accumulatedUsage: AccumulatedUsage;
5152
promptRunning: boolean;

0 commit comments

Comments
 (0)