feat: default Anthropic to Opus 4.7

This commit is contained in:
Peter Steinberger
2026-04-16 16:10:47 +01:00
parent 75c551e89e
commit 628b454eff
12 changed files with 108 additions and 28 deletions

View File

@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
### Changes
- Google/TTS: add Gemini text-to-speech support to the bundled `google` plugin, including provider registration, voice selection, WAV reply output, PCM telephony output, and setup/docs guidance. (#67515) Thanks @barronlroth.
- Anthropic/models: default Anthropic selections, `opus` aliases, Claude CLI defaults, and bundled image understanding to Claude Opus 4.7.
### Fixes

View File

@@ -101,11 +101,11 @@ describe("anthropic cli migration", () => {
agents: {
defaults: {
model: {
primary: "anthropic/claude-sonnet-4-6",
primary: "anthropic/claude-opus-4-7",
fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.2"],
},
models: {
"anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
"anthropic/claude-opus-4-7": { alias: "Opus" },
"anthropic/claude-opus-4-6": { alias: "Opus" },
"openai/gpt-5.2": {},
},
@@ -114,16 +114,17 @@ describe("anthropic cli migration", () => {
});
expect(result.profiles).toEqual([]);
expect(result.defaultModel).toBe("claude-cli/claude-sonnet-4-6");
expect(result.defaultModel).toBe("claude-cli/claude-opus-4-7");
expect(result.configPatch).toEqual({
agents: {
defaults: {
model: {
primary: "claude-cli/claude-sonnet-4-6",
primary: "claude-cli/claude-opus-4-7",
fallbacks: ["claude-cli/claude-opus-4-6", "openai/gpt-5.2"],
},
models: {
"claude-cli/claude-sonnet-4-6": { alias: "Sonnet" },
"claude-cli/claude-opus-4-7": { alias: "Opus" },
"claude-cli/claude-sonnet-4-6": {},
"claude-cli/claude-opus-4-6": { alias: "Opus" },
"claude-cli/claude-opus-4-5": {},
"claude-cli/claude-sonnet-4-5": {},
@@ -147,12 +148,13 @@ describe("anthropic cli migration", () => {
},
});
expect(result.defaultModel).toBe("claude-cli/claude-sonnet-4-6");
expect(result.defaultModel).toBe("claude-cli/claude-opus-4-7");
expect(result.configPatch).toEqual({
agents: {
defaults: {
models: {
"openai/gpt-5.2": {},
"claude-cli/claude-opus-4-7": {},
"claude-cli/claude-sonnet-4-6": {},
"claude-cli/claude-opus-4-6": {},
"claude-cli/claude-opus-4-5": {},
@@ -168,9 +170,9 @@ describe("anthropic cli migration", () => {
const result = buildAnthropicCliMigrationResult({
agents: {
defaults: {
model: { primary: "claude-cli/claude-sonnet-4-6" },
model: { primary: "claude-cli/claude-opus-4-7" },
models: {
"claude-cli/claude-sonnet-4-6": {},
"claude-cli/claude-opus-4-7": {},
},
},
},
@@ -180,6 +182,7 @@ describe("anthropic cli migration", () => {
agents: {
defaults: {
models: {
"claude-cli/claude-opus-4-7": {},
"claude-cli/claude-sonnet-4-6": {},
"claude-cli/claude-opus-4-6": {},
"claude-cli/claude-opus-4-5": {},
@@ -217,11 +220,11 @@ describe("anthropic cli migration", () => {
agents: {
defaults: {
model: {
primary: "anthropic/claude-sonnet-4-6",
primary: "anthropic/claude-opus-4-7",
fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.2"],
},
models: {
"anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
"anthropic/claude-opus-4-7": { alias: "Opus" },
"anthropic/claude-opus-4-6": { alias: "Opus" },
"openai/gpt-5.2": {},
},
@@ -297,11 +300,11 @@ describe("anthropic cli migration", () => {
agents: {
defaults: {
model: {
primary: "anthropic/claude-sonnet-4-6",
primary: "anthropic/claude-opus-4-7",
fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.2"],
},
models: {
"anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
"anthropic/claude-opus-4-7": { alias: "Opus" },
"anthropic/claude-opus-4-6": { alias: "Opus" },
"openai/gpt-5.2": {},
},
@@ -315,11 +318,11 @@ describe("anthropic cli migration", () => {
agents: {
defaults: {
model: {
primary: "claude-cli/claude-sonnet-4-6",
primary: "claude-cli/claude-opus-4-7",
fallbacks: ["claude-cli/claude-opus-4-6", "openai/gpt-5.2"],
},
models: {
"claude-cli/claude-sonnet-4-6": { alias: "Sonnet" },
"claude-cli/claude-opus-4-7": { alias: "Opus" },
"claude-cli/claude-opus-4-6": { alias: "Opus" },
"openai/gpt-5.2": {},
},

View File

@@ -2,9 +2,10 @@ import type { CliBackendConfig } from "openclaw/plugin-sdk/cli-backend";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
export const CLAUDE_CLI_BACKEND_ID = "claude-cli";
export const CLAUDE_CLI_DEFAULT_MODEL_REF = `${CLAUDE_CLI_BACKEND_ID}/claude-sonnet-4-6`;
export const CLAUDE_CLI_DEFAULT_MODEL_REF = `${CLAUDE_CLI_BACKEND_ID}/claude-opus-4-7`;
export const CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS = [
CLAUDE_CLI_DEFAULT_MODEL_REF,
`${CLAUDE_CLI_BACKEND_ID}/claude-sonnet-4-6`,
`${CLAUDE_CLI_BACKEND_ID}/claude-opus-4-6`,
`${CLAUDE_CLI_BACKEND_ID}/claude-opus-4-5`,
`${CLAUDE_CLI_BACKEND_ID}/claude-sonnet-4-5`,
@@ -13,9 +14,11 @@ export const CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS = [
export const CLAUDE_CLI_MODEL_ALIASES: Record<string, string> = {
opus: "opus",
"opus-4.7": "opus",
"opus-4.6": "opus",
"opus-4.5": "opus",
"opus-4": "opus",
"claude-opus-4-7": "opus",
"claude-opus-4-6": "opus",
"claude-opus-4-5": "opus",
"claude-opus-4": "opus",

View File

@@ -87,7 +87,7 @@ function resolveAnthropicPrimaryModelRef(raw?: string): string | null {
}
const aliasKey = normalizeLowercaseStringOrEmpty(trimmed);
if (aliasKey === "opus") {
return "anthropic/claude-opus-4-6";
return "anthropic/claude-opus-4-7";
}
if (aliasKey === "sonnet") {
return "anthropic/claude-sonnet-4-6";

View File

@@ -1,3 +1,7 @@
import type {
ProviderResolveDynamicModelContext,
ProviderRuntimeModel,
} from "openclaw/plugin-sdk/plugin-entry";
import { capturePluginRegistration } from "openclaw/plugin-sdk/testing";
import { describe, expect, it, vi } from "vitest";
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
@@ -18,6 +22,19 @@ vi.mock("./cli-auth-seam.js", () => {
import anthropicPlugin from "./index.js";
function createModelRegistry(models: ProviderRuntimeModel[]) {
return {
find(providerId: string, modelId: string) {
return (
models.find(
(model) =>
model.provider === providerId && model.id.toLowerCase() === modelId.toLowerCase(),
) ?? null
);
},
};
}
describe("anthropic provider replay hooks", () => {
it("registers the claude-cli backend", async () => {
const captured = capturePluginRegistration({ register: anthropicPlugin.register });
@@ -129,9 +146,9 @@ describe("anthropic provider replay hooks", () => {
},
agents: {
defaults: {
model: { primary: "claude-cli/claude-sonnet-4-6" },
model: { primary: "claude-cli/claude-opus-4-7" },
models: {
"claude-cli/claude-sonnet-4-6": {},
"claude-cli/claude-opus-4-7": {},
},
},
},
@@ -142,6 +159,7 @@ describe("anthropic provider replay hooks", () => {
every: "1h",
});
expect(next?.agents?.defaults?.models).toMatchObject({
"claude-cli/claude-opus-4-7": {},
"claude-cli/claude-sonnet-4-6": {},
"claude-cli/claude-opus-4-6": {},
"claude-cli/claude-opus-4-5": {},
@@ -150,6 +168,40 @@ describe("anthropic provider replay hooks", () => {
});
});
it("resolves explicit claude-opus-4-7 refs from the 4.6 template family", async () => {
const provider = await registerSingleProviderPlugin(anthropicPlugin);
const resolved = provider.resolveDynamicModel?.({
provider: "anthropic",
modelId: "claude-opus-4-7",
modelRegistry: createModelRegistry([
{
id: "claude-opus-4-6",
name: "Claude Opus 4.6",
provider: "anthropic",
api: "anthropic-messages",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200_000,
maxTokens: 32_000,
} as ProviderRuntimeModel,
]),
} as ProviderResolveDynamicModelContext);
expect(resolved).toMatchObject({
provider: "anthropic",
id: "claude-opus-4-7",
api: "anthropic-messages",
reasoning: true,
});
expect(
provider.resolveDefaultThinkingLevel?.({
provider: "anthropic",
modelId: "claude-opus-4-7",
} as never),
).toBe("adaptive");
});
it("resolves claude-cli synthetic oauth auth", async () => {
readClaudeCliCredentialsForRuntimeMock.mockReset();
readClaudeCliCredentialsForRuntimeMock.mockReturnValue({

View File

@@ -7,7 +7,7 @@ import {
export const anthropicMediaUnderstandingProvider: MediaUnderstandingProvider = {
id: "anthropic",
capabilities: ["image"],
defaultModels: { image: "claude-opus-4-6" },
defaultModels: { image: "claude-opus-4-7" },
autoPriority: { image: 20 },
nativeDocumentInputs: ["pdf"],
describeImage: describeImageWithModel,

View File

@@ -38,14 +38,23 @@ import { buildAnthropicReplayPolicy } from "./replay-policy.js";
import { wrapAnthropicProviderStream } from "./stream-wrappers.js";
const PROVIDER_ID = "anthropic";
const DEFAULT_ANTHROPIC_MODEL = "anthropic/claude-sonnet-4-6";
const DEFAULT_ANTHROPIC_MODEL = "anthropic/claude-opus-4-7";
const ANTHROPIC_OPUS_47_MODEL_ID = "claude-opus-4-7";
const ANTHROPIC_OPUS_47_DOT_MODEL_ID = "claude-opus-4.7";
const ANTHROPIC_OPUS_46_MODEL_ID = "claude-opus-4-6";
const ANTHROPIC_OPUS_46_DOT_MODEL_ID = "claude-opus-4.6";
const ANTHROPIC_OPUS_47_TEMPLATE_MODEL_IDS = [
ANTHROPIC_OPUS_46_MODEL_ID,
ANTHROPIC_OPUS_46_DOT_MODEL_ID,
"claude-opus-4-5",
"claude-opus-4.5",
] as const;
const ANTHROPIC_OPUS_TEMPLATE_MODEL_IDS = ["claude-opus-4-5", "claude-opus-4.5"] as const;
const ANTHROPIC_SONNET_46_MODEL_ID = "claude-sonnet-4-6";
const ANTHROPIC_SONNET_46_DOT_MODEL_ID = "claude-sonnet-4.6";
const ANTHROPIC_SONNET_TEMPLATE_MODEL_IDS = ["claude-sonnet-4-5", "claude-sonnet-4.5"] as const;
const ANTHROPIC_MODERN_MODEL_PREFIXES = [
"claude-opus-4-7",
"claude-opus-4-6",
"claude-sonnet-4-6",
"claude-opus-4-5",
@@ -221,6 +230,14 @@ function resolveAnthropicForwardCompatModel(
ctx: ProviderResolveDynamicModelContext,
): ProviderRuntimeModel | undefined {
return (
resolveAnthropic46ForwardCompatModel({
ctx,
dashModelId: ANTHROPIC_OPUS_47_MODEL_ID,
dotModelId: ANTHROPIC_OPUS_47_DOT_MODEL_ID,
dashTemplateId: ANTHROPIC_OPUS_46_MODEL_ID,
dotTemplateId: ANTHROPIC_OPUS_46_DOT_MODEL_ID,
fallbackTemplateIds: ANTHROPIC_OPUS_47_TEMPLATE_MODEL_IDS,
}) ??
resolveAnthropic46ForwardCompatModel({
ctx,
dashModelId: ANTHROPIC_OPUS_46_MODEL_ID,
@@ -243,6 +260,8 @@ function resolveAnthropicForwardCompatModel(
function shouldUseAnthropicAdaptiveThinkingDefault(modelId: string): boolean {
const lowerModelId = normalizeLowercaseStringOrEmpty(modelId);
return (
lowerModelId.startsWith(ANTHROPIC_OPUS_47_MODEL_ID) ||
lowerModelId.startsWith(ANTHROPIC_OPUS_47_DOT_MODEL_ID) ||
lowerModelId.startsWith(ANTHROPIC_OPUS_46_MODEL_ID) ||
lowerModelId.startsWith(ANTHROPIC_OPUS_46_DOT_MODEL_ID) ||
lowerModelId.startsWith(ANTHROPIC_SONNET_46_MODEL_ID) ||
@@ -372,7 +391,7 @@ async function runAnthropicCliMigrationNonInteractive(ctx: {
export function registerAnthropicPlugin(api: OpenClawPluginApi): void {
const providerId = "anthropic";
const defaultAnthropicModel = "anthropic/claude-sonnet-4-6";
const defaultAnthropicModel = DEFAULT_ANTHROPIC_MODEL;
api.registerCliBackend(buildAnthropicCliBackend());
api.registerProvider({
id: providerId,

View File

@@ -8,6 +8,7 @@ export type ModelRef = {
};
const HIGH_SIGNAL_LIVE_MODEL_PRIORITY = [
"anthropic/claude-opus-4-7",
"anthropic/claude-opus-4-6",
"anthropic/claude-sonnet-4-6",
"google/gemini-3.1-pro-preview",

View File

@@ -485,6 +485,7 @@ describe("isHighSignalLiveModelRef", () => {
describe("selectHighSignalLiveItems", () => {
it("prefers curated Google replacements before fallback provider spread", () => {
const items = [
{ provider: "anthropic", id: "claude-opus-4-7" },
{ provider: "anthropic", id: "claude-opus-4-6" },
{ provider: "google", id: "gemini-3.1-pro-preview" },
{ provider: "google", id: "gemini-3-flash-preview" },
@@ -500,10 +501,10 @@ describe("selectHighSignalLiveItems", () => {
(item) => item.provider,
),
).toEqual([
{ provider: "anthropic", id: "claude-opus-4-7" },
{ provider: "anthropic", id: "claude-opus-4-6" },
{ provider: "google", id: "gemini-3.1-pro-preview" },
{ provider: "google", id: "gemini-3-flash-preview" },
{ provider: "openai", id: "gpt-5.2" },
]);
});
});

View File

@@ -15,7 +15,7 @@ let defaultWarnState: WarnState = { warned: false };
const DEFAULT_MODEL_ALIASES: Readonly<Record<string, string>> = {
// Anthropic (pi-ai catalog uses "latest" ids without date suffix)
opus: "anthropic/claude-opus-4-6",
opus: "anthropic/claude-opus-4-7",
sonnet: "anthropic/claude-sonnet-4-6",
// OpenAI

View File

@@ -63,7 +63,7 @@ describe("applyModelDefaults", () => {
agents: {
defaults: {
models: {
"anthropic/claude-opus-4-6": {},
"anthropic/claude-opus-4-7": {},
"openai/gpt-5.4": {},
},
},
@@ -71,7 +71,7 @@ describe("applyModelDefaults", () => {
} satisfies OpenClawConfig;
const next = applyModelDefaults(cfg);
expect(next.agents?.defaults?.models?.["anthropic/claude-opus-4-6"]?.alias).toBe("opus");
expect(next.agents?.defaults?.models?.["anthropic/claude-opus-4-7"]?.alias).toBe("opus");
expect(next.agents?.defaults?.models?.["openai/gpt-5.4"]?.alias).toBe("gpt");
});
@@ -80,7 +80,7 @@ describe("applyModelDefaults", () => {
agents: {
defaults: {
models: {
"anthropic/claude-opus-4-6": { alias: "Opus" },
"anthropic/claude-opus-4-7": { alias: "Opus" },
},
},
},
@@ -88,7 +88,7 @@ describe("applyModelDefaults", () => {
const next = applyModelDefaults(cfg);
expect(next.agents?.defaults?.models?.["anthropic/claude-opus-4-6"]?.alias).toBe("Opus");
expect(next.agents?.defaults?.models?.["anthropic/claude-opus-4-7"]?.alias).toBe("Opus");
});
it("respects explicit empty alias disables", () => {

View File

@@ -16,7 +16,7 @@ const BUNDLED_MEDIA_PROVIDER_DEFAULTS: Record<string, BundledMediaProviderDefaul
defaultModels: { image: "gpt-5.4" },
},
anthropic: {
defaultModels: { image: "claude-opus-4-6" },
defaultModels: { image: "claude-opus-4-7" },
autoPriority: { image: 20 },
nativeDocumentInputs: ["pdf"],
},