Skip to content
1 change: 1 addition & 0 deletions apps/server/src/mcp/toolkits/subagent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ const buildModelSources = (runtime: SubagentRuntime) =>
driverKind: providerInstance.driverKind,
models: snapshot.models.map((providerModel) => ({
slug: providerModel.slug,
isCustom: providerModel.isCustom,
optionDescriptors: providerModel.capabilities?.optionDescriptors,
defaultOptions: buildProviderOptionSelectionsFromDescriptors(
providerModel.capabilities?.optionDescriptors,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/mcp/toolkits/thread/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ const makeActiveThreadStartRuntime = Effect.fn("ThreadToolkit.makeActiveRuntime"
driverKind: providerInstance.driverKind,
models: snapshot.models.map((providerModel) => ({
slug: providerModel.slug,
isCustom: providerModel.isCustom,
optionDescriptors: providerModel.capabilities?.optionDescriptors,
defaultOptions: buildProviderOptionSelectionsFromDescriptors(
providerModel.capabilities?.optionDescriptors,
Expand Down Expand Up @@ -728,6 +729,7 @@ const makeActiveThreadStartRuntime = Effect.fn("ThreadToolkit.makeActiveRuntime"
driverKind: providerInstance.driverKind,
models: snapshot.models.map((providerModel) => ({
slug: providerModel.slug,
isCustom: providerModel.isCustom,
optionDescriptors: providerModel.capabilities?.optionDescriptors,
defaultOptions: buildProviderOptionSelectionsFromDescriptors(
providerModel.capabilities?.optionDescriptors,
Expand Down
29 changes: 26 additions & 3 deletions apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,29 @@ describe("ClaudeAdapterLive", () => {
);
});

it.effect("preserves xhigh effort for Claude Opus 5", () => {
const harness = makeHarness();
return Effect.gen(function* () {
const adapter = yield* ClaudeAdapter;
yield* adapter.startSession({
threadId: THREAD_ID,
provider: ProviderDriverKind.make("claudeAgent"),
modelSelection: createModelSelection(
ProviderInstanceId.make("claudeAgent"),
"claude-opus-5",
[{ id: "effort", value: "xhigh" }],
),
runtimeMode: "full-access",
});

const createInput = harness.getLastCreateQueryInput();
assert.equal(createInput?.options.effort, "xhigh");
}).pipe(
Effect.provideService(Random.Random, makeDeterministicRandomService()),
Effect.provide(harness.layer),
);
});

it.effect("falls back to default effort when unsupported max is requested for Sonnet 4.6", () => {
const harness = makeHarness();
return Effect.gen(function* () {
Expand Down Expand Up @@ -1435,7 +1458,7 @@ describe("ClaudeAdapterLive", () => {
const adapter = yield* ClaudeAdapter;
const requestedModel = createModelSelection(
ProviderInstanceId.make("claudeAgent"),
"claude-fable-5",
"claude-opus-5",
);
const runtimeEventsFiber = yield* Stream.takeUntil(
adapter.streamEvents,
Expand Down Expand Up @@ -1615,7 +1638,7 @@ describe("ClaudeAdapterLive", () => {
parent_tool_use_id: null,
message: {
id: "assistant-message-effective-model-alias",
model: "claude-opus-4-8-20260718",
model: "claude-opus-5-20260718",
content: [{ type: "text", text: "Alias answer" }],
},
} as unknown as SDKMessage);
Expand All @@ -1628,7 +1651,7 @@ describe("ClaudeAdapterLive", () => {
session_id: "sdk-session-effective-model-alias",
uuid: "result-effective-model-alias",
modelUsage: {
"claude-opus-4-8-20260718": {
"claude-opus-5-20260718": {
contextWindow: 1_000_000,
maxOutputTokens: 128_000,
},
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ function selectedClaudeContextWindow(
modelSelection: ModelSelection | undefined,
): number | undefined {
switch (modelSelection?.model) {
case "claude-opus-5":
case "claude-opus-4-8":
case "claude-opus-4-7":
// Always 1M at the API; these models expose no contextWindow option.
Expand Down
105 changes: 57 additions & 48 deletions apps/server/src/provider/Layers/ClaudeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,33 @@ const CLAUDE_PRESENTATION = {
displayName: "Claude",
showInteractionModeToggle: true,
} as const;
const MINIMUM_CLAUDE_OPUS_5_VERSION = "2.1.219";
const MINIMUM_CLAUDE_FABLE_5_VERSION = "2.1.169";
const MINIMUM_CLAUDE_OPUS_4_8_VERSION = "2.1.154";
const MINIMUM_CLAUDE_OPUS_4_7_VERSION = "2.1.111";

const CLAUDE_OPUS_CAPABILITIES = createModelCapabilities({
optionDescriptors: [
buildSelectOptionDescriptor({
id: "effort",
label: "Reasoning",
options: [
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High", isDefault: true },
{ value: "xhigh", label: "Extra High" },
{ value: "max", label: "Max" },
{ value: "ultracode", label: "Ultracode" },
{ value: "ultrathink", label: "Ultrathink" },
],
promptInjectedValues: ["ultrathink"],
}),
buildBooleanOptionDescriptor({
id: "fastMode",
label: "Fast Mode",
}),
],
});

const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
{
slug: "claude-fable-5",
Expand Down Expand Up @@ -84,31 +107,10 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
}),
},
{
slug: "claude-opus-4-8",
name: "Claude Opus 4.8",
slug: "claude-opus-5",
name: "Claude Opus 5",
isCustom: false,
capabilities: createModelCapabilities({
optionDescriptors: [
buildSelectOptionDescriptor({
id: "effort",
label: "Reasoning",
options: [
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High", isDefault: true },
{ value: "xhigh", label: "Extra High" },
{ value: "max", label: "Max" },
{ value: "ultracode", label: "Ultracode" },
{ value: "ultrathink", label: "Ultrathink" },
],
promptInjectedValues: ["ultrathink"],
}),
buildBooleanOptionDescriptor({
id: "fastMode",
label: "Fast Mode",
}),
],
}),
capabilities: CLAUDE_OPUS_CAPABILITIES,
},
{
slug: "claude-opus-4-7",
Expand Down Expand Up @@ -268,12 +270,12 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
},
];

function supportsClaudeFable5(version: string | null | undefined): boolean {
return version ? compareSemverVersions(version, MINIMUM_CLAUDE_FABLE_5_VERSION) >= 0 : false;
function supportsClaudeOpus5(version: string | null | undefined): boolean {
return version ? compareSemverVersions(version, MINIMUM_CLAUDE_OPUS_5_VERSION) >= 0 : false;
}

function supportsClaudeOpus48(version: string | null | undefined): boolean {
return version ? compareSemverVersions(version, MINIMUM_CLAUDE_OPUS_4_8_VERSION) >= 0 : false;
function supportsClaudeFable5(version: string | null | undefined): boolean {
return version ? compareSemverVersions(version, MINIMUM_CLAUDE_FABLE_5_VERSION) >= 0 : false;
}

function supportsClaudeOpus47(version: string | null | undefined): boolean {
Expand All @@ -284,27 +286,27 @@ function getBuiltInClaudeModelsForVersion(
version: string | null | undefined,
): ReadonlyArray<ServerProviderModel> {
return BUILT_IN_MODELS.filter((model) => {
if (model.slug === "claude-opus-5") {
return supportsClaudeOpus5(version);
}
if (model.slug === "claude-fable-5") {
return supportsClaudeFable5(version);
}
if (model.slug === "claude-opus-4-8") {
return supportsClaudeOpus48(version);
}
if (model.slug === "claude-opus-4-7") {
return supportsClaudeOpus47(version);
}
return true;
});
}

function formatClaudeFable5UpgradeMessage(version: string | null): string {
function formatClaudeOpus5UpgradeMessage(version: string | null): string {
const versionLabel = version ? `v${version}` : "the installed version";
return `Claude Code ${versionLabel} is too old for Claude Fable 5. Upgrade to v${MINIMUM_CLAUDE_FABLE_5_VERSION} or newer to access it.`;
return `Claude Code ${versionLabel} is too old for Claude Opus 5. Upgrade to v${MINIMUM_CLAUDE_OPUS_5_VERSION} or newer to access it.`;
}

function formatClaudeOpus48UpgradeMessage(version: string | null): string {
function formatClaudeFable5UpgradeMessage(version: string | null): string {
const versionLabel = version ? `v${version}` : "the installed version";
return `Claude Code ${versionLabel} is too old for Claude Opus 4.8. Upgrade to v${MINIMUM_CLAUDE_OPUS_4_8_VERSION} or newer to access it.`;
return `Claude Code ${versionLabel} is too old for Claude Fable 5. Upgrade to v${MINIMUM_CLAUDE_FABLE_5_VERSION} or newer to access it.`;
}

function formatClaudeOpus47UpgradeMessage(version: string | null): string {
Expand All @@ -314,6 +316,11 @@ function formatClaudeOpus47UpgradeMessage(version: string | null): string {

export function getClaudeModelCapabilities(model: string | null | undefined): ModelCapabilities {
const slug = model?.trim();
// Persisted 4.8 selections remain runnable, but 4.8 is intentionally absent
// from the advertised model catalog now that Opus 5 replaces it.
if (slug === "claude-opus-4-8") {
return CLAUDE_OPUS_CAPABILITIES;
}
return (
BUILT_IN_MODELS.find((candidate) => candidate.slug === slug)?.capabilities ??
DEFAULT_CLAUDE_MODEL_CAPABILITIES
Expand Down Expand Up @@ -356,6 +363,7 @@ export function normalizeClaudeCliEffort(
if (
effort === "xhigh" &&
model !== "claude-fable-5" &&
model !== "claude-opus-5" &&
model !== "claude-opus-4-8" &&
model !== "claude-sonnet-5"
) {
Expand Down Expand Up @@ -704,7 +712,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
const resolvedEnvironment = environment ?? process.env;
const checkedAt = DateTime.formatIso(yield* DateTime.now);
const allModels = providerModelsFromSettings(
BUILT_IN_MODELS,
getBuiltInClaudeModelsForVersion(null),
Comment thread
wizzoapp[bot] marked this conversation as resolved.
claudeSettings.customModels,
DEFAULT_CLAUDE_MODEL_CAPABILITIES,
);
Expand Down Expand Up @@ -772,6 +780,11 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(

const version = versionProbe.success.value;
const parsedVersion = parseGenericCliVersion(`${version.stdout}\n${version.stderr}`);
const versionedModels = providerModelsFromSettings(
getBuiltInClaudeModelsForVersion(parsedVersion),
claudeSettings.customModels,
DEFAULT_CLAUDE_MODEL_CAPABILITIES,
);
if (version.code !== 0) {
yield* Effect.logWarning("Claude Agent CLI version probe exited with a non-zero status.", {
exitCode: version.code,
Expand All @@ -782,7 +795,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
presentation: CLAUDE_PRESENTATION,
enabled: claudeSettings.enabled,
checkedAt,
models: allModels,
models: versionedModels,
probe: {
installed: true,
version: parsedVersion,
Expand All @@ -793,17 +806,13 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
});
}

const models = providerModelsFromSettings(
getBuiltInClaudeModelsForVersion(parsedVersion),
claudeSettings.customModels,
DEFAULT_CLAUDE_MODEL_CAPABILITIES,
);
const versionUpgradeMessage = supportsClaudeFable5(parsedVersion)
const models = versionedModels;
const versionUpgradeMessage = supportsClaudeOpus5(parsedVersion)
? undefined
: supportsClaudeOpus48(parsedVersion)
? formatClaudeFable5UpgradeMessage(parsedVersion)
: supportsClaudeFable5(parsedVersion)
? formatClaudeOpus5UpgradeMessage(parsedVersion)
: supportsClaudeOpus47(parsedVersion)
? formatClaudeOpus48UpgradeMessage(parsedVersion)
? formatClaudeFable5UpgradeMessage(parsedVersion)
: formatClaudeOpus47UpgradeMessage(parsedVersion);

const capabilities = resolveCapabilities
Expand Down Expand Up @@ -862,7 +871,7 @@ export const makePendingClaudeProvider = (
Effect.gen(function* () {
const checkedAt = yield* nowIso;
const models = providerModelsFromSettings(
BUILT_IN_MODELS,
getBuiltInClaudeModelsForVersion(null),
claudeSettings.customModels,
DEFAULT_CLAUDE_MODEL_CAPABILITIES,
);
Expand Down
Loading
Loading