Skip to content

Commit 3c9368f

Browse files
thomaslittleclaudet3dotgg
authored andcommitted
Add Claude Opus 5 model (pingdotgg#4472)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Theo Browne <me@t3.gg>
1 parent 0d2809a commit 3c9368f

4 files changed

Lines changed: 119 additions & 8 deletions

File tree

apps/server/src/provider/Layers/ClaudeProvider.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const CLAUDE_PRESENTATION = {
5151
displayName: "Claude",
5252
showInteractionModeToggle: true,
5353
} as const;
54+
const MINIMUM_CLAUDE_OPUS_5_VERSION = "2.1.219";
5455
const MINIMUM_CLAUDE_FABLE_5_VERSION = "2.1.169";
5556
const MINIMUM_CLAUDE_OPUS_4_8_VERSION = "2.1.154";
5657
const MINIMUM_CLAUDE_OPUS_4_7_VERSION = "2.1.111";
@@ -87,6 +88,42 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
8788
],
8889
}),
8990
},
91+
{
92+
slug: "claude-opus-5",
93+
name: "Claude Opus 5",
94+
isCustom: false,
95+
capabilities: createModelCapabilities({
96+
optionDescriptors: [
97+
buildSelectOptionDescriptor({
98+
id: "effort",
99+
label: "Reasoning",
100+
options: [
101+
{ value: "low", label: "Low" },
102+
{ value: "medium", label: "Medium" },
103+
{ value: "high", label: "High", isDefault: true },
104+
{ value: "xhigh", label: "Extra High" },
105+
{ value: "max", label: "Max" },
106+
{ value: "ultracode", label: "Ultracode" },
107+
{ value: "ultrathink", label: "Ultrathink" },
108+
],
109+
promptInjectedValues: ["ultrathink"],
110+
}),
111+
buildBooleanOptionDescriptor({
112+
id: "fastMode",
113+
label: "Fast Mode",
114+
}),
115+
buildSelectOptionDescriptor({
116+
id: "contextWindow",
117+
label: "Context Window",
118+
// Claude Code selects the 1M variant explicitly (`claude-opus-5[1m]`).
119+
options: [
120+
{ value: "200k", label: "200k" },
121+
{ value: "1m", label: "1M", isDefault: true },
122+
],
123+
}),
124+
],
125+
}),
126+
},
90127
{
91128
slug: "claude-opus-4-8",
92129
name: "Claude Opus 4.8",
@@ -272,6 +309,10 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
272309
},
273310
];
274311

312+
function supportsClaudeOpus5(version: string | null | undefined): boolean {
313+
return version ? compareSemverVersions(version, MINIMUM_CLAUDE_OPUS_5_VERSION) >= 0 : false;
314+
}
315+
275316
function supportsClaudeFable5(version: string | null | undefined): boolean {
276317
return version ? compareSemverVersions(version, MINIMUM_CLAUDE_FABLE_5_VERSION) >= 0 : false;
277318
}
@@ -288,6 +329,9 @@ function getBuiltInClaudeModelsForVersion(
288329
version: string | null | undefined,
289330
): ReadonlyArray<ServerProviderModel> {
290331
return BUILT_IN_MODELS.filter((model) => {
332+
if (model.slug === "claude-opus-5") {
333+
return supportsClaudeOpus5(version);
334+
}
291335
if (model.slug === "claude-fable-5") {
292336
return supportsClaudeFable5(version);
293337
}
@@ -301,6 +345,11 @@ function getBuiltInClaudeModelsForVersion(
301345
});
302346
}
303347

348+
function formatClaudeOpus5UpgradeMessage(version: string | null): string {
349+
const versionLabel = version ? `v${version}` : "the installed version";
350+
return `Claude Code ${versionLabel} is too old for Claude Opus 5. Upgrade to v${MINIMUM_CLAUDE_OPUS_5_VERSION} or newer to access it.`;
351+
}
352+
304353
function formatClaudeFable5UpgradeMessage(version: string | null): string {
305354
const versionLabel = version ? `v${version}` : "the installed version";
306355
return `Claude Code ${versionLabel} is too old for Claude Fable 5. Upgrade to v${MINIMUM_CLAUDE_FABLE_5_VERSION} or newer to access it.`;
@@ -360,6 +409,7 @@ export function normalizeClaudeCliEffort(
360409
if (
361410
effort === "xhigh" &&
362411
model !== "claude-fable-5" &&
412+
model !== "claude-opus-5" &&
363413
model !== "claude-opus-4-8" &&
364414
model !== "claude-sonnet-5"
365415
) {
@@ -837,13 +887,15 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
837887
claudeSettings.customModels,
838888
DEFAULT_CLAUDE_MODEL_CAPABILITIES,
839889
);
840-
const versionUpgradeMessage = supportsClaudeFable5(parsedVersion)
890+
const versionUpgradeMessage = supportsClaudeOpus5(parsedVersion)
841891
? undefined
842-
: supportsClaudeOpus48(parsedVersion)
843-
? formatClaudeFable5UpgradeMessage(parsedVersion)
844-
: supportsClaudeOpus47(parsedVersion)
845-
? formatClaudeOpus48UpgradeMessage(parsedVersion)
846-
: formatClaudeOpus47UpgradeMessage(parsedVersion);
892+
: supportsClaudeFable5(parsedVersion)
893+
? formatClaudeOpus5UpgradeMessage(parsedVersion)
894+
: supportsClaudeOpus48(parsedVersion)
895+
? formatClaudeFable5UpgradeMessage(parsedVersion)
896+
: supportsClaudeOpus47(parsedVersion)
897+
? formatClaudeOpus48UpgradeMessage(parsedVersion)
898+
: formatClaudeOpus47UpgradeMessage(parsedVersion);
847899

848900
const capabilities = resolveCapabilities
849901
? yield* resolveCapabilities(claudeSettings).pipe(Effect.orElseSucceed(() => undefined))

apps/server/src/provider/Layers/ProviderRegistry.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,6 +1842,62 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
18421842
),
18431843
);
18441844

1845+
it.effect("includes Claude Opus 5 on supported Claude Code versions", () =>
1846+
Effect.gen(function* () {
1847+
const status = yield* checkClaudeProviderStatus(
1848+
defaultClaudeSettings,
1849+
claudeCapabilities(),
1850+
);
1851+
const opus5 = status.models.find((model) => model.slug === "claude-opus-5");
1852+
assert.strictEqual(opus5?.name, "Claude Opus 5");
1853+
}).pipe(
1854+
Effect.provide(
1855+
mockSpawnerLayer((args) => {
1856+
const joined = args.join(" ");
1857+
if (joined === "--version") return { stdout: "2.1.219\n", stderr: "", code: 0 };
1858+
if (joined === "auth status")
1859+
return {
1860+
stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
1861+
stderr: "",
1862+
code: 0,
1863+
};
1864+
throw new Error(`Unexpected args: ${joined}`);
1865+
}),
1866+
),
1867+
),
1868+
);
1869+
1870+
it.effect("hides Claude Opus 5 on older Claude Code versions", () =>
1871+
Effect.gen(function* () {
1872+
const status = yield* checkClaudeProviderStatus(
1873+
defaultClaudeSettings,
1874+
claudeCapabilities(),
1875+
);
1876+
assert.strictEqual(
1877+
status.models.some((model) => model.slug === "claude-opus-5"),
1878+
false,
1879+
);
1880+
assert.strictEqual(
1881+
status.message,
1882+
"Claude Code v2.1.218 is too old for Claude Opus 5. Upgrade to v2.1.219 or newer to access it.",
1883+
);
1884+
}).pipe(
1885+
Effect.provide(
1886+
mockSpawnerLayer((args) => {
1887+
const joined = args.join(" ");
1888+
if (joined === "--version") return { stdout: "2.1.218\n", stderr: "", code: 0 };
1889+
if (joined === "auth status")
1890+
return {
1891+
stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
1892+
stderr: "",
1893+
code: 0,
1894+
};
1895+
throw new Error(`Unexpected args: ${joined}`);
1896+
}),
1897+
),
1898+
),
1899+
);
1900+
18451901
it.effect("includes Claude Fable 5 on supported Claude Code versions", () =>
18461902
Effect.gen(function* () {
18471903
const status = yield* checkClaudeProviderStatus(

packages/contracts/src/model.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,10 @@ export const MODEL_SLUG_ALIASES_BY_PROVIDER: Partial<
178178
"gpt-5.3-spark": "gpt-5.3-codex-spark",
179179
},
180180
[CLAUDE_DRIVER_KIND]: {
181-
opus: "claude-opus-4-8",
181+
opus: "claude-opus-5",
182+
"opus-5": "claude-opus-5",
183+
"claude-opus-5.0": "claude-opus-5",
184+
"claude-opus-5-0": "claude-opus-5",
182185
"opus-4.8": "claude-opus-4-8",
183186
"claude-opus-4.8": "claude-opus-4-8",
184187
"opus-4.7": "claude-opus-4-7",

packages/shared/src/model.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe("model slug normalization", () => {
3939
it("preserves exact custom slugs instead of expanding provider aliases", () => {
4040
const claude = ProviderDriverKind.make("claudeAgent");
4141

42-
expect(normalizeModelSlug("opus", claude)).toBe("claude-opus-4-8");
42+
expect(normalizeModelSlug("opus", claude)).toBe("claude-opus-5");
4343
expect(normalizeCustomModelSlug(" opus ")).toBe("opus");
4444
});
4545
});

0 commit comments

Comments
 (0)