diff --git a/packages/contracts/src/model.ts b/packages/contracts/src/model.ts index 083b4cc536bb90eaff2b98b8a4f1ea81d75b6c46..07a2aee520b65f1546d29a3309b97324555ed26c 100644 --- a/packages/contracts/src/model.ts +++ b/packages/contracts/src/model.ts @@ -1,5 +1,4 @@ import { Schema } from "effect"; -import type { ProviderKind } from "./orchestration"; export const CURSOR_REASONING_OPTIONS = ["low", "normal", "high", "xhigh"] as const; export type CursorReasoningOption = (typeof CURSOR_REASONING_OPTIONS)[number]; @@ -58,11 +57,6 @@ export const MODEL_OPTIONS_BY_PROVIDER = { { slug: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, { slug: "gpt-5.2", name: "GPT-5.2" }, ], - claudeCode: [ - { slug: "claude-opus-4-6", name: "Claude Opus 4.6" }, - { slug: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, - { slug: "claude-haiku-4-5", name: "Claude Haiku 4.5" }, - ], cursor: [ { slug: "auto", name: "Auto" }, { slug: "composer-1.5", name: "Composer 1.5" }, @@ -84,23 +78,23 @@ export const MODEL_OPTIONS_BY_PROVIDER = { { 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; +} as const satisfies Record<"codex" | "cursor", readonly ModelOption[]>; -type BuiltInModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)[ProviderKind][number]["slug"]; +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: Record = { +export const DEFAULT_MODEL_BY_PROVIDER = { codex: "gpt-5.3-codex", - claudeCode: "claude-sonnet-4-6", cursor: "opus-4.6-thinking", -}; +} as const satisfies Record; // Backward compatibility for existing Codex-only call sites. export const MODEL_OPTIONS = MODEL_OPTIONS_BY_PROVIDER.codex; export const DEFAULT_MODEL = DEFAULT_MODEL_BY_PROVIDER.codex; -export const MODEL_SLUG_ALIASES_BY_PROVIDER: Record> = { +export const MODEL_SLUG_ALIASES_BY_PROVIDER = { codex: { "5.4": "gpt-5.4", "5.3": "gpt-5.3-codex", @@ -108,20 +102,6 @@ export const MODEL_SLUG_ALIASES_BY_PROVIDER: Record>; export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = { codex: CODEX_REASONING_EFFORT_OPTIONS, - claudeCode: [], cursor: [], -} as const satisfies Record; +} as const satisfies Record< + keyof typeof MODEL_OPTIONS_BY_PROVIDER, + readonly CodexReasoningEffort[] +>; export const DEFAULT_REASONING_EFFORT_BY_PROVIDER = { codex: "high", - claudeCode: null, cursor: null, -} as const satisfies Record; +} as const satisfies Record; diff --git a/packages/shared/src/model.test.ts b/packages/shared/src/model.test.ts index e09f0bf87c0feba6e3f10e11c7b014ae05c322da..7e85d16a16b15cc2c5822619216b5d5c970a0639 100644 --- a/packages/shared/src/model.test.ts +++ b/packages/shared/src/model.test.ts @@ -4,10 +4,8 @@ import { CURSOR_REASONING_OPTIONS, DEFAULT_MODEL, DEFAULT_MODEL_BY_PROVIDER, - DEFAULT_REASONING_EFFORT_BY_PROVIDER, MODEL_OPTIONS, MODEL_OPTIONS_BY_PROVIDER, - REASONING_EFFORT_OPTIONS_BY_PROVIDER, } from "../../contracts/src"; import { @@ -42,11 +40,6 @@ describe("normalizeModelSlug", () => { }); it("uses provider-specific aliases", () => { - expect(normalizeModelSlug("sonnet", "claudeCode")).toBe("claude-sonnet-4-6"); - expect(normalizeModelSlug("opus-4.6", "claudeCode")).toBe("claude-opus-4-6"); - expect(normalizeModelSlug("claude-haiku-4-5-20251001", "claudeCode")).toBe( - "claude-haiku-4-5", - ); expect(normalizeModelSlug("composer", "cursor")).toBe("composer-1.5"); expect(normalizeModelSlug("gpt-5.3-codex-spark", "cursor")).toBe( "gpt-5.3-codex-spark-preview", @@ -76,13 +69,6 @@ describe("resolveModelSlug", () => { }); it("supports provider-aware resolution", () => { - expect(resolveModelSlugForProvider("claudeCode", undefined)).toBe( - DEFAULT_MODEL_BY_PROVIDER.claudeCode, - ); - expect(resolveModelSlugForProvider("claudeCode", "sonnet")).toBe("claude-sonnet-4-6"); - expect(resolveModelSlugForProvider("claudeCode", "gpt-5.3-codex")).toBe( - DEFAULT_MODEL_BY_PROVIDER.claudeCode, - ); expect(resolveModelSlugForProvider("cursor", undefined)).toBe( DEFAULT_MODEL_BY_PROVIDER.cursor, ); @@ -98,7 +84,7 @@ describe("resolveModelSlug", () => { it("keeps codex defaults for backward compatibility", () => { expect(getDefaultModel()).toBe(DEFAULT_MODEL); expect(getModelOptions()).toEqual(MODEL_OPTIONS); - expect(getModelOptions("claudeCode")).toEqual(MODEL_OPTIONS_BY_PROVIDER.claudeCode); + expect(getModelOptions("claudeCode")).toEqual([]); expect(getModelOptions("cursor")).toEqual(MODEL_OPTIONS_BY_PROVIDER.cursor); expect(getCursorModelFamilyOptions()).toEqual(CURSOR_MODEL_FAMILY_OPTIONS); }); @@ -151,13 +137,7 @@ describe("cursor model selection", () => { describe("getReasoningEffortOptions", () => { it("returns codex reasoning options for codex", () => { - expect(getReasoningEffortOptions("codex")).toEqual( - REASONING_EFFORT_OPTIONS_BY_PROVIDER.codex, - ); - }); - - it("returns no reasoning options for claudeCode", () => { - expect(getReasoningEffortOptions("claudeCode")).toEqual([]); + expect(getReasoningEffortOptions("codex")).toEqual(["xhigh", "high", "medium", "low"]); }); it("returns no reasoning options for cursor", () => { @@ -167,14 +147,7 @@ describe("getReasoningEffortOptions", () => { describe("getDefaultReasoningEffort", () => { it("returns provider-scoped defaults", () => { - expect(getDefaultReasoningEffort("codex")).toBe( - DEFAULT_REASONING_EFFORT_BY_PROVIDER.codex, - ); - expect(getDefaultReasoningEffort("claudeCode")).toBe( - DEFAULT_REASONING_EFFORT_BY_PROVIDER.claudeCode, - ); - expect(getDefaultReasoningEffort("cursor")).toBe( - DEFAULT_REASONING_EFFORT_BY_PROVIDER.cursor, - ); + expect(getDefaultReasoningEffort("codex")).toBe("high"); + expect(getDefaultReasoningEffort("cursor")).toBeNull(); }); }); diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts index 7343a2c50cde2f177ce2f699b67e9abddbbb8076..8dd4150a6cadbef5905330b32f3f2dd2b0707244 100644 --- a/packages/shared/src/model.ts +++ b/packages/shared/src/model.ts @@ -2,11 +2,10 @@ import { CODEX_REASONING_EFFORT_OPTIONS, CURSOR_MODEL_FAMILY_OPTIONS, CURSOR_REASONING_OPTIONS, + DEFAULT_MODEL, DEFAULT_MODEL_BY_PROVIDER, - DEFAULT_REASONING_EFFORT_BY_PROVIDER, MODEL_OPTIONS_BY_PROVIDER, MODEL_SLUG_ALIASES_BY_PROVIDER, - REASONING_EFFORT_OPTIONS_BY_PROVIDER, type CodexReasoningEffort, type CursorModelFamily, type CursorModelSlug, @@ -15,6 +14,8 @@ import { type ProviderKind, } from "../../contracts/src"; +type CatalogProvider = keyof typeof MODEL_OPTIONS_BY_PROVIDER; + type CursorModelCapability = { readonly supportsReasoning: boolean; readonly supportsFast: boolean; @@ -89,8 +90,11 @@ const CURSOR_MODEL_CAPABILITY_BY_FAMILY: Record> = { - claudeCode: new Set(MODEL_OPTIONS_BY_PROVIDER.claudeCode.map((option) => option.slug)), +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)), }; @@ -107,7 +111,7 @@ export interface CursorModelSelection { } export function getModelOptions(provider: ProviderKind = "codex") { - return MODEL_OPTIONS_BY_PROVIDER[provider]; + return hasModelCatalog(provider) ? MODEL_OPTIONS_BY_PROVIDER[provider] : []; } export function getCursorModelFamilyOptions() { @@ -223,7 +227,7 @@ export function resolveCursorModelFromSelection(input: { } export function getDefaultModel(provider: ProviderKind = "codex"): ModelSlug { - return DEFAULT_MODEL_BY_PROVIDER[provider]; + return hasModelCatalog(provider) ? DEFAULT_MODEL_BY_PROVIDER[provider] : DEFAULT_MODEL; } export function normalizeModelSlug( @@ -239,7 +243,12 @@ export function normalizeModelSlug( return null; } - return MODEL_SLUG_ALIASES_BY_PROVIDER[provider][trimmed] ?? (trimmed as ModelSlug); + if (!hasModelCatalog(provider)) { + return trimmed as ModelSlug; + } + + const aliases = MODEL_SLUG_ALIASES_BY_PROVIDER[provider] as Record; + return aliases[trimmed] ?? (trimmed as ModelSlug); } export function resolveModelSlug( @@ -248,12 +257,14 @@ export function resolveModelSlug( ): ModelSlug { const normalized = normalizeModelSlug(model, provider); if (!normalized) { - return DEFAULT_MODEL_BY_PROVIDER[provider]; + return getDefaultModel(provider); } - return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized) - ? normalized - : DEFAULT_MODEL_BY_PROVIDER[provider]; + if (!hasModelCatalog(provider)) { + return getDefaultModel(provider); + } + + return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized) ? normalized : getDefaultModel(provider); } export function resolveModelSlugForProvider( @@ -266,7 +277,7 @@ export function resolveModelSlugForProvider( export function getReasoningEffortOptions( provider: ProviderKind = "codex", ): ReadonlyArray { - return REASONING_EFFORT_OPTIONS_BY_PROVIDER[provider]; + return provider === "codex" ? CODEX_REASONING_EFFORT_OPTIONS : []; } export function getDefaultReasoningEffort(provider: "codex"): CodexReasoningEffort; @@ -274,7 +285,7 @@ export function getDefaultReasoningEffort(provider: ProviderKind): CodexReasonin export function getDefaultReasoningEffort( provider: ProviderKind = "codex", ): CodexReasoningEffort | null { - return DEFAULT_REASONING_EFFORT_BY_PROVIDER[provider]; + return provider === "codex" ? "high" : null; } export { CODEX_REASONING_EFFORT_OPTIONS };