mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
feat: implement cursor model migration and enhance auto mode functionality
This commit introduces significant updates to the cursor model handling and auto mode features. The cursor model IDs have been standardized to a canonical format, ensuring backward compatibility while migrating legacy IDs. New endpoints for starting and stopping the auto mode loop have been added, allowing for better control over project-specific auto mode operations. Key changes: - Updated cursor model IDs to use the 'cursor-' prefix for consistency. - Added new API endpoints: `/start` and `/stop` for managing auto mode. - Enhanced the status endpoint to provide detailed project-specific auto mode information. - Improved error handling and logging throughout the auto mode service. - Migrated legacy model IDs to their canonical counterparts in various components. This update aims to streamline the user experience and ensure a smooth transition for existing users while providing new functionalities.
This commit is contained in:
@@ -6,10 +6,16 @@
|
||||
* - Passes through Cursor models unchanged (handled by CursorProvider)
|
||||
* - Provides default models per provider
|
||||
* - Handles multiple model sources with priority
|
||||
*
|
||||
* With canonical model IDs:
|
||||
* - Cursor: cursor-auto, cursor-composer-1, cursor-gpt-5.2
|
||||
* - OpenCode: opencode-big-pickle, opencode-grok-code
|
||||
* - Claude: claude-haiku, claude-sonnet, claude-opus (also supports legacy aliases)
|
||||
*/
|
||||
|
||||
import {
|
||||
CLAUDE_MODEL_MAP,
|
||||
CLAUDE_CANONICAL_MAP,
|
||||
CURSOR_MODEL_MAP,
|
||||
CODEX_MODEL_MAP,
|
||||
DEFAULT_MODELS,
|
||||
@@ -17,6 +23,7 @@ import {
|
||||
isCursorModel,
|
||||
isOpencodeModel,
|
||||
stripProviderPrefix,
|
||||
migrateModelId,
|
||||
type PhaseModelEntry,
|
||||
type ThinkingLevel,
|
||||
} from '@automaker/types';
|
||||
@@ -29,7 +36,11 @@ const OPENAI_O_SERIES_ALLOWED_MODELS = new Set<string>();
|
||||
/**
|
||||
* Resolve a model key/alias to a full model string
|
||||
*
|
||||
* @param modelKey - Model key (e.g., "opus", "cursor-composer-1", "claude-sonnet-4-20250514")
|
||||
* Handles both canonical prefixed IDs and legacy aliases:
|
||||
* - Canonical: cursor-auto, cursor-gpt-5.2, opencode-big-pickle, claude-sonnet
|
||||
* - Legacy: auto, composer-1, sonnet, opus
|
||||
*
|
||||
* @param modelKey - Model key (e.g., "claude-opus", "cursor-composer-1", "sonnet")
|
||||
* @param defaultModel - Fallback model if modelKey is undefined
|
||||
* @returns Full model string
|
||||
*/
|
||||
@@ -47,74 +58,65 @@ export function resolveModelString(
|
||||
return defaultModel;
|
||||
}
|
||||
|
||||
// Cursor model with explicit prefix (e.g., "cursor-composer-1") - pass through unchanged
|
||||
// CursorProvider will strip the prefix when calling the CLI
|
||||
if (modelKey.startsWith(PROVIDER_PREFIXES.cursor)) {
|
||||
const cursorModelId = stripProviderPrefix(modelKey);
|
||||
// Verify it's a valid Cursor model
|
||||
if (cursorModelId in CURSOR_MODEL_MAP) {
|
||||
console.log(
|
||||
`[ModelResolver] Using Cursor model: ${modelKey} (valid model ID: ${cursorModelId})`
|
||||
);
|
||||
return modelKey;
|
||||
}
|
||||
// Could be a cursor-prefixed model not in our map yet - still pass through
|
||||
console.log(`[ModelResolver] Passing through cursor-prefixed model: ${modelKey}`);
|
||||
return modelKey;
|
||||
// First, migrate legacy IDs to canonical format
|
||||
const canonicalKey = migrateModelId(modelKey);
|
||||
if (canonicalKey !== modelKey) {
|
||||
console.log(`[ModelResolver] Migrated legacy ID: "${modelKey}" -> "${canonicalKey}"`);
|
||||
}
|
||||
|
||||
// Codex model with explicit prefix (e.g., "codex-gpt-5.1-codex-max") - pass through unchanged
|
||||
if (modelKey.startsWith(PROVIDER_PREFIXES.codex)) {
|
||||
console.log(`[ModelResolver] Using Codex model: ${modelKey}`);
|
||||
return modelKey;
|
||||
// Cursor model with explicit prefix (e.g., "cursor-auto", "cursor-composer-1")
|
||||
// Pass through unchanged - provider will extract bare ID for CLI
|
||||
if (canonicalKey.startsWith(PROVIDER_PREFIXES.cursor)) {
|
||||
console.log(`[ModelResolver] Using Cursor model: ${canonicalKey}`);
|
||||
return canonicalKey;
|
||||
}
|
||||
|
||||
// OpenCode model (static or dynamic) - pass through unchanged
|
||||
// This handles models like:
|
||||
// - opencode-* (Automaker routing prefix)
|
||||
// - opencode/* (free tier models)
|
||||
// - amazon-bedrock/* (AWS Bedrock models)
|
||||
// - provider/model-name (dynamic models like github-copilot/gpt-4o, google/gemini-2.5-pro)
|
||||
if (isOpencodeModel(modelKey)) {
|
||||
console.log(`[ModelResolver] Using OpenCode model: ${modelKey}`);
|
||||
return modelKey;
|
||||
// Codex model with explicit prefix (e.g., "codex-gpt-5.1-codex-max")
|
||||
if (canonicalKey.startsWith(PROVIDER_PREFIXES.codex)) {
|
||||
console.log(`[ModelResolver] Using Codex model: ${canonicalKey}`);
|
||||
return canonicalKey;
|
||||
}
|
||||
|
||||
// Full Claude model string - pass through unchanged
|
||||
if (modelKey.includes('claude-')) {
|
||||
console.log(`[ModelResolver] Using full Claude model string: ${modelKey}`);
|
||||
return modelKey;
|
||||
// OpenCode model (static with opencode- prefix or dynamic with provider/model format)
|
||||
if (isOpencodeModel(canonicalKey)) {
|
||||
console.log(`[ModelResolver] Using OpenCode model: ${canonicalKey}`);
|
||||
return canonicalKey;
|
||||
}
|
||||
|
||||
// Look up Claude model alias
|
||||
const resolved = CLAUDE_MODEL_MAP[modelKey];
|
||||
if (resolved) {
|
||||
console.log(`[ModelResolver] Resolved Claude model alias: "${modelKey}" -> "${resolved}"`);
|
||||
// Claude canonical ID (claude-haiku, claude-sonnet, claude-opus)
|
||||
// Map to full model string
|
||||
if (canonicalKey in CLAUDE_CANONICAL_MAP) {
|
||||
const resolved = CLAUDE_CANONICAL_MAP[canonicalKey as keyof typeof CLAUDE_CANONICAL_MAP];
|
||||
console.log(`[ModelResolver] Resolved Claude canonical ID: "${canonicalKey}" -> "${resolved}"`);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// OpenAI/Codex models - check for codex- or gpt- prefix
|
||||
if (
|
||||
CODEX_MODEL_PREFIXES.some((prefix) => modelKey.startsWith(prefix)) ||
|
||||
(OPENAI_O_SERIES_PATTERN.test(modelKey) && OPENAI_O_SERIES_ALLOWED_MODELS.has(modelKey))
|
||||
) {
|
||||
console.log(`[ModelResolver] Using OpenAI/Codex model: ${modelKey}`);
|
||||
return modelKey;
|
||||
// Full Claude model string (e.g., claude-sonnet-4-5-20250929) - pass through
|
||||
if (canonicalKey.includes('claude-')) {
|
||||
console.log(`[ModelResolver] Using full Claude model string: ${canonicalKey}`);
|
||||
return canonicalKey;
|
||||
}
|
||||
|
||||
// Check if it's a bare Cursor model ID (e.g., "composer-1", "auto", "gpt-4o")
|
||||
// Note: This is checked AFTER Codex check to prioritize Codex for bare gpt-* models
|
||||
if (modelKey in CURSOR_MODEL_MAP) {
|
||||
// Return with cursor- prefix so provider routing works correctly
|
||||
const prefixedModel = `${PROVIDER_PREFIXES.cursor}${modelKey}`;
|
||||
console.log(
|
||||
`[ModelResolver] Detected bare Cursor model ID: "${modelKey}" -> "${prefixedModel}"`
|
||||
);
|
||||
return prefixedModel;
|
||||
// Legacy Claude model alias (sonnet, opus, haiku) - support for backward compatibility
|
||||
const resolved = CLAUDE_MODEL_MAP[canonicalKey];
|
||||
if (resolved) {
|
||||
console.log(`[ModelResolver] Resolved Claude legacy alias: "${canonicalKey}" -> "${resolved}"`);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// OpenAI/Codex models - check for gpt- prefix
|
||||
if (
|
||||
CODEX_MODEL_PREFIXES.some((prefix) => canonicalKey.startsWith(prefix)) ||
|
||||
(OPENAI_O_SERIES_PATTERN.test(canonicalKey) && OPENAI_O_SERIES_ALLOWED_MODELS.has(canonicalKey))
|
||||
) {
|
||||
console.log(`[ModelResolver] Using OpenAI/Codex model: ${canonicalKey}`);
|
||||
return canonicalKey;
|
||||
}
|
||||
|
||||
// Unknown model key - use default
|
||||
console.warn(`[ModelResolver] Unknown model key "${modelKey}", using default: "${defaultModel}"`);
|
||||
console.warn(
|
||||
`[ModelResolver] Unknown model key "${canonicalKey}", using default: "${defaultModel}"`
|
||||
);
|
||||
return defaultModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,8 +78,9 @@ describe('model-resolver', () => {
|
||||
const result = resolveModelString('sonnet');
|
||||
|
||||
expect(result).toBe(CLAUDE_MODEL_MAP.sonnet);
|
||||
// Legacy aliases are migrated to canonical IDs then resolved
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Resolved Claude model alias: "sonnet"')
|
||||
expect.stringContaining('Migrated legacy ID: "sonnet" -> "claude-sonnet"')
|
||||
);
|
||||
});
|
||||
|
||||
@@ -88,7 +89,7 @@ describe('model-resolver', () => {
|
||||
|
||||
expect(result).toBe(CLAUDE_MODEL_MAP.opus);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Resolved Claude model alias: "opus"')
|
||||
expect.stringContaining('Migrated legacy ID: "opus" -> "claude-opus"')
|
||||
);
|
||||
});
|
||||
|
||||
@@ -101,8 +102,9 @@ describe('model-resolver', () => {
|
||||
it('should log the resolution for aliases', () => {
|
||||
resolveModelString('sonnet');
|
||||
|
||||
// Legacy aliases get migrated and resolved via canonical map
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Resolved Claude model alias')
|
||||
expect.stringContaining('Resolved Claude canonical ID')
|
||||
);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(CLAUDE_MODEL_MAP.sonnet)
|
||||
@@ -134,8 +136,9 @@ describe('model-resolver', () => {
|
||||
const result = resolveModelString('composer-1');
|
||||
|
||||
expect(result).toBe('cursor-composer-1');
|
||||
// Legacy bare IDs are migrated to canonical prefixed format
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Detected bare Cursor model ID')
|
||||
expect.stringContaining('Migrated legacy ID: "composer-1" -> "cursor-composer-1"')
|
||||
);
|
||||
});
|
||||
|
||||
@@ -149,17 +152,18 @@ describe('model-resolver', () => {
|
||||
const result = resolveModelString('cursor-unknown-future-model');
|
||||
|
||||
expect(result).toBe('cursor-unknown-future-model');
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Passing through cursor-prefixed model')
|
||||
);
|
||||
// Unknown cursor-prefixed models pass through as Cursor models
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Using Cursor model'));
|
||||
});
|
||||
|
||||
it('should handle all known Cursor model IDs', () => {
|
||||
// CURSOR_MODEL_MAP now uses prefixed keys (e.g., 'cursor-auto')
|
||||
const cursorModelIds = Object.keys(CURSOR_MODEL_MAP);
|
||||
|
||||
for (const modelId of cursorModelIds) {
|
||||
const result = resolveModelString(`cursor-${modelId}`);
|
||||
expect(result).toBe(`cursor-${modelId}`);
|
||||
// modelId is already prefixed (e.g., 'cursor-auto')
|
||||
const result = resolveModelString(modelId);
|
||||
expect(result).toBe(modelId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
* Cursor CLI Model IDs
|
||||
* Reference: https://cursor.com/docs
|
||||
*
|
||||
* IMPORTANT: GPT models use 'cursor-' prefix to distinguish from Codex CLI models
|
||||
* All Cursor model IDs use 'cursor-' prefix for consistent provider routing.
|
||||
* This prevents naming collisions (e.g., cursor-gpt-5.2-codex vs codex-gpt-5.2-codex).
|
||||
*/
|
||||
export type CursorModelId =
|
||||
| 'auto' // Auto-select best model
|
||||
| 'composer-1' // Cursor Composer agent model
|
||||
| 'sonnet-4.5' // Claude Sonnet 4.5
|
||||
| 'sonnet-4.5-thinking' // Claude Sonnet 4.5 with extended thinking
|
||||
| 'opus-4.5' // Claude Opus 4.5
|
||||
| 'opus-4.5-thinking' // Claude Opus 4.5 with extended thinking
|
||||
| 'opus-4.1' // Claude Opus 4.1
|
||||
| 'gemini-3-pro' // Gemini 3 Pro
|
||||
| 'gemini-3-flash' // Gemini 3 Flash
|
||||
| 'cursor-auto' // Auto-select best model
|
||||
| 'cursor-composer-1' // Cursor Composer agent model
|
||||
| 'cursor-sonnet-4.5' // Claude Sonnet 4.5
|
||||
| 'cursor-sonnet-4.5-thinking' // Claude Sonnet 4.5 with extended thinking
|
||||
| 'cursor-opus-4.5' // Claude Opus 4.5
|
||||
| 'cursor-opus-4.5-thinking' // Claude Opus 4.5 with extended thinking
|
||||
| 'cursor-opus-4.1' // Claude Opus 4.1
|
||||
| 'cursor-gemini-3-pro' // Gemini 3 Pro
|
||||
| 'cursor-gemini-3-flash' // Gemini 3 Flash
|
||||
| 'cursor-gpt-5.2' // GPT-5.2 via Cursor
|
||||
| 'cursor-gpt-5.1' // GPT-5.1 via Cursor
|
||||
| 'cursor-gpt-5.2-high' // GPT-5.2 High via Cursor
|
||||
@@ -26,7 +27,22 @@ export type CursorModelId =
|
||||
| 'cursor-gpt-5.2-codex-high' // GPT-5.2 Codex High via Cursor
|
||||
| 'cursor-gpt-5.2-codex-max' // GPT-5.2 Codex Max via Cursor
|
||||
| 'cursor-gpt-5.2-codex-max-high' // GPT-5.2 Codex Max High via Cursor
|
||||
| 'grok'; // Grok
|
||||
| 'cursor-grok'; // Grok
|
||||
|
||||
/**
|
||||
* Legacy Cursor model IDs (without prefix) for migration support
|
||||
*/
|
||||
export type LegacyCursorModelId =
|
||||
| 'auto'
|
||||
| 'composer-1'
|
||||
| 'sonnet-4.5'
|
||||
| 'sonnet-4.5-thinking'
|
||||
| 'opus-4.5'
|
||||
| 'opus-4.5-thinking'
|
||||
| 'opus-4.1'
|
||||
| 'gemini-3-pro'
|
||||
| 'gemini-3-flash'
|
||||
| 'grok';
|
||||
|
||||
/**
|
||||
* Cursor model metadata
|
||||
@@ -42,66 +58,67 @@ export interface CursorModelConfig {
|
||||
|
||||
/**
|
||||
* Complete model map for Cursor CLI
|
||||
* All keys use 'cursor-' prefix for consistent provider routing.
|
||||
*/
|
||||
export const CURSOR_MODEL_MAP: Record<CursorModelId, CursorModelConfig> = {
|
||||
auto: {
|
||||
id: 'auto',
|
||||
'cursor-auto': {
|
||||
id: 'cursor-auto',
|
||||
label: 'Auto (Recommended)',
|
||||
description: 'Automatically selects the best model for each task',
|
||||
hasThinking: false,
|
||||
supportsVision: false, // Vision not yet supported by Cursor CLI
|
||||
},
|
||||
'composer-1': {
|
||||
id: 'composer-1',
|
||||
'cursor-composer-1': {
|
||||
id: 'cursor-composer-1',
|
||||
label: 'Composer 1',
|
||||
description: 'Cursor Composer agent model optimized for multi-file edits',
|
||||
hasThinking: false,
|
||||
supportsVision: false,
|
||||
},
|
||||
'sonnet-4.5': {
|
||||
id: 'sonnet-4.5',
|
||||
'cursor-sonnet-4.5': {
|
||||
id: 'cursor-sonnet-4.5',
|
||||
label: 'Claude Sonnet 4.5',
|
||||
description: 'Anthropic Claude Sonnet 4.5 via Cursor',
|
||||
hasThinking: false,
|
||||
supportsVision: false, // Model supports vision but Cursor CLI doesn't pass images
|
||||
},
|
||||
'sonnet-4.5-thinking': {
|
||||
id: 'sonnet-4.5-thinking',
|
||||
'cursor-sonnet-4.5-thinking': {
|
||||
id: 'cursor-sonnet-4.5-thinking',
|
||||
label: 'Claude Sonnet 4.5 (Thinking)',
|
||||
description: 'Claude Sonnet 4.5 with extended thinking enabled',
|
||||
hasThinking: true,
|
||||
supportsVision: false,
|
||||
},
|
||||
'opus-4.5': {
|
||||
id: 'opus-4.5',
|
||||
'cursor-opus-4.5': {
|
||||
id: 'cursor-opus-4.5',
|
||||
label: 'Claude Opus 4.5',
|
||||
description: 'Anthropic Claude Opus 4.5 via Cursor',
|
||||
hasThinking: false,
|
||||
supportsVision: false,
|
||||
},
|
||||
'opus-4.5-thinking': {
|
||||
id: 'opus-4.5-thinking',
|
||||
'cursor-opus-4.5-thinking': {
|
||||
id: 'cursor-opus-4.5-thinking',
|
||||
label: 'Claude Opus 4.5 (Thinking)',
|
||||
description: 'Claude Opus 4.5 with extended thinking enabled',
|
||||
hasThinking: true,
|
||||
supportsVision: false,
|
||||
},
|
||||
'opus-4.1': {
|
||||
id: 'opus-4.1',
|
||||
'cursor-opus-4.1': {
|
||||
id: 'cursor-opus-4.1',
|
||||
label: 'Claude Opus 4.1',
|
||||
description: 'Anthropic Claude Opus 4.1 via Cursor',
|
||||
hasThinking: false,
|
||||
supportsVision: false,
|
||||
},
|
||||
'gemini-3-pro': {
|
||||
id: 'gemini-3-pro',
|
||||
'cursor-gemini-3-pro': {
|
||||
id: 'cursor-gemini-3-pro',
|
||||
label: 'Gemini 3 Pro',
|
||||
description: 'Google Gemini 3 Pro via Cursor',
|
||||
hasThinking: false,
|
||||
supportsVision: false,
|
||||
},
|
||||
'gemini-3-flash': {
|
||||
id: 'gemini-3-flash',
|
||||
'cursor-gemini-3-flash': {
|
||||
id: 'cursor-gemini-3-flash',
|
||||
label: 'Gemini 3 Flash',
|
||||
description: 'Google Gemini 3 Flash (faster)',
|
||||
hasThinking: false,
|
||||
@@ -191,8 +208,8 @@ export const CURSOR_MODEL_MAP: Record<CursorModelId, CursorModelConfig> = {
|
||||
hasThinking: false,
|
||||
supportsVision: false,
|
||||
},
|
||||
grok: {
|
||||
id: 'grok',
|
||||
'cursor-grok': {
|
||||
id: 'cursor-grok',
|
||||
label: 'Grok',
|
||||
description: 'xAI Grok via Cursor',
|
||||
hasThinking: false,
|
||||
@@ -200,6 +217,22 @@ export const CURSOR_MODEL_MAP: Record<CursorModelId, CursorModelConfig> = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Map from legacy model IDs to canonical prefixed IDs
|
||||
*/
|
||||
export const LEGACY_CURSOR_MODEL_MAP: Record<LegacyCursorModelId, CursorModelId> = {
|
||||
auto: 'cursor-auto',
|
||||
'composer-1': 'cursor-composer-1',
|
||||
'sonnet-4.5': 'cursor-sonnet-4.5',
|
||||
'sonnet-4.5-thinking': 'cursor-sonnet-4.5-thinking',
|
||||
'opus-4.5': 'cursor-opus-4.5',
|
||||
'opus-4.5-thinking': 'cursor-opus-4.5-thinking',
|
||||
'opus-4.1': 'cursor-opus-4.1',
|
||||
'gemini-3-pro': 'cursor-gemini-3-pro',
|
||||
'gemini-3-flash': 'cursor-gemini-3-flash',
|
||||
grok: 'cursor-grok',
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper: Check if model has thinking capability
|
||||
*/
|
||||
@@ -254,6 +287,7 @@ export interface GroupedModel {
|
||||
|
||||
/**
|
||||
* Configuration for grouping Cursor models with variants
|
||||
* All variant IDs use 'cursor-' prefix for consistent provider routing.
|
||||
*/
|
||||
export const CURSOR_MODEL_GROUPS: GroupedModel[] = [
|
||||
// GPT-5.2 group (compute levels)
|
||||
@@ -346,14 +380,14 @@ export const CURSOR_MODEL_GROUPS: GroupedModel[] = [
|
||||
},
|
||||
// Sonnet 4.5 group (thinking mode)
|
||||
{
|
||||
baseId: 'sonnet-4.5-group',
|
||||
baseId: 'cursor-sonnet-4.5-group',
|
||||
label: 'Claude Sonnet 4.5',
|
||||
description: 'Anthropic Claude Sonnet 4.5 via Cursor',
|
||||
variantType: 'thinking',
|
||||
variants: [
|
||||
{ id: 'sonnet-4.5', label: 'Standard', description: 'Fast responses' },
|
||||
{ id: 'cursor-sonnet-4.5', label: 'Standard', description: 'Fast responses' },
|
||||
{
|
||||
id: 'sonnet-4.5-thinking',
|
||||
id: 'cursor-sonnet-4.5-thinking',
|
||||
label: 'Thinking',
|
||||
description: 'Extended reasoning',
|
||||
badge: 'Reasoning',
|
||||
@@ -362,14 +396,14 @@ export const CURSOR_MODEL_GROUPS: GroupedModel[] = [
|
||||
},
|
||||
// Opus 4.5 group (thinking mode)
|
||||
{
|
||||
baseId: 'opus-4.5-group',
|
||||
baseId: 'cursor-opus-4.5-group',
|
||||
label: 'Claude Opus 4.5',
|
||||
description: 'Anthropic Claude Opus 4.5 via Cursor',
|
||||
variantType: 'thinking',
|
||||
variants: [
|
||||
{ id: 'opus-4.5', label: 'Standard', description: 'Fast responses' },
|
||||
{ id: 'cursor-opus-4.5', label: 'Standard', description: 'Fast responses' },
|
||||
{
|
||||
id: 'opus-4.5-thinking',
|
||||
id: 'cursor-opus-4.5-thinking',
|
||||
label: 'Thinking',
|
||||
description: 'Extended reasoning',
|
||||
badge: 'Reasoning',
|
||||
@@ -380,14 +414,15 @@ export const CURSOR_MODEL_GROUPS: GroupedModel[] = [
|
||||
|
||||
/**
|
||||
* Cursor models that are not part of any group (standalone)
|
||||
* All IDs use 'cursor-' prefix for consistent provider routing.
|
||||
*/
|
||||
export const STANDALONE_CURSOR_MODELS: CursorModelId[] = [
|
||||
'auto',
|
||||
'composer-1',
|
||||
'opus-4.1',
|
||||
'gemini-3-pro',
|
||||
'gemini-3-flash',
|
||||
'grok',
|
||||
'cursor-auto',
|
||||
'cursor-composer-1',
|
||||
'cursor-opus-4.1',
|
||||
'cursor-gemini-3-pro',
|
||||
'cursor-gemini-3-flash',
|
||||
'cursor-grok',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -77,12 +77,15 @@ export type { ImageData, ImageContentBlock } from './image.js';
|
||||
// Model types and constants
|
||||
export {
|
||||
CLAUDE_MODEL_MAP,
|
||||
CLAUDE_CANONICAL_MAP,
|
||||
LEGACY_CLAUDE_ALIAS_MAP,
|
||||
CODEX_MODEL_MAP,
|
||||
CODEX_MODEL_IDS,
|
||||
REASONING_CAPABLE_MODELS,
|
||||
supportsReasoningEffort,
|
||||
getAllCodexModelIds,
|
||||
DEFAULT_MODELS,
|
||||
type ClaudeCanonicalId,
|
||||
type ModelAlias,
|
||||
type CodexModelId,
|
||||
type AgentModel,
|
||||
@@ -237,6 +240,18 @@ export {
|
||||
validateBareModelId,
|
||||
} from './provider-utils.js';
|
||||
|
||||
// Model migration utilities
|
||||
export {
|
||||
isLegacyCursorModelId,
|
||||
isLegacyOpencodeModelId,
|
||||
isLegacyClaudeAlias,
|
||||
migrateModelId,
|
||||
migrateCursorModelIds,
|
||||
migrateOpencodeModelIds,
|
||||
migratePhaseModelEntry,
|
||||
getBareModelIdForCli,
|
||||
} from './model-migration.js';
|
||||
|
||||
// Pipeline types
|
||||
export type {
|
||||
PipelineStep,
|
||||
|
||||
218
libs/types/src/model-migration.ts
Normal file
218
libs/types/src/model-migration.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Model ID Migration Utilities
|
||||
*
|
||||
* Provides functions to migrate legacy model IDs to the canonical prefixed format.
|
||||
* This ensures backward compatibility when loading settings from older versions.
|
||||
*/
|
||||
|
||||
import type { CursorModelId, LegacyCursorModelId } from './cursor-models.js';
|
||||
import { LEGACY_CURSOR_MODEL_MAP, CURSOR_MODEL_MAP } from './cursor-models.js';
|
||||
import type { OpencodeModelId, LegacyOpencodeModelId } from './opencode-models.js';
|
||||
import { LEGACY_OPENCODE_MODEL_MAP, OPENCODE_MODEL_CONFIG_MAP } from './opencode-models.js';
|
||||
import type { ClaudeCanonicalId } from './model.js';
|
||||
import { LEGACY_CLAUDE_ALIAS_MAP, CLAUDE_CANONICAL_MAP, CLAUDE_MODEL_MAP } from './model.js';
|
||||
import type { PhaseModelEntry } from './settings.js';
|
||||
|
||||
/**
|
||||
* Check if a string is a legacy Cursor model ID (without prefix)
|
||||
*/
|
||||
export function isLegacyCursorModelId(id: string): id is LegacyCursorModelId {
|
||||
return id in LEGACY_CURSOR_MODEL_MAP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a legacy OpenCode model ID (with slash format)
|
||||
*/
|
||||
export function isLegacyOpencodeModelId(id: string): id is LegacyOpencodeModelId {
|
||||
return id in LEGACY_OPENCODE_MODEL_MAP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a legacy Claude alias (short name without prefix)
|
||||
*/
|
||||
export function isLegacyClaudeAlias(id: string): boolean {
|
||||
return id in LEGACY_CLAUDE_ALIAS_MAP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate a single model ID to canonical format
|
||||
*
|
||||
* Handles:
|
||||
* - Legacy Cursor IDs (e.g., 'auto' -> 'cursor-auto')
|
||||
* - Legacy OpenCode IDs (e.g., 'opencode/big-pickle' -> 'opencode-big-pickle')
|
||||
* - Legacy Claude aliases (e.g., 'sonnet' -> 'claude-sonnet')
|
||||
* - Already-canonical IDs are passed through unchanged
|
||||
*
|
||||
* @param legacyId - The model ID to migrate
|
||||
* @returns The canonical model ID
|
||||
*/
|
||||
export function migrateModelId(legacyId: string | undefined | null): string {
|
||||
if (!legacyId) {
|
||||
return legacyId as string;
|
||||
}
|
||||
|
||||
// Already has cursor- prefix and is in the map - it's canonical
|
||||
if (legacyId.startsWith('cursor-') && legacyId in CURSOR_MODEL_MAP) {
|
||||
return legacyId;
|
||||
}
|
||||
|
||||
// Legacy Cursor model ID (without prefix)
|
||||
if (isLegacyCursorModelId(legacyId)) {
|
||||
return LEGACY_CURSOR_MODEL_MAP[legacyId];
|
||||
}
|
||||
|
||||
// Already has opencode- prefix - it's canonical
|
||||
if (legacyId.startsWith('opencode-') && legacyId in OPENCODE_MODEL_CONFIG_MAP) {
|
||||
return legacyId;
|
||||
}
|
||||
|
||||
// Legacy OpenCode model ID (with slash format)
|
||||
if (isLegacyOpencodeModelId(legacyId)) {
|
||||
return LEGACY_OPENCODE_MODEL_MAP[legacyId];
|
||||
}
|
||||
|
||||
// Already has claude- prefix and is in canonical map
|
||||
if (legacyId.startsWith('claude-') && legacyId in CLAUDE_CANONICAL_MAP) {
|
||||
return legacyId;
|
||||
}
|
||||
|
||||
// Legacy Claude alias (short name)
|
||||
if (isLegacyClaudeAlias(legacyId)) {
|
||||
return LEGACY_CLAUDE_ALIAS_MAP[legacyId];
|
||||
}
|
||||
|
||||
// Unknown or already canonical - pass through
|
||||
return legacyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate an array of Cursor model IDs to canonical format
|
||||
*
|
||||
* @param ids - Array of legacy or canonical Cursor model IDs
|
||||
* @returns Array of canonical Cursor model IDs
|
||||
*/
|
||||
export function migrateCursorModelIds(ids: string[]): CursorModelId[] {
|
||||
if (!ids || !Array.isArray(ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ids.map((id) => {
|
||||
// Already canonical
|
||||
if (id.startsWith('cursor-') && id in CURSOR_MODEL_MAP) {
|
||||
return id as CursorModelId;
|
||||
}
|
||||
|
||||
// Legacy ID
|
||||
if (isLegacyCursorModelId(id)) {
|
||||
return LEGACY_CURSOR_MODEL_MAP[id];
|
||||
}
|
||||
|
||||
// Unknown - assume it might be a valid cursor model with prefix
|
||||
if (id.startsWith('cursor-')) {
|
||||
return id as CursorModelId;
|
||||
}
|
||||
|
||||
// Add prefix if not present
|
||||
return `cursor-${id}` as CursorModelId;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate an array of OpenCode model IDs to canonical format
|
||||
*
|
||||
* @param ids - Array of legacy or canonical OpenCode model IDs
|
||||
* @returns Array of canonical OpenCode model IDs
|
||||
*/
|
||||
export function migrateOpencodeModelIds(ids: string[]): OpencodeModelId[] {
|
||||
if (!ids || !Array.isArray(ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ids.map((id) => {
|
||||
// Already canonical (dash format)
|
||||
if (id.startsWith('opencode-') && id in OPENCODE_MODEL_CONFIG_MAP) {
|
||||
return id as OpencodeModelId;
|
||||
}
|
||||
|
||||
// Legacy ID (slash format)
|
||||
if (isLegacyOpencodeModelId(id)) {
|
||||
return LEGACY_OPENCODE_MODEL_MAP[id];
|
||||
}
|
||||
|
||||
// Convert slash to dash format for unknown models
|
||||
if (id.startsWith('opencode/')) {
|
||||
return id.replace('opencode/', 'opencode-') as OpencodeModelId;
|
||||
}
|
||||
|
||||
// Add prefix if not present
|
||||
if (!id.startsWith('opencode-')) {
|
||||
return `opencode-${id}` as OpencodeModelId;
|
||||
}
|
||||
|
||||
return id as OpencodeModelId;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate a PhaseModelEntry to use canonical model IDs
|
||||
*
|
||||
* @param entry - The phase model entry to migrate
|
||||
* @returns Migrated phase model entry with canonical model ID
|
||||
*/
|
||||
export function migratePhaseModelEntry(
|
||||
entry: PhaseModelEntry | string | undefined | null
|
||||
): PhaseModelEntry {
|
||||
// Handle null/undefined
|
||||
if (!entry) {
|
||||
return { model: 'claude-sonnet' }; // Default
|
||||
}
|
||||
|
||||
// Handle legacy string format
|
||||
if (typeof entry === 'string') {
|
||||
return { model: migrateModelId(entry) };
|
||||
}
|
||||
|
||||
// Handle PhaseModelEntry object
|
||||
return {
|
||||
...entry,
|
||||
model: migrateModelId(entry.model),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bare model ID for CLI calls (strip provider prefix)
|
||||
*
|
||||
* When calling provider CLIs, we need to strip the provider prefix:
|
||||
* - 'cursor-auto' -> 'auto' (for Cursor CLI)
|
||||
* - 'cursor-composer-1' -> 'composer-1' (for Cursor CLI)
|
||||
* - 'opencode-big-pickle' -> 'big-pickle' (for OpenCode CLI)
|
||||
*
|
||||
* Note: GPT models via Cursor keep the gpt- part: 'cursor-gpt-5.2' -> 'gpt-5.2'
|
||||
*
|
||||
* @param modelId - The canonical model ID with provider prefix
|
||||
* @returns The bare model ID for CLI usage
|
||||
*/
|
||||
export function getBareModelIdForCli(modelId: string): string {
|
||||
if (!modelId) return modelId;
|
||||
|
||||
// Cursor models
|
||||
if (modelId.startsWith('cursor-')) {
|
||||
const bareId = modelId.slice(7); // Remove 'cursor-'
|
||||
// For GPT models, keep the gpt- prefix since that's what the CLI expects
|
||||
// e.g., 'cursor-gpt-5.2' -> 'gpt-5.2'
|
||||
return bareId;
|
||||
}
|
||||
|
||||
// OpenCode models - strip prefix
|
||||
if (modelId.startsWith('opencode-')) {
|
||||
return modelId.slice(9); // Remove 'opencode-'
|
||||
}
|
||||
|
||||
// Codex models - strip prefix
|
||||
if (modelId.startsWith('codex-')) {
|
||||
return modelId.slice(6); // Remove 'codex-'
|
||||
}
|
||||
|
||||
// Claude and other models - pass through
|
||||
return modelId;
|
||||
}
|
||||
@@ -4,12 +4,42 @@
|
||||
import type { CursorModelId } from './cursor-models.js';
|
||||
import type { OpencodeModelId } from './opencode-models.js';
|
||||
|
||||
/**
|
||||
* Canonical Claude model IDs with provider prefix
|
||||
* Used for internal storage and consistent provider routing.
|
||||
*/
|
||||
export type ClaudeCanonicalId = 'claude-haiku' | 'claude-sonnet' | 'claude-opus';
|
||||
|
||||
/**
|
||||
* Canonical Claude model map - maps prefixed IDs to full model strings
|
||||
* Use these IDs for internal storage and routing.
|
||||
*/
|
||||
export const CLAUDE_CANONICAL_MAP: Record<ClaudeCanonicalId, string> = {
|
||||
'claude-haiku': 'claude-haiku-4-5-20251001',
|
||||
'claude-sonnet': 'claude-sonnet-4-5-20250929',
|
||||
'claude-opus': 'claude-opus-4-5-20251101',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Legacy Claude model aliases (short names) for backward compatibility
|
||||
* These map to the same full model strings as the canonical map.
|
||||
* @deprecated Use CLAUDE_CANONICAL_MAP for new code
|
||||
*/
|
||||
export const CLAUDE_MODEL_MAP: Record<string, string> = {
|
||||
haiku: 'claude-haiku-4-5-20251001',
|
||||
sonnet: 'claude-sonnet-4-5-20250929',
|
||||
opus: 'claude-opus-4-5-20251101',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Map from legacy aliases to canonical IDs
|
||||
*/
|
||||
export const LEGACY_CLAUDE_ALIAS_MAP: Record<string, ClaudeCanonicalId> = {
|
||||
haiku: 'claude-haiku',
|
||||
sonnet: 'claude-sonnet',
|
||||
opus: 'claude-opus',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Codex/OpenAI model identifiers
|
||||
* Based on OpenAI Codex CLI official models
|
||||
@@ -62,10 +92,11 @@ export function getAllCodexModelIds(): CodexModelId[] {
|
||||
|
||||
/**
|
||||
* Default models per provider
|
||||
* Uses canonical prefixed IDs for consistent routing.
|
||||
*/
|
||||
export const DEFAULT_MODELS = {
|
||||
claude: 'claude-opus-4-5-20251101',
|
||||
cursor: 'auto', // Cursor's recommended default
|
||||
cursor: 'cursor-auto', // Cursor's recommended default (with prefix)
|
||||
codex: CODEX_MODEL_MAP.gpt52Codex, // GPT-5.2-Codex is the most advanced agentic coding model
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
/**
|
||||
* OpenCode Model IDs
|
||||
* Models available via OpenCode CLI (opencode models command)
|
||||
*
|
||||
* All OpenCode model IDs use 'opencode-' prefix for consistent provider routing.
|
||||
* This prevents naming collisions and ensures clear provider attribution.
|
||||
*/
|
||||
export type OpencodeModelId =
|
||||
// OpenCode Free Tier Models
|
||||
| 'opencode-big-pickle'
|
||||
| 'opencode-glm-4.7-free'
|
||||
| 'opencode-gpt-5-nano'
|
||||
| 'opencode-grok-code'
|
||||
| 'opencode-minimax-m2.1-free';
|
||||
|
||||
/**
|
||||
* Legacy OpenCode model IDs (with slash format) for migration support
|
||||
*/
|
||||
export type LegacyOpencodeModelId =
|
||||
| 'opencode/big-pickle'
|
||||
| 'opencode/glm-4.7-free'
|
||||
| 'opencode/gpt-5-nano'
|
||||
@@ -20,16 +33,27 @@ export type OpencodeProvider = 'opencode';
|
||||
*/
|
||||
export const OPENCODE_MODEL_MAP: Record<string, OpencodeModelId> = {
|
||||
// OpenCode free tier aliases
|
||||
'big-pickle': 'opencode/big-pickle',
|
||||
pickle: 'opencode/big-pickle',
|
||||
'glm-free': 'opencode/glm-4.7-free',
|
||||
'gpt-nano': 'opencode/gpt-5-nano',
|
||||
nano: 'opencode/gpt-5-nano',
|
||||
'grok-code': 'opencode/grok-code',
|
||||
grok: 'opencode/grok-code',
|
||||
minimax: 'opencode/minimax-m2.1-free',
|
||||
'big-pickle': 'opencode-big-pickle',
|
||||
pickle: 'opencode-big-pickle',
|
||||
'glm-free': 'opencode-glm-4.7-free',
|
||||
'gpt-nano': 'opencode-gpt-5-nano',
|
||||
nano: 'opencode-gpt-5-nano',
|
||||
'grok-code': 'opencode-grok-code',
|
||||
grok: 'opencode-grok-code',
|
||||
minimax: 'opencode-minimax-m2.1-free',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Map from legacy slash-format model IDs to canonical prefixed IDs
|
||||
*/
|
||||
export const LEGACY_OPENCODE_MODEL_MAP: Record<LegacyOpencodeModelId, OpencodeModelId> = {
|
||||
'opencode/big-pickle': 'opencode-big-pickle',
|
||||
'opencode/glm-4.7-free': 'opencode-glm-4.7-free',
|
||||
'opencode/gpt-5-nano': 'opencode-gpt-5-nano',
|
||||
'opencode/grok-code': 'opencode-grok-code',
|
||||
'opencode/minimax-m2.1-free': 'opencode-minimax-m2.1-free',
|
||||
};
|
||||
|
||||
/**
|
||||
* OpenCode model metadata
|
||||
*/
|
||||
@@ -44,11 +68,12 @@ export interface OpencodeModelConfig {
|
||||
|
||||
/**
|
||||
* Complete list of OpenCode model configurations
|
||||
* All IDs use 'opencode-' prefix for consistent provider routing.
|
||||
*/
|
||||
export const OPENCODE_MODELS: OpencodeModelConfig[] = [
|
||||
// OpenCode Free Tier Models
|
||||
{
|
||||
id: 'opencode/big-pickle',
|
||||
id: 'opencode-big-pickle',
|
||||
label: 'Big Pickle',
|
||||
description: 'OpenCode free tier model - great for general coding',
|
||||
supportsVision: false,
|
||||
@@ -56,7 +81,7 @@ export const OPENCODE_MODELS: OpencodeModelConfig[] = [
|
||||
tier: 'free',
|
||||
},
|
||||
{
|
||||
id: 'opencode/glm-4.7-free',
|
||||
id: 'opencode-glm-4.7-free',
|
||||
label: 'GLM 4.7 Free',
|
||||
description: 'OpenCode free tier GLM model',
|
||||
supportsVision: false,
|
||||
@@ -64,7 +89,7 @@ export const OPENCODE_MODELS: OpencodeModelConfig[] = [
|
||||
tier: 'free',
|
||||
},
|
||||
{
|
||||
id: 'opencode/gpt-5-nano',
|
||||
id: 'opencode-gpt-5-nano',
|
||||
label: 'GPT-5 Nano',
|
||||
description: 'OpenCode free tier nano model - fast and lightweight',
|
||||
supportsVision: false,
|
||||
@@ -72,7 +97,7 @@ export const OPENCODE_MODELS: OpencodeModelConfig[] = [
|
||||
tier: 'free',
|
||||
},
|
||||
{
|
||||
id: 'opencode/grok-code',
|
||||
id: 'opencode-grok-code',
|
||||
label: 'Grok Code',
|
||||
description: 'OpenCode free tier Grok model for coding',
|
||||
supportsVision: false,
|
||||
@@ -80,7 +105,7 @@ export const OPENCODE_MODELS: OpencodeModelConfig[] = [
|
||||
tier: 'free',
|
||||
},
|
||||
{
|
||||
id: 'opencode/minimax-m2.1-free',
|
||||
id: 'opencode-minimax-m2.1-free',
|
||||
label: 'MiniMax M2.1 Free',
|
||||
description: 'OpenCode free tier MiniMax model',
|
||||
supportsVision: false,
|
||||
@@ -104,7 +129,7 @@ export const OPENCODE_MODEL_CONFIG_MAP: Record<OpencodeModelId, OpencodeModelCon
|
||||
/**
|
||||
* Default OpenCode model - OpenCode free tier
|
||||
*/
|
||||
export const DEFAULT_OPENCODE_MODEL: OpencodeModelId = 'opencode/big-pickle';
|
||||
export const DEFAULT_OPENCODE_MODEL: OpencodeModelId = 'opencode-big-pickle';
|
||||
|
||||
/**
|
||||
* Helper: Get display name for model
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
*/
|
||||
|
||||
import type { ModelProvider } from './settings.js';
|
||||
import { CURSOR_MODEL_MAP } from './cursor-models.js';
|
||||
import { CURSOR_MODEL_MAP, LEGACY_CURSOR_MODEL_MAP } from './cursor-models.js';
|
||||
import { CLAUDE_MODEL_MAP, CODEX_MODEL_MAP } from './model.js';
|
||||
import { OPENCODE_MODEL_CONFIG_MAP } from './opencode-models.js';
|
||||
import { OPENCODE_MODEL_CONFIG_MAP, LEGACY_OPENCODE_MODEL_MAP } from './opencode-models.js';
|
||||
|
||||
/** Provider prefix constants */
|
||||
export const PROVIDER_PREFIXES = {
|
||||
@@ -21,20 +21,23 @@ export const PROVIDER_PREFIXES = {
|
||||
/**
|
||||
* Check if a model string represents a Cursor model
|
||||
*
|
||||
* @param model - Model string to check (e.g., "cursor-composer-1" or "composer-1")
|
||||
* @returns true if the model is a Cursor model (excluding Codex-specific models)
|
||||
* With canonical model IDs, Cursor models always have 'cursor-' prefix.
|
||||
* Legacy IDs without prefix are handled by migration utilities.
|
||||
*
|
||||
* @param model - Model string to check (e.g., "cursor-auto", "cursor-composer-1")
|
||||
* @returns true if the model is a Cursor model
|
||||
*/
|
||||
export function isCursorModel(model: string | undefined | null): boolean {
|
||||
if (!model || typeof model !== 'string') return false;
|
||||
|
||||
// Check for explicit cursor- prefix
|
||||
// Canonical format: all Cursor models have cursor- prefix
|
||||
if (model.startsWith(PROVIDER_PREFIXES.cursor)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a bare Cursor model ID (excluding Codex-specific models)
|
||||
// Codex-specific models should always route to Codex provider, not Cursor
|
||||
if (model in CURSOR_MODEL_MAP) {
|
||||
// Legacy support: check if it's a known legacy bare ID
|
||||
// This handles transition period before migration
|
||||
if (model in LEGACY_CURSOR_MODEL_MAP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -90,12 +93,14 @@ export function isCodexModel(model: string | undefined | null): boolean {
|
||||
/**
|
||||
* Check if a model string represents an OpenCode model
|
||||
*
|
||||
* With canonical model IDs, static OpenCode models use 'opencode-' prefix.
|
||||
* Dynamic models from OpenCode CLI still use provider/model format.
|
||||
*
|
||||
* OpenCode models can be identified by:
|
||||
* - Explicit 'opencode-' prefix (for routing in Automaker)
|
||||
* - 'opencode/' prefix (OpenCode free tier models)
|
||||
* - 'opencode-' prefix (canonical format for static models)
|
||||
* - 'opencode/' prefix (legacy format, will be migrated)
|
||||
* - 'amazon-bedrock/' prefix (AWS Bedrock models via OpenCode)
|
||||
* - Full model ID from OPENCODE_MODEL_CONFIG_MAP
|
||||
* - Dynamic models from OpenCode CLI with provider/model format (e.g., "github-copilot/gpt-4o", "google/gemini-2.5-pro")
|
||||
* - Dynamic models with provider/model format (e.g., "github-copilot/gpt-4o")
|
||||
*
|
||||
* @param model - Model string to check
|
||||
* @returns true if the model is an OpenCode model
|
||||
@@ -103,19 +108,18 @@ export function isCodexModel(model: string | undefined | null): boolean {
|
||||
export function isOpencodeModel(model: string | undefined | null): boolean {
|
||||
if (!model || typeof model !== 'string') return false;
|
||||
|
||||
// Check for explicit opencode- prefix (Automaker routing prefix)
|
||||
// Canonical format: opencode- prefix for static models
|
||||
if (model.startsWith(PROVIDER_PREFIXES.opencode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a known OpenCode model ID
|
||||
// Check if it's a known OpenCode model ID (handles both formats during transition)
|
||||
if (model in OPENCODE_MODEL_CONFIG_MAP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for OpenCode native model prefixes
|
||||
// - opencode/ = OpenCode free tier models
|
||||
// - amazon-bedrock/ = AWS Bedrock models
|
||||
// Legacy format: opencode/ prefix (will be migrated to opencode-)
|
||||
// Also supports amazon-bedrock/ for AWS Bedrock models
|
||||
if (model.startsWith('opencode/') || model.startsWith('amazon-bedrock/')) {
|
||||
return true;
|
||||
}
|
||||
@@ -228,32 +232,47 @@ export function getBareModelId(model: string): string {
|
||||
|
||||
/**
|
||||
* Normalize a model string to its canonical form
|
||||
* - For Cursor: adds cursor- prefix if missing
|
||||
* - For Codex: can add codex- prefix (but bare gpt-* is also valid)
|
||||
* - For Claude: returns as-is
|
||||
*
|
||||
* With the new canonical format:
|
||||
* - Cursor models: always have cursor- prefix
|
||||
* - OpenCode models: always have opencode- prefix (static) or provider/model format (dynamic)
|
||||
* - Claude models: can use legacy aliases or claude- prefix
|
||||
* - Codex models: always have codex- prefix
|
||||
*
|
||||
* @param model - Model string to normalize
|
||||
* @returns Normalized model string
|
||||
*/
|
||||
export function normalizeModelString(model: string | undefined | null): string {
|
||||
if (!model || typeof model !== 'string') return 'sonnet'; // Default
|
||||
if (!model || typeof model !== 'string') return 'claude-sonnet'; // Default to canonical
|
||||
|
||||
// If it's a Cursor model without prefix, add the prefix
|
||||
if (model in CURSOR_MODEL_MAP && !model.startsWith(PROVIDER_PREFIXES.cursor)) {
|
||||
return `${PROVIDER_PREFIXES.cursor}${model}`;
|
||||
// Already has a canonical prefix - return as-is
|
||||
if (
|
||||
model.startsWith(PROVIDER_PREFIXES.cursor) ||
|
||||
model.startsWith(PROVIDER_PREFIXES.codex) ||
|
||||
model.startsWith(PROVIDER_PREFIXES.opencode) ||
|
||||
model.startsWith('claude-')
|
||||
) {
|
||||
return model;
|
||||
}
|
||||
|
||||
// For Codex, bare gpt-* and o-series models are valid canonical forms
|
||||
// Check if it's in the CODEX_MODEL_MAP
|
||||
if (model in CODEX_MODEL_MAP) {
|
||||
// If it already starts with gpt- or o, it's canonical
|
||||
if (model.startsWith('gpt-') || /^o\d/.test(model)) {
|
||||
return model;
|
||||
}
|
||||
// Otherwise, it might need a prefix (though this is unlikely)
|
||||
if (!model.startsWith(PROVIDER_PREFIXES.codex)) {
|
||||
return `${PROVIDER_PREFIXES.codex}${model}`;
|
||||
}
|
||||
// Check if it's a legacy Cursor model ID
|
||||
if (model in LEGACY_CURSOR_MODEL_MAP) {
|
||||
return LEGACY_CURSOR_MODEL_MAP[model as keyof typeof LEGACY_CURSOR_MODEL_MAP];
|
||||
}
|
||||
|
||||
// Check if it's a legacy OpenCode model ID
|
||||
if (model in LEGACY_OPENCODE_MODEL_MAP) {
|
||||
return LEGACY_OPENCODE_MODEL_MAP[model as keyof typeof LEGACY_OPENCODE_MODEL_MAP];
|
||||
}
|
||||
|
||||
// Legacy Claude aliases
|
||||
if (model in CLAUDE_MODEL_MAP) {
|
||||
return `claude-${model}`;
|
||||
}
|
||||
|
||||
// For Codex, bare gpt-* and o-series models need codex- prefix
|
||||
if (model.startsWith('gpt-') || /^o\d/.test(model)) {
|
||||
return `${PROVIDER_PREFIXES.codex}${model}`;
|
||||
}
|
||||
|
||||
return model;
|
||||
|
||||
@@ -780,34 +780,42 @@ export interface ProjectSettings {
|
||||
* Value: agent configuration
|
||||
*/
|
||||
customSubagents?: Record<string, import('./provider.js').AgentDefinition>;
|
||||
|
||||
// Auto Mode Configuration (per-project)
|
||||
/** Whether auto mode is enabled for this project (backend-controlled loop) */
|
||||
automodeEnabled?: boolean;
|
||||
/** Maximum concurrent agents for this project (overrides global maxConcurrency) */
|
||||
maxConcurrentAgents?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values and constants
|
||||
*/
|
||||
|
||||
/** Default phase model configuration - sensible defaults for each task type */
|
||||
/** Default phase model configuration - sensible defaults for each task type
|
||||
* Uses canonical prefixed model IDs for consistent routing.
|
||||
*/
|
||||
export const DEFAULT_PHASE_MODELS: PhaseModelConfig = {
|
||||
// Quick tasks - use fast models for speed and cost
|
||||
enhancementModel: { model: 'sonnet' },
|
||||
fileDescriptionModel: { model: 'haiku' },
|
||||
imageDescriptionModel: { model: 'haiku' },
|
||||
enhancementModel: { model: 'claude-sonnet' },
|
||||
fileDescriptionModel: { model: 'claude-haiku' },
|
||||
imageDescriptionModel: { model: 'claude-haiku' },
|
||||
|
||||
// Validation - use smart models for accuracy
|
||||
validationModel: { model: 'sonnet' },
|
||||
validationModel: { model: 'claude-sonnet' },
|
||||
|
||||
// Generation - use powerful models for quality
|
||||
specGenerationModel: { model: 'opus' },
|
||||
featureGenerationModel: { model: 'sonnet' },
|
||||
backlogPlanningModel: { model: 'sonnet' },
|
||||
projectAnalysisModel: { model: 'sonnet' },
|
||||
suggestionsModel: { model: 'sonnet' },
|
||||
specGenerationModel: { model: 'claude-opus' },
|
||||
featureGenerationModel: { model: 'claude-sonnet' },
|
||||
backlogPlanningModel: { model: 'claude-sonnet' },
|
||||
projectAnalysisModel: { model: 'claude-sonnet' },
|
||||
suggestionsModel: { model: 'claude-sonnet' },
|
||||
|
||||
// Memory - use fast model for learning extraction (cost-effective)
|
||||
memoryExtractionModel: { model: 'haiku' },
|
||||
memoryExtractionModel: { model: 'claude-haiku' },
|
||||
|
||||
// Commit messages - use fast model for speed
|
||||
commitMessageModel: { model: 'haiku' },
|
||||
commitMessageModel: { model: 'claude-haiku' },
|
||||
};
|
||||
|
||||
/** Current version of the global settings schema */
|
||||
@@ -857,18 +865,18 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
||||
useWorktrees: true,
|
||||
defaultPlanningMode: 'skip',
|
||||
defaultRequirePlanApproval: false,
|
||||
defaultFeatureModel: { model: 'opus' },
|
||||
defaultFeatureModel: { model: 'claude-opus' }, // Use canonical ID
|
||||
muteDoneSound: false,
|
||||
serverLogLevel: 'info',
|
||||
enableRequestLogging: true,
|
||||
enableAiCommitMessages: true,
|
||||
phaseModels: DEFAULT_PHASE_MODELS,
|
||||
enhancementModel: 'sonnet',
|
||||
validationModel: 'opus',
|
||||
enabledCursorModels: getAllCursorModelIds(),
|
||||
cursorDefaultModel: 'auto',
|
||||
enabledOpencodeModels: getAllOpencodeModelIds(),
|
||||
opencodeDefaultModel: DEFAULT_OPENCODE_MODEL,
|
||||
enhancementModel: 'sonnet', // Legacy alias still supported
|
||||
validationModel: 'opus', // Legacy alias still supported
|
||||
enabledCursorModels: getAllCursorModelIds(), // Returns prefixed IDs
|
||||
cursorDefaultModel: 'cursor-auto', // Use canonical prefixed ID
|
||||
enabledOpencodeModels: getAllOpencodeModelIds(), // Returns prefixed IDs
|
||||
opencodeDefaultModel: DEFAULT_OPENCODE_MODEL, // Already prefixed
|
||||
enabledDynamicModelIds: [],
|
||||
disabledProviders: [],
|
||||
keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS,
|
||||
|
||||
Reference in New Issue
Block a user