feat(models): support anthropic sonnet 4.6

This commit is contained in:
Peter Steinberger
2026-02-18 00:00:20 +01:00
parent a333d92013
commit ae2c8f2cf0
12 changed files with 93 additions and 10 deletions

View File

@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
- Plugins: add `before_agent_start` model/provider overrides before resolution. (#18568) Thanks @natefikru.
- Memory/Search: add FTS fallback plus query expansion for memory search. (#18304) Thanks @irchelper.
- Agents/Models: support per-model `thinkingDefault` overrides in model config. (#18152) Thanks @wu-tian807.
- Agents/Models: support Anthropic Sonnet 4.6 (`anthropic/claude-sonnet-4-6`) across aliases/defaults with forward-compat fallback when upstream catalogs still only expose Sonnet 4.5.
- Agents: enable `llms.txt` discovery in default behavior. (#18158) Thanks @yolo-maxi.
- Feishu: add Bitable create-app/create-field tools for automation workflows. (#17963) Thanks @gaowanqi08141999.
- Cron/Gateway: separate per-job webhook delivery (`delivery.mode = "webhook"`) from announce delivery, enforce valid HTTP(S) webhook URLs, and keep a temporary legacy `notify + cron.webhook` fallback for stored jobs. (#17901) Thanks @advaitpaliwal.

View File

@@ -200,7 +200,7 @@ OPENCLAW_LIVE_SETUP_TOKEN=1 OPENCLAW_LIVE_SETUP_TOKEN_PROFILE=anthropic:setup-to
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
- `OPENCLAW_LIVE_CLI_BACKEND=1`
- Defaults:
- Model: `claude-cli/claude-sonnet-4-5`
- Model: `claude-cli/claude-sonnet-4-6`
- Command: `claude`
- Args: `["-p","--output-format","json","--dangerously-skip-permissions"]`
- Overrides (optional):
@@ -219,7 +219,7 @@ Example:
```bash
OPENCLAW_LIVE_CLI_BACKEND=1 \
OPENCLAW_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-sonnet-4-5" \
OPENCLAW_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-sonnet-4-6" \
pnpm test:live src/gateway/gateway-cli-backend.live.test.ts
```

View File

@@ -1,8 +1,8 @@
import { type Api, completeSimple, type Model } from "@mariozechner/pi-ai";
import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { type Api, completeSimple, type Model } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import {
ANTHROPIC_SETUP_TOKEN_PREFIX,
@@ -142,6 +142,7 @@ function pickModel(models: Array<Model<Api>>, raw?: string): Model<Api> | null {
const preferred = [
"claude-opus-4-5",
"claude-sonnet-4-6",
"claude-sonnet-4-5",
"claude-sonnet-4-0",
"claude-haiku-3-5",

View File

@@ -20,9 +20,11 @@ const CLAUDE_MODEL_ALIASES: Record<string, string> = {
"claude-opus-4-5": "opus",
"claude-opus-4": "opus",
sonnet: "sonnet",
"sonnet-4.6": "sonnet",
"sonnet-4.5": "sonnet",
"sonnet-4.1": "sonnet",
"sonnet-4.0": "sonnet",
"claude-sonnet-4-6": "sonnet",
"claude-sonnet-4-5": "sonnet",
"claude-sonnet-4-1": "sonnet",
"claude-sonnet-4-0": "sonnet",

View File

@@ -5,6 +5,7 @@ export type ModelRef = {
const ANTHROPIC_PREFIXES = [
"claude-opus-4-6",
"claude-sonnet-4-6",
"claude-opus-4-5",
"claude-sonnet-4-5",
"claude-haiku-4-5",

View File

@@ -1,8 +1,8 @@
import type { Api, Model } from "@mariozechner/pi-ai";
import type { ModelRegistry } from "./pi-model-discovery.js";
import { DEFAULT_CONTEXT_TOKENS } from "./defaults.js";
import { normalizeModelCompat } from "./model-compat.js";
import { normalizeProviderId } from "./model-selection.js";
import type { ModelRegistry } from "./pi-model-discovery.js";
const OPENAI_CODEX_GPT_53_MODEL_ID = "gpt-5.3-codex";
const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"] as const;
@@ -10,6 +10,9 @@ const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"] as const;
const ANTHROPIC_OPUS_46_MODEL_ID = "claude-opus-4-6";
const ANTHROPIC_OPUS_46_DOT_MODEL_ID = "claude-opus-4.6";
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 ZAI_GLM5_MODEL_ID = "glm-5";
const ZAI_GLM5_TEMPLATE_MODEL_IDS = ["glm-4.7"] as const;
@@ -139,6 +142,44 @@ function resolveAnthropicOpus46ForwardCompatModel(
});
}
function resolveAnthropicSonnet46ForwardCompatModel(
provider: string,
modelId: string,
modelRegistry: ModelRegistry,
): Model<Api> | undefined {
const normalizedProvider = normalizeProviderId(provider);
if (normalizedProvider !== "anthropic") {
return undefined;
}
const trimmedModelId = modelId.trim();
const lower = trimmedModelId.toLowerCase();
const isSonnet46 =
lower === ANTHROPIC_SONNET_46_MODEL_ID ||
lower === ANTHROPIC_SONNET_46_DOT_MODEL_ID ||
lower.startsWith(`${ANTHROPIC_SONNET_46_MODEL_ID}-`) ||
lower.startsWith(`${ANTHROPIC_SONNET_46_DOT_MODEL_ID}-`);
if (!isSonnet46) {
return undefined;
}
const templateIds: string[] = [];
if (lower.startsWith(ANTHROPIC_SONNET_46_MODEL_ID)) {
templateIds.push(lower.replace(ANTHROPIC_SONNET_46_MODEL_ID, "claude-sonnet-4-5"));
}
if (lower.startsWith(ANTHROPIC_SONNET_46_DOT_MODEL_ID)) {
templateIds.push(lower.replace(ANTHROPIC_SONNET_46_DOT_MODEL_ID, "claude-sonnet-4.5"));
}
templateIds.push(...ANTHROPIC_SONNET_TEMPLATE_MODEL_IDS);
return cloneFirstTemplateModel({
normalizedProvider,
trimmedModelId,
templateIds,
modelRegistry,
});
}
// Z.ai's GLM-5 may not be present in pi-ai's built-in model catalog yet.
// When a user configures zai/glm-5 without a models.json entry, clone glm-4.7 as a forward-compat fallback.
function resolveZaiGlm5ForwardCompatModel(
@@ -243,6 +284,7 @@ export function resolveForwardCompatModel(
return (
resolveOpenAICodexGpt53FallbackModel(provider, modelId, modelRegistry) ??
resolveAnthropicOpus46ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveAnthropicSonnet46ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveAntigravityOpus46ForwardCompatModel(provider, modelId, modelRegistry)
);

View File

@@ -45,6 +45,14 @@ describe("model-selection", () => {
provider: "anthropic",
model: "claude-opus-4-6",
});
expect(parseModelRef("anthropic/sonnet-4.6", "openai")).toEqual({
provider: "anthropic",
model: "claude-sonnet-4-6",
});
expect(parseModelRef("sonnet-4.6", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-sonnet-4-6",
});
});
it("should use default provider if none specified", () => {

View File

@@ -1,7 +1,7 @@
import type { OpenClawConfig } from "../config/config.js";
import type { ModelCatalogEntry } from "./model-catalog.js";
import { resolveAgentModelPrimary } from "./agent-scope.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
import type { ModelCatalogEntry } from "./model-catalog.js";
import { normalizeGoogleModelId } from "./models-config.providers.js";
export type ModelRef = {
@@ -19,6 +19,7 @@ export type ModelAliasIndex = {
const ANTHROPIC_MODEL_ALIASES: Record<string, string> = {
"opus-4.6": "claude-opus-4-6",
"opus-4.5": "claude-opus-4-5",
"sonnet-4.6": "claude-sonnet-4-6",
"sonnet-4.5": "claude-sonnet-4-5",
};
const OPENAI_CODEX_OAUTH_MODEL_PREFIXES = ["gpt-5.3-codex"] as const;

View File

@@ -217,6 +217,32 @@ describe("resolveModel", () => {
});
});
it("builds an anthropic forward-compat fallback for claude-sonnet-4-6", () => {
mockDiscoveredModel({
provider: "anthropic",
modelId: "claude-sonnet-4-5",
templateModel: buildForwardCompatTemplate({
id: "claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
provider: "anthropic",
api: "anthropic-messages",
baseUrl: "https://api.anthropic.com",
}),
});
expectResolvedForwardCompatFallback({
provider: "anthropic",
id: "claude-sonnet-4-6",
expectedModel: {
provider: "anthropic",
id: "claude-sonnet-4-6",
api: "anthropic-messages",
baseUrl: "https://api.anthropic.com",
reasoning: true,
},
});
});
it("builds an antigravity forward-compat fallback for claude-opus-4-6-thinking", () => {
mockDiscoveredModel({
provider: "google-antigravity",

View File

@@ -1,7 +1,7 @@
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
import type { OpenClawConfig, GatewayAuthConfig } from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
import { promptAuthChoiceGrouped } from "./auth-choice-prompt.js";
import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js";
import {
@@ -30,6 +30,7 @@ function sanitizeTokenValue(value: string | undefined): string | undefined {
const ANTHROPIC_OAUTH_MODEL_KEYS = [
"anthropic/claude-opus-4-6",
"anthropic/claude-sonnet-4-6",
"anthropic/claude-opus-4-5",
"anthropic/claude-sonnet-4-5",
"anthropic/claude-haiku-4-5",

View File

@@ -1,9 +1,9 @@
import type { OpenClawConfig } from "./types.js";
import type { ModelDefinitionConfig } from "./types.models.js";
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
import { parseModelRef } from "../agents/model-selection.js";
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
import { resolveTalkApiKey } from "./talk.js";
import type { OpenClawConfig } from "./types.js";
import type { ModelDefinitionConfig } from "./types.models.js";
type WarnState = { warned: boolean };
@@ -14,7 +14,7 @@ type AnthropicAuthDefaultsMode = "api_key" | "oauth";
const DEFAULT_MODEL_ALIASES: Readonly<Record<string, string>> = {
// Anthropic (pi-ai catalog uses "latest" ids without date suffix)
opus: "anthropic/claude-opus-4-6",
sonnet: "anthropic/claude-sonnet-4-5",
sonnet: "anthropic/claude-sonnet-4-6",
// OpenAI
gpt: "openai/gpt-5.2",

View File

@@ -18,7 +18,7 @@ const CLI_IMAGE = isTruthyEnvValue(process.env.OPENCLAW_LIVE_CLI_BACKEND_IMAGE_P
const CLI_RESUME = isTruthyEnvValue(process.env.OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE);
const describeLive = LIVE && CLI_LIVE ? describe : describe.skip;
const DEFAULT_MODEL = "claude-cli/claude-sonnet-4-5";
const DEFAULT_MODEL = "claude-cli/claude-sonnet-4-6";
const DEFAULT_CLAUDE_ARGS = ["-p", "--output-format", "json", "--dangerously-skip-permissions"];
const DEFAULT_CODEX_ARGS = [
"exec",