diff --git a/apps/server/src/providers/index.ts b/apps/server/src/providers/index.ts new file mode 100644 index 00000000..5aafe36c --- /dev/null +++ b/apps/server/src/providers/index.ts @@ -0,0 +1,23 @@ +/** + * Provider exports + */ + +// Base provider +export { BaseProvider } from './base-provider.js'; +export type { + ProviderConfig, + ExecuteOptions, + ProviderMessage, + InstallationStatus, + ModelDefinition, +} from './types.js'; + +// Claude provider +export { ClaudeProvider } from './claude-provider.js'; + +// Cursor provider +export { CursorProvider, CursorErrorCode, CursorError } from './cursor-provider.js'; +export { CursorConfigManager } from './cursor-config-manager.js'; + +// Provider factory +export { ProviderFactory } from './provider-factory.js'; diff --git a/apps/server/src/providers/provider-factory.ts b/apps/server/src/providers/provider-factory.ts index 0ef9b36e..9ed540b2 100644 --- a/apps/server/src/providers/provider-factory.ts +++ b/apps/server/src/providers/provider-factory.ts @@ -8,33 +8,56 @@ import { BaseProvider } from './base-provider.js'; import { ClaudeProvider } from './claude-provider.js'; -import type { InstallationStatus } from './types.js'; +import { CursorProvider } from './cursor-provider.js'; +import type { InstallationStatus, ModelDefinition } from './types.js'; +import { CURSOR_MODEL_MAP } from '@automaker/types'; export class ProviderFactory { + /** + * Determine which provider to use for a given model + * + * @param model Model identifier + * @returns Provider name ('claude' | 'cursor') + */ + static getProviderNameForModel(model: string): 'claude' | 'cursor' { + const lowerModel = model.toLowerCase(); + + // Check for explicit cursor prefix + if (lowerModel.startsWith('cursor-')) { + return 'cursor'; + } + + // Check if it's a known Cursor model ID (without prefix) + const cursorModelId = lowerModel.replace('cursor-', ''); + if (cursorModelId in CURSOR_MODEL_MAP) { + return 'cursor'; + } + + // Check for Claude model patterns + if ( + lowerModel.startsWith('claude-') || + ['opus', 'sonnet', 'haiku'].some((n) => lowerModel.includes(n)) + ) { + return 'claude'; + } + + // Default to Claude + return 'claude'; + } + /** * Get the appropriate provider for a given model ID * - * @param modelId Model identifier (e.g., "claude-opus-4-5-20251101", "gpt-5.2", "cursor-fast") + * @param modelId Model identifier (e.g., "claude-opus-4-5-20251101", "cursor-gpt-4o", "cursor-auto") * @returns Provider instance for the model */ static getProviderForModel(modelId: string): BaseProvider { - const lowerModel = modelId.toLowerCase(); + const providerName = this.getProviderNameForModel(modelId); - // Claude models (claude-*, opus, sonnet, haiku) - if (lowerModel.startsWith('claude-') || ['haiku', 'sonnet', 'opus'].includes(lowerModel)) { - return new ClaudeProvider(); + if (providerName === 'cursor') { + return new CursorProvider(); } - // Future providers: - // if (lowerModel.startsWith("cursor-")) { - // return new CursorProvider(); - // } - // if (lowerModel.startsWith("opencode-")) { - // return new OpenCodeProvider(); - // } - - // Default to Claude for unknown models - console.warn(`[ProviderFactory] Unknown model prefix for "${modelId}", defaulting to Claude`); return new ClaudeProvider(); } @@ -42,10 +65,7 @@ export class ProviderFactory { * Get all available providers */ static getAllProviders(): BaseProvider[] { - return [ - new ClaudeProvider(), - // Future providers... - ]; + return [new ClaudeProvider(), new CursorProvider()]; } /** @@ -80,11 +100,8 @@ export class ProviderFactory { case 'anthropic': return new ClaudeProvider(); - // Future providers: - // case "cursor": - // return new CursorProvider(); - // case "opencode": - // return new OpenCodeProvider(); + case 'cursor': + return new CursorProvider(); default: return null; @@ -94,15 +111,8 @@ export class ProviderFactory { /** * Get all available models from all providers */ - static getAllAvailableModels() { + static getAllAvailableModels(): ModelDefinition[] { const providers = this.getAllProviders(); - const allModels = []; - - for (const provider of providers) { - const models = provider.getAvailableModels(); - allModels.push(...models); - } - - return allModels; + return providers.flatMap((p) => p.getAvailableModels()); } } diff --git a/plan/cursor-cli-integration/README.md b/plan/cursor-cli-integration/README.md index 80d540aa..164d9ef9 100644 --- a/plan/cursor-cli-integration/README.md +++ b/plan/cursor-cli-integration/README.md @@ -9,7 +9,7 @@ | 0 | [Analysis & Documentation](phases/phase-0-analysis.md) | `completed` | ✅ | | 1 | [Core Types & Configuration](phases/phase-1-types.md) | `completed` | ✅ | | 2 | [Cursor Provider Implementation](phases/phase-2-provider.md) | `completed` | ✅ | -| 3 | [Provider Factory Integration](phases/phase-3-factory.md) | `pending` | - | +| 3 | [Provider Factory Integration](phases/phase-3-factory.md) | `completed` | ✅ | | 4 | [Setup Routes & Status Endpoints](phases/phase-4-routes.md) | `pending` | - | | 5 | [Log Parser Integration](phases/phase-5-log-parser.md) | `pending` | - | | 6 | [UI Setup Wizard](phases/phase-6-setup-wizard.md) | `pending` | - | diff --git a/plan/cursor-cli-integration/phases/phase-3-factory.md b/plan/cursor-cli-integration/phases/phase-3-factory.md index 551d7af5..62fa7035 100644 --- a/plan/cursor-cli-integration/phases/phase-3-factory.md +++ b/plan/cursor-cli-integration/phases/phase-3-factory.md @@ -1,6 +1,6 @@ # Phase 3: Provider Factory Integration -**Status:** `pending` +**Status:** `completed` **Dependencies:** Phase 2 (Provider) **Estimated Effort:** Small (routing logic only) @@ -16,7 +16,7 @@ Integrate CursorProvider into the ProviderFactory so models are automatically ro ### Task 3.1: Update Provider Factory -**Status:** `pending` +**Status:** `completed` **File:** `apps/server/src/providers/provider-factory.ts` @@ -132,7 +132,7 @@ export class ProviderFactory { ### Task 3.2: Export CursorProvider -**Status:** `pending` +**Status:** `completed` **File:** `apps/server/src/providers/index.ts` @@ -203,13 +203,13 @@ console.log('Total models:', allModels.length); Before marking this phase complete: -- [ ] ProviderFactory routes `cursor-*` models to CursorProvider -- [ ] ProviderFactory routes Claude models to ClaudeProvider -- [ ] `getAllProviders()` returns both providers -- [ ] `getProviderByName('cursor')` returns CursorProvider -- [ ] `checkAllProviders()` returns status for both providers -- [ ] `getAllAvailableModels()` includes Cursor models -- [ ] Existing Claude routing not broken +- [x] ProviderFactory routes `cursor-*` models to CursorProvider +- [x] ProviderFactory routes Claude models to ClaudeProvider +- [x] `getAllProviders()` returns both providers +- [x] `getProviderByName('cursor')` returns CursorProvider +- [x] `checkAllProviders()` returns status for both providers +- [x] `getAllAvailableModels()` includes Cursor models +- [x] Existing Claude routing not broken ---