mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 04:17:11 +00:00
feat(cursor): sync model mapping across config and raw settings
- add cursor model fields to unified config defaults and YAML comments - expand Cursor model catalog/defaults and return configured current model - keep cursor.settings.json env model fields in sync with config updates - align unit tests for new model defaults and route merge behavior Refs #555
This commit is contained in:
@@ -306,6 +306,10 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
|
||||
port: partial.cursor?.port ?? DEFAULT_CURSOR_CONFIG.port,
|
||||
auto_start: partial.cursor?.auto_start ?? DEFAULT_CURSOR_CONFIG.auto_start,
|
||||
ghost_mode: partial.cursor?.ghost_mode ?? DEFAULT_CURSOR_CONFIG.ghost_mode,
|
||||
model: partial.cursor?.model ?? DEFAULT_CURSOR_CONFIG.model,
|
||||
opus_model: partial.cursor?.opus_model,
|
||||
sonnet_model: partial.cursor?.sonnet_model,
|
||||
haiku_model: partial.cursor?.haiku_model,
|
||||
},
|
||||
// Global env - injected into all non-Claude subscription profiles
|
||||
global_env: {
|
||||
@@ -603,6 +607,8 @@ function generateYamlWithComments(config: UnifiedConfig): string {
|
||||
lines.push('# port: Port for cursor proxy daemon (default: 20129)');
|
||||
lines.push('# auto_start: Auto-start daemon when CCS starts (default: false)');
|
||||
lines.push('# ghost_mode: Disable telemetry for privacy (default: true)');
|
||||
lines.push('# model: Default model ID (used for ANTHROPIC_MODEL)');
|
||||
lines.push('# opus_model/sonnet_model/haiku_model: Optional tier model mapping');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ cursor: config.cursor }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
|
||||
@@ -304,6 +304,14 @@ export interface CursorConfig {
|
||||
auto_start: boolean;
|
||||
/** Enable ghost mode to disable telemetry (default: true) */
|
||||
ghost_mode: boolean;
|
||||
/** Default model ID used by Cursor integration */
|
||||
model: string;
|
||||
/** Optional tier mapping for Claude-compatible model routing */
|
||||
opus_model?: string;
|
||||
/** Optional tier mapping for Claude-compatible model routing */
|
||||
sonnet_model?: string;
|
||||
/** Optional tier mapping for Claude-compatible model routing */
|
||||
haiku_model?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -723,6 +731,7 @@ export const DEFAULT_CURSOR_CONFIG: CursorConfig = {
|
||||
port: 20129,
|
||||
auto_start: false,
|
||||
ghost_mode: true,
|
||||
model: 'gpt-5.3-codex',
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+106
-23
@@ -13,61 +13,143 @@ import { isDaemonRunning } from './cursor-daemon';
|
||||
export const DEFAULT_CURSOR_PORT = 20129;
|
||||
|
||||
/** Default model ID */
|
||||
export const DEFAULT_CURSOR_MODEL = 'gpt-4.1';
|
||||
export const DEFAULT_CURSOR_MODEL = 'gpt-5.3-codex';
|
||||
|
||||
/**
|
||||
* Default models available through Cursor IDE.
|
||||
* Used as fallback when daemon is not reachable.
|
||||
* Source: Cursor IDE supported models (Feb 2025)
|
||||
* Source: Cursor docs model catalog (Feb 2026)
|
||||
*/
|
||||
export const DEFAULT_CURSOR_MODELS: CursorModel[] = [
|
||||
// Anthropic Models
|
||||
{
|
||||
id: 'claude-sonnet-4',
|
||||
name: 'Claude Sonnet 4',
|
||||
id: 'claude-4.6-opus',
|
||||
name: 'Claude 4.6 Opus',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
{
|
||||
id: 'claude-sonnet-4.5',
|
||||
name: 'Claude Sonnet 4.5',
|
||||
id: 'claude-4.6-opus-fast-mode',
|
||||
name: 'Claude 4.6 Opus (Fast mode)',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
{
|
||||
id: 'claude-opus-4',
|
||||
name: 'Claude Opus 4',
|
||||
id: 'claude-4.5-sonnet',
|
||||
name: 'Claude 4.5 Sonnet',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
{
|
||||
id: 'claude-4.5-opus',
|
||||
name: 'Claude 4.5 Opus',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
{
|
||||
id: 'claude-4.5-haiku',
|
||||
name: 'Claude 4.5 Haiku',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
{
|
||||
id: 'claude-4-sonnet',
|
||||
name: 'Claude 4 Sonnet',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
{
|
||||
id: 'claude-4-sonnet-1m',
|
||||
name: 'Claude 4 Sonnet 1M',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
|
||||
// Cursor Models
|
||||
{
|
||||
id: 'composer-1.5',
|
||||
name: 'Composer 1.5',
|
||||
provider: 'cursor',
|
||||
},
|
||||
{
|
||||
id: 'composer-1',
|
||||
name: 'Composer 1',
|
||||
provider: 'cursor',
|
||||
},
|
||||
|
||||
// OpenAI Models
|
||||
{
|
||||
id: 'gpt-4.1',
|
||||
name: 'GPT-4.1',
|
||||
id: 'gpt-5.3-codex',
|
||||
name: 'GPT-5.3 Codex',
|
||||
provider: 'openai',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.2-codex',
|
||||
name: 'GPT-5.2 Codex',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.2',
|
||||
name: 'GPT-5.2',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.1-codex',
|
||||
name: 'GPT-5.1 Codex',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.1-codex-max',
|
||||
name: 'GPT-5.1 Codex Max',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.1-codex-mini',
|
||||
name: 'GPT-5.1 Codex Mini',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'gpt-5-codex',
|
||||
name: 'GPT-5-Codex',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'gpt-5',
|
||||
name: 'GPT-5',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'gpt-5-fast',
|
||||
name: 'GPT-5 Fast',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'gpt-5-mini',
|
||||
name: 'GPT-5 Mini',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'o3-mini',
|
||||
name: 'O3 Mini',
|
||||
provider: 'openai',
|
||||
},
|
||||
|
||||
// Google Models
|
||||
{
|
||||
id: 'gemini-2.5-pro',
|
||||
name: 'Gemini 2.5 Pro',
|
||||
id: 'gemini-3-pro',
|
||||
name: 'Gemini 3 Pro',
|
||||
provider: 'google',
|
||||
},
|
||||
{
|
||||
id: 'gemini-3-pro-image-preview',
|
||||
name: 'Gemini 3 Pro Image Preview',
|
||||
provider: 'google',
|
||||
},
|
||||
{
|
||||
id: 'gemini-3-flash',
|
||||
name: 'Gemini 3 Flash',
|
||||
provider: 'google',
|
||||
},
|
||||
{
|
||||
id: 'gemini-2.5-flash',
|
||||
name: 'Gemini 2.5 Flash',
|
||||
provider: 'google',
|
||||
},
|
||||
|
||||
// Cursor Custom Models
|
||||
// xAI Models
|
||||
{
|
||||
id: 'cursor-small',
|
||||
name: 'Cursor Small',
|
||||
provider: 'cursor',
|
||||
id: 'grok-code',
|
||||
name: 'Grok Code',
|
||||
provider: 'xai',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -154,7 +236,7 @@ export async function getAvailableModels(port: number): Promise<CursorModel[]> {
|
||||
|
||||
/**
|
||||
* Get the default model.
|
||||
* Uses gpt-4.1 as it's commonly available.
|
||||
* Uses GPT-5.3 Codex as default.
|
||||
*/
|
||||
export function getDefaultModel(): string {
|
||||
return DEFAULT_CURSOR_MODEL;
|
||||
@@ -167,7 +249,8 @@ export function detectProvider(modelId: string): string {
|
||||
if (modelId.includes('claude')) return 'anthropic';
|
||||
if (modelId.includes('gpt') || /^o[1-9]\d*(-|$)/.test(modelId)) return 'openai';
|
||||
if (modelId.includes('gemini')) return 'google';
|
||||
if (modelId.includes('cursor')) return 'cursor';
|
||||
if (modelId.includes('cursor') || modelId.includes('composer')) return 'cursor';
|
||||
if (modelId.includes('grok')) return 'xai';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Router } from 'express';
|
||||
import {
|
||||
getDaemonStatus,
|
||||
getAvailableModels,
|
||||
getDefaultModel,
|
||||
startDaemon,
|
||||
stopDaemon,
|
||||
checkAuthStatus,
|
||||
@@ -150,7 +149,7 @@ router.get('/models', async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const cursorConfig = getCursorConfig();
|
||||
const models = await getAvailableModels(cursorConfig.port);
|
||||
res.json({ models, current: getDefaultModel() });
|
||||
res.json({ models, current: cursorConfig.model });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
saveUnifiedConfig,
|
||||
getCursorConfig,
|
||||
} from '../../config/unified-config-loader';
|
||||
import type { CursorConfig } from '../../config/unified-config-types';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -34,8 +35,78 @@ function parseLocalCursorPort(settings: unknown): number | null {
|
||||
}
|
||||
}
|
||||
|
||||
function parseRequiredModel(value: unknown): string | null {
|
||||
if (typeof value !== 'string') return null;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
function parseOptionalModel(value: unknown): string | undefined {
|
||||
if (typeof value !== 'string') return undefined;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function getDefaultCursorSettings(cursorConfig: CursorConfig): { env: Record<string, string> } {
|
||||
const model = cursorConfig.model || DEFAULT_CURSOR_CONFIG.model;
|
||||
return {
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${cursorConfig.port}`,
|
||||
ANTHROPIC_AUTH_TOKEN: 'cursor-managed',
|
||||
ANTHROPIC_MODEL: model,
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: cursorConfig.opus_model || model,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: cursorConfig.sonnet_model || model,
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: cursorConfig.haiku_model || model,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function syncRawSettingsFromCursorConfig(cursorConfig: CursorConfig): void {
|
||||
const settingsPath = path.join(getCcsDir(), 'cursor.settings.json');
|
||||
|
||||
let settings: Record<string, unknown> = {};
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
||||
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
||||
settings = parsed as Record<string, unknown>;
|
||||
}
|
||||
} catch {
|
||||
settings = {};
|
||||
}
|
||||
}
|
||||
|
||||
const envSource = settings.env;
|
||||
const env =
|
||||
typeof envSource === 'object' && envSource !== null && !Array.isArray(envSource)
|
||||
? { ...(envSource as Record<string, string>) }
|
||||
: {};
|
||||
|
||||
const model = cursorConfig.model || DEFAULT_CURSOR_CONFIG.model;
|
||||
const localPort = parseLocalCursorPort({ env });
|
||||
if (!env.ANTHROPIC_BASE_URL || localPort !== null) {
|
||||
env.ANTHROPIC_BASE_URL = `http://127.0.0.1:${cursorConfig.port}`;
|
||||
}
|
||||
if (!env.ANTHROPIC_AUTH_TOKEN) {
|
||||
env.ANTHROPIC_AUTH_TOKEN = 'cursor-managed';
|
||||
}
|
||||
env.ANTHROPIC_MODEL = model;
|
||||
env.ANTHROPIC_DEFAULT_OPUS_MODEL = cursorConfig.opus_model || model;
|
||||
env.ANTHROPIC_DEFAULT_SONNET_MODEL = cursorConfig.sonnet_model || model;
|
||||
env.ANTHROPIC_DEFAULT_HAIKU_MODEL = cursorConfig.haiku_model || model;
|
||||
|
||||
const nextSettings = {
|
||||
...settings,
|
||||
env,
|
||||
};
|
||||
|
||||
const tempPath = settingsPath + '.tmp';
|
||||
fs.writeFileSync(tempPath, JSON.stringify(nextSettings, null, 2) + '\n');
|
||||
fs.renameSync(tempPath, settingsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/cursor/settings - Get cursor config (port, auto_start, ghost_mode)
|
||||
* GET /api/cursor/settings - Get cursor config
|
||||
*/
|
||||
router.get('/', (_req: Request, res: Response): void => {
|
||||
try {
|
||||
@@ -82,8 +153,40 @@ router.put('/', (req: Request, res: Response): void => {
|
||||
res.status(400).json({ error: 'ghost_mode must be a boolean' });
|
||||
return;
|
||||
}
|
||||
if ('model' in updates && !parseRequiredModel(updates.model)) {
|
||||
res.status(400).json({ error: 'model must be a non-empty string' });
|
||||
return;
|
||||
}
|
||||
if (
|
||||
'opus_model' in updates &&
|
||||
updates.opus_model !== undefined &&
|
||||
updates.opus_model !== null &&
|
||||
typeof updates.opus_model !== 'string'
|
||||
) {
|
||||
res.status(400).json({ error: 'opus_model must be a string' });
|
||||
return;
|
||||
}
|
||||
if (
|
||||
'sonnet_model' in updates &&
|
||||
updates.sonnet_model !== undefined &&
|
||||
updates.sonnet_model !== null &&
|
||||
typeof updates.sonnet_model !== 'string'
|
||||
) {
|
||||
res.status(400).json({ error: 'sonnet_model must be a string' });
|
||||
return;
|
||||
}
|
||||
if (
|
||||
'haiku_model' in updates &&
|
||||
updates.haiku_model !== undefined &&
|
||||
updates.haiku_model !== null &&
|
||||
typeof updates.haiku_model !== 'string'
|
||||
) {
|
||||
res.status(400).json({ error: 'haiku_model must be a string' });
|
||||
return;
|
||||
}
|
||||
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
const normalizedModel = parseRequiredModel(updates.model);
|
||||
|
||||
// Merge updates with existing config
|
||||
// Only known fields are merged — unknown properties are ignored
|
||||
@@ -94,9 +197,23 @@ router.put('/', (req: Request, res: Response): void => {
|
||||
updates.auto_start ?? config.cursor?.auto_start ?? DEFAULT_CURSOR_CONFIG.auto_start,
|
||||
ghost_mode:
|
||||
updates.ghost_mode ?? config.cursor?.ghost_mode ?? DEFAULT_CURSOR_CONFIG.ghost_mode,
|
||||
model: normalizedModel ?? config.cursor?.model ?? DEFAULT_CURSOR_CONFIG.model,
|
||||
opus_model:
|
||||
'opus_model' in updates
|
||||
? parseOptionalModel(updates.opus_model)
|
||||
: config.cursor?.opus_model,
|
||||
sonnet_model:
|
||||
'sonnet_model' in updates
|
||||
? parseOptionalModel(updates.sonnet_model)
|
||||
: config.cursor?.sonnet_model,
|
||||
haiku_model:
|
||||
'haiku_model' in updates
|
||||
? parseOptionalModel(updates.haiku_model)
|
||||
: config.cursor?.haiku_model,
|
||||
};
|
||||
|
||||
saveUnifiedConfig(config);
|
||||
syncRawSettingsFromCursorConfig(config.cursor);
|
||||
res.json({ success: true, cursor: config.cursor });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
@@ -114,14 +231,7 @@ router.get('/raw', (_req: Request, res: Response): void => {
|
||||
|
||||
// If file doesn't exist, return default structure
|
||||
if (!fs.existsSync(settingsPath)) {
|
||||
// Create settings structure matching Cursor pattern
|
||||
// Use 127.0.0.1 instead of localhost for more reliable local connections
|
||||
const defaultSettings = {
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${cursorConfig.port}`,
|
||||
ANTHROPIC_AUTH_TOKEN: 'cursor-managed',
|
||||
},
|
||||
};
|
||||
const defaultSettings = getDefaultCursorSettings(cursorConfig);
|
||||
|
||||
res.json({
|
||||
settings: defaultSettings,
|
||||
@@ -186,14 +296,19 @@ router.put('/raw', (req: Request, res: Response): void => {
|
||||
|
||||
// Keep unified config aligned with raw settings edits (parity with Copilot raw editor).
|
||||
const parsedPort = parseLocalCursorPort(settings);
|
||||
if (parsedPort) {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
config.cursor = {
|
||||
...(config.cursor ?? DEFAULT_CURSOR_CONFIG),
|
||||
port: parsedPort,
|
||||
};
|
||||
saveUnifiedConfig(config);
|
||||
}
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
const env = (settings as { env?: Record<string, unknown> }).env ?? {};
|
||||
const model = parseRequiredModel(env.ANTHROPIC_MODEL) ?? config.cursor?.model;
|
||||
|
||||
config.cursor = {
|
||||
...(config.cursor ?? DEFAULT_CURSOR_CONFIG),
|
||||
...(parsedPort !== null ? { port: parsedPort } : {}),
|
||||
...(model ? { model } : {}),
|
||||
opus_model: parseOptionalModel(env.ANTHROPIC_DEFAULT_OPUS_MODEL),
|
||||
sonnet_model: parseOptionalModel(env.ANTHROPIC_DEFAULT_SONNET_MODEL),
|
||||
haiku_model: parseOptionalModel(env.ANTHROPIC_DEFAULT_HAIKU_MODEL),
|
||||
};
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
const stat = fs.statSync(settingsPath);
|
||||
res.json({ success: true, mtime: stat.mtimeMs });
|
||||
|
||||
@@ -35,8 +35,8 @@ describe('DEFAULT_CURSOR_PORT', () => {
|
||||
});
|
||||
|
||||
describe('DEFAULT_CURSOR_MODEL', () => {
|
||||
it('is gpt-4.1', () => {
|
||||
expect(DEFAULT_CURSOR_MODEL).toBe('gpt-4.1');
|
||||
it('is gpt-5.3-codex', () => {
|
||||
expect(DEFAULT_CURSOR_MODEL).toBe('gpt-5.3-codex');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,12 +48,12 @@ describe('getDefaultModel', () => {
|
||||
|
||||
describe('detectProvider', () => {
|
||||
it('detects anthropic models', () => {
|
||||
expect(detectProvider('claude-sonnet-4')).toBe('anthropic');
|
||||
expect(detectProvider('claude-opus-4')).toBe('anthropic');
|
||||
expect(detectProvider('claude-4.5-sonnet')).toBe('anthropic');
|
||||
expect(detectProvider('claude-4.6-opus')).toBe('anthropic');
|
||||
});
|
||||
|
||||
it('detects openai models', () => {
|
||||
expect(detectProvider('gpt-4.1')).toBe('openai');
|
||||
expect(detectProvider('gpt-5.3-codex')).toBe('openai');
|
||||
expect(detectProvider('gpt-5-mini')).toBe('openai');
|
||||
expect(detectProvider('o3-mini')).toBe('openai');
|
||||
});
|
||||
@@ -65,11 +65,16 @@ describe('detectProvider', () => {
|
||||
});
|
||||
|
||||
it('detects google models', () => {
|
||||
expect(detectProvider('gemini-2.5-pro')).toBe('google');
|
||||
expect(detectProvider('gemini-3-pro')).toBe('google');
|
||||
});
|
||||
|
||||
it('detects cursor models', () => {
|
||||
expect(detectProvider('cursor-small')).toBe('cursor');
|
||||
expect(detectProvider('composer-1.5')).toBe('cursor');
|
||||
expect(detectProvider('cursor-model')).toBe('cursor');
|
||||
});
|
||||
|
||||
it('detects xai models', () => {
|
||||
expect(detectProvider('grok-code')).toBe('xai');
|
||||
});
|
||||
|
||||
it('defaults to unknown for unrecognized models', () => {
|
||||
@@ -79,8 +84,8 @@ describe('detectProvider', () => {
|
||||
|
||||
describe('formatModelName', () => {
|
||||
it('returns catalog name for known models', () => {
|
||||
expect(formatModelName('claude-sonnet-4')).toBe('Claude Sonnet 4');
|
||||
expect(formatModelName('gpt-4.1')).toBe('GPT-4.1');
|
||||
expect(formatModelName('claude-4.6-opus')).toBe('Claude 4.6 Opus');
|
||||
expect(formatModelName('gpt-5.3-codex')).toBe('GPT-5.3 Codex');
|
||||
});
|
||||
|
||||
it('converts kebab-case to title case for unknown models', () => {
|
||||
|
||||
@@ -128,6 +128,13 @@ describe('Cursor Settings Routes Logic', () => {
|
||||
port: updates.port ?? config.cursor?.port ?? 3000,
|
||||
auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false,
|
||||
ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false,
|
||||
model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex',
|
||||
opus_model:
|
||||
updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model,
|
||||
sonnet_model:
|
||||
updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model,
|
||||
haiku_model:
|
||||
updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model,
|
||||
};
|
||||
|
||||
expect(cursorConfig.port).toBe(5000);
|
||||
@@ -137,7 +144,13 @@ describe('Cursor Settings Routes Logic', () => {
|
||||
|
||||
it('updates port only', () => {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
config.cursor = { enabled: false, port: 3000, auto_start: false, ghost_mode: false };
|
||||
config.cursor = {
|
||||
enabled: false,
|
||||
port: 3000,
|
||||
auto_start: false,
|
||||
ghost_mode: false,
|
||||
model: 'gpt-5.3-codex',
|
||||
};
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
const updates = { port: 4000 };
|
||||
@@ -146,6 +159,13 @@ describe('Cursor Settings Routes Logic', () => {
|
||||
port: updates.port ?? config.cursor?.port ?? 3000,
|
||||
auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false,
|
||||
ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false,
|
||||
model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex',
|
||||
opus_model:
|
||||
updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model,
|
||||
sonnet_model:
|
||||
updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model,
|
||||
haiku_model:
|
||||
updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model,
|
||||
};
|
||||
|
||||
expect(cursorConfig.port).toBe(4000);
|
||||
@@ -155,7 +175,13 @@ describe('Cursor Settings Routes Logic', () => {
|
||||
|
||||
it('updates auto_start only', () => {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
config.cursor = { enabled: false, port: 3000, auto_start: false, ghost_mode: false };
|
||||
config.cursor = {
|
||||
enabled: false,
|
||||
port: 3000,
|
||||
auto_start: false,
|
||||
ghost_mode: false,
|
||||
model: 'gpt-5.3-codex',
|
||||
};
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
const updates = { auto_start: true };
|
||||
@@ -164,6 +190,13 @@ describe('Cursor Settings Routes Logic', () => {
|
||||
port: updates.port ?? config.cursor?.port ?? 3000,
|
||||
auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false,
|
||||
ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false,
|
||||
model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex',
|
||||
opus_model:
|
||||
updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model,
|
||||
sonnet_model:
|
||||
updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model,
|
||||
haiku_model:
|
||||
updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model,
|
||||
};
|
||||
|
||||
expect(cursorConfig.port).toBe(3000);
|
||||
@@ -173,7 +206,13 @@ describe('Cursor Settings Routes Logic', () => {
|
||||
|
||||
it('updates ghost_mode only', () => {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
config.cursor = { enabled: false, port: 3000, auto_start: false, ghost_mode: false };
|
||||
config.cursor = {
|
||||
enabled: false,
|
||||
port: 3000,
|
||||
auto_start: false,
|
||||
ghost_mode: false,
|
||||
model: 'gpt-5.3-codex',
|
||||
};
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
const updates = { ghost_mode: true };
|
||||
@@ -182,6 +221,13 @@ describe('Cursor Settings Routes Logic', () => {
|
||||
port: updates.port ?? config.cursor?.port ?? 3000,
|
||||
auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false,
|
||||
ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false,
|
||||
model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex',
|
||||
opus_model:
|
||||
updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model,
|
||||
sonnet_model:
|
||||
updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model,
|
||||
haiku_model:
|
||||
updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model,
|
||||
};
|
||||
|
||||
expect(cursorConfig.port).toBe(3000);
|
||||
@@ -203,11 +249,16 @@ describe('Cursor Settings Routes Logic', () => {
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${cursorPort}`,
|
||||
ANTHROPIC_AUTH_TOKEN: 'cursor-managed',
|
||||
ANTHROPIC_MODEL: 'gpt-5.3-codex',
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gpt-5.3-codex',
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: 'gpt-5.3-codex',
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gpt-5.3-codex',
|
||||
},
|
||||
};
|
||||
|
||||
expect(defaultSettings.env.ANTHROPIC_BASE_URL).toContain('http://127.0.0.1:');
|
||||
expect(defaultSettings.env.ANTHROPIC_AUTH_TOKEN).toBe('cursor-managed');
|
||||
expect(defaultSettings.env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex');
|
||||
});
|
||||
|
||||
it('reads existing file', () => {
|
||||
|
||||
Reference in New Issue
Block a user