diff --git a/apps/server/integration/OrchestrationEngineHarness.integration.ts b/apps/server/integration/OrchestrationEngineHarness.integration.ts index 07cfe01e704afb5babb4bf81860b4af4c999e494..33e29dfec2341f91ce6b2ef4bae7dffe06d13a4d 100644 --- a/apps/server/integration/OrchestrationEngineHarness.integration.ts +++ b/apps/server/integration/OrchestrationEngineHarness.integration.ts @@ -188,7 +188,7 @@ export interface OrchestrationIntegrationHarness { } interface MakeOrchestrationIntegrationHarnessOptions { - readonly provider?: "codex" | "cursor"; + readonly provider?: "codex"; readonly realCodex?: boolean; } diff --git a/apps/server/integration/TestProviderAdapter.integration.ts b/apps/server/integration/TestProviderAdapter.integration.ts index e86b3e8491adfddadaa37504df5e8f56e2c5d3ec..82c08da3e9578ee6e23df4b62df6e52f7100a58f 100644 --- a/apps/server/integration/TestProviderAdapter.integration.ts +++ b/apps/server/integration/TestProviderAdapter.integration.ts @@ -35,7 +35,7 @@ export interface TestTurnResponse { export type FixtureProviderRuntimeEvent = { readonly type: string; readonly eventId: EventId; - readonly provider: "codex" | "cursor"; + readonly provider: "codex"; readonly createdAt: string; readonly threadId: string; readonly turnId?: string | undefined; @@ -178,7 +178,7 @@ function normalizeFixtureEvent(rawEvent: Record): ProviderRunti export interface TestProviderAdapterHarness { readonly adapter: ProviderAdapterShape; - readonly provider: "codex" | "cursor"; + readonly provider: "codex"; readonly queueTurnResponse: ( threadId: ThreadId, response: TestTurnResponse, @@ -198,7 +198,7 @@ export interface TestProviderAdapterHarness { } interface MakeTestProviderAdapterHarnessOptions { - readonly provider?: "codex" | "cursor"; + readonly provider?: "codex"; } function nowIso(): string { @@ -206,7 +206,7 @@ function nowIso(): string { } function sessionNotFound( - provider: "codex" | "cursor", + provider: "codex", threadId: ThreadId, ): ProviderAdapterSessionNotFoundError { return new ProviderAdapterSessionNotFoundError({ @@ -216,7 +216,7 @@ function sessionNotFound( } function missingSessionEffect( - provider: "codex" | "cursor", + provider: "codex", threadId: ThreadId, ): Effect.Effect { return Effect.fail(sessionNotFound(provider, threadId)); diff --git a/apps/server/integration/orchestrationEngine.integration.test.ts b/apps/server/integration/orchestrationEngine.integration.test.ts index c8219b260a81eda498225587213ab1261cc3b3b8..2ad353188bcaa5c9f06a6bb2377bc3fa601b0dce 100644 --- a/apps/server/integration/orchestrationEngine.integration.test.ts +++ b/apps/server/integration/orchestrationEngine.integration.test.ts @@ -32,7 +32,7 @@ const PROJECT_ID = asProjectId("project-1"); const THREAD_ID = ThreadId.makeUnsafe("thread-1"); const FIXTURE_TURN_ID = "fixture-turn"; const APPROVAL_REQUEST_ID = asApprovalRequestId("req-approval-1"); -type IntegrationProvider = "codex" | "cursor"; +type IntegrationProvider = "codex"; function nowIso() { return new Date().toISOString(); diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts index 3716a063099f8141c469b30478912f35c8a7b3a1..e1e75682bbdfef95905f18b77990ad229bc31ed7 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts @@ -43,7 +43,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); type LegacyProviderRuntimeEvent = { readonly type: string; readonly eventId: EventId; - readonly provider: "codex" | "cursor"; + readonly provider: "codex"; readonly createdAt: string; readonly threadId: ThreadId; readonly turnId?: string | undefined; diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index 67a6aacd753a7ed25e81c4570e195c350a81a2ce..e29adb2b43fa5034ac7aa7f77939866731751b84 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -93,8 +93,7 @@ describe("ProviderCommandReactor", () => { typeof input === "object" && input !== null && "provider" in input && - (input.provider === "codex" || - input.provider === "cursor") + input.provider === "codex" ? input.provider : "codex"; const resumeCursor = @@ -186,7 +185,7 @@ describe("ProviderCommandReactor", () => { listSessions: () => Effect.succeed(runtimeSessions), getCapabilities: (provider) => Effect.succeed({ - sessionModelSwitch: provider === "cursor" ? "unsupported" : "in-session", + sessionModelSwitch: provider === "codex" ? "in-session" : "in-session", }), rollbackConversation: () => unsupported(), stopAll: () => Effect.void, @@ -384,43 +383,6 @@ describe("ProviderCommandReactor", () => { }); }); - it("starts first turn with cursor provider when provider is specified", async () => { - const harness = await createHarness(); - const now = new Date().toISOString(); - - await Effect.runPromise( - harness.engine.dispatch({ - type: "thread.turn.start", - commandId: CommandId.makeUnsafe("cmd-turn-start-provider-cursor"), - threadId: ThreadId.makeUnsafe("thread-1"), - message: { - messageId: asMessageId("user-message-provider-cursor"), - role: "user", - text: "hello cursor", - attachments: [], - }, - provider: "cursor", - interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, - runtimeMode: "approval-required", - createdAt: now, - }), - ); - - await waitFor(() => harness.startSession.mock.calls.length === 1); - await waitFor(() => harness.sendTurn.mock.calls.length === 1); - expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ - provider: "cursor", - cwd: "/tmp/provider-project", - model: "gpt-5-codex", - runtimeMode: "approval-required", - }); - - const readModel = await Effect.runPromise(harness.engine.getReadModel()); - const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); - expect(thread?.session?.providerName).toBe("cursor"); - expect(thread?.session?.threadId).toBe("thread-1"); - }); - it("reuses the same provider session when runtime mode is unchanged", async () => { const harness = await createHarness(); const now = new Date().toISOString(); @@ -467,111 +429,6 @@ describe("ProviderCommandReactor", () => { expect(harness.stopSession.mock.calls.length).toBe(0); }); - it("reuses the same cursor session when requested model is unchanged", async () => { - const harness = await createHarness(); - const now = new Date().toISOString(); - - await Effect.runPromise( - harness.engine.dispatch({ - type: "thread.turn.start", - commandId: CommandId.makeUnsafe("cmd-turn-start-cursor-model-same-1"), - threadId: ThreadId.makeUnsafe("thread-1"), - message: { - messageId: asMessageId("user-message-cursor-model-same-1"), - role: "user", - text: "first", - attachments: [], - }, - provider: "cursor", - model: "composer-1.5", - interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, - runtimeMode: "approval-required", - createdAt: now, - }), - ); - - await waitFor(() => harness.startSession.mock.calls.length === 1); - await waitFor(() => harness.sendTurn.mock.calls.length === 1); - - await Effect.runPromise( - harness.engine.dispatch({ - type: "thread.turn.start", - commandId: CommandId.makeUnsafe("cmd-turn-start-cursor-model-same-2"), - threadId: ThreadId.makeUnsafe("thread-1"), - message: { - messageId: asMessageId("user-message-cursor-model-same-2"), - role: "user", - text: "second", - attachments: [], - }, - provider: "cursor", - model: "composer-1.5", - interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, - runtimeMode: "approval-required", - createdAt: now, - }), - ); - - await waitFor(() => harness.sendTurn.mock.calls.length === 2); - expect(harness.startSession.mock.calls.length).toBe(1); - expect(harness.stopSession.mock.calls.length).toBe(0); - }); - - it("keeps cursor session/model when model change is unsupported", async () => { - const harness = await createHarness(); - const now = new Date().toISOString(); - - await Effect.runPromise( - harness.engine.dispatch({ - type: "thread.turn.start", - commandId: CommandId.makeUnsafe("cmd-turn-start-cursor-model-change-1"), - threadId: ThreadId.makeUnsafe("thread-1"), - message: { - messageId: asMessageId("user-message-cursor-model-change-1"), - role: "user", - text: "first", - attachments: [], - }, - provider: "cursor", - model: "gpt-5.3-codex", - interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, - runtimeMode: "approval-required", - createdAt: now, - }), - ); - - await waitFor(() => harness.startSession.mock.calls.length === 1); - await waitFor(() => harness.sendTurn.mock.calls.length === 1); - - await Effect.runPromise( - harness.engine.dispatch({ - type: "thread.turn.start", - commandId: CommandId.makeUnsafe("cmd-turn-start-cursor-model-change-2"), - threadId: ThreadId.makeUnsafe("thread-1"), - message: { - messageId: asMessageId("user-message-cursor-model-change-2"), - role: "user", - text: "second", - attachments: [], - }, - provider: "cursor", - model: "composer-1.5", - interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, - runtimeMode: "approval-required", - createdAt: now, - }), - ); - - await waitFor(() => harness.sendTurn.mock.calls.length === 2); - - expect(harness.stopSession.mock.calls.length).toBe(0); - expect(harness.startSession.mock.calls.length).toBe(1); - expect(harness.sendTurn.mock.calls[1]?.[0]).toMatchObject({ - threadId: ThreadId.makeUnsafe("thread-1"), - model: "gpt-5.3-codex", - }); - }); - it("restarts the provider session when runtime mode is updated on the thread", async () => { const harness = await createHarness(); const now = new Date().toISOString(); @@ -852,7 +709,7 @@ describe("ProviderCommandReactor", () => { harness.respondToRequest.mockImplementation(() => Effect.fail( new ProviderAdapterRequestError({ - provider: "cursor", + provider: "codex", method: "session/request_permission", detail: "Unknown pending permission request: approval-request-1", }), @@ -867,7 +724,7 @@ describe("ProviderCommandReactor", () => { session: { threadId: ThreadId.makeUnsafe("thread-1"), status: "running", - providerName: "cursor", + providerName: "codex", runtimeMode: "approval-required", activeTurnId: null, lastError: null, diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts index 5bfabc2751270af551db57308b6fbc1ea8adf0d0..d26d02628830e42f3d6134c500b9f700a3b5dca0 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts @@ -211,10 +211,7 @@ const make = Effect.gen(function* () { const desiredRuntimeMode = thread.runtimeMode; const currentProvider: ProviderKind | undefined = - thread.session?.providerName === "codex" || - thread.session?.providerName === "cursor" - ? thread.session.providerName - : undefined; + thread.session?.providerName === "codex" ? thread.session.providerName : undefined; const preferredProvider: ProviderKind | undefined = options?.provider ?? currentProvider; const desiredModel = options?.model ?? thread.model; const effectiveCwd = resolveThreadWorkspaceCwd({ diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts index 77a6578995262cf41f49fb284c28c6b8379f30cc..09fae007ebe9f8f6c93c3d9d8c97e1868f0d4595 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts @@ -41,7 +41,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); type LegacyProviderRuntimeEvent = { readonly type: string; readonly eventId: EventId; - readonly provider: "codex" | "cursor"; + readonly provider: "codex"; readonly createdAt: string; readonly threadId: ThreadId; readonly turnId?: string | undefined; diff --git a/apps/server/src/provider/Layers/CodexAdapter.test.ts b/apps/server/src/provider/Layers/CodexAdapter.test.ts index c26ccc74c68ee8219e18cecd27e888fe339bfb68..8584832f734d197c07738305d605ad636ab3ce8f 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.test.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.test.ts @@ -157,30 +157,6 @@ const validationLayer = it.layer( ); validationLayer("CodexAdapterLive validation", (it) => { - it.effect("returns validation error for non-codex provider on startSession", () => - Effect.gen(function* () { - const adapter = yield* CodexAdapter; - const result = yield* adapter - .startSession({ - provider: "cursor", - threadId: asThreadId("thread-1"), - runtimeMode: "full-access", - }) - .pipe(Effect.result); - - assert.equal(result._tag, "Failure"); - assert.deepStrictEqual( - result.failure, - new ProviderAdapterValidationError({ - provider: "codex", - operation: "startSession", - issue: "Expected provider 'codex' but received 'cursor'.", - }), - ); - assert.equal(validationManager.startSessionImpl.mock.calls.length, 0); - }), - ); - it.effect("maps codex model options before starting a session", () => Effect.gen(function* () { validationManager.startSessionImpl.mockClear(); diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index 9c2d8278e3b27485ee007d2c26033516537246bb..1b9b87e9f267f0011f191551e05ffd654f537fc3 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -51,7 +51,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); type LegacyProviderRuntimeEvent = { readonly type: string; readonly eventId: EventId; - readonly provider: "codex" | "cursor"; + readonly provider: "codex"; readonly createdAt: string; readonly threadId: ThreadId; readonly turnId?: string | undefined; diff --git a/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts b/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts index 22d415562241004df6b4f0f038acd9fadb3658d7..6efde41e7c192017d2f2562214ecd795875608f3 100644 --- a/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts +++ b/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts @@ -174,26 +174,4 @@ it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryL fs.rmSync(tempDir, { recursive: true, force: true }); })); - - it("accepts cursor provider bindings", () => - Effect.gen(function* () { - const directory = yield* ProviderSessionDirectory; - const threadId = ThreadId.makeUnsafe("thread-cursor"); - - yield* directory.upsert({ - provider: "cursor", - threadId, - }); - - const provider = yield* directory.getProvider(threadId); - assert.equal(provider, "cursor"); - const resolvedBinding = yield* directory.getBinding(threadId); - assertSome(resolvedBinding, { - threadId, - provider: "cursor", - }); - if (Option.isSome(resolvedBinding)) { - assert.equal(resolvedBinding.value.threadId, threadId); - } - })); }); diff --git a/apps/server/src/provider/Layers/ProviderSessionDirectory.ts b/apps/server/src/provider/Layers/ProviderSessionDirectory.ts index e35103ea7d695164ee0ea175e7c470f4d3c99403..5615b96c6cbfb2bd088c850d801e89cf2b80f2d8 100644 --- a/apps/server/src/provider/Layers/ProviderSessionDirectory.ts +++ b/apps/server/src/provider/Layers/ProviderSessionDirectory.ts @@ -25,7 +25,7 @@ function decodeProviderKind( providerName: string, operation: string, ): Effect.Effect { - if (providerName === "codex" || providerName === "cursor") { + if (providerName === "codex") { return Effect.succeed(providerName); } return Effect.fail( diff --git a/apps/web/src/appSettings.test.ts b/apps/web/src/appSettings.test.ts index def5bf73239b9344167d6c4ee3f060e67663a7f2..40130ecb5a39c5d5086a357100326d62130dafbf 100644 --- a/apps/web/src/appSettings.test.ts +++ b/apps/web/src/appSettings.test.ts @@ -15,13 +15,6 @@ describe("normalizeCustomModelSlugs", () => { ]), ).toEqual(["custom/internal-model"]); }); - - it("normalizes provider-specific aliases for cursor", () => { - expect(normalizeCustomModelSlugs(["composer"], "cursor")).toEqual([]); - expect(normalizeCustomModelSlugs(["cursor/custom-model"], "cursor")).toEqual([ - "cursor/custom-model", - ]); - }); }); describe("getAppModelOptions", () => { @@ -47,14 +40,6 @@ describe("getAppModelOptions", () => { isCustom: true, }); }); - - it("keeps a saved custom provider model available as an exact slug option", () => { - const options = getAppModelOptions("cursor", ["cursor/custom-model"], "cursor/custom-model"); - - expect(options.some((option) => option.slug === "cursor/custom-model" && option.isCustom)).toBe( - true, - ); - }); }); describe("getSlashModelOptions", () => { @@ -79,10 +64,4 @@ describe("getSlashModelOptions", () => { expect(options.map((option) => option.slug)).toEqual(["openai/gpt-oss-120b"]); }); - - it("includes provider-specific custom slugs in non-codex model lists", () => { - const cursorOptions = getAppModelOptions("cursor", ["cursor/custom-model"]); - - expect(cursorOptions.some((option) => option.slug === "cursor/custom-model")).toBe(true); - }); }); diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 1e37f1b39dc40350e69502139552481e71de4778..876153ce778bd867dc629851739665f080ae6518 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -8,7 +8,6 @@ const MAX_CUSTOM_MODEL_COUNT = 32; export const MAX_CUSTOM_MODEL_LENGTH = 256; const BUILT_IN_MODEL_SLUGS_BY_PROVIDER: Record> = { codex: new Set(getModelOptions("codex").map((option) => option.slug)), - cursor: new Set(getModelOptions("cursor").map((option) => option.slug)), }; const AppSettingsSchema = Schema.Struct({ @@ -25,9 +24,6 @@ const AppSettingsSchema = Schema.Struct({ customCodexModels: Schema.Array(Schema.String).pipe( Schema.withConstructorDefault(() => Option.some([])), ), - customCursorModels: Schema.Array(Schema.String).pipe( - Schema.withConstructorDefault(() => Option.some([])), - ), }); export type AppSettings = typeof AppSettingsSchema.Type; export interface AppModelOption { @@ -75,7 +71,6 @@ function normalizeAppSettings(settings: AppSettings): AppSettings { return { ...settings, customCodexModels: normalizeCustomModelSlugs(settings.customCodexModels, "codex"), - customCursorModels: normalizeCustomModelSlugs(settings.customCursorModels, "cursor"), }; } diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 695da69aadc0bb287ecb1f6c4335bd34881bde3f..ff9ef7600743ed6f7c89b380309847870f0d29f6 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -1,12 +1,10 @@ import { type ApprovalRequestId, - CURSOR_REASONING_OPTIONS, DEFAULT_MODEL, EDITORS, type EditorId, type KeybindingCommand, type CodexReasoningEffort, - type CursorReasoningOption, type MessageId, type ProjectId, type ProjectEntry, @@ -27,12 +25,8 @@ import { import { getDefaultModel, getDefaultReasoningEffort, - getCursorModelCapabilities, - getCursorModelFamilyOptions, getReasoningEffortOptions, normalizeModelSlug, - parseCursorModelSelection, - resolveCursorModelFromSelection, resolveModelSlugForProvider, } from "@t3tools/shared/model"; import { @@ -761,10 +755,6 @@ export default function ChatView({ threadId }: ChatViewProps) { ? (sessionProvider ?? selectedProviderByThreadId ?? null) : null; const selectedProvider: ProviderKind = lockedProvider ?? selectedProviderByThreadId ?? "codex"; - const cursorModelSelectionLockedReason = - hasThreadStarted && selectedProvider === "cursor" - ? "Cursor currently does not support changing models after the first message in a thread." - : null; const baseThreadModel = resolveModelSlugForProvider( selectedProvider, activeThread?.model ?? @@ -811,40 +801,16 @@ export default function ChatView({ threadId }: ChatViewProps) { }; return Object.keys(codexOptions).length > 0 ? { codex: codexOptions } : undefined; }, [selectedCodexFastModeEnabled, selectedEffort, selectedProvider, supportsReasoningEffort]); - const selectedCursorModel = useMemo( - () => (selectedProvider === "cursor" ? parseCursorModelSelection(selectedModel) : null), - [selectedModel, selectedProvider], - ); - const selectedCursorModelCapabilities = useMemo( - () => (selectedCursorModel ? getCursorModelCapabilities(selectedCursorModel.family) : null), - [selectedCursorModel], - ); - const hasSelectedCursorTraits = Boolean( - selectedCursorModelCapabilities && - (selectedCursorModelCapabilities.supportsReasoning || - selectedCursorModelCapabilities.supportsFast || - selectedCursorModelCapabilities.supportsThinking), - ); - const selectedModelForPicker = - selectedProvider === "cursor" && selectedCursorModel - ? selectedCursorModel.family - : selectedModel; + const selectedModelForPicker = selectedModel; const modelOptionsByProvider = useMemo( () => getCustomModelOptionsByProvider(settings), [settings], ); const selectedModelForPickerWithCustomFallback = useMemo(() => { - if (selectedProvider !== "cursor") { - const currentOptions = modelOptionsByProvider[selectedProvider]; - return currentOptions.some((option) => option.slug === selectedModelForPicker) - ? selectedModelForPicker - : (normalizeModelSlug(selectedModelForPicker, selectedProvider) ?? selectedModelForPicker); - } - - const currentOptions = modelOptionsByProvider.cursor; + const currentOptions = modelOptionsByProvider[selectedProvider]; return currentOptions.some((option) => option.slug === selectedModelForPicker) ? selectedModelForPicker - : selectedModelForPicker; + : (normalizeModelSlug(selectedModelForPicker, selectedProvider) ?? selectedModelForPicker); }, [modelOptionsByProvider, selectedModelForPicker, selectedProvider]); const searchableModelOptions = useMemo( () => @@ -3009,73 +2975,23 @@ export default function ChatView({ threadId }: ChatViewProps) { const onProviderModelSelect = useCallback( (provider: ProviderKind, model: ModelSlug) => { if (!activeThread) return; - if (cursorModelSelectionLockedReason !== null && provider === "cursor") { - scheduleComposerFocus(); - return; - } if (lockedProvider !== null && provider !== lockedProvider) { scheduleComposerFocus(); return; } - const resolvedModel = - provider === "cursor" - ? resolveCursorModelFromSelection(parseCursorModelSelection(model)) - : resolveModelSlugForProvider(provider, model); + const resolvedModel = resolveModelSlugForProvider(provider, model); setComposerDraftProvider(activeThread.id, provider); setComposerDraftModel(activeThread.id, resolvedModel); scheduleComposerFocus(); }, [ activeThread, - cursorModelSelectionLockedReason, lockedProvider, scheduleComposerFocus, setComposerDraftModel, setComposerDraftProvider, ], ); - const onCursorReasoningSelect = useCallback( - (reasoning: CursorReasoningOption) => { - if (selectedProvider !== "cursor") return; - const cursorSelection = parseCursorModelSelection(selectedModel); - const nextModel = resolveCursorModelFromSelection({ - family: cursorSelection.family, - reasoning, - fast: cursorSelection.fast, - thinking: cursorSelection.thinking, - }); - onProviderModelSelect("cursor", nextModel); - }, - [onProviderModelSelect, selectedModel, selectedProvider], - ); - const onCursorFastModeChange = useCallback( - (enabled: boolean) => { - if (selectedProvider !== "cursor") return; - const cursorSelection = parseCursorModelSelection(selectedModel); - const nextModel = resolveCursorModelFromSelection({ - family: cursorSelection.family, - reasoning: cursorSelection.reasoning, - fast: enabled, - thinking: cursorSelection.thinking, - }); - onProviderModelSelect("cursor", nextModel); - }, - [onProviderModelSelect, selectedModel, selectedProvider], - ); - const onCursorThinkingModeChange = useCallback( - (enabled: boolean) => { - if (selectedProvider !== "cursor") return; - const cursorSelection = parseCursorModelSelection(selectedModel); - const nextModel = resolveCursorModelFromSelection({ - family: cursorSelection.family, - reasoning: cursorSelection.reasoning, - fast: cursorSelection.fast, - thinking: enabled, - }); - onProviderModelSelect("cursor", nextModel); - }, - [onProviderModelSelect, selectedModel, selectedProvider], - ); const onEffortSelect = useCallback( (effort: CodexReasoningEffort) => { setComposerDraftEffort(threadId, effort); @@ -3594,82 +3510,15 @@ export default function ChatView({ threadId }: ChatViewProps) {
{/* Provider/model picker */} - {cursorModelSelectionLockedReason ? ( - - - - - } - /> - - {cursorModelSelectionLockedReason} - - - ) : ( - - )} + - {selectedProvider === "cursor" ? ( - <> - {hasSelectedCursorTraits && ( - - )} - - {selectedCursorModel && - selectedCursorModelCapabilities && - hasSelectedCursorTraits && ( - <> - {cursorModelSelectionLockedReason ? ( - - - - - } - /> - - {cursorModelSelectionLockedReason} - - - ) : ( - - )} - - )} - - ) : selectedProvider === "codex" && selectedEffort != null ? ( + {selectedProvider === "codex" && selectedEffort != null ? ( <> > { - const cursorFamilyOptions = getCursorModelFamilyOptions(); return { codex: getAppModelOptions("codex", settings.customCodexModels), - cursor: [ - ...cursorFamilyOptions, - ...getAppModelOptions("cursor", settings.customCursorModels).filter( - (option) => - option.isCustom && !cursorFamilyOptions.some((family) => family.slug === option.slug), - ), - ], }; } @@ -5348,10 +5188,6 @@ function resolveModelForProviderPicker( return resolved.slug; } - if (provider === "cursor") { - return parseCursorModelSelection(normalized).family; - } - return null; } @@ -5562,117 +5398,6 @@ const CodexTraitsPicker = memo(function CodexTraitsPicker(props: { ); }); -const CursorTraitsPicker = memo(function CursorTraitsPicker(props: { - selection: ReturnType; - capabilities: ReturnType; - disabled?: boolean; - onReasoningChange: (reasoning: CursorReasoningOption) => void; - onFastModeChange: (enabled: boolean) => void; - onThinkingModeChange: (enabled: boolean) => void; -}) { - const [isMenuOpen, setIsMenuOpen] = useState(false); - const reasoningLabelByOption: Record = { - low: "Low", - normal: "Normal", - high: "High", - xhigh: "Extra High", - }; - const traitSummary = [ - ...(props.capabilities.supportsReasoning - ? [reasoningLabelByOption[props.selection.reasoning]] - : []), - ...(props.capabilities.supportsFast && props.selection.fast ? ["Fast"] : []), - ...(props.capabilities.supportsThinking && props.selection.thinking ? ["Thinking"] : []), - ]; - const triggerLabel = traitSummary.length > 0 ? traitSummary.join(" ยท ") : "Traits"; - - return ( - { - if (props.disabled) { - setIsMenuOpen(false); - return; - } - setIsMenuOpen(open); - }} - > - - } - > - {triggerLabel} - - - {props.capabilities.supportsReasoning && ( - -
Reasoning
- { - if (props.disabled) return; - if (!value) return; - const nextReasoning = CURSOR_REASONING_OPTIONS.find((option) => option === value); - if (!nextReasoning) return; - props.onReasoningChange(nextReasoning); - }} - > - {CURSOR_REASONING_OPTIONS.map((reasoning) => ( - - {reasoning} - {reasoning === "normal" ? " (default)" : ""} - - ))} - -
- )} - {props.capabilities.supportsReasoning && - (props.capabilities.supportsFast || props.capabilities.supportsThinking) && ( - - )} - {props.capabilities.supportsFast && ( - -
Fast Mode
- { - if (props.disabled) return; - props.onFastModeChange(value === "on"); - }} - > - off - on - -
- )} - {props.capabilities.supportsFast && props.capabilities.supportsThinking && } - {props.capabilities.supportsThinking && ( - -
Thinking
- { - if (props.disabled) return; - props.onThinkingModeChange(value === "on"); - }} - > - off - on - -
- )} -
-
- ); -}); - const OpenInPicker = memo(function OpenInPicker({ keybindings, availableEditors, diff --git a/apps/web/src/composerDraftStore.test.ts b/apps/web/src/composerDraftStore.test.ts index ce0113058aeb80243507e6c3376db3f3a7037bde..f133d377f24fe9af080bfe20c07864285673bcaa 100644 --- a/apps/web/src/composerDraftStore.test.ts +++ b/apps/web/src/composerDraftStore.test.ts @@ -394,15 +394,15 @@ describe("composerDraftStore setProvider", () => { it("persists provider-only selection even when prompt/model are empty", () => { const store = useComposerDraftStore.getState(); - store.setProvider(threadId, "cursor"); + store.setProvider(threadId, "codex"); - expect(useComposerDraftStore.getState().draftsByThreadId[threadId]?.provider).toBe("cursor"); + expect(useComposerDraftStore.getState().draftsByThreadId[threadId]?.provider).toBe("codex"); }); it("removes empty provider-only draft when provider is reset", () => { const store = useComposerDraftStore.getState(); - store.setProvider(threadId, "cursor"); + store.setProvider(threadId, "codex"); store.setProvider(threadId, null); expect(useComposerDraftStore.getState().draftsByThreadId[threadId]).toBeUndefined(); diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index 4887e9bac883f846844346adcb1055c520bcfa29..2ac03a3ed3aa5e376b19d1ad4965b1579f8bfb7d 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -208,7 +208,7 @@ function shouldRemoveDraft(draft: ComposerThreadDraftState): boolean { } function normalizeProviderKind(value: unknown): ProviderKind | null { - return value === "codex" || value === "cursor" ? value : null; + return value === "codex" ? value : null; } function revokeObjectPreviewUrl(previewUrl: string): void { diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index 9fa15dd44800b1e14ff8b88ae5527f5342efd649..8dfeb210f284599ff6471310724df721061c87fe 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -54,8 +54,6 @@ function getCustomModelsForProvider( provider: ProviderKind, ) { switch (provider) { - case "cursor": - return settings.customCursorModels; case "codex": default: return settings.customCodexModels; @@ -67,8 +65,6 @@ function getDefaultCustomModelsForProvider( provider: ProviderKind, ) { switch (provider) { - case "cursor": - return defaults.customCursorModels; case "codex": default: return defaults.customCodexModels; @@ -77,8 +73,6 @@ function getDefaultCustomModelsForProvider( function patchCustomModels(provider: ProviderKind, models: string[]) { switch (provider) { - case "cursor": - return { customCursorModels: models }; case "codex": default: return { customCodexModels: models }; @@ -95,7 +89,6 @@ function SettingsRouteView() { Record >({ codex: "", - cursor: "", }); const [customModelErrorByProvider, setCustomModelErrorByProvider] = useState< Partial> diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index ec83b7e2ebefea25fb9cb88ac0a5342757b99e48..6a94c49737f50ca45903f9182d99b62513cc3773 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -10,7 +10,7 @@ import { import type { ChatMessage, ProposedPlan, SessionPhase, ThreadSession, TurnDiffSummary } from "./types"; -export type ProviderPickerKind = ProviderKind | "claudeCode"; +export type ProviderPickerKind = ProviderKind | "claudeCode" | "cursor"; export const PROVIDER_OPTIONS: Array<{ value: ProviderPickerKind; diff --git a/apps/web/src/store.test.ts b/apps/web/src/store.test.ts index 90cbdc000eac2a17ed653c252692b23ffe8c2d3c..77b913c3d32ceca2c99bd7dd79aaa4b69f32f508 100644 --- a/apps/web/src/store.test.ts +++ b/apps/web/src/store.test.ts @@ -141,27 +141,4 @@ describe("store read model sync", () => { expect(next.threads[0]?.model).toBe("gpt-5.3-codex"); }); - - it("resolves cursor aliases when session provider is cursor", () => { - const initialState = makeState(makeThread()); - const readModel = makeReadModel( - makeReadModelThread({ - model: "composer", - session: { - threadId: ThreadId.makeUnsafe("thread-1"), - status: "ready", - providerName: "cursor", - runtimeMode: "approval-required", - activeTurnId: null, - lastError: null, - updatedAt: "2026-02-27T00:00:00.000Z", - }, - }), - ); - - const next = syncServerReadModel(initialState, readModel); - - expect(next.threads[0]?.model).toBe("composer-1.5"); - expect(next.threads[0]?.session?.provider).toBe("cursor"); - }); }); diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts index 48a6355e7818045e96e681a7503dd7e6b68f0da5..8bea15ea7facf04a678145aab7a231749126e47a 100644 --- a/apps/web/src/store.ts +++ b/apps/web/src/store.ts @@ -145,43 +145,25 @@ function toLegacySessionStatus( } function toLegacyProvider(providerName: string | null): ProviderKind { - if (providerName === "codex" || providerName === "cursor") { + if (providerName === "codex") { return providerName; } return "codex"; } const CODEX_MODEL_SLUGS = new Set(getModelOptions("codex").map((option) => option.slug)); -const CURSOR_MODEL_SLUGS = new Set(getModelOptions("cursor").map((option) => option.slug)); -const CURSOR_DISTINCT_MODEL_SLUGS = new Set( - [...CURSOR_MODEL_SLUGS].filter((slug) => !CODEX_MODEL_SLUGS.has(slug)), -); function inferProviderForThreadModel(input: { readonly model: string; readonly sessionProviderName: string | null; }): ProviderKind { - if ( - input.sessionProviderName === "codex" || - input.sessionProviderName === "cursor" - ) { + if (input.sessionProviderName === "codex") { return input.sessionProviderName; } - const normalizedCursor = normalizeModelSlug(input.model, "cursor"); - if (normalizedCursor && CURSOR_DISTINCT_MODEL_SLUGS.has(normalizedCursor)) { - return "cursor"; - } const normalizedCodex = normalizeModelSlug(input.model, "codex"); if (normalizedCodex && CODEX_MODEL_SLUGS.has(normalizedCodex)) { return "codex"; } - if ( - input.model.trim().startsWith("composer-") || - input.model.trim().startsWith("gemini-") || - input.model.trim().endsWith("-thinking") - ) { - return "cursor"; - } return "codex"; } diff --git a/packages/contracts/src/model.ts b/packages/contracts/src/model.ts index 07a2aee520b65f1546d29a3309b97324555ed26c..8c68690dfc250a439b0d655c277a59b08a5f96ee 100644 --- a/packages/contracts/src/model.ts +++ b/packages/contracts/src/model.ts @@ -1,8 +1,5 @@ import { Schema } from "effect"; -export const CURSOR_REASONING_OPTIONS = ["low", "normal", "high", "xhigh"] as const; -export type CursorReasoningOption = (typeof CURSOR_REASONING_OPTIONS)[number]; - export const CODEX_REASONING_EFFORT_OPTIONS = ["xhigh", "high", "medium", "low"] as const; export type CodexReasoningEffort = (typeof CODEX_REASONING_EFFORT_OPTIONS)[number]; @@ -12,16 +9,8 @@ export const CodexModelOptions = Schema.Struct({ }); export type CodexModelOptions = typeof CodexModelOptions.Type; -export const CursorModelOptions = Schema.Struct({ - reasoning: Schema.optional(Schema.Literals(CURSOR_REASONING_OPTIONS)), - fastMode: Schema.optional(Schema.Boolean), - thinking: Schema.optional(Schema.Boolean), -}); -export type CursorModelOptions = typeof CursorModelOptions.Type; - export const ProviderModelOptions = Schema.Struct({ codex: Schema.optional(CodexModelOptions), - cursor: Schema.optional(CursorModelOptions), }); export type ProviderModelOptions = typeof ProviderModelOptions.Type; @@ -30,25 +19,6 @@ type ModelOption = { readonly name: string; }; -type CursorModelFamilyOption = { - readonly slug: string; - readonly name: string; -}; - -export const CURSOR_MODEL_FAMILY_OPTIONS = [ - { slug: "auto", name: "Auto" }, - { slug: "composer-1.5", name: "Composer 1.5" }, - { slug: "composer-1", name: "Composer 1" }, - { slug: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, - { slug: "gpt-5.3-codex-spark-preview", name: "GPT-5.3 Codex Spark" }, - { slug: "opus-4.6", name: "Claude 4.6 Opus" }, - { slug: "opus-4.5", name: "Claude 4.5 Opus" }, - { slug: "sonnet-4.6", name: "Claude 4.6 Sonnet" }, - { slug: "gemini-3.1-pro", name: "Gemini 3.1 Pro" }, -] as const satisfies readonly CursorModelFamilyOption[]; - -export type CursorModelFamily = (typeof CURSOR_MODEL_FAMILY_OPTIONS)[number]["slug"]; - export const MODEL_OPTIONS_BY_PROVIDER = { codex: [ { slug: "gpt-5.4", name: "GPT-5.4" }, @@ -57,37 +27,14 @@ export const MODEL_OPTIONS_BY_PROVIDER = { { slug: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, { slug: "gpt-5.2", name: "GPT-5.2" }, ], - cursor: [ - { slug: "auto", name: "Auto" }, - { slug: "composer-1.5", name: "Composer 1.5" }, - { slug: "composer-1", name: "Composer 1" }, - { slug: "gpt-5.3-codex-low", name: "GPT-5.3 Codex Low" }, - { slug: "gpt-5.3-codex-low-fast", name: "GPT-5.3 Codex Low Fast" }, - { slug: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, - { slug: "gpt-5.3-codex-fast", name: "GPT-5.3 Codex Fast" }, - { slug: "gpt-5.3-codex-high", name: "GPT-5.3 Codex High" }, - { slug: "gpt-5.3-codex-high-fast", name: "GPT-5.3 Codex High Fast" }, - { slug: "gpt-5.3-codex-xhigh", name: "GPT-5.3 Codex Extra High" }, - { slug: "gpt-5.3-codex-xhigh-fast", name: "GPT-5.3 Codex Extra High Fast" }, - { slug: "gpt-5.3-codex-spark-preview", name: "GPT-5.3 Codex Spark" }, - { slug: "opus-4.6", name: "Claude 4.6 Opus" }, - { slug: "opus-4.6-thinking", name: "Claude 4.6 Opus (Thinking)" }, - { slug: "opus-4.5", name: "Claude 4.5 Opus" }, - { slug: "opus-4.5-thinking", name: "Claude 4.5 Opus (Thinking)" }, - { slug: "sonnet-4.6", name: "Claude 4.6 Sonnet" }, - { slug: "sonnet-4.6-thinking", name: "Claude 4.6 Sonnet (Thinking)" }, - { slug: "gemini-3.1-pro", name: "Gemini 3.1 Pro" }, - ], -} as const satisfies Record<"codex" | "cursor", readonly ModelOption[]>; +} as const satisfies Record<"codex", readonly ModelOption[]>; type BuiltInModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)[keyof typeof MODEL_OPTIONS_BY_PROVIDER][number]["slug"]; export type ModelSlug = BuiltInModelSlug | (string & {}); -export type CursorModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)["cursor"][number]["slug"]; export const DEFAULT_MODEL_BY_PROVIDER = { codex: "gpt-5.3-codex", - cursor: "opus-4.6-thinking", } as const satisfies Record; // Backward compatibility for existing Codex-only call sites. @@ -102,25 +49,10 @@ export const MODEL_SLUG_ALIASES_BY_PROVIDER = { "5.3-spark": "gpt-5.3-codex-spark", "gpt-5.3-spark": "gpt-5.3-codex-spark", }, - cursor: { - composer: "composer-1.5", - "composer-1.5": "composer-1.5", - "composer-1": "composer-1", - "gpt-5.3-codex": "gpt-5.3-codex", - "gpt-5.3-codex-spark": "gpt-5.3-codex-spark-preview", - "gemini-3.1-pro": "gemini-3.1-pro", - "claude-4.6-sonnet-thinking": "sonnet-4.6-thinking", - "claude-4.6-opus-thinking": "opus-4.6-thinking", - "claude-4.5-opus-thinking": "opus-4.5-thinking", - "sonnet-4.6-thinking": "sonnet-4.6-thinking", - "opus-4.6-thinking": "opus-4.6-thinking", - "opus-4.5-thinking": "opus-4.5-thinking", - }, } as const satisfies Record>; export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = { codex: CODEX_REASONING_EFFORT_OPTIONS, - cursor: [], } as const satisfies Record< keyof typeof MODEL_OPTIONS_BY_PROVIDER, readonly CodexReasoningEffort[] @@ -128,5 +60,4 @@ export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = { export const DEFAULT_REASONING_EFFORT_BY_PROVIDER = { codex: "high", - cursor: null, } as const satisfies Record; diff --git a/packages/contracts/src/orchestration.test.ts b/packages/contracts/src/orchestration.test.ts index d483f2669b9f73dc52d87ef6df877a64ef5a2871..16c6036cf0fa8812161f8d9c2c54f65e7b3e3cf5 100644 --- a/packages/contracts/src/orchestration.test.ts +++ b/packages/contracts/src/orchestration.test.ts @@ -128,11 +128,11 @@ it.effect("preserves explicit provider and runtime mode in thread.turn.start", ( text: "hello", attachments: [], }, - provider: "cursor", + provider: "codex", runtimeMode: "full-access", createdAt: "2026-01-01T00:00:00.000Z", }); - assert.strictEqual(parsed.provider, "cursor"); + assert.strictEqual(parsed.provider, "codex"); assert.strictEqual(parsed.runtimeMode, "full-access"); assert.strictEqual(parsed.interactionMode, DEFAULT_PROVIDER_INTERACTION_MODE); }), @@ -166,26 +166,6 @@ it.effect("accepts provider-scoped model options in thread.turn.start", () => }), ); -it.effect("accepts cursor provider in thread.turn.start", () => - Effect.gen(function* () { - const parsed = yield* decodeThreadTurnStartCommand({ - type: "thread.turn.start", - commandId: "cmd-turn-cursor", - threadId: "thread-1", - message: { - messageId: "msg-3", - role: "user", - text: "hello", - attachments: [], - }, - provider: "cursor", - createdAt: "2026-01-01T00:00:00.000Z", - }); - assert.strictEqual(parsed.provider, "cursor"); - assert.strictEqual(parsed.interactionMode, DEFAULT_PROVIDER_INTERACTION_MODE); - }), -); - it.effect( "decodes thread.turn-start-requested defaults for provider, runtime mode, and interaction mode", () => diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts index e0150b18aef0d7d7397d15ac918397341b41f31a..212f2ae05eaa81c085a0b23e8f57fec5251ffe81 100644 --- a/packages/contracts/src/orchestration.ts +++ b/packages/contracts/src/orchestration.ts @@ -27,7 +27,7 @@ export const ORCHESTRATION_WS_CHANNELS = { domainEvent: "orchestration.domainEvent", } as const; -export const ProviderKind = Schema.Literals(["codex", "cursor"]); +export const ProviderKind = Schema.Literal("codex"); export type ProviderKind = typeof ProviderKind.Type; export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex"; export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]); diff --git a/packages/contracts/src/provider.test.ts b/packages/contracts/src/provider.test.ts index 6cc258acac1987a7bb1fe98d909eab2b3cbeb6a6..997db09b78ad8d1fd23798af10fa5ce90322959e 100644 --- a/packages/contracts/src/provider.test.ts +++ b/packages/contracts/src/provider.test.ts @@ -42,31 +42,6 @@ describe("ProviderSessionStartInput", () => { }), ).toThrow(); }); - - it("accepts cursor provider payloads", () => { - const parsed = decodeProviderSessionStartInput({ - threadId: "thread-1", - provider: "cursor", - cwd: "/tmp/workspace", - model: "composer-1.5", - modelOptions: { - cursor: { - thinking: true, - }, - }, - providerOptions: { - cursor: { - binaryPath: "/usr/local/bin/agent", - }, - }, - runtimeMode: "approval-required", - }); - expect(parsed.provider).toBe("cursor"); - expect(parsed.model).toBe("composer-1.5"); - expect(parsed.modelOptions?.cursor?.thinking).toBe(true); - expect(parsed.providerOptions?.cursor?.binaryPath).toBe("/usr/local/bin/agent"); - expect(parsed.runtimeMode).toBe("approval-required"); - }); }); describe("ProviderSendTurnInput", () => { diff --git a/packages/contracts/src/provider.ts b/packages/contracts/src/provider.ts index 28a659f5f4897159644db7a2190697f419052cd7..270c24032965a98a97fe9f146ccbac3ad12b6597 100644 --- a/packages/contracts/src/provider.ts +++ b/packages/contracts/src/provider.ts @@ -50,13 +50,8 @@ const CodexProviderStartOptions = Schema.Struct({ homePath: Schema.optional(TrimmedNonEmptyStringSchema), }); -const CursorProviderStartOptions = Schema.Struct({ - binaryPath: Schema.optional(TrimmedNonEmptyStringSchema), -}); - const ProviderStartOptions = Schema.Struct({ codex: Schema.optional(CodexProviderStartOptions), - cursor: Schema.optional(CursorProviderStartOptions), }); export const ProviderSessionStartInput = Schema.Struct({ diff --git a/packages/contracts/src/providerRuntime.ts b/packages/contracts/src/providerRuntime.ts index 6eefebfe39a0ba01325a12d2641580f8eb95f2ee..903bb5da782d255c2b5e2ac2e54e408d82020ea9 100644 --- a/packages/contracts/src/providerRuntime.ts +++ b/packages/contracts/src/providerRuntime.ts @@ -20,9 +20,6 @@ const RuntimeEventRawSource = Schema.Literals([ "codex.app-server.request", "codex.eventmsg", "codex.sdk.thread-event", - "cursor.acp.notification", - "cursor.acp.request", - "cursor.acp.response", ]); export type RuntimeEventRawSource = typeof RuntimeEventRawSource.Type; diff --git a/packages/shared/src/model.test.ts b/packages/shared/src/model.test.ts index 5528ed3d58ddae1f6518c5f1d8e6ceff33fe2971..e3f3b6925b50f09b2da3935bebf73f0a66e3d19b 100644 --- a/packages/shared/src/model.test.ts +++ b/packages/shared/src/model.test.ts @@ -1,24 +1,13 @@ import { describe, expect, it } from "vitest"; -import { - CURSOR_MODEL_FAMILY_OPTIONS, - CURSOR_REASONING_OPTIONS, - DEFAULT_MODEL, - DEFAULT_MODEL_BY_PROVIDER, - MODEL_OPTIONS, - MODEL_OPTIONS_BY_PROVIDER, -} from "../../contracts/src"; +import { DEFAULT_MODEL, MODEL_OPTIONS } from "@t3tools/contracts"; import { getDefaultModel, getDefaultReasoningEffort, - getCursorModelFamilyOptions, getModelOptions, getReasoningEffortOptions, normalizeModelSlug, - parseCursorModelSelection, - resolveCursorModelFromSelection, resolveModelSlug, - resolveModelSlugForProvider, } from "./model"; describe("normalizeModelSlug", () => { @@ -38,17 +27,6 @@ describe("normalizeModelSlug", () => { expect(normalizeModelSlug("gpt-5.2")).toBe("gpt-5.2"); expect(normalizeModelSlug("gpt-5.2-codex")).toBe("gpt-5.2-codex"); }); - - it("uses provider-specific aliases", () => { - expect(normalizeModelSlug("composer", "cursor")).toBe("composer-1.5"); - expect(normalizeModelSlug("gpt-5.3-codex-spark", "cursor")).toBe( - "gpt-5.3-codex-spark-preview", - ); - expect(normalizeModelSlug("gemini-3.1", "cursor")).toBe("gemini-3.1"); - expect(normalizeModelSlug("claude-4.6-sonnet-thinking", "cursor")).toBe( - "sonnet-4.6-thinking", - ); - }); }); describe("resolveModelSlug", () => { @@ -67,70 +45,9 @@ describe("resolveModelSlug", () => { expect(resolveModelSlug(model.slug)).toBe(model.slug); } }); - - it("supports provider-aware resolution", () => { - expect(resolveModelSlugForProvider("cursor", undefined)).toBe( - DEFAULT_MODEL_BY_PROVIDER.cursor, - ); - expect(resolveModelSlugForProvider("cursor", "composer")).toBe("composer-1.5"); - expect(resolveModelSlugForProvider("cursor", "gpt-5.3-codex-high-fast")).toBe( - "gpt-5.3-codex-high-fast", - ); - expect(resolveModelSlugForProvider("cursor", "claude-sonnet-4-6")).toBe( - DEFAULT_MODEL_BY_PROVIDER.cursor, - ); - }); - it("keeps codex defaults for backward compatibility", () => { expect(getDefaultModel()).toBe(DEFAULT_MODEL); expect(getModelOptions()).toEqual(MODEL_OPTIONS); - expect(getModelOptions("cursor")).toEqual(MODEL_OPTIONS_BY_PROVIDER.cursor); - expect(getCursorModelFamilyOptions()).toEqual(CURSOR_MODEL_FAMILY_OPTIONS); - }); -}); - -describe("cursor model selection", () => { - it("includes the expected cursor reasoning levels and families", () => { - expect(CURSOR_REASONING_OPTIONS).toEqual(["low", "normal", "high", "xhigh"]); - expect(getCursorModelFamilyOptions().map((option) => option.slug)).toContain("gpt-5.3-codex"); - expect(getCursorModelFamilyOptions().map((option) => option.slug)).toContain("opus-4.6"); - }); - - it("parses codex reasoning and fast mode variants", () => { - expect(parseCursorModelSelection("gpt-5.3-codex-high-fast")).toEqual({ - family: "gpt-5.3-codex", - reasoning: "high", - fast: true, - thinking: false, - }); - expect(parseCursorModelSelection("gpt-5.2-codex")).toEqual( - parseCursorModelSelection(DEFAULT_MODEL_BY_PROVIDER.cursor), - ); - }); - - it("parses and resolves thinking variants", () => { - expect(parseCursorModelSelection("sonnet-4.6-thinking")).toEqual({ - family: "sonnet-4.6", - reasoning: "normal", - fast: false, - thinking: true, - }); - expect( - resolveCursorModelFromSelection({ - family: "sonnet-4.6", - thinking: true, - }), - ).toBe("sonnet-4.6-thinking"); - }); - - it("resolves codex family selections into concrete model ids", () => { - expect( - resolveCursorModelFromSelection({ - family: "gpt-5.3-codex", - reasoning: "xhigh", - fast: true, - }), - ).toBe("gpt-5.3-codex-xhigh-fast"); }); }); @@ -138,15 +55,10 @@ describe("getReasoningEffortOptions", () => { it("returns codex reasoning options for codex", () => { expect(getReasoningEffortOptions("codex")).toEqual(["xhigh", "high", "medium", "low"]); }); - - it("returns no reasoning options for cursor", () => { - expect(getReasoningEffortOptions("cursor")).toEqual([]); - }); }); describe("getDefaultReasoningEffort", () => { it("returns provider-scoped defaults", () => { expect(getDefaultReasoningEffort("codex")).toBe("high"); - expect(getDefaultReasoningEffort("cursor")).toBeNull(); }); }); diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts index 8dd4150a6cadbef5905330b32f3f2dd2b0707244..9b80199574a8d642582f04a2a1e374c762b0af16 100644 --- a/packages/shared/src/model.ts +++ b/packages/shared/src/model.ts @@ -1,233 +1,26 @@ import { CODEX_REASONING_EFFORT_OPTIONS, - CURSOR_MODEL_FAMILY_OPTIONS, - CURSOR_REASONING_OPTIONS, DEFAULT_MODEL, DEFAULT_MODEL_BY_PROVIDER, MODEL_OPTIONS_BY_PROVIDER, MODEL_SLUG_ALIASES_BY_PROVIDER, type CodexReasoningEffort, - type CursorModelFamily, - type CursorModelSlug, - type CursorReasoningOption, type ModelSlug, type ProviderKind, -} from "../../contracts/src"; +} from "@t3tools/contracts"; type CatalogProvider = keyof typeof MODEL_OPTIONS_BY_PROVIDER; -type CursorModelCapability = { - readonly supportsReasoning: boolean; - readonly supportsFast: boolean; - readonly supportsThinking: boolean; - readonly defaultReasoning: CursorReasoningOption; - readonly defaultThinking: boolean; -}; - -const CURSOR_MODEL_CAPABILITY_BY_FAMILY: Record = { - auto: { - supportsReasoning: false, - supportsFast: false, - supportsThinking: false, - defaultReasoning: "normal", - defaultThinking: false, - }, - "composer-1.5": { - supportsReasoning: false, - supportsFast: false, - supportsThinking: false, - defaultReasoning: "normal", - defaultThinking: false, - }, - "composer-1": { - supportsReasoning: false, - supportsFast: false, - supportsThinking: false, - defaultReasoning: "normal", - defaultThinking: false, - }, - "gpt-5.3-codex": { - supportsReasoning: true, - supportsFast: true, - supportsThinking: false, - defaultReasoning: "normal", - defaultThinking: false, - }, - "gpt-5.3-codex-spark-preview": { - supportsReasoning: false, - supportsFast: false, - supportsThinking: false, - defaultReasoning: "normal", - defaultThinking: false, - }, - "opus-4.6": { - supportsReasoning: false, - supportsFast: false, - supportsThinking: true, - defaultReasoning: "normal", - defaultThinking: true, - }, - "opus-4.5": { - supportsReasoning: false, - supportsFast: false, - supportsThinking: true, - defaultReasoning: "normal", - defaultThinking: true, - }, - "sonnet-4.6": { - supportsReasoning: false, - supportsFast: false, - supportsThinking: true, - defaultReasoning: "normal", - defaultThinking: true, - }, - "gemini-3.1-pro": { - supportsReasoning: false, - supportsFast: false, - supportsThinking: false, - defaultReasoning: "normal", - defaultThinking: false, - }, -}; - -function hasModelCatalog(provider: ProviderKind): provider is CatalogProvider { - return Object.hasOwn(MODEL_OPTIONS_BY_PROVIDER, provider); -} - const MODEL_SLUG_SET_BY_PROVIDER: Record> = { codex: new Set(MODEL_OPTIONS_BY_PROVIDER.codex.map((option) => option.slug)), - cursor: new Set(MODEL_OPTIONS_BY_PROVIDER.cursor.map((option) => option.slug)), }; -const CURSOR_MODEL_FAMILY_SET = new Set( - CURSOR_MODEL_FAMILY_OPTIONS.map((option) => option.slug), -); - -export interface CursorModelSelection { - readonly family: CursorModelFamily; - readonly reasoning: CursorReasoningOption; - readonly fast: boolean; - readonly thinking: boolean; -} - export function getModelOptions(provider: ProviderKind = "codex") { - return hasModelCatalog(provider) ? MODEL_OPTIONS_BY_PROVIDER[provider] : []; -} - -export function getCursorModelFamilyOptions() { - return CURSOR_MODEL_FAMILY_OPTIONS; -} - -export function getCursorModelCapabilities(family: CursorModelFamily) { - return CURSOR_MODEL_CAPABILITY_BY_FAMILY[family]; -} - -function fallbackCursorModelFamily(): CursorModelFamily { - const fallback = parseCursorModelSelection(DEFAULT_MODEL_BY_PROVIDER.cursor); - return fallback.family; -} - -function resolveCursorModelFamily(model: string | null | undefined): CursorModelFamily { - const normalized = normalizeModelSlug(model, "cursor"); - if (!normalized) { - return fallbackCursorModelFamily(); - } - - if ( - normalized === "gpt-5.3-codex" || - normalized === "gpt-5.3-codex-fast" || - normalized === "gpt-5.3-codex-low" || - normalized === "gpt-5.3-codex-low-fast" || - normalized === "gpt-5.3-codex-high" || - normalized === "gpt-5.3-codex-high-fast" || - normalized === "gpt-5.3-codex-xhigh" || - normalized === "gpt-5.3-codex-xhigh-fast" - ) { - return "gpt-5.3-codex"; - } - - if (normalized === "sonnet-4.6-thinking") { - return "sonnet-4.6"; - } - if (normalized === "opus-4.6-thinking") { - return "opus-4.6"; - } - if (normalized === "opus-4.5-thinking") { - return "opus-4.5"; - } - - return CURSOR_MODEL_FAMILY_SET.has(normalized as CursorModelFamily) - ? (normalized as CursorModelFamily) - : fallbackCursorModelFamily(); -} - -function resolveCursorReasoning(model: CursorModelSlug): CursorReasoningOption { - if (model.includes("-xhigh")) return "xhigh"; - if (model.includes("-high")) return "high"; - if (model.includes("-low")) return "low"; - return "normal"; -} - -export function parseCursorModelSelection(model: string | null | undefined): CursorModelSelection { - const family = resolveCursorModelFamily(model); - const capability = CURSOR_MODEL_CAPABILITY_BY_FAMILY[family]; - const normalized = resolveModelSlugForProvider("cursor", model) as CursorModelSlug; - - if (capability.supportsReasoning) { - return { - family, - reasoning: resolveCursorReasoning(normalized), - fast: normalized.endsWith("-fast"), - thinking: false, - }; - } - - if (capability.supportsThinking) { - return { - family, - reasoning: capability.defaultReasoning, - fast: false, - thinking: normalized.endsWith("-thinking"), - }; - } - - return { - family, - reasoning: capability.defaultReasoning, - fast: false, - thinking: capability.defaultThinking, - }; -} - -export function resolveCursorModelFromSelection(input: { - readonly family: CursorModelFamily; - readonly reasoning?: CursorReasoningOption | null; - readonly fast?: boolean; - readonly thinking?: boolean; -}): CursorModelSlug { - const family = resolveCursorModelFamily(input.family); - const capability = CURSOR_MODEL_CAPABILITY_BY_FAMILY[family]; - - if (capability.supportsReasoning) { - const reasoning = CURSOR_REASONING_OPTIONS.includes(input.reasoning ?? "normal") - ? (input.reasoning ?? "normal") - : capability.defaultReasoning; - const reasoningSuffix = reasoning === "normal" ? "" : `-${reasoning}`; - const fastSuffix = input.fast ? "-fast" : ""; - const candidate = `${family}${reasoningSuffix}${fastSuffix}`; - return resolveModelSlugForProvider("cursor", candidate) as CursorModelSlug; - } - - if (capability.supportsThinking) { - const candidate = input.thinking ? `${family}-thinking` : family; - return resolveModelSlugForProvider("cursor", candidate) as CursorModelSlug; - } - - return resolveModelSlugForProvider("cursor", family) as CursorModelSlug; + return MODEL_OPTIONS_BY_PROVIDER[provider]; } export function getDefaultModel(provider: ProviderKind = "codex"): ModelSlug { - return hasModelCatalog(provider) ? DEFAULT_MODEL_BY_PROVIDER[provider] : DEFAULT_MODEL; + return DEFAULT_MODEL_BY_PROVIDER[provider] ?? DEFAULT_MODEL; } export function normalizeModelSlug( @@ -243,10 +36,6 @@ export function normalizeModelSlug( return null; } - if (!hasModelCatalog(provider)) { - return trimmed as ModelSlug; - } - const aliases = MODEL_SLUG_ALIASES_BY_PROVIDER[provider] as Record; return aliases[trimmed] ?? (trimmed as ModelSlug); } @@ -260,11 +49,9 @@ export function resolveModelSlug( return getDefaultModel(provider); } - if (!hasModelCatalog(provider)) { - return getDefaultModel(provider); - } - - return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized) ? normalized : getDefaultModel(provider); + return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized) + ? normalized + : getDefaultModel(provider); } export function resolveModelSlugForProvider(