From 628b454eff6ecc240398728dfed11af61b11e202 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 16 Apr 2026 16:10:47 +0100 Subject: [PATCH] feat: default Anthropic to Opus 4.7 --- CHANGELOG.md | 1 + extensions/anthropic/cli-migration.test.ts | 31 +++++----- extensions/anthropic/cli-shared.ts | 5 +- extensions/anthropic/config-defaults.ts | 2 +- extensions/anthropic/index.test.ts | 56 ++++++++++++++++++- .../anthropic/media-understanding-provider.ts | 2 +- extensions/anthropic/register.runtime.ts | 23 +++++++- src/agents/live-model-filter.ts | 1 + src/agents/model-compat.test.ts | 3 +- src/config/defaults.ts | 2 +- src/config/model-alias-defaults.test.ts | 8 +-- src/media-understanding/bundled-defaults.ts | 2 +- 12 files changed, 108 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de399d35098..1b0fc74c989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/extensions/anthropic/cli-migration.test.ts b/extensions/anthropic/cli-migration.test.ts index 1796c4b148e..d029e9425e1 100644 --- a/extensions/anthropic/cli-migration.test.ts +++ b/extensions/anthropic/cli-migration.test.ts @@ -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": {}, }, diff --git a/extensions/anthropic/cli-shared.ts b/extensions/anthropic/cli-shared.ts index 3d1e51f5e96..288f0be4ba3 100644 --- a/extensions/anthropic/cli-shared.ts +++ b/extensions/anthropic/cli-shared.ts @@ -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 = { 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", diff --git a/extensions/anthropic/config-defaults.ts b/extensions/anthropic/config-defaults.ts index 16c8ed83954..ddad9dd5829 100644 --- a/extensions/anthropic/config-defaults.ts +++ b/extensions/anthropic/config-defaults.ts @@ -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"; diff --git a/extensions/anthropic/index.test.ts b/extensions/anthropic/index.test.ts index d23f1b88e6b..446d69b2613 100644 --- a/extensions/anthropic/index.test.ts +++ b/extensions/anthropic/index.test.ts @@ -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({ diff --git a/extensions/anthropic/media-understanding-provider.ts b/extensions/anthropic/media-understanding-provider.ts index 34732e2c7e3..0eec5c14ecc 100644 --- a/extensions/anthropic/media-understanding-provider.ts +++ b/extensions/anthropic/media-understanding-provider.ts @@ -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, diff --git a/extensions/anthropic/register.runtime.ts b/extensions/anthropic/register.runtime.ts index 968f7f36aa2..46ebe93ae42 100644 --- a/extensions/anthropic/register.runtime.ts +++ b/extensions/anthropic/register.runtime.ts @@ -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, diff --git a/src/agents/live-model-filter.ts b/src/agents/live-model-filter.ts index 9665e3ab6d0..c44c8d89115 100644 --- a/src/agents/live-model-filter.ts +++ b/src/agents/live-model-filter.ts @@ -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", diff --git a/src/agents/model-compat.test.ts b/src/agents/model-compat.test.ts index a4371bbb4d8..e2aed179fbe 100644 --- a/src/agents/model-compat.test.ts +++ b/src/agents/model-compat.test.ts @@ -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" }, ]); }); }); diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 159502c54f2..e4908db5b5c 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -15,7 +15,7 @@ let defaultWarnState: WarnState = { warned: false }; const DEFAULT_MODEL_ALIASES: Readonly> = { // 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 diff --git a/src/config/model-alias-defaults.test.ts b/src/config/model-alias-defaults.test.ts index 666720a8638..4386163056e 100644 --- a/src/config/model-alias-defaults.test.ts +++ b/src/config/model-alias-defaults.test.ts @@ -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", () => { diff --git a/src/media-understanding/bundled-defaults.ts b/src/media-understanding/bundled-defaults.ts index edd5caf4c3e..0c3ad8e8935 100644 --- a/src/media-understanding/bundled-defaults.ts +++ b/src/media-understanding/bundled-defaults.ts @@ -16,7 +16,7 @@ const BUNDLED_MEDIA_PROVIDER_DEFAULTS: Record