Skip to content

Commit 15809ee

Browse files
juliusmarmingecodex
andcommitted
model: restore Claude catalogs on sibling stack
Co-authored-by: codex <[email protected]>
1 parent 0f53638 commit 15809ee

File tree

3 files changed

+75
-39
lines changed

3 files changed

+75
-39
lines changed

packages/contracts/src/model.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Schema } from "effect";
2+
import type { ProviderKind } from "./orchestration";
23

34
export const CURSOR_REASONING_OPTIONS = ["low", "normal", "high", "xhigh"] as const;
45
export type CursorReasoningOption = (typeof CURSOR_REASONING_OPTIONS)[number];
@@ -63,6 +64,11 @@ export const MODEL_OPTIONS_BY_PROVIDER = {
6364
{ slug: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
6465
{ slug: "gpt-5.2", name: "GPT-5.2" },
6566
],
67+
claudeCode: [
68+
{ slug: "claude-opus-4-6", name: "Claude Opus 4.6" },
69+
{ slug: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
70+
{ slug: "claude-haiku-4-5", name: "Claude Haiku 4.5" },
71+
],
6672
cursor: [
6773
{ slug: "auto", name: "Auto" },
6874
{ slug: "composer-1.5", name: "Composer 1.5" },
@@ -84,30 +90,44 @@ export const MODEL_OPTIONS_BY_PROVIDER = {
8490
{ slug: "sonnet-4.6-thinking", name: "Claude 4.6 Sonnet (Thinking)" },
8591
{ slug: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
8692
],
87-
} as const satisfies Record<"codex" | "cursor", readonly ModelOption[]>;
93+
} as const satisfies Record<ProviderKind, readonly ModelOption[]>;
8894

89-
type BuiltInModelSlug =
90-
(typeof MODEL_OPTIONS_BY_PROVIDER)[keyof typeof MODEL_OPTIONS_BY_PROVIDER][number]["slug"];
95+
type BuiltInModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)[ProviderKind][number]["slug"];
9196
export type ModelSlug = BuiltInModelSlug | (string & {});
9297
export type CursorModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)["cursor"][number]["slug"];
9398

94-
export const DEFAULT_MODEL_BY_PROVIDER = {
99+
export const DEFAULT_MODEL_BY_PROVIDER: Record<ProviderKind, ModelSlug> = {
95100
codex: "gpt-5.3-codex",
101+
claudeCode: "claude-sonnet-4-6",
96102
cursor: "opus-4.6-thinking",
97-
} as const satisfies Record<keyof typeof MODEL_OPTIONS_BY_PROVIDER, ModelSlug>;
103+
};
98104

99105
// Backward compatibility for existing Codex-only call sites.
100106
export const MODEL_OPTIONS = MODEL_OPTIONS_BY_PROVIDER.codex;
101107
export const DEFAULT_MODEL = DEFAULT_MODEL_BY_PROVIDER.codex;
102108

103-
export const MODEL_SLUG_ALIASES_BY_PROVIDER = {
109+
export const MODEL_SLUG_ALIASES_BY_PROVIDER: Record<ProviderKind, Record<string, ModelSlug>> = {
104110
codex: {
105111
"5.4": "gpt-5.4",
106112
"5.3": "gpt-5.3-codex",
107113
"gpt-5.3": "gpt-5.3-codex",
108114
"5.3-spark": "gpt-5.3-codex-spark",
109115
"gpt-5.3-spark": "gpt-5.3-codex-spark",
110116
},
117+
claudeCode: {
118+
opus: "claude-opus-4-6",
119+
"opus-4.6": "claude-opus-4-6",
120+
"claude-opus-4.6": "claude-opus-4-6",
121+
"claude-opus-4-6-20251117": "claude-opus-4-6",
122+
sonnet: "claude-sonnet-4-6",
123+
"sonnet-4.6": "claude-sonnet-4-6",
124+
"claude-sonnet-4.6": "claude-sonnet-4-6",
125+
"claude-sonnet-4-6-20251117": "claude-sonnet-4-6",
126+
haiku: "claude-haiku-4-5",
127+
"haiku-4.5": "claude-haiku-4-5",
128+
"claude-haiku-4.5": "claude-haiku-4-5",
129+
"claude-haiku-4-5-20251001": "claude-haiku-4-5",
130+
},
111131
cursor: {
112132
composer: "composer-1.5",
113133
"composer-1.5": "composer-1.5",
@@ -122,17 +142,16 @@ export const MODEL_SLUG_ALIASES_BY_PROVIDER = {
122142
"opus-4.6-thinking": "opus-4.6-thinking",
123143
"opus-4.5-thinking": "opus-4.5-thinking",
124144
},
125-
} as const satisfies Record<keyof typeof MODEL_OPTIONS_BY_PROVIDER, Record<string, ModelSlug>>;
145+
};
126146

127147
export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = {
128148
codex: CODEX_REASONING_EFFORT_OPTIONS,
149+
claudeCode: [],
129150
cursor: [],
130-
} as const satisfies Record<
131-
keyof typeof MODEL_OPTIONS_BY_PROVIDER,
132-
readonly CodexReasoningEffort[]
133-
>;
151+
} as const satisfies Record<ProviderKind, readonly CodexReasoningEffort[]>;
134152

135153
export const DEFAULT_REASONING_EFFORT_BY_PROVIDER = {
136154
codex: "high",
155+
claudeCode: null,
137156
cursor: null,
138-
} as const satisfies Record<keyof typeof MODEL_OPTIONS_BY_PROVIDER, CodexReasoningEffort | null>;
157+
} as const satisfies Record<ProviderKind, CodexReasoningEffort | null>;

packages/shared/src/model.test.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
CURSOR_REASONING_OPTIONS,
55
DEFAULT_MODEL,
66
DEFAULT_MODEL_BY_PROVIDER,
7+
DEFAULT_REASONING_EFFORT_BY_PROVIDER,
78
MODEL_OPTIONS,
89
MODEL_OPTIONS_BY_PROVIDER,
10+
REASONING_EFFORT_OPTIONS_BY_PROVIDER,
911
} from "../../contracts/src";
1012

1113
import {
@@ -40,6 +42,11 @@ describe("normalizeModelSlug", () => {
4042
});
4143

4244
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+
);
4350
expect(normalizeModelSlug("composer", "cursor")).toBe("composer-1.5");
4451
expect(normalizeModelSlug("gpt-5.3-codex-spark", "cursor")).toBe(
4552
"gpt-5.3-codex-spark-preview",
@@ -69,6 +76,13 @@ describe("resolveModelSlug", () => {
6976
});
7077

7178
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+
);
7286
expect(resolveModelSlugForProvider("cursor", undefined)).toBe(
7387
DEFAULT_MODEL_BY_PROVIDER.cursor,
7488
);
@@ -84,6 +98,7 @@ describe("resolveModelSlug", () => {
8498
it("keeps codex defaults for backward compatibility", () => {
8599
expect(getDefaultModel()).toBe(DEFAULT_MODEL);
86100
expect(getModelOptions()).toEqual(MODEL_OPTIONS);
101+
expect(getModelOptions("claudeCode")).toEqual(MODEL_OPTIONS_BY_PROVIDER.claudeCode);
87102
expect(getModelOptions("cursor")).toEqual(MODEL_OPTIONS_BY_PROVIDER.cursor);
88103
expect(getCursorModelFamilyOptions()).toEqual(CURSOR_MODEL_FAMILY_OPTIONS);
89104
});
@@ -136,7 +151,13 @@ describe("cursor model selection", () => {
136151

137152
describe("getReasoningEffortOptions", () => {
138153
it("returns codex reasoning options for codex", () => {
139-
expect(getReasoningEffortOptions("codex")).toEqual(["xhigh", "high", "medium", "low"]);
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([]);
140161
});
141162

142163
it("returns no reasoning options for cursor", () => {
@@ -146,7 +167,14 @@ describe("getReasoningEffortOptions", () => {
146167

147168
describe("getDefaultReasoningEffort", () => {
148169
it("returns provider-scoped defaults", () => {
149-
expect(getDefaultReasoningEffort("codex")).toBe("high");
150-
expect(getDefaultReasoningEffort("cursor")).toBeNull();
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+
);
151179
});
152180
});

packages/shared/src/model.ts

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import {
22
CODEX_REASONING_EFFORT_OPTIONS,
33
CURSOR_MODEL_FAMILY_OPTIONS,
44
CURSOR_REASONING_OPTIONS,
5-
DEFAULT_MODEL,
65
DEFAULT_MODEL_BY_PROVIDER,
6+
DEFAULT_REASONING_EFFORT_BY_PROVIDER,
77
MODEL_OPTIONS_BY_PROVIDER,
88
MODEL_SLUG_ALIASES_BY_PROVIDER,
9+
REASONING_EFFORT_OPTIONS_BY_PROVIDER,
910
type CodexReasoningEffort,
1011
type CursorModelFamily,
1112
type CursorModelSlug,
@@ -14,8 +15,6 @@ import {
1415
type ProviderKind,
1516
} from "../../contracts/src";
1617

17-
type CatalogProvider = keyof typeof MODEL_OPTIONS_BY_PROVIDER;
18-
1918
type CursorModelCapability = {
2019
readonly supportsReasoning: boolean;
2120
readonly supportsFast: boolean;
@@ -90,11 +89,8 @@ const CURSOR_MODEL_CAPABILITY_BY_FAMILY: Record<CursorModelFamily, CursorModelCa
9089
},
9190
};
9291

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>> = {
92+
const MODEL_SLUG_SET_BY_PROVIDER: Record<ProviderKind, ReadonlySet<ModelSlug>> = {
93+
claudeCode: new Set(MODEL_OPTIONS_BY_PROVIDER.claudeCode.map((option) => option.slug)),
9894
codex: new Set(MODEL_OPTIONS_BY_PROVIDER.codex.map((option) => option.slug)),
9995
cursor: new Set(MODEL_OPTIONS_BY_PROVIDER.cursor.map((option) => option.slug)),
10096
};
@@ -111,7 +107,7 @@ export interface CursorModelSelection {
111107
}
112108

113109
export function getModelOptions(provider: ProviderKind = "codex") {
114-
return hasModelCatalog(provider) ? MODEL_OPTIONS_BY_PROVIDER[provider] : [];
110+
return MODEL_OPTIONS_BY_PROVIDER[provider];
115111
}
116112

117113
export function getCursorModelFamilyOptions() {
@@ -227,7 +223,7 @@ export function resolveCursorModelFromSelection(input: {
227223
}
228224

229225
export function getDefaultModel(provider: ProviderKind = "codex"): ModelSlug {
230-
return hasModelCatalog(provider) ? DEFAULT_MODEL_BY_PROVIDER[provider] : DEFAULT_MODEL;
226+
return DEFAULT_MODEL_BY_PROVIDER[provider];
231227
}
232228

233229
export function normalizeModelSlug(
@@ -243,12 +239,7 @@ export function normalizeModelSlug(
243239
return null;
244240
}
245241

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);
242+
return MODEL_SLUG_ALIASES_BY_PROVIDER[provider][trimmed] ?? (trimmed as ModelSlug);
252243
}
253244

254245
export function resolveModelSlug(
@@ -257,14 +248,12 @@ export function resolveModelSlug(
257248
): ModelSlug {
258249
const normalized = normalizeModelSlug(model, provider);
259250
if (!normalized) {
260-
return getDefaultModel(provider);
261-
}
262-
263-
if (!hasModelCatalog(provider)) {
264-
return getDefaultModel(provider);
251+
return DEFAULT_MODEL_BY_PROVIDER[provider];
265252
}
266253

267-
return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized) ? normalized : getDefaultModel(provider);
254+
return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized)
255+
? normalized
256+
: DEFAULT_MODEL_BY_PROVIDER[provider];
268257
}
269258

270259
export function resolveModelSlugForProvider(
@@ -277,15 +266,15 @@ export function resolveModelSlugForProvider(
277266
export function getReasoningEffortOptions(
278267
provider: ProviderKind = "codex",
279268
): ReadonlyArray<CodexReasoningEffort> {
280-
return provider === "codex" ? CODEX_REASONING_EFFORT_OPTIONS : [];
269+
return REASONING_EFFORT_OPTIONS_BY_PROVIDER[provider];
281270
}
282271

283272
export function getDefaultReasoningEffort(provider: "codex"): CodexReasoningEffort;
284273
export function getDefaultReasoningEffort(provider: ProviderKind): CodexReasoningEffort | null;
285274
export function getDefaultReasoningEffort(
286275
provider: ProviderKind = "codex",
287276
): CodexReasoningEffort | null {
288-
return provider === "codex" ? "high" : null;
277+
return DEFAULT_REASONING_EFFORT_BY_PROVIDER[provider];
289278
}
290279

291280
export { CODEX_REASONING_EFFORT_OPTIONS };

0 commit comments

Comments
 (0)