diff --git a/src/agents/live-model-errors.test.ts b/src/agents/live-model-errors.test.ts new file mode 100644 index 000000000..a0db57799 --- /dev/null +++ b/src/agents/live-model-errors.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { + isMiniMaxModelNotFoundErrorMessage, + isModelNotFoundErrorMessage, +} from "./live-model-errors.js"; + +describe("live model error helpers", () => { + it("detects generic model-not-found messages", () => { + expect(isModelNotFoundErrorMessage('{"code":404,"message":"model not found"}')).toBe(true); + expect(isModelNotFoundErrorMessage("model: MiniMax-M2.5-highspeed not found")).toBe(true); + expect(isModelNotFoundErrorMessage("request ended without sending any chunks")).toBe(false); + }); + + it("detects bare minimax 404 page-not-found responses", () => { + expect(isMiniMaxModelNotFoundErrorMessage("404 page not found")).toBe(true); + expect(isMiniMaxModelNotFoundErrorMessage("Error: 404 404 page not found")).toBe(true); + expect(isMiniMaxModelNotFoundErrorMessage("request ended without sending any chunks")).toBe( + false, + ); + }); +}); diff --git a/src/agents/live-model-errors.ts b/src/agents/live-model-errors.ts new file mode 100644 index 000000000..56ba30a82 --- /dev/null +++ b/src/agents/live-model-errors.ts @@ -0,0 +1,24 @@ +export function isModelNotFoundErrorMessage(raw: string): boolean { + const msg = raw.trim(); + if (!msg) { + return false; + } + if (/\b404\b/.test(msg) && /not(?:[_\-\s])?found/i.test(msg)) { + return true; + } + if (/not_found_error/i.test(msg)) { + return true; + } + if (/model:\s*[a-z0-9._-]+/i.test(msg) && /not(?:[_\-\s])?found/i.test(msg)) { + return true; + } + return false; +} + +export function isMiniMaxModelNotFoundErrorMessage(raw: string): boolean { + const msg = raw.trim(); + if (!msg) { + return false; + } + return /\b404\b.*\bpage not found\b/i.test(msg); +} diff --git a/src/agents/models.profiles.live.test.ts b/src/agents/models.profiles.live.test.ts index c257c24f1..6386eaef1 100644 --- a/src/agents/models.profiles.live.test.ts +++ b/src/agents/models.profiles.live.test.ts @@ -9,6 +9,10 @@ import { isAnthropicBillingError, isAnthropicRateLimitError, } from "./live-auth-keys.js"; +import { + isMiniMaxModelNotFoundErrorMessage, + isModelNotFoundErrorMessage, +} from "./live-model-errors.js"; import { isModernModelRef } from "./live-model-filter.js"; import { getApiKeyForModel, requireApiKey } from "./model-auth.js"; import { ensureOpenClawModelsJson } from "./models-config.js"; @@ -82,23 +86,6 @@ function isGoogleModelNotFoundError(err: unknown): boolean { return false; } -function isModelNotFoundErrorMessage(raw: string): boolean { - const msg = raw.trim(); - if (!msg) { - return false; - } - if (/\b404\b/.test(msg) && /not[_-]?found/i.test(msg)) { - return true; - } - if (/not_found_error/i.test(msg)) { - return true; - } - if (/model:\s*[a-z0-9._-]+/i.test(msg) && /not[_-]?found/i.test(msg)) { - return true; - } - return false; -} - function isChatGPTUsageLimitErrorMessage(raw: string): boolean { const msg = raw.toLowerCase(); return msg.includes("hit your chatgpt usage limit") && msg.includes("try again in"); @@ -488,7 +475,11 @@ describeLive("live models (profile keys)", () => { if (ok.res.stopReason === "error") { const msg = ok.res.errorMessage ?? ""; - if (allowNotFoundSkip && isModelNotFoundErrorMessage(msg)) { + if ( + allowNotFoundSkip && + (isModelNotFoundErrorMessage(msg) || + (model.provider === "minimax" && isMiniMaxModelNotFoundErrorMessage(msg))) + ) { skipped.push({ model: id, reason: msg }); logProgress(`${progressLabel}: skip (model not found)`); break; @@ -572,6 +563,15 @@ describeLive("live models (profile keys)", () => { logProgress(`${progressLabel}: skip (google model not found)`); break; } + if ( + allowNotFoundSkip && + model.provider === "minimax" && + isMiniMaxModelNotFoundErrorMessage(message) + ) { + skipped.push({ model: id, reason: message }); + logProgress(`${progressLabel}: skip (model not found)`); + break; + } if ( allowNotFoundSkip && model.provider === "minimax" &&