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
4 changes: 4 additions & 0 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
models: selectedProviderModels,
promptInjectionState: composerPromptInjectionState,
modelOptions: composerModelOptions?.[selectedInstanceId],
planModeEnabled: settings.planModeEnabled,
}),
[
composerModelOptions,
Expand All @@ -870,6 +871,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
selectedModel,
selectedProvider,
selectedProviderModels,
settings.planModeEnabled,
],
);

Expand Down Expand Up @@ -1206,6 +1208,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
modelOptions: composerModelOptions?.[selectedInstanceId],
prompt,
onPromptChange: setPromptFromTraits,
planModeEnabled: settings.planModeEnabled,
});
const providerTraitsPicker = renderProviderTraitsPicker({
provider: selectedProvider,
Expand All @@ -1217,6 +1220,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
modelOptions: composerModelOptions?.[selectedInstanceId],
prompt,
onPromptChange: setPromptFromTraits,
planModeEnabled: settings.planModeEnabled,
});
const pendingPrimaryAction = useMemo(
() =>
Expand Down
13 changes: 12 additions & 1 deletion apps/web/src/components/chat/TraitsPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ function getSelectedTraits(
prompt: string,
modelOptions: ProviderOptions | null | undefined,
allowPromptInjectedEffort: boolean,
planModeEnabled: boolean,
) {
const caps = getProviderModelCapabilities(models, model, provider);
const caps = getProviderModelCapabilities(models, model, provider, planModeEnabled);
const descriptors = getProviderOptionDescriptors({
caps,
selections: modelOptions,
Expand Down Expand Up @@ -167,6 +168,7 @@ function getTraitsSectionVisibility(input: {
prompt: string;
modelOptions: ProviderOptions | null | undefined;
allowPromptInjectedEffort?: boolean;
planModeEnabled: boolean;
}) {
const selected = getSelectedTraits(
input.provider,
Expand All @@ -175,6 +177,7 @@ function getTraitsSectionVisibility(input: {
input.prompt,
input.modelOptions,
input.allowPromptInjectedEffort ?? true,
input.planModeEnabled,
);

const showEffort = selected.primarySelectDescriptor !== null;
Expand All @@ -201,6 +204,7 @@ export function shouldRenderTraitsControls(input: {
prompt: string;
modelOptions: ProviderOptions | null | undefined;
allowPromptInjectedEffort?: boolean;
planModeEnabled: boolean;
}): boolean {
return getTraitsSectionVisibility(input).hasAnyControls;
}
Expand All @@ -214,6 +218,7 @@ export interface TraitsMenuContentProps {
onPromptChange: (prompt: string) => void;
modelOptions?: ProviderOptions | null | undefined;
allowPromptInjectedEffort?: boolean;
planModeEnabled: boolean;
triggerVariant?: VariantProps<typeof buttonVariants>["variant"];
triggerClassName?: string;
}
Expand All @@ -227,6 +232,7 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
onPromptChange,
modelOptions,
allowPromptInjectedEffort = true,
planModeEnabled,
...persistence
}: TraitsMenuContentProps & TraitsPersistence) {
const setProviderModelOptions = useComposerDraftStore((store) => store.setProviderModelOptions);
Expand Down Expand Up @@ -263,6 +269,7 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
prompt,
modelOptions,
allowPromptInjectedEffort,
planModeEnabled,
});
const updateDescriptors = (nextDescriptors: ReadonlyArray<ProviderOptionDescriptor>) => {
updateModelOptions(buildProviderOptionSelectionsFromDescriptors(nextDescriptors));
Expand Down Expand Up @@ -444,6 +451,7 @@ export const TraitsPicker = memo(function TraitsPicker({
onPromptChange,
modelOptions,
allowPromptInjectedEffort = true,
planModeEnabled,
triggerVariant,
triggerClassName,
...persistence
Expand All @@ -457,6 +465,7 @@ export const TraitsPicker = memo(function TraitsPicker({
prompt,
modelOptions,
allowPromptInjectedEffort,
planModeEnabled,
});
if (
!shouldRenderTraitsControls({
Expand All @@ -466,6 +475,7 @@ export const TraitsPicker = memo(function TraitsPicker({
prompt,
modelOptions,
allowPromptInjectedEffort,
planModeEnabled,
})
) {
return null;
Expand Down Expand Up @@ -536,6 +546,7 @@ export const TraitsPicker = memo(function TraitsPicker({
onPromptChange={onPromptChange}
modelOptions={modelOptions}
allowPromptInjectedEffort={allowPromptInjectedEffort}
planModeEnabled={planModeEnabled}
{...persistence}
/>
</MenuPopup>
Expand Down
44 changes: 44 additions & 0 deletions apps/web/src/components/chat/composerProviderState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ describe("getComposerProviderState", () => {
]),
]),
modelOptions: undefined,
planModeEnabled: true,
});

expect(state).toEqual({
Expand All @@ -101,6 +102,7 @@ describe("getComposerProviderState", () => {
booleanDescriptor("fastMode"),
]),
modelOptions: selections(["effort", "low"], ["fastMode", true]),
planModeEnabled: true,
});

expect(state).toEqual({
Expand All @@ -119,6 +121,7 @@ describe("getComposerProviderState", () => {
booleanDescriptor("fastMode"),
]),
modelOptions: selections(["effort", "high"], ["fastMode", false]),
planModeEnabled: true,
});

expect(state.modelOptionsForDispatch).toEqual(
Expand All @@ -132,6 +135,7 @@ describe("getComposerProviderState", () => {
model: MODEL,
models: modelWith([booleanDescriptor("thinking")]),
modelOptions: selections(["effort", "max"], ["thinking", false]),
planModeEnabled: true,
});

expect(state).toEqual({
Expand All @@ -157,6 +161,7 @@ describe("getComposerProviderState", () => {
]),
]),
modelOptions: selections(["agent", "plan"]),
planModeEnabled: true,
});

expect(state.promptEffort).toBe("high");
Expand All @@ -165,12 +170,48 @@ describe("getComposerProviderState", () => {
);
});

it("drops the plan agent from dispatch when legacy plan mode is disabled", () => {
const state = getComposerProviderState({
provider: PROVIDER,
model: MODEL,
models: modelWith([
selectDescriptor("agent", [
{ id: "build", label: "Build", isDefault: true },
{ id: "plan", label: "Plan" },
]),
]),
modelOptions: selections(["agent", "plan"]),
planModeEnabled: false,
});

expect(state.modelOptionsForDispatch).toEqual(selections(["agent", "build"]));
});

it("drops the agent descriptor entirely when plan is the only option and plan mode is disabled", () => {
const state = getComposerProviderState({
provider: PROVIDER,
model: MODEL,
models: modelWith([
selectDescriptor("agent", [{ id: "plan", label: "Plan", isDefault: true }]),
]),
modelOptions: selections(["agent", "plan"]),
planModeEnabled: false,
});

expect(state).toEqual({
provider: PROVIDER,
promptEffort: null,
modelOptionsForDispatch: undefined,
});
});

it("returns undefined dispatch options when the model declares no descriptors", () => {
const state = getComposerProviderState({
provider: PROVIDER,
model: MODEL,
models: modelWith([]),
modelOptions: selections(["anything", "value"]),
planModeEnabled: true,
});

expect(state).toEqual({
Expand Down Expand Up @@ -199,6 +240,7 @@ describe("getComposerProviderState", () => {
"Ultrathink:\nInvestigate this failure",
),
modelOptions: selections(["effort", "medium"]),
planModeEnabled: true,
});

expect(state).toEqual({
Expand All @@ -220,6 +262,7 @@ describe("getComposerProviderState", () => {
"Ultrathink:\nInvestigate this failure",
),
modelOptions: undefined,
planModeEnabled: true,
});

expect(state).not.toHaveProperty("composerFrameClassName");
Expand All @@ -240,6 +283,7 @@ describe("provider traits render guards", () => {
modelOptions: undefined,
prompt: "",
onPromptChange: () => {},
planModeEnabled: true,
};

expect(renderProviderTraitsPicker(args)).toBeNull();
Expand Down
24 changes: 21 additions & 3 deletions apps/web/src/components/chat/composerProviderState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type ComposerProviderStateInput = {
models: ReadonlyArray<ServerProviderModel>;
promptInjectionState?: ComposerPromptInjectionState;
modelOptions: ReadonlyArray<ProviderOptionSelection> | null | undefined;
planModeEnabled: boolean;
};

export type ComposerPromptInjectionState = "none" | "ultrathink";
Expand All @@ -46,15 +47,23 @@ type TraitsRenderInput = {
modelOptions: ReadonlyArray<ProviderOptionSelection> | undefined;
prompt: string;
onPromptChange: (prompt: string) => void;
planModeEnabled: boolean;
};

export function getComposerPromptInjectionState(prompt: string): ComposerPromptInjectionState {
return isClaudeUltrathinkPrompt(prompt) ? "ultrathink" : "none";
}

export function getComposerProviderState(input: ComposerProviderStateInput): ComposerProviderState {
const { provider, model, models, modelOptions, promptInjectionState = "none" } = input;
const caps = getProviderModelCapabilities(models, model, provider);
const {
provider,
model,
models,
modelOptions,
promptInjectionState = "none",
planModeEnabled,
} = input;
const caps = getProviderModelCapabilities(models, model, provider, planModeEnabled);
const descriptors = getProviderOptionDescriptors({ caps, selections: modelOptions });
const primarySelectDescriptor = descriptors.find(
(descriptor): descriptor is Extract<(typeof descriptors)[number], { type: "select" }> =>
Expand Down Expand Up @@ -94,11 +103,19 @@ function renderTraitsControl(
modelOptions,
prompt,
onPromptChange,
planModeEnabled,
} = input;
const hasTarget = threadRef !== undefined || draftId !== undefined;
if (
!hasTarget ||
!shouldRenderTraitsControls({ provider, models, model, modelOptions, prompt })
!shouldRenderTraitsControls({
provider,
models,
model,
modelOptions,
prompt,
planModeEnabled,
})
) {
return null;
}
Expand All @@ -113,6 +130,7 @@ function renderTraitsControl(
modelOptions={modelOptions}
prompt={prompt}
onPromptChange={onPromptChange}
planModeEnabled={planModeEnabled}
/>
);
}
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/settings/ProjectSettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
onPromptChange={() => {}}
modelOptions={resolvedSelection.options ?? []}
allowPromptInjectedEffort={false}
planModeEnabled={settings.planModeEnabled}
triggerVariant="outline"
triggerClassName="min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground"
onModelOptionsChange={(nextOptions) => {
Expand Down
30 changes: 27 additions & 3 deletions apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import { useDesktopUpdateState } from "../../state/desktopUpdate";
import {
getCustomModelOptionsByInstance,
resolveAppModelSelectionState,
withoutPlanAgentSelection,
} from "../../modelSelection";
import {
applyProviderInstanceSettings,
Expand Down Expand Up @@ -1683,9 +1684,31 @@ function LegacyFeaturesSection() {
control={
<Switch
checked={settings.planModeEnabled}
onCheckedChange={(checked) =>
updateSettings({ planModeEnabled: Boolean(checked) })
}
onCheckedChange={(checked) => {
const planModeEnabled = Boolean(checked);
const textGenerationModelSelection = withoutPlanAgentSelection(
settings.textGenerationModelSelection,
);
const sourceControlWriterModelSelection = withoutPlanAgentSelection(
settings.sourceControlWriterModelSelection,
);
updateSettings({
planModeEnabled,
...(planModeEnabled
? {}
: {
...(textGenerationModelSelection &&
textGenerationModelSelection !== settings.textGenerationModelSelection
? { textGenerationModelSelection }
: {}),
...(sourceControlWriterModelSelection &&
sourceControlWriterModelSelection !==
settings.sourceControlWriterModelSelection
? { sourceControlWriterModelSelection }
: {}),
}),
});
Comment thread
cursor[bot] marked this conversation as resolved.
}}
aria-label="Plan mode (legacy)"
/>
}
Expand Down Expand Up @@ -2254,6 +2277,7 @@ export function GeneralSettingsPanel() {
onPromptChange={() => {}}
modelOptions={textGenModelOptions}
allowPromptInjectedEffort={false}
planModeEnabled={settings.planModeEnabled}
triggerVariant="outline"
triggerClassName="min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground"
onModelOptionsChange={(nextOptions) => {
Expand Down
Loading
Loading