fix(web): show Auto instead of Default for Cursor model picker (#1248)

* fix(web): show Auto instead of Default for Cursor model picker

Cursor CLI uses `auto` for automatic model selection; labeling it
"Default" in HAPI was confusing and inconsistent with `agent --list-models`.

Fixes #1247

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): align Cursor unavailable copy with Auto label

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: re-trigger Codex PR review

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(web): update Cursor catalog JSDoc for Auto label

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
SSU-WEI HUANG
2026-07-31 18:17:11 +01:00
committed by GitHub
co-authored by Cursor
parent 259d69cd8f
commit da921821ee
7 changed files with 23 additions and 18 deletions
@@ -89,7 +89,7 @@ describe('getModelOptionsForFlavor', () => {
it('returns only default/current for cursor before models are discovered (no claude fallback)', () => { it('returns only default/current for cursor before models are discovered (no claude fallback)', () => {
const options = getModelOptionsForFlavor('cursor', 'composer-2.5') const options = getModelOptionsForFlavor('cursor', 'composer-2.5')
expect(options).toEqual([ expect(options).toEqual([
{ value: null, label: 'Default' }, { value: null, label: 'Auto' },
{ value: 'composer-2.5', label: 'composer-2.5' } { value: 'composer-2.5', label: 'composer-2.5' }
]) ])
}) })
@@ -108,12 +108,12 @@ describe('getModelOptionsForFlavor', () => {
it('does not inject raw wire id when dual picker base is already listed', () => { it('does not inject raw wire id when dual picker base is already listed', () => {
const wire = 'claude-opus-4-8[thinking=true,context=300k,effort=high,fast=false]' const wire = 'claude-opus-4-8[thinking=true,context=300k,effort=high,fast=false]'
const options = getModelOptionsForFlavor('cursor', wire, [ const options = getModelOptionsForFlavor('cursor', wire, [
{ value: null, label: 'Default' }, { value: null, label: 'Auto' },
{ value: 'claude-opus-4-8', label: 'Claude Opus 4.8' }, { value: 'claude-opus-4-8', label: 'Claude Opus 4.8' },
{ value: 'composer-2.5', label: 'Composer 2.5' }, { value: 'composer-2.5', label: 'Composer 2.5' },
]) ])
expect(options).toEqual([ expect(options).toEqual([
{ value: null, label: 'Default' }, { value: null, label: 'Auto' },
{ value: 'claude-opus-4-8', label: 'Claude Opus 4.8' }, { value: 'claude-opus-4-8', label: 'Claude Opus 4.8' },
{ value: 'composer-2.5', label: 'Composer 2.5' }, { value: 'composer-2.5', label: 'Composer 2.5' },
]) ])
@@ -122,11 +122,11 @@ describe('getModelOptionsForFlavor', () => {
it('injects unknown wire id only when catalog lacks base and wire', () => { it('injects unknown wire id only when catalog lacks base and wire', () => {
const wire = 'claude-opus-4-9[effort=high,fast=false]' const wire = 'claude-opus-4-9[effort=high,fast=false]'
const options = getModelOptionsForFlavor('cursor', wire, [ const options = getModelOptionsForFlavor('cursor', wire, [
{ value: null, label: 'Default' }, { value: null, label: 'Auto' },
{ value: 'composer-2.5', label: 'Composer 2.5' }, { value: 'composer-2.5', label: 'Composer 2.5' },
]) ])
expect(options).toEqual([ expect(options).toEqual([
{ value: null, label: 'Default' }, { value: null, label: 'Auto' },
{ value: wire, label: wire }, { value: wire, label: wire },
{ value: 'composer-2.5', label: 'Composer 2.5' }, { value: 'composer-2.5', label: 'Composer 2.5' },
]) ])
@@ -1,4 +1,5 @@
import { MODEL_OPTIONS } from '@/components/NewSession/types' import { MODEL_OPTIONS } from '@/components/NewSession/types'
import { CURSOR_AUTO_MODEL_LABEL } from '@/lib/cursorModelOptions'
import { getClaudeComposerModelOptions, getNextClaudeComposerModel } from './claudeModelOptions' import { getClaudeComposerModelOptions, getNextClaudeComposerModel } from './claudeModelOptions'
import type { ClaudeComposerModelOption } from './claudeModelOptions' import type { ClaudeComposerModelOption } from './claudeModelOptions'
@@ -120,7 +121,7 @@ export function getModelOptionsForFlavor(
return [] return []
} }
if (flavor === 'cursor') { if (flavor === 'cursor') {
return withCurrentModelOption([{ value: null, label: 'Default' }], currentModel) return withCurrentModelOption([{ value: null, label: CURSOR_AUTO_MODEL_LABEL }], currentModel)
} }
// Kimi has no predefined model list — show just the auto/default option. // Kimi has no predefined model list — show just the auto/default option.
if (flavor === 'kimi') { if (flavor === 'kimi') {
@@ -97,7 +97,7 @@ describe('flat vs dual cursor model pickers', () => {
const options = buildNewSessionCursorModelOptions(picker) const options = buildNewSessionCursorModelOptions(picker)
expect(picker.mode).toBe('flat') expect(picker.mode).toBe('flat')
expect(options).toEqual([ expect(options).toEqual([
{ value: 'auto', label: 'Default' }, { value: 'auto', label: 'Auto' },
{ {
value: 'claude-opus-4-7[thinking=true,context=300k,effort=xhigh,fast=false]', value: 'claude-opus-4-7[thinking=true,context=300k,effort=xhigh,fast=false]',
label: 'claude-opus-4-7 · thinking=true,context=300k,effort=xhigh,fast=false' label: 'claude-opus-4-7 · thinking=true,context=300k,effort=xhigh,fast=false'
@@ -110,7 +110,7 @@ describe('flat vs dual cursor model pickers', () => {
const picker = buildNewSessionCursorPickerState([...acpModels], 'auto') const picker = buildNewSessionCursorPickerState([...acpModels], 'auto')
expect(picker.mode).toBe('dual') expect(picker.mode).toBe('dual')
expect(buildNewSessionCursorModelOptions(picker)).toEqual([ expect(buildNewSessionCursorModelOptions(picker)).toEqual([
{ value: 'auto', label: 'Default' }, { value: 'auto', label: 'Auto' },
{ value: 'composer-2.5', label: 'composer-2.5' }, { value: 'composer-2.5', label: 'composer-2.5' },
]) ])
}) })
@@ -154,7 +154,7 @@ describe('probe slug catalog (New Session cold start)', () => {
] ]
const picker = buildNewSessionCursorPickerState(probeOnly, 'composer-2.5') const picker = buildNewSessionCursorPickerState(probeOnly, 'composer-2.5')
expect(picker.mode).toBe('flat') expect(picker.mode).toBe('flat')
expect(picker.modelOptions).toEqual([{ value: 'auto', label: 'Default' }]) expect(picker.modelOptions).toEqual([{ value: 'auto', label: 'Auto' }])
expect(picker.effortOptions).toEqual([]) expect(picker.effortOptions).toEqual([])
expect(shouldShowCursorModelsUnavailable({ expect(shouldShowCursorModelsUnavailable({
agent: 'cursor', agent: 'cursor',
@@ -169,7 +169,7 @@ describe('new session cursor model options', () => {
it('maps base options with auto default and raw variant labels', () => { it('maps base options with auto default and raw variant labels', () => {
const picker = buildNewSessionCursorPickerState([...acpModels], 'composer-2.5[fast=true]') const picker = buildNewSessionCursorPickerState([...acpModels], 'composer-2.5[fast=true]')
expect(buildNewSessionCursorModelOptions(picker)).toEqual([ expect(buildNewSessionCursorModelOptions(picker)).toEqual([
{ value: 'auto', label: 'Default' }, { value: 'auto', label: 'Auto' },
{ value: 'composer-2.5', label: 'composer-2.5' }, { value: 'composer-2.5', label: 'composer-2.5' },
]) ])
expect(buildNewSessionCursorEffortOptions(picker)).toEqual([ expect(buildNewSessionCursorEffortOptions(picker)).toEqual([
+4 -3
View File
@@ -3,6 +3,7 @@ import {
buildCursorEffortPickerOptions, buildCursorEffortPickerOptions,
buildCursorModelCatalog, buildCursorModelCatalog,
buildFlatCursorModelPickerOptions, buildFlatCursorModelPickerOptions,
CURSOR_AUTO_MODEL_LABEL,
cursorCatalogHasMultiVariantBases, cursorCatalogHasMultiVariantBases,
shouldUseCursorDualPickers, shouldUseCursorDualPickers,
cursorEffortPickerLabel, cursorEffortPickerLabel,
@@ -56,7 +57,7 @@ describe('buildCursorModelCatalog', () => {
it('groups exact ACP wire variants by raw base id and sorts model bases', () => { it('groups exact ACP wire variants by raw base id and sorts model bases', () => {
const catalog = buildCursorModelCatalog([...acpModels]) const catalog = buildCursorModelCatalog([...acpModels])
expect(catalog.baseOptions.map((o) => o.label)).toEqual([ expect(catalog.baseOptions.map((o) => o.label)).toEqual([
'Default', CURSOR_AUTO_MODEL_LABEL,
'claude-opus-4-8', 'claude-opus-4-8',
'composer-2.5', 'composer-2.5',
]) ])
@@ -102,7 +103,7 @@ describe('picker labels and modes', () => {
], { defaultValue: 'auto' }) ], { defaultValue: 'auto' })
expect(cursorCatalogHasMultiVariantBases(catalog)).toBe(false) expect(cursorCatalogHasMultiVariantBases(catalog)).toBe(false)
expect(buildFlatCursorModelPickerOptions(catalog, { defaultValue: 'auto' })).toEqual([ expect(buildFlatCursorModelPickerOptions(catalog, { defaultValue: 'auto' })).toEqual([
{ value: 'auto', label: 'Default' }, { value: 'auto', label: CURSOR_AUTO_MODEL_LABEL },
{ {
value: 'claude-opus-4-7[thinking=true,context=300k,effort=xhigh,fast=false]', value: 'claude-opus-4-7[thinking=true,context=300k,effort=xhigh,fast=false]',
label: 'claude-opus-4-7 · thinking=true,context=300k,effort=xhigh,fast=false' label: 'claude-opus-4-7 · thinking=true,context=300k,effort=xhigh,fast=false'
@@ -129,7 +130,7 @@ describe('picker labels and modes', () => {
{ modelId: 'composer-2.5[fast=false]', name: 'Composer 2.5' }, { modelId: 'composer-2.5[fast=false]', name: 'Composer 2.5' },
], { defaultValue: 'auto' }) ], { defaultValue: 'auto' })
expect(buildFlatCursorModelPickerOptions(catalog, { defaultValue: 'auto' })).toEqual([ expect(buildFlatCursorModelPickerOptions(catalog, { defaultValue: 'auto' })).toEqual([
{ value: 'auto', label: 'Default' }, { value: 'auto', label: CURSOR_AUTO_MODEL_LABEL },
{ value: 'composer-2.5[fast=true]', label: 'fast=true' }, { value: 'composer-2.5[fast=true]', label: 'fast=true' },
{ value: 'composer-2.5[fast=false]', label: 'fast=false' }, { value: 'composer-2.5[fast=false]', label: 'fast=false' },
]) ])
+6 -3
View File
@@ -15,6 +15,9 @@ export type CursorModelCatalog = {
wireToBase: Map<string, string> wireToBase: Map<string, string>
} }
/** User-facing label for Cursor automatic model selection (`auto` / ACP `default[]`). */
export const CURSOR_AUTO_MODEL_LABEL = 'Auto'
/** Base model id before ACP wire suffix, e.g. `composer-2.5[fast=true]` → `composer-2.5`. */ /** Base model id before ACP wire suffix, e.g. `composer-2.5[fast=true]` → `composer-2.5`. */
export function cursorModelBaseId(modelId: string): string { export function cursorModelBaseId(modelId: string): string {
const trimmed = modelId.trim() const trimmed = modelId.trim()
@@ -134,7 +137,7 @@ export function cursorVariantDisambiguationSuffix(modelId: string): string {
/** /**
* Group ACP wire ids by raw base model. Labels are raw base ids; variant labels are raw suffixes. * Group ACP wire ids by raw base model. Labels are raw base ids; variant labels are raw suffixes.
* Catalog order follows the input order; only `Default` is prepended. * Catalog order follows the input order; only "Auto" is prepended.
*/ */
export function buildCursorModelCatalog( export function buildCursorModelCatalog(
availableModels: readonly CursorModelSummary[], availableModels: readonly CursorModelSummary[],
@@ -185,7 +188,7 @@ export function buildCursorModelCatalog(
.sort((a, b) => a[1].localeCompare(b[1], undefined, { sensitivity: 'base' })) .sort((a, b) => a[1].localeCompare(b[1], undefined, { sensitivity: 'base' }))
const baseOptions: CursorModelOption[] = [ const baseOptions: CursorModelOption[] = [
{ value: defaultValue, label: 'Default' }, { value: defaultValue, label: CURSOR_AUTO_MODEL_LABEL },
...sortedBaseEntries.map(([baseId, label]) => ({ value: baseId, label })) ...sortedBaseEntries.map(([baseId, label]) => ({ value: baseId, label }))
] ]
@@ -312,7 +315,7 @@ export function buildFlatCursorModelPickerOptions(
} }
return [ return [
{ value: defaultValue ?? 'auto', label: 'Default' }, { value: defaultValue ?? 'auto', label: CURSOR_AUTO_MODEL_LABEL },
...rows ...rows
] ]
} }
+1 -1
View File
@@ -269,7 +269,7 @@ export default {
'newSession.model.optional': 'optional', 'newSession.model.optional': 'optional',
'newSession.model.loadFailed': 'Failed to load models', 'newSession.model.loadFailed': 'Failed to load models',
'newSession.model.selectVariant': 'Select variant', 'newSession.model.selectVariant': 'Select variant',
'newSession.model.cursorUnavailable': 'No Cursor models yet. Start a Cursor session once, or use Default.', 'newSession.model.cursorUnavailable': 'No Cursor models yet. Start a Cursor session once, or use Auto.',
'newSession.opencodeModel.loading': 'Discovering OpenCode models…', 'newSession.opencodeModel.loading': 'Discovering OpenCode models…',
'newSession.opencodeModel.loadFailed': 'Failed to load OpenCode models', 'newSession.opencodeModel.loadFailed': 'Failed to load OpenCode models',
'newSession.opencodeModel.retry': 'Retry', 'newSession.opencodeModel.retry': 'Retry',
+1 -1
View File
@@ -273,7 +273,7 @@ export default {
'newSession.model.optional': '可选', 'newSession.model.optional': '可选',
'newSession.model.loadFailed': '加载模型失败', 'newSession.model.loadFailed': '加载模型失败',
'newSession.model.selectVariant': '选择变体', 'newSession.model.selectVariant': '选择变体',
'newSession.model.cursorUnavailable': '暂无 Cursor 模型列表。请先运行一次 Cursor 会话,或使用默认模型。', 'newSession.model.cursorUnavailable': '暂无 Cursor 模型列表。请先运行一次 Cursor 会话,或使用 Auto。',
'newSession.opencodeModel.loading': '正在发现 OpenCode 模型…', 'newSession.opencodeModel.loading': '正在发现 OpenCode 模型…',
'newSession.opencodeModel.loadFailed': '加载 OpenCode 模型失败', 'newSession.opencodeModel.loadFailed': '加载 OpenCode 模型失败',
'newSession.opencodeModel.retry': '重试', 'newSession.opencodeModel.retry': '重试',