Skip to content

Commit 95ee120

Browse files
mrdearaltaywtf
andauthored
fix: classify openrouter json 404 model errors
Rewrites the stale branch on top of current `main` and preserves the original issue as regression coverage for the exact OpenRouter JSON 404 payload from openclaw#51571. No production behavior changes are introduced here; current `main` already classifies this payload as `model_not_found`, and this merge locks that in across the shared matcher, failover classifier, and fallback loop. Co-authored-by: 屈定 <mrdear@users.noreply.github.com> Co-authored-by: Altay <altay@uinaf.dev>
1 parent 961eb95 commit 95ee120

3 files changed

Lines changed: 49 additions & 0 deletions

File tree

src/agents/failover-error.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const GEMINI_RESOURCE_EXHAUSTED_MESSAGE =
1919
"RESOURCE_EXHAUSTED: Resource has been exhausted (e.g. check quota).";
2020
// OpenRouter 402 billing example: https://openrouter.ai/docs/api-reference/errors
2121
const OPENROUTER_CREDITS_MESSAGE = "Payment Required: insufficient credits";
22+
const OPENROUTER_MODEL_NOT_FOUND_PAYLOAD =
23+
'{"error":{"message":"Healer Alpha was a stealth model revealed on March 18th as an early testing version of MiMo-V2-Omni. Find it here: https://openrouter.ai/xiaomi/mimo-v2-omni","code":404},"user_id":"user_33GTyP8uDSYYbaeBO48AGHXyuMC"}';
2224
const TOGETHER_MONTHLY_SPEND_CAP_MESSAGE =
2325
"The account associated with this API key has reached its maximum allowed monthly spending limit.";
2426
// Issue-backed Anthropic/OpenAI-compatible insufficient_quota payload under HTTP 400:
@@ -195,6 +197,14 @@ describe("failover-error", () => {
195197
).toBe("model_not_found");
196198
});
197199

200+
it("classifies JSON-wrapped OpenRouter stealth-model 404s as model_not_found", () => {
201+
expect(
202+
resolveFailoverReasonFromError({
203+
message: OPENROUTER_MODEL_NOT_FOUND_PAYLOAD,
204+
}),
205+
).toBe("model_not_found");
206+
});
207+
198208
it("classifies generic model-does-not-exist messages as model_not_found", () => {
199209
expect(
200210
resolveFailoverReasonFromError({
@@ -569,6 +579,16 @@ describe("failover-error", () => {
569579
expect(err?.model).toBe("claude-opus-4-6");
570580
});
571581

582+
it("coerces JSON-wrapped OpenRouter stealth-model 404s into FailoverError", () => {
583+
const err = coerceToFailoverError(OPENROUTER_MODEL_NOT_FOUND_PAYLOAD, {
584+
provider: "openrouter",
585+
model: "openrouter/healer-alpha",
586+
});
587+
588+
expect(err?.reason).toBe("model_not_found");
589+
expect(err?.status).toBe(404);
590+
});
591+
572592
it("maps overloaded to a 503 fallback status", () => {
573593
expect(resolveFailoverStatus("overloaded")).toBe(503);
574594
});

src/agents/live-model-errors.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ import {
66

77
describe("live model error helpers", () => {
88
it("detects generic model-not-found messages", () => {
9+
const openRouterJson404Payload =
10+
'{"error":{"message":"Healer Alpha was a stealth model revealed on March 18th as an early testing version of MiMo-V2-Omni. Find it here: https://openrouter.ai/xiaomi/mimo-v2-omni","code":404},"user_id":"user_33GTyP8uDSYYbaeBO48AGHXyuMC"}';
11+
912
expect(isModelNotFoundErrorMessage("Model not found: openai/gpt-6")).toBe(true);
1013
expect(isModelNotFoundErrorMessage("model_not_found")).toBe(true);
1114
expect(isModelNotFoundErrorMessage("The model gpt-foo does not exist.")).toBe(true);
1215
expect(isModelNotFoundErrorMessage('{"code":404,"message":"model not found"}')).toBe(true);
16+
expect(isModelNotFoundErrorMessage(openRouterJson404Payload)).toBe(true);
1317
expect(isModelNotFoundErrorMessage("model: MiniMax-M2.7-highspeed not found")).toBe(true);
1418
expect(
1519
isModelNotFoundErrorMessage("404 No endpoints found for deepseek/deepseek-r1:free."),
@@ -32,6 +36,9 @@ describe("live model error helpers", () => {
3236
expect(
3337
isModelNotFoundErrorMessage("The deployment does not exist or you do not have access."),
3438
).toBe(false);
39+
expect(isModelNotFoundErrorMessage('{"error":{"message":"Resource missing","code":404}}')).toBe(
40+
false,
41+
);
3542
expect(isModelNotFoundErrorMessage("request ended without sending any chunks")).toBe(false);
3643
});
3744

src/agents/model-fallback.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ vi.mock("../plugins/provider-runtime.js", () => ({
2222
}));
2323

2424
const makeCfg = makeModelFallbackCfg;
25+
const OPENROUTER_MODEL_NOT_FOUND_PAYLOAD =
26+
'{"error":{"message":"Healer Alpha was a stealth model revealed on March 18th as an early testing version of MiMo-V2-Omni. Find it here: https://openrouter.ai/xiaomi/mimo-v2-omni","code":404},"user_id":"user_33GTyP8uDSYYbaeBO48AGHXyuMC"}';
2527

2628
function makeFallbacksOnlyCfg(): OpenClawConfig {
2729
return {
@@ -569,6 +571,26 @@ describe("runWithModelFallback", () => {
569571
expect(run.mock.calls[1]?.[1]).toBe("claude-haiku-3-5");
570572
});
571573

574+
it("falls back on JSON-wrapped OpenRouter stealth-model 404s", async () => {
575+
const cfg = makeCfg();
576+
const run = vi
577+
.fn()
578+
.mockRejectedValueOnce(new Error(OPENROUTER_MODEL_NOT_FOUND_PAYLOAD))
579+
.mockResolvedValueOnce("ok");
580+
581+
const result = await runWithModelFallback({
582+
cfg,
583+
provider: "openrouter",
584+
model: "openrouter/healer-alpha",
585+
run,
586+
});
587+
588+
expect(result.result).toBe("ok");
589+
expect(run).toHaveBeenCalledTimes(2);
590+
expect(run.mock.calls[1]?.[0]).toBe("openai");
591+
expect(run.mock.calls[1]?.[1]).toBe("gpt-4.1-mini");
592+
});
593+
572594
it("warns when falling back due to model_not_found", async () => {
573595
setLoggerOverride({ level: "silent", consoleLevel: "warn" });
574596
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});

0 commit comments

Comments
 (0)