mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 18:31:26 +00:00
fix(agents): harden model fallback failover paths
This commit is contained in:
@@ -8,7 +8,7 @@ import type { AuthProfileStore } from "./auth-profiles.js";
|
||||
import { saveAuthProfileStore } from "./auth-profiles.js";
|
||||
import { AUTH_STORE_VERSION } from "./auth-profiles/constants.js";
|
||||
import { isAnthropicBillingError } from "./live-auth-keys.js";
|
||||
import { runWithModelFallback } from "./model-fallback.js";
|
||||
import { runWithImageModelFallback, runWithModelFallback } from "./model-fallback.js";
|
||||
import { makeModelFallbackCfg } from "./test-helpers/model-fallback-config-fixture.js";
|
||||
|
||||
const makeCfg = makeModelFallbackCfg;
|
||||
@@ -581,6 +581,39 @@ describe("runWithModelFallback", () => {
|
||||
expect(calls).toEqual([{ provider: "anthropic", model: "claude-opus-4-5" }]);
|
||||
});
|
||||
|
||||
it("keeps explicit fallbacks reachable when models allowlist is present", async () => {
|
||||
const cfg = makeCfg({
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "anthropic/claude-sonnet-4",
|
||||
fallbacks: ["openai/gpt-4o", "ollama/llama-3"],
|
||||
},
|
||||
models: {
|
||||
"anthropic/claude-sonnet-4": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const run = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(Object.assign(new Error("rate limited"), { status: 429 }))
|
||||
.mockResolvedValueOnce("ok");
|
||||
|
||||
const result = await runWithModelFallback({
|
||||
cfg,
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4",
|
||||
run,
|
||||
});
|
||||
|
||||
expect(result.result).toBe("ok");
|
||||
expect(run.mock.calls).toEqual([
|
||||
["anthropic", "claude-sonnet-4"],
|
||||
["openai", "gpt-4o"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("defaults provider/model when missing (regression #946)", async () => {
|
||||
const cfg = makeCfg({
|
||||
agents: {
|
||||
@@ -721,6 +754,39 @@ describe("runWithModelFallback", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("runWithImageModelFallback", () => {
|
||||
it("keeps explicit image fallbacks reachable when models allowlist is present", async () => {
|
||||
const cfg = makeCfg({
|
||||
agents: {
|
||||
defaults: {
|
||||
imageModel: {
|
||||
primary: "openai/gpt-image-1",
|
||||
fallbacks: ["google/gemini-2.5-flash-image-preview"],
|
||||
},
|
||||
models: {
|
||||
"openai/gpt-image-1": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const run = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error("rate limited"))
|
||||
.mockResolvedValueOnce("ok");
|
||||
|
||||
const result = await runWithImageModelFallback({
|
||||
cfg,
|
||||
run,
|
||||
});
|
||||
|
||||
expect(result.result).toBe("ok");
|
||||
expect(run.mock.calls).toEqual([
|
||||
["openai", "gpt-image-1"],
|
||||
["google", "gemini-2.5-flash-image-preview"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isAnthropicBillingError", () => {
|
||||
it("does not false-positive on plain 'a 402' prose", () => {
|
||||
const samples = [
|
||||
|
||||
Reference in New Issue
Block a user