| author | |
| committer | |
| log | e972fad30075aeff9831f38052ef05badabd236f |
| tree | 08b832a973c333feff735bd29eda1456d272913a |
| parent | 1bd4d6d953a01232a049485612d40faa1a48afa8 |
Co-authored-by: codex <codex@users.noreply.github.com>3 files changed, 40 insertions(+), 75 deletions(-)
packages/contracts/src/model.ts+12-31| ... | ... | @@ -1,5 +1,4 @@ |
| 1 | 1 | import { Schema } from "effect"; |
| 2 | import type { ProviderKind } from "./orchestration"; | |
| 3 | 2 | |
| 4 | 3 | export const CURSOR_REASONING_OPTIONS = ["low", "normal", "high", "xhigh"] as const; |
| 5 | 4 | export type CursorReasoningOption = (typeof CURSOR_REASONING_OPTIONS)[number]; |
| ... | ... | @@ -58,11 +57,6 @@ export const MODEL_OPTIONS_BY_PROVIDER = { |
| 58 | 57 | { slug: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, |
| 59 | 58 | { slug: "gpt-5.2", name: "GPT-5.2" }, |
| 60 | 59 | ], |
| 61 | claudeCode: [ | |
| 62 | { slug: "claude-opus-4-6", name: "Claude Opus 4.6" }, | |
| 63 | { slug: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, | |
| 64 | { slug: "claude-haiku-4-5", name: "Claude Haiku 4.5" }, | |
| 65 | ], | |
| 66 | 60 | cursor: [ |
| 67 | 61 | { slug: "auto", name: "Auto" }, |
| 68 | 62 | { slug: "composer-1.5", name: "Composer 1.5" }, |
| ... | ... | @@ -84,23 +78,23 @@ export const MODEL_OPTIONS_BY_PROVIDER = { |
| 84 | 78 | { slug: "sonnet-4.6-thinking", name: "Claude 4.6 Sonnet (Thinking)" }, |
| 85 | 79 | { slug: "gemini-3.1-pro", name: "Gemini 3.1 Pro" }, |
| 86 | 80 | ], |
| 87 | } as const satisfies Record<ProviderKind, readonly ModelOption[]>; | |
| 81 | } as const satisfies Record<"codex" | "cursor", readonly ModelOption[]>; | |
| 88 | 82 | |
| 89 | type BuiltInModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)[ProviderKind][number]["slug"]; | |
| 83 | type BuiltInModelSlug = | |
| 84 | (typeof MODEL_OPTIONS_BY_PROVIDER)[keyof typeof MODEL_OPTIONS_BY_PROVIDER][number]["slug"]; | |
| 90 | 85 | export type ModelSlug = BuiltInModelSlug | (string & {}); |
| 91 | 86 | export type CursorModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)["cursor"][number]["slug"]; |
| 92 | 87 | |
| 93 | export const DEFAULT_MODEL_BY_PROVIDER: Record<ProviderKind, ModelSlug> = { | |
| 88 | export const DEFAULT_MODEL_BY_PROVIDER = { | |
| 94 | 89 | codex: "gpt-5.3-codex", |
| 95 | claudeCode: "claude-sonnet-4-6", | |
| 96 | 90 | cursor: "opus-4.6-thinking", |
| 97 | }; | |
| 91 | } as const satisfies Record<keyof typeof MODEL_OPTIONS_BY_PROVIDER, ModelSlug>; | |
| 98 | 92 | |
| 99 | 93 | // Backward compatibility for existing Codex-only call sites. |
| 100 | 94 | export const MODEL_OPTIONS = MODEL_OPTIONS_BY_PROVIDER.codex; |
| 101 | 95 | export const DEFAULT_MODEL = DEFAULT_MODEL_BY_PROVIDER.codex; |
| 102 | 96 | |
| 103 | export const MODEL_SLUG_ALIASES_BY_PROVIDER: Record<ProviderKind, Record<string, ModelSlug>> = { | |
| 97 | export const MODEL_SLUG_ALIASES_BY_PROVIDER = { | |
| 104 | 98 | codex: { |
| 105 | 99 | "5.4": "gpt-5.4", |
| 106 | 100 | "5.3": "gpt-5.3-codex", |
| ... | ... | @@ -108,20 +102,6 @@ export const MODEL_SLUG_ALIASES_BY_PROVIDER: Record<ProviderKind, Record<string, |
| 108 | 102 | "5.3-spark": "gpt-5.3-codex-spark", |
| 109 | 103 | "gpt-5.3-spark": "gpt-5.3-codex-spark", |
| 110 | 104 | }, |
| 111 | claudeCode: { | |
| 112 | opus: "claude-opus-4-6", | |
| 113 | "opus-4.6": "claude-opus-4-6", | |
| 114 | "claude-opus-4.6": "claude-opus-4-6", | |
| 115 | "claude-opus-4-6-20251117": "claude-opus-4-6", | |
| 116 | sonnet: "claude-sonnet-4-6", | |
| 117 | "sonnet-4.6": "claude-sonnet-4-6", | |
| 118 | "claude-sonnet-4.6": "claude-sonnet-4-6", | |
| 119 | "claude-sonnet-4-6-20251117": "claude-sonnet-4-6", | |
| 120 | haiku: "claude-haiku-4-5", | |
| 121 | "haiku-4.5": "claude-haiku-4-5", | |
| 122 | "claude-haiku-4.5": "claude-haiku-4-5", | |
| 123 | "claude-haiku-4-5-20251001": "claude-haiku-4-5", | |
| 124 | }, | |
| 125 | 105 | cursor: { |
| 126 | 106 | composer: "composer-1.5", |
| 127 | 107 | "composer-1.5": "composer-1.5", |
| ... | ... | @@ -136,16 +116,17 @@ export const MODEL_SLUG_ALIASES_BY_PROVIDER: Record<ProviderKind, Record<string, |
| 136 | 116 | "opus-4.6-thinking": "opus-4.6-thinking", |
| 137 | 117 | "opus-4.5-thinking": "opus-4.5-thinking", |
| 138 | 118 | }, |
| 139 | }; | |
| 119 | } as const satisfies Record<keyof typeof MODEL_OPTIONS_BY_PROVIDER, Record<string, ModelSlug>>; | |
| 140 | 120 | |
| 141 | 121 | export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = { |
| 142 | 122 | codex: CODEX_REASONING_EFFORT_OPTIONS, |
| 143 | claudeCode: [], | |
| 144 | 123 | cursor: [], |
| 145 | } as const satisfies Record<ProviderKind, readonly CodexReasoningEffort[]>; | |
| 124 | } as const satisfies Record< | |
| 125 | keyof typeof MODEL_OPTIONS_BY_PROVIDER, | |
| 126 | readonly CodexReasoningEffort[] | |
| 127 | >; | |
| 146 | 128 | |
| 147 | 129 | export const DEFAULT_REASONING_EFFORT_BY_PROVIDER = { |
| 148 | 130 | codex: "high", |
| 149 | claudeCode: null, | |
| 150 | 131 | cursor: null, |
| 151 | } as const satisfies Record<ProviderKind, CodexReasoningEffort | null>; | |
| 132 | } as const satisfies Record<keyof typeof MODEL_OPTIONS_BY_PROVIDER, CodexReasoningEffort | null>; |
packages/shared/src/model.test.ts+4-31| ... | ... | @@ -4,10 +4,8 @@ import { |
| 4 | 4 | CURSOR_REASONING_OPTIONS, |
| 5 | 5 | DEFAULT_MODEL, |
| 6 | 6 | DEFAULT_MODEL_BY_PROVIDER, |
| 7 | DEFAULT_REASONING_EFFORT_BY_PROVIDER, | |
| 8 | 7 | MODEL_OPTIONS, |
| 9 | 8 | MODEL_OPTIONS_BY_PROVIDER, |
| 10 | REASONING_EFFORT_OPTIONS_BY_PROVIDER, | |
| 11 | 9 | } from "../../contracts/src"; |
| 12 | 10 | |
| 13 | 11 | import { |
| ... | ... | @@ -42,11 +40,6 @@ describe("normalizeModelSlug", () => { |
| 42 | 40 | }); |
| 43 | 41 | |
| 44 | 42 | it("uses provider-specific aliases", () => { |
| 45 | expect(normalizeModelSlug("sonnet", "claudeCode")).toBe("claude-sonnet-4-6"); | |
| 46 | expect(normalizeModelSlug("opus-4.6", "claudeCode")).toBe("claude-opus-4-6"); | |
| 47 | expect(normalizeModelSlug("claude-haiku-4-5-20251001", "claudeCode")).toBe( | |
| 48 | "claude-haiku-4-5", | |
| 49 | ); | |
| 50 | 43 | expect(normalizeModelSlug("composer", "cursor")).toBe("composer-1.5"); |
| 51 | 44 | expect(normalizeModelSlug("gpt-5.3-codex-spark", "cursor")).toBe( |
| 52 | 45 | "gpt-5.3-codex-spark-preview", |
| ... | ... | @@ -76,13 +69,6 @@ describe("resolveModelSlug", () => { |
| 76 | 69 | }); |
| 77 | 70 | |
| 78 | 71 | it("supports provider-aware resolution", () => { |
| 79 | expect(resolveModelSlugForProvider("claudeCode", undefined)).toBe( | |
| 80 | DEFAULT_MODEL_BY_PROVIDER.claudeCode, | |
| 81 | ); | |
| 82 | expect(resolveModelSlugForProvider("claudeCode", "sonnet")).toBe("claude-sonnet-4-6"); | |
| 83 | expect(resolveModelSlugForProvider("claudeCode", "gpt-5.3-codex")).toBe( | |
| 84 | DEFAULT_MODEL_BY_PROVIDER.claudeCode, | |
| 85 | ); | |
| 86 | 72 | expect(resolveModelSlugForProvider("cursor", undefined)).toBe( |
| 87 | 73 | DEFAULT_MODEL_BY_PROVIDER.cursor, |
| 88 | 74 | ); |
| ... | ... | @@ -98,7 +84,7 @@ describe("resolveModelSlug", () => { |
| 98 | 84 | it("keeps codex defaults for backward compatibility", () => { |
| 99 | 85 | expect(getDefaultModel()).toBe(DEFAULT_MODEL); |
| 100 | 86 | expect(getModelOptions()).toEqual(MODEL_OPTIONS); |
| 101 | expect(getModelOptions("claudeCode")).toEqual(MODEL_OPTIONS_BY_PROVIDER.claudeCode); | |
| 87 | expect(getModelOptions("claudeCode")).toEqual([]); | |
| 102 | 88 | expect(getModelOptions("cursor")).toEqual(MODEL_OPTIONS_BY_PROVIDER.cursor); |
| 103 | 89 | expect(getCursorModelFamilyOptions()).toEqual(CURSOR_MODEL_FAMILY_OPTIONS); |
| 104 | 90 | }); |
| ... | ... | @@ -151,13 +137,7 @@ describe("cursor model selection", () => { |
| 151 | 137 | |
| 152 | 138 | describe("getReasoningEffortOptions", () => { |
| 153 | 139 | it("returns codex reasoning options for codex", () => { |
| 154 | expect(getReasoningEffortOptions("codex")).toEqual( | |
| 155 | REASONING_EFFORT_OPTIONS_BY_PROVIDER.codex, | |
| 156 | ); | |
| 157 | }); | |
| 158 | ||
| 159 | it("returns no reasoning options for claudeCode", () => { | |
| 160 | expect(getReasoningEffortOptions("claudeCode")).toEqual([]); | |
| 140 | expect(getReasoningEffortOptions("codex")).toEqual(["xhigh", "high", "medium", "low"]); | |
| 161 | 141 | }); |
| 162 | 142 | |
| 163 | 143 | it("returns no reasoning options for cursor", () => { |
| ... | ... | @@ -167,14 +147,7 @@ describe("getReasoningEffortOptions", () => { |
| 167 | 147 | |
| 168 | 148 | describe("getDefaultReasoningEffort", () => { |
| 169 | 149 | it("returns provider-scoped defaults", () => { |
| 170 | expect(getDefaultReasoningEffort("codex")).toBe( | |
| 171 | DEFAULT_REASONING_EFFORT_BY_PROVIDER.codex, | |
| 172 | ); | |
| 173 | expect(getDefaultReasoningEffort("claudeCode")).toBe( | |
| 174 | DEFAULT_REASONING_EFFORT_BY_PROVIDER.claudeCode, | |
| 175 | ); | |
| 176 | expect(getDefaultReasoningEffort("cursor")).toBe( | |
| 177 | DEFAULT_REASONING_EFFORT_BY_PROVIDER.cursor, | |
| 178 | ); | |
| 150 | expect(getDefaultReasoningEffort("codex")).toBe("high"); | |
| 151 | expect(getDefaultReasoningEffort("cursor")).toBeNull(); | |
| 179 | 152 | }); |
| 180 | 153 | }); |
packages/shared/src/model.ts+24-13| ... | ... | @@ -2,11 +2,10 @@ import { |
| 2 | 2 | CODEX_REASONING_EFFORT_OPTIONS, |
| 3 | 3 | CURSOR_MODEL_FAMILY_OPTIONS, |
| 4 | 4 | CURSOR_REASONING_OPTIONS, |
| 5 | DEFAULT_MODEL, | |
| 5 | 6 | DEFAULT_MODEL_BY_PROVIDER, |
| 6 | DEFAULT_REASONING_EFFORT_BY_PROVIDER, | |
| 7 | 7 | MODEL_OPTIONS_BY_PROVIDER, |
| 8 | 8 | MODEL_SLUG_ALIASES_BY_PROVIDER, |
| 9 | REASONING_EFFORT_OPTIONS_BY_PROVIDER, | |
| 10 | 9 | type CodexReasoningEffort, |
| 11 | 10 | type CursorModelFamily, |
| 12 | 11 | type CursorModelSlug, |
| ... | ... | @@ -15,6 +14,8 @@ import { |
| 15 | 14 | type ProviderKind, |
| 16 | 15 | } from "../../contracts/src"; |
| 17 | 16 | |
| 17 | type CatalogProvider = keyof typeof MODEL_OPTIONS_BY_PROVIDER; | |
| 18 | ||
| 18 | 19 | type CursorModelCapability = { |
| 19 | 20 | readonly supportsReasoning: boolean; |
| 20 | 21 | readonly supportsFast: boolean; |
| ... | ... | @@ -89,8 +90,11 @@ const CURSOR_MODEL_CAPABILITY_BY_FAMILY: Record<CursorModelFamily, CursorModelCa |
| 89 | 90 | }, |
| 90 | 91 | }; |
| 91 | 92 | |
| 92 | const MODEL_SLUG_SET_BY_PROVIDER: Record<ProviderKind, ReadonlySet<ModelSlug>> = { | |
| 93 | claudeCode: new Set(MODEL_OPTIONS_BY_PROVIDER.claudeCode.map((option) => option.slug)), | |
| 93 | function hasModelCatalog(provider: ProviderKind): provider is CatalogProvider { | |
| 94 | return Object.hasOwn(MODEL_OPTIONS_BY_PROVIDER, provider); | |
| 95 | } | |
| 96 | ||
| 97 | const MODEL_SLUG_SET_BY_PROVIDER: Record<CatalogProvider, ReadonlySet<ModelSlug>> = { | |
| 94 | 98 | codex: new Set(MODEL_OPTIONS_BY_PROVIDER.codex.map((option) => option.slug)), |
| 95 | 99 | cursor: new Set(MODEL_OPTIONS_BY_PROVIDER.cursor.map((option) => option.slug)), |
| 96 | 100 | }; |
| ... | ... | @@ -107,7 +111,7 @@ export interface CursorModelSelection { |
| 107 | 111 | } |
| 108 | 112 | |
| 109 | 113 | export function getModelOptions(provider: ProviderKind = "codex") { |
| 110 | return MODEL_OPTIONS_BY_PROVIDER[provider]; | |
| 114 | return hasModelCatalog(provider) ? MODEL_OPTIONS_BY_PROVIDER[provider] : []; | |
| 111 | 115 | } |
| 112 | 116 | |
| 113 | 117 | export function getCursorModelFamilyOptions() { |
| ... | ... | @@ -223,7 +227,7 @@ export function resolveCursorModelFromSelection(input: { |
| 223 | 227 | } |
| 224 | 228 | |
| 225 | 229 | export function getDefaultModel(provider: ProviderKind = "codex"): ModelSlug { |
| 226 | return DEFAULT_MODEL_BY_PROVIDER[provider]; | |
| 230 | return hasModelCatalog(provider) ? DEFAULT_MODEL_BY_PROVIDER[provider] : DEFAULT_MODEL; | |
| 227 | 231 | } |
| 228 | 232 | |
| 229 | 233 | export function normalizeModelSlug( |
| ... | ... | @@ -239,7 +243,12 @@ export function normalizeModelSlug( |
| 239 | 243 | return null; |
| 240 | 244 | } |
| 241 | 245 | |
| 242 | return MODEL_SLUG_ALIASES_BY_PROVIDER[provider][trimmed] ?? (trimmed as ModelSlug); | |
| 246 | if (!hasModelCatalog(provider)) { | |
| 247 | return trimmed as ModelSlug; | |
| 248 | } | |
| 249 | ||
| 250 | const aliases = MODEL_SLUG_ALIASES_BY_PROVIDER[provider] as Record<string, ModelSlug>; | |
| 251 | return aliases[trimmed] ?? (trimmed as ModelSlug); | |
| 243 | 252 | } |
| 244 | 253 | |
| 245 | 254 | export function resolveModelSlug( |
| ... | ... | @@ -248,12 +257,14 @@ export function resolveModelSlug( |
| 248 | 257 | ): ModelSlug { |
| 249 | 258 | const normalized = normalizeModelSlug(model, provider); |
| 250 | 259 | if (!normalized) { |
| 251 | return DEFAULT_MODEL_BY_PROVIDER[provider]; | |
| 260 | return getDefaultModel(provider); | |
| 261 | } | |
| 262 | ||
| 263 | if (!hasModelCatalog(provider)) { | |
| 264 | return getDefaultModel(provider); | |
| 252 | 265 | } |
| 253 | 266 | |
| 254 | return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized) | |
| 255 | ? normalized | |
| 256 | : DEFAULT_MODEL_BY_PROVIDER[provider]; | |
| 267 | return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized) ? normalized : getDefaultModel(provider); | |
| 257 | 268 | } |
| 258 | 269 | |
| 259 | 270 | export function resolveModelSlugForProvider( |
| ... | ... | @@ -266,7 +277,7 @@ export function resolveModelSlugForProvider( |
| 266 | 277 | export function getReasoningEffortOptions( |
| 267 | 278 | provider: ProviderKind = "codex", |
| 268 | 279 | ): ReadonlyArray<CodexReasoningEffort> { |
| 269 | return REASONING_EFFORT_OPTIONS_BY_PROVIDER[provider]; | |
| 280 | return provider === "codex" ? CODEX_REASONING_EFFORT_OPTIONS : []; | |
| 270 | 281 | } |
| 271 | 282 | |
| 272 | 283 | export function getDefaultReasoningEffort(provider: "codex"): CodexReasoningEffort; |
| ... | ... | @@ -274,7 +285,7 @@ export function getDefaultReasoningEffort(provider: ProviderKind): CodexReasonin |
| 274 | 285 | export function getDefaultReasoningEffort( |
| 275 | 286 | provider: ProviderKind = "codex", |
| 276 | 287 | ): CodexReasoningEffort | null { |
| 277 | return DEFAULT_REASONING_EFFORT_BY_PROVIDER[provider]; | |
| 288 | return provider === "codex" ? "high" : null; | |
| 278 | 289 | } |
| 279 | 290 | |
| 280 | 291 | export { CODEX_REASONING_EFFORT_OPTIONS }; |