Skip to content
Open
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
3 changes: 3 additions & 0 deletions apps/code/src/main/services/agent/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const sessionConfigSchema = z.object({
additionalDirectories: z.array(z.string()).optional(),
/** Permission mode to use for the session (e.g. "default", "acceptEdits", "plan", "bypassPermissions") */
permissionMode: z.string().optional(),
/** Preferred model ID for the session (e.g. "claude-sonnet-4-6") */
model: z.string().optional(),
});

export type SessionConfig = z.infer<typeof sessionConfigSchema>;
Expand All @@ -49,6 +51,7 @@ export const startSessionInput = z.object({
additionalDirectories: z.array(z.string()).optional(),
customInstructions: z.string().max(2000).optional(),
effort: effortLevelSchema.optional(),
model: z.string().optional(),
});

export type StartSessionInput = z.infer<typeof startSessionInput>;
Expand Down
6 changes: 6 additions & 0 deletions apps/code/src/main/services/agent/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ interface SessionConfig {
customInstructions?: string;
/** Effort level for Claude sessions */
effort?: EffortLevel;
/** Model to use for the session (e.g. "claude-sonnet-4-6") */
model?: string;
}

interface ManagedSession {
Expand Down Expand Up @@ -465,6 +467,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
permissionMode,
customInstructions,
effort,
model,
} = config;

// Preview sessions don't need a real repo — use a temp directory
Expand Down Expand Up @@ -644,6 +647,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
additionalDirectories,
}),
...(effort && { effort }),
...(model && { model }),
plugins,
},
},
Expand Down Expand Up @@ -673,6 +677,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
options: {
...(additionalDirectories?.length && { additionalDirectories }),
...(effort && { effort }),
...(model && { model }),
plugins,
},
},
Expand Down Expand Up @@ -1362,6 +1367,7 @@ For git operations while detached:
customInstructions:
"customInstructions" in params ? params.customInstructions : undefined,
effort: "effort" in params ? params.effort : undefined,
model: "model" in params ? params.model : undefined,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ export class SessionService {
effort: effortLevelSchema.safeParse(reasoningLevel).success
? (reasoningLevel as EffortLevel)
: undefined,
model,
});

const session = this.createBaseSession(taskRun.id, taskId, taskTitle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ export function TaskInput({ onTaskCreated }: TaskInputProps = {}) {
null,
);

// Track the user's explicit model selection independently of the preview
// session. The preview session's config can be reset (e.g. adapter change),
// which would lose the user's choice if we only read from the store.
const [selectedModel, setSelectedModel] = useState<string | null>(null);

const [selectedDirectory, setSelectedDirectory] = useState("");
const workspaceMode = lastUsedWorkspaceMode || "local";
const adapter = lastUsedAdapter;
Expand All @@ -94,8 +99,10 @@ export function TaskInput({ onTaskCreated }: TaskInputProps = {}) {
setLastUsedLocalWorkspaceMode(mode);
}
};
const setAdapter = (newAdapter: AgentAdapter) =>
const setAdapter = (newAdapter: AgentAdapter) => {
setLastUsedAdapter(newAdapter);
setSelectedModel(null);
};

const { githubIntegration, repositories, isLoadingRepos } =
useRepositoryIntegration();
Expand Down Expand Up @@ -192,8 +199,11 @@ export function TaskInput({ onTaskCreated }: TaskInputProps = {}) {

// Get current values from preview session config options for task creation.
// Defaults ensure values are always passed even before the preview session loads.
const currentModel =
// Prefer the user's explicit selection (local state) over the preview session value,
// since the preview session's config can be transiently reset.
const previewModel =
modelOption?.type === "select" ? modelOption.currentValue : undefined;
const currentModel = selectedModel ?? previewModel;
const modeFallback =
defaultInitialTaskMode === "last_used" ? lastUsedInitialTaskMode : "plan";
const currentExecutionMode =
Expand Down Expand Up @@ -461,6 +471,7 @@ export function TaskInput({ onTaskCreated }: TaskInputProps = {}) {
previewTaskId={previewTaskId}
onAdapterChange={setAdapter}
isPreviewConnecting={isConnecting}
onModelChange={setSelectedModel}
/>

<ModeIndicatorInput
Expand Down
12 changes: 9 additions & 3 deletions packages/agent/src/adapters/claude/claude-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
const meta = params._meta as NewSessionMeta | undefined;
const taskId = meta?.persistence?.taskId;
const effort = meta?.claudeCode?.options?.effort as EffortLevel | undefined;
const modelOverride = meta?.claudeCode?.options?.model as
| string
| undefined;

// We want to create a new session id unless it is resume,
// but not resume + forkSession.
Expand Down Expand Up @@ -919,10 +922,13 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
});
}

// Resolve model: settings model takes priority, then gateway
// Resolve model: explicit UI selection takes priority, then settings, then gateway default
const settingsModel = settingsManager.getSettings().model;
const modelOptions = await this.getModelConfigOptions();
const resolvedModelId = settingsModel || modelOptions.currentModelId;
const modelOptions = await this.getModelConfigOptions(
modelOverride ?? undefined,
);
const resolvedModelId =
modelOverride || settingsModel || modelOptions.currentModelId;
session.modelId = resolvedModelId;
session.lastContextWindowSize =
this.getContextWindowForModel(resolvedModelId);
Expand Down
Loading