diff --git a/apps/server/src/provider/Layers/CursorProvider.test.ts b/apps/server/src/provider/Layers/CursorProvider.test.ts index 98983b4b4434..08bf15913591 100644 --- a/apps/server/src/provider/Layers/CursorProvider.test.ts +++ b/apps/server/src/provider/Layers/CursorProvider.test.ts @@ -299,6 +299,29 @@ const parameterizedClaudeModelOptionConfigOptions = [ }, ] satisfies ReadonlyArray; +const parameterizedAutoSmartConfigOptions = [ + { + type: "select", + currentValue: "auto-smart", + options: [{ name: "Auto", value: "auto-smart" }], + category: "model", + id: "model", + name: "Model", + }, + { + type: "select", + currentValue: "balanced", + options: [ + { name: "Intelligence", value: "intelligence" }, + { name: "Balance", value: "balanced" }, + { name: "Cost", value: "cost" }, + ], + category: "model_config", + id: "optimize_for", + name: "Optimize For", + }, +] satisfies ReadonlyArray; + const baseCursorSettings: CursorSettings = { enabled: true, binaryPath: "cursor-agent", @@ -523,6 +546,20 @@ describe("buildCursorCapabilitiesFromConfigOptions", () => { }), ); }); + + it("derives Optimize For modes for Cursor Router Auto", () => { + expect(buildCursorCapabilitiesFromConfigOptions(parameterizedAutoSmartConfigOptions)).toEqual( + createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("optimizeFor", "Optimize For", [ + { id: "intelligence", label: "Intelligence" }, + { id: "balanced", label: "Balance", isDefault: true }, + { id: "cost", label: "Cost" }, + ]), + ], + }), + ); + }); }); describe("checkCursorProviderStatus", () => { @@ -775,4 +812,12 @@ describe("resolveCursorAcpConfigUpdates", () => { { configId: "thinking", value: "false" }, ]); }); + + it("writes Cursor Router Optimize For Cost through optimize_for", () => { + expect( + resolveCursorAcpConfigUpdates(parameterizedAutoSmartConfigOptions, [ + { id: "optimizeFor", value: "cost" }, + ]), + ).toEqual([{ configId: "optimize_for", value: "cost" }]); + }); }); diff --git a/apps/server/src/provider/Layers/CursorProvider.ts b/apps/server/src/provider/Layers/CursorProvider.ts index e6c7853844e0..748f494e6b9b 100644 --- a/apps/server/src/provider/Layers/CursorProvider.ts +++ b/apps/server/src/provider/Layers/CursorProvider.ts @@ -197,6 +197,23 @@ function findCursorEffortConfigOption( ); } +function isCursorOptimizeForConfigOption(option: EffectAcpSchema.SessionConfigOption): boolean { + const id = option.id.trim().toLowerCase(); + const name = option.name.trim().toLowerCase(); + return id === "optimize_for" || name.includes("optimize"); +} + +function findCursorOptimizeForConfigOption( + configOptions: ReadonlyArray, +): EffectAcpSchema.SessionConfigOption | undefined { + return configOptions.find( + (option) => + option.type === "select" && + getCursorConfigOptionCategory(option) === "model_config" && + isCursorOptimizeForConfigOption(option), + ); +} + function isCursorContextConfigOption(option: EffectAcpSchema.SessionConfigOption): boolean { const id = option.id.trim().toLowerCase(); const name = option.name.trim().toLowerCase(); @@ -257,6 +274,24 @@ export function buildCursorCapabilitiesFromConfigOptions( return EMPTY_CAPABILITIES; } + const optimizeForOption = findCursorOptimizeForConfigOption(configOptions); + const optimizeForOptions = + optimizeForOption?.type === "select" + ? flattenSessionConfigSelectOptions(optimizeForOption).map((entry) => { + if (optimizeForOption.currentValue === entry.value) { + return { + value: entry.value, + label: entry.name, + isDefault: true, + }; + } + return { + value: entry.value, + label: entry.name, + }; + }) + : []; + const reasoningConfig = findCursorEffortConfigOption(configOptions); const reasoningEffortLevels = reasoningConfig?.type === "select" @@ -306,6 +341,15 @@ export function buildCursorCapabilitiesFromConfigOptions( const fastCurrentValue = getBooleanCurrentValue(fastOption); const thinkingCurrentValue = getBooleanCurrentValue(thinkingOption); const optionDescriptors = [ + ...(optimizeForOptions.length > 0 + ? [ + buildSelectOptionDescriptor({ + id: "optimizeFor", + label: optimizeForOption?.name?.trim() || "Optimize For", + options: optimizeForOptions, + }), + ] + : []), ...(reasoningEffortLevels.length > 0 ? [ buildSelectOptionDescriptor({ @@ -493,6 +537,22 @@ export function resolveCursorAcpConfigUpdates( readonly value: string | boolean; }> = []; + const optimizeForOption = findCursorOptimizeForConfigOption(configOptions); + const requestedOptimizeFor = getProviderOptionStringSelectionValue(selections, "optimizeFor"); + if (optimizeForOption && requestedOptimizeFor) { + const value = findCursorSelectOptionValue( + optimizeForOption, + (option) => + normalizeCursorConfigOptionToken(option.value) === + normalizeCursorConfigOptionToken(requestedOptimizeFor) || + normalizeCursorConfigOptionToken(option.name) === + normalizeCursorConfigOptionToken(requestedOptimizeFor), + ); + if (value) { + updates.push({ configId: optimizeForOption.id, value }); + } + } + const reasoningOption = findCursorEffortConfigOption(configOptions); const requestedReasoning = normalizeCursorReasoningValue( getProviderOptionStringSelectionValue(selections, "reasoning"), diff --git a/apps/web/src/components/settings/ProviderModelsSection.tsx b/apps/web/src/components/settings/ProviderModelsSection.tsx index 7b98dbe42991..90d7017d3cb8 100644 --- a/apps/web/src/components/settings/ProviderModelsSection.tsx +++ b/apps/web/src/components/settings/ProviderModelsSection.tsx @@ -57,6 +57,7 @@ function describeModelCapabilities(model: ServerProviderModel): string[] { (descriptor.id === "reasoningEffort" || descriptor.id === "effort" || descriptor.id === "reasoning" || + descriptor.id === "optimizeFor" || descriptor.id === "variant"), ) ) {