mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 13:41:35 +00:00
* feat(tools): per-model code-mode capability flags and auto master-switch tier * feat(anthropic): flag claude-sonnet-4-6 as code-mode preferred * revert(anthropic): drop claude-sonnet-4-6 from the code-mode preferred set * test(anthropic): type the manifest compat field in the catalog contract test * fix(anthropic): carry catalog compat onto hand-built forward-compat model rows
154 lines
5.1 KiB
TypeScript
154 lines
5.1 KiB
TypeScript
// Anthropic tests cover provider manifest model catalog behavior.
|
|
import { readFileSync } from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
type AnthropicCatalogModel = {
|
|
id?: string;
|
|
name?: string;
|
|
reasoning?: boolean;
|
|
input?: string[];
|
|
mediaInput?: {
|
|
image?: {
|
|
maxSidePx?: number;
|
|
preferredSidePx?: number;
|
|
tokenMode?: string;
|
|
};
|
|
};
|
|
contextWindow?: number;
|
|
maxTokens?: number;
|
|
cost?: {
|
|
input?: number;
|
|
output?: number;
|
|
cacheRead?: number;
|
|
cacheWrite?: number;
|
|
};
|
|
thinkingLevelMap?: Record<string, string | null>;
|
|
status?: string;
|
|
replacedBy?: string;
|
|
compat?: { codeMode?: string };
|
|
};
|
|
|
|
type AnthropicManifest = {
|
|
modelCatalog?: {
|
|
providers?: {
|
|
anthropic?: { models?: AnthropicCatalogModel[] };
|
|
"claude-cli"?: { models?: AnthropicCatalogModel[] };
|
|
};
|
|
discovery?: Record<string, string>;
|
|
};
|
|
};
|
|
|
|
const manifest = JSON.parse(
|
|
readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"),
|
|
) as AnthropicManifest;
|
|
|
|
describe("Anthropic plugin manifest", () => {
|
|
it("flags every static Anthropic API model as code-mode preferred", () => {
|
|
const models = manifest.modelCatalog?.providers?.anthropic?.models ?? [];
|
|
expect(models.length).toBeGreaterThan(0);
|
|
for (const model of models) {
|
|
expect(model.compat?.codeMode, model.id).toBe("preferred");
|
|
}
|
|
});
|
|
|
|
it("publishes the exact Claude Opus 5 API contract", () => {
|
|
const models = manifest.modelCatalog?.providers?.anthropic?.models ?? [];
|
|
expect(models.find((model) => model.id === "claude-opus-5")).toEqual({
|
|
id: "claude-opus-5",
|
|
name: "Claude Opus 5",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
mediaInput: {
|
|
image: { maxSidePx: 2576, preferredSidePx: 2576, tokenMode: "provider" },
|
|
},
|
|
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 128_000,
|
|
thinkingLevelMap: { xhigh: "xhigh", max: "max" },
|
|
compat: { codeMode: "preferred" },
|
|
});
|
|
});
|
|
|
|
it("publishes the exact Claude Sonnet 5 API contract", () => {
|
|
const models = manifest.modelCatalog?.providers?.anthropic?.models ?? [];
|
|
expect(models.find((model) => model.id === "claude-sonnet-5")).toEqual({
|
|
id: "claude-sonnet-5",
|
|
name: "Claude Sonnet 5",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
mediaInput: {
|
|
image: { maxSidePx: 2576, preferredSidePx: 2576, tokenMode: "provider" },
|
|
},
|
|
cost: { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 128_000,
|
|
thinkingLevelMap: { xhigh: "xhigh", max: "max" },
|
|
compat: { codeMode: "preferred" },
|
|
});
|
|
});
|
|
|
|
it("publishes the exact Claude Opus 5 API contract", () => {
|
|
const models = manifest.modelCatalog?.providers?.anthropic?.models ?? [];
|
|
expect(models.find((model) => model.id === "claude-opus-5")).toEqual({
|
|
id: "claude-opus-5",
|
|
name: "Claude Opus 5",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
mediaInput: {
|
|
image: { maxSidePx: 2576, preferredSidePx: 2576, tokenMode: "provider" },
|
|
},
|
|
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 128_000,
|
|
thinkingLevelMap: { xhigh: "xhigh", max: "max" },
|
|
compat: { codeMode: "preferred" },
|
|
});
|
|
// Opus 5's 1M window is the model default, so the CLI row is not clamped to 200k.
|
|
const cliModels = manifest.modelCatalog?.providers?.["claude-cli"]?.models ?? [];
|
|
expect(cliModels.find((model) => model.id === "claude-opus-5")).toMatchObject({
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 128_000,
|
|
});
|
|
});
|
|
|
|
it("declares Opus 4.8 limits without overstating bare Claude CLI context", () => {
|
|
const models = manifest.modelCatalog?.providers?.anthropic?.models ?? [];
|
|
expect(models.find((model) => model.id === "claude-opus-4-8")).toMatchObject({
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 128_000,
|
|
status: "deprecated",
|
|
replacedBy: "claude-opus-5",
|
|
});
|
|
const cliModels = manifest.modelCatalog?.providers?.["claude-cli"]?.models ?? [];
|
|
expect(cliModels.find((model) => model.id === "claude-opus-4-8")).toMatchObject({
|
|
contextWindow: 200_000,
|
|
maxTokens: 128_000,
|
|
status: "deprecated",
|
|
replacedBy: "claude-opus-5",
|
|
});
|
|
});
|
|
|
|
it("keeps only the dateless Claude Haiku 4.5 identifier in the static catalog", () => {
|
|
expect(manifest.modelCatalog?.discovery?.anthropic).toBe("refreshable");
|
|
|
|
const models = manifest.modelCatalog?.providers?.anthropic?.models ?? [];
|
|
expect(models.find((model) => model.id === "claude-haiku-4-5")).toEqual({
|
|
id: "claude-haiku-4-5",
|
|
name: "Claude Haiku 4.5",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
mediaInput: {
|
|
image: {
|
|
maxSidePx: 1568,
|
|
preferredSidePx: 1568,
|
|
tokenMode: "provider",
|
|
},
|
|
},
|
|
contextWindow: 200000,
|
|
maxTokens: 64000,
|
|
compat: { codeMode: "preferred" },
|
|
});
|
|
expect(models.find((model) => model.id === "claude-haiku-4-5-20251001")).toBeUndefined();
|
|
});
|
|
});
|