mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
5bea268128
* feat(models): add Claude Opus 5 support * test(models): align Opus 5 setup fixtures * test(models): update ambient Opus 5 detection * fix(models): reconcile Opus 5 support with main
152 lines
5.0 KiB
TypeScript
152 lines
5.0 KiB
TypeScript
// Amazon Bedrock Mantle tests cover index plugin behavior.
|
|
import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import bedrockMantlePlugin from "./index.js";
|
|
|
|
describe("amazon-bedrock-mantle provider plugin", () => {
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("uses live plugin config to disable catalog discovery", async () => {
|
|
const fetchMock = vi
|
|
.spyOn(globalThis, "fetch")
|
|
.mockRejectedValue(new Error("unexpected fetch"));
|
|
const provider = await registerSingleProviderPlugin(bedrockMantlePlugin);
|
|
const catalog = provider.catalog;
|
|
if (!catalog) {
|
|
throw new Error("catalog registration missing");
|
|
}
|
|
|
|
const result = await catalog.run({
|
|
config: {
|
|
plugins: {
|
|
entries: {
|
|
"amazon-bedrock-mantle": {
|
|
config: {
|
|
discovery: { enabled: false },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: {
|
|
AWS_BEARER_TOKEN_BEDROCK: "test-token",
|
|
AWS_REGION: "us-east-1",
|
|
},
|
|
} as never);
|
|
|
|
expect(result).toBeNull();
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("registers with correct provider ID and label", async () => {
|
|
const provider = await registerSingleProviderPlugin(bedrockMantlePlugin);
|
|
expect(provider.id).toBe("amazon-bedrock-mantle");
|
|
expect(provider.label).toBe("Amazon Bedrock Mantle (OpenAI-compatible)");
|
|
});
|
|
|
|
it("classifies rate limit errors for failover", async () => {
|
|
const provider = await registerSingleProviderPlugin(bedrockMantlePlugin);
|
|
expect(
|
|
provider.classifyFailoverReason?.({ errorMessage: "rate_limit exceeded" } as never),
|
|
).toBe("rate_limit");
|
|
expect(
|
|
provider.classifyFailoverReason?.({ errorMessage: "429 Too Many Requests" } as never),
|
|
).toBe("rate_limit");
|
|
expect(
|
|
provider.classifyFailoverReason?.({ errorMessage: "some other error" } as never),
|
|
).toBeUndefined();
|
|
expect(provider.classifyFailoverReason?.({ errorMessage: "overloaded_error" } as never)).toBe(
|
|
"overloaded",
|
|
);
|
|
});
|
|
|
|
it("provides a custom stream only for Mantle Anthropic models", async () => {
|
|
const provider = await registerSingleProviderPlugin(bedrockMantlePlugin);
|
|
|
|
expect(
|
|
typeof provider.createStreamFn?.({
|
|
provider: "amazon-bedrock-mantle",
|
|
modelId: "anthropic.claude-opus-4-7",
|
|
model: {
|
|
api: "anthropic-messages",
|
|
},
|
|
} as never),
|
|
).toBe("function");
|
|
|
|
expect(
|
|
provider.createStreamFn?.({
|
|
provider: "amazon-bedrock-mantle",
|
|
modelId: "openai.gpt-oss-120b",
|
|
model: {
|
|
api: "openai-completions",
|
|
},
|
|
} as never),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it("restores missing or stale Opus 5 pricing during runtime normalization", async () => {
|
|
const provider = await registerSingleProviderPlugin(bedrockMantlePlugin);
|
|
const model = {
|
|
id: "anthropic.claude-opus-5",
|
|
name: "Claude Opus 5",
|
|
api: "anthropic-messages",
|
|
provider: "amazon-bedrock-mantle",
|
|
baseUrl: "https://bedrock-mantle.us-east-1.api.aws/anthropic",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 128_000,
|
|
params: { canonicalModelId: "claude-opus-5" },
|
|
};
|
|
const expectedCost = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
|
|
|
|
for (const cost of [undefined, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }]) {
|
|
const normalized = provider.normalizeResolvedModel?.({
|
|
provider: "amazon-bedrock-mantle",
|
|
modelId: "anthropic.claude-opus-5",
|
|
model: { ...model, cost },
|
|
} as never);
|
|
expect(normalized?.cost).toEqual(expectedCost);
|
|
}
|
|
});
|
|
|
|
it("restores missing or stale Sonnet 5 pricing during runtime normalization", async () => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(Date.UTC(2026, 8, 1));
|
|
try {
|
|
const provider = await registerSingleProviderPlugin(bedrockMantlePlugin);
|
|
const model = {
|
|
id: "anthropic.claude-sonnet-5",
|
|
name: "Claude Sonnet 5",
|
|
api: "anthropic-messages",
|
|
provider: "amazon-bedrock-mantle",
|
|
baseUrl: "https://bedrock-mantle.us-east-1.api.aws/anthropic",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 128_000,
|
|
params: { canonicalModelId: "claude-sonnet-5" },
|
|
};
|
|
const expectedCost = {
|
|
input: 3,
|
|
output: 15,
|
|
cacheRead: 0.3,
|
|
cacheWrite: 3.75,
|
|
};
|
|
|
|
for (const cost of [undefined, { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 }]) {
|
|
const normalized = provider.normalizeResolvedModel?.({
|
|
provider: "amazon-bedrock-mantle",
|
|
modelId: "anthropic.claude-sonnet-5",
|
|
model: { ...model, cost },
|
|
} as never);
|
|
expect(normalized?.cost).toEqual(expectedCost);
|
|
}
|
|
} finally {
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
});
|