mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
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:
@@ -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([
|
||||||
|
|||||||
@@ -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' },
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -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
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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': '重试',
|
||||||
|
|||||||
Reference in New Issue
Block a user