mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
feat(models): support anthropic sonnet 4.6
This commit is contained in:
@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Plugins: add `before_agent_start` model/provider overrides before resolution. (#18568) Thanks @natefikru.
|
- 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.
|
- 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 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.
|
- 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.
|
- 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.
|
- 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.
|
||||||
|
|||||||
@@ -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)
|
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
|
||||||
- `OPENCLAW_LIVE_CLI_BACKEND=1`
|
- `OPENCLAW_LIVE_CLI_BACKEND=1`
|
||||||
- Defaults:
|
- Defaults:
|
||||||
- Model: `claude-cli/claude-sonnet-4-5`
|
- Model: `claude-cli/claude-sonnet-4-6`
|
||||||
- Command: `claude`
|
- Command: `claude`
|
||||||
- Args: `["-p","--output-format","json","--dangerously-skip-permissions"]`
|
- Args: `["-p","--output-format","json","--dangerously-skip-permissions"]`
|
||||||
- Overrides (optional):
|
- Overrides (optional):
|
||||||
@@ -219,7 +219,7 @@ Example:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
OPENCLAW_LIVE_CLI_BACKEND=1 \
|
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
|
pnpm test:live src/gateway/gateway-cli-backend.live.test.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { type Api, completeSimple, type Model } from "@mariozechner/pi-ai";
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { type Api, completeSimple, type Model } from "@mariozechner/pi-ai";
|
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
ANTHROPIC_SETUP_TOKEN_PREFIX,
|
ANTHROPIC_SETUP_TOKEN_PREFIX,
|
||||||
@@ -142,6 +142,7 @@ function pickModel(models: Array<Model<Api>>, raw?: string): Model<Api> | null {
|
|||||||
|
|
||||||
const preferred = [
|
const preferred = [
|
||||||
"claude-opus-4-5",
|
"claude-opus-4-5",
|
||||||
|
"claude-sonnet-4-6",
|
||||||
"claude-sonnet-4-5",
|
"claude-sonnet-4-5",
|
||||||
"claude-sonnet-4-0",
|
"claude-sonnet-4-0",
|
||||||
"claude-haiku-3-5",
|
"claude-haiku-3-5",
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ const CLAUDE_MODEL_ALIASES: Record<string, string> = {
|
|||||||
"claude-opus-4-5": "opus",
|
"claude-opus-4-5": "opus",
|
||||||
"claude-opus-4": "opus",
|
"claude-opus-4": "opus",
|
||||||
sonnet: "sonnet",
|
sonnet: "sonnet",
|
||||||
|
"sonnet-4.6": "sonnet",
|
||||||
"sonnet-4.5": "sonnet",
|
"sonnet-4.5": "sonnet",
|
||||||
"sonnet-4.1": "sonnet",
|
"sonnet-4.1": "sonnet",
|
||||||
"sonnet-4.0": "sonnet",
|
"sonnet-4.0": "sonnet",
|
||||||
|
"claude-sonnet-4-6": "sonnet",
|
||||||
"claude-sonnet-4-5": "sonnet",
|
"claude-sonnet-4-5": "sonnet",
|
||||||
"claude-sonnet-4-1": "sonnet",
|
"claude-sonnet-4-1": "sonnet",
|
||||||
"claude-sonnet-4-0": "sonnet",
|
"claude-sonnet-4-0": "sonnet",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type ModelRef = {
|
|||||||
|
|
||||||
const ANTHROPIC_PREFIXES = [
|
const ANTHROPIC_PREFIXES = [
|
||||||
"claude-opus-4-6",
|
"claude-opus-4-6",
|
||||||
|
"claude-sonnet-4-6",
|
||||||
"claude-opus-4-5",
|
"claude-opus-4-5",
|
||||||
"claude-sonnet-4-5",
|
"claude-sonnet-4-5",
|
||||||
"claude-haiku-4-5",
|
"claude-haiku-4-5",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||||
|
import type { ModelRegistry } from "./pi-model-discovery.js";
|
||||||
import { DEFAULT_CONTEXT_TOKENS } from "./defaults.js";
|
import { DEFAULT_CONTEXT_TOKENS } from "./defaults.js";
|
||||||
import { normalizeModelCompat } from "./model-compat.js";
|
import { normalizeModelCompat } from "./model-compat.js";
|
||||||
import { normalizeProviderId } from "./model-selection.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_GPT_53_MODEL_ID = "gpt-5.3-codex";
|
||||||
const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"] as const;
|
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_MODEL_ID = "claude-opus-4-6";
|
||||||
const ANTHROPIC_OPUS_46_DOT_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_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_MODEL_ID = "glm-5";
|
||||||
const ZAI_GLM5_TEMPLATE_MODEL_IDS = ["glm-4.7"] as const;
|
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.
|
// 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.
|
// When a user configures zai/glm-5 without a models.json entry, clone glm-4.7 as a forward-compat fallback.
|
||||||
function resolveZaiGlm5ForwardCompatModel(
|
function resolveZaiGlm5ForwardCompatModel(
|
||||||
@@ -243,6 +284,7 @@ export function resolveForwardCompatModel(
|
|||||||
return (
|
return (
|
||||||
resolveOpenAICodexGpt53FallbackModel(provider, modelId, modelRegistry) ??
|
resolveOpenAICodexGpt53FallbackModel(provider, modelId, modelRegistry) ??
|
||||||
resolveAnthropicOpus46ForwardCompatModel(provider, modelId, modelRegistry) ??
|
resolveAnthropicOpus46ForwardCompatModel(provider, modelId, modelRegistry) ??
|
||||||
|
resolveAnthropicSonnet46ForwardCompatModel(provider, modelId, modelRegistry) ??
|
||||||
resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) ??
|
resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) ??
|
||||||
resolveAntigravityOpus46ForwardCompatModel(provider, modelId, modelRegistry)
|
resolveAntigravityOpus46ForwardCompatModel(provider, modelId, modelRegistry)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ describe("model-selection", () => {
|
|||||||
provider: "anthropic",
|
provider: "anthropic",
|
||||||
model: "claude-opus-4-6",
|
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", () => {
|
it("should use default provider if none specified", () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
|
import type { ModelCatalogEntry } from "./model-catalog.js";
|
||||||
import { resolveAgentModelPrimary } from "./agent-scope.js";
|
import { resolveAgentModelPrimary } from "./agent-scope.js";
|
||||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
|
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
|
||||||
import type { ModelCatalogEntry } from "./model-catalog.js";
|
|
||||||
import { normalizeGoogleModelId } from "./models-config.providers.js";
|
import { normalizeGoogleModelId } from "./models-config.providers.js";
|
||||||
|
|
||||||
export type ModelRef = {
|
export type ModelRef = {
|
||||||
@@ -19,6 +19,7 @@ export type ModelAliasIndex = {
|
|||||||
const ANTHROPIC_MODEL_ALIASES: Record<string, string> = {
|
const ANTHROPIC_MODEL_ALIASES: Record<string, string> = {
|
||||||
"opus-4.6": "claude-opus-4-6",
|
"opus-4.6": "claude-opus-4-6",
|
||||||
"opus-4.5": "claude-opus-4-5",
|
"opus-4.5": "claude-opus-4-5",
|
||||||
|
"sonnet-4.6": "claude-sonnet-4-6",
|
||||||
"sonnet-4.5": "claude-sonnet-4-5",
|
"sonnet-4.5": "claude-sonnet-4-5",
|
||||||
};
|
};
|
||||||
const OPENAI_CODEX_OAUTH_MODEL_PREFIXES = ["gpt-5.3-codex"] as const;
|
const OPENAI_CODEX_OAUTH_MODEL_PREFIXES = ["gpt-5.3-codex"] as const;
|
||||||
|
|||||||
@@ -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", () => {
|
it("builds an antigravity forward-compat fallback for claude-opus-4-6-thinking", () => {
|
||||||
mockDiscoveredModel({
|
mockDiscoveredModel({
|
||||||
provider: "google-antigravity",
|
provider: "google-antigravity",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
|
||||||
import type { OpenClawConfig, GatewayAuthConfig } from "../config/config.js";
|
import type { OpenClawConfig, GatewayAuthConfig } from "../config/config.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||||
|
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
||||||
import { promptAuthChoiceGrouped } from "./auth-choice-prompt.js";
|
import { promptAuthChoiceGrouped } from "./auth-choice-prompt.js";
|
||||||
import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js";
|
import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js";
|
||||||
import {
|
import {
|
||||||
@@ -30,6 +30,7 @@ function sanitizeTokenValue(value: string | undefined): string | undefined {
|
|||||||
|
|
||||||
const ANTHROPIC_OAUTH_MODEL_KEYS = [
|
const ANTHROPIC_OAUTH_MODEL_KEYS = [
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
|
"anthropic/claude-sonnet-4-6",
|
||||||
"anthropic/claude-opus-4-5",
|
"anthropic/claude-opus-4-5",
|
||||||
"anthropic/claude-sonnet-4-5",
|
"anthropic/claude-sonnet-4-5",
|
||||||
"anthropic/claude-haiku-4-5",
|
"anthropic/claude-haiku-4-5",
|
||||||
|
|||||||
@@ -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 { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
|
||||||
import { parseModelRef } from "../agents/model-selection.js";
|
import { parseModelRef } from "../agents/model-selection.js";
|
||||||
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
|
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
|
||||||
import { resolveTalkApiKey } from "./talk.js";
|
import { resolveTalkApiKey } from "./talk.js";
|
||||||
import type { OpenClawConfig } from "./types.js";
|
|
||||||
import type { ModelDefinitionConfig } from "./types.models.js";
|
|
||||||
|
|
||||||
type WarnState = { warned: boolean };
|
type WarnState = { warned: boolean };
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ type AnthropicAuthDefaultsMode = "api_key" | "oauth";
|
|||||||
const DEFAULT_MODEL_ALIASES: Readonly<Record<string, string>> = {
|
const DEFAULT_MODEL_ALIASES: Readonly<Record<string, string>> = {
|
||||||
// Anthropic (pi-ai catalog uses "latest" ids without date suffix)
|
// Anthropic (pi-ai catalog uses "latest" ids without date suffix)
|
||||||
opus: "anthropic/claude-opus-4-6",
|
opus: "anthropic/claude-opus-4-6",
|
||||||
sonnet: "anthropic/claude-sonnet-4-5",
|
sonnet: "anthropic/claude-sonnet-4-6",
|
||||||
|
|
||||||
// OpenAI
|
// OpenAI
|
||||||
gpt: "openai/gpt-5.2",
|
gpt: "openai/gpt-5.2",
|
||||||
|
|||||||
@@ -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 CLI_RESUME = isTruthyEnvValue(process.env.OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE);
|
||||||
const describeLive = LIVE && CLI_LIVE ? describe : describe.skip;
|
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_CLAUDE_ARGS = ["-p", "--output-format", "json", "--dangerously-skip-permissions"];
|
||||||
const DEFAULT_CODEX_ARGS = [
|
const DEFAULT_CODEX_ARGS = [
|
||||||
"exec",
|
"exec",
|
||||||
|
|||||||
Reference in New Issue
Block a user