Merge eb0457b30c into da71eaebd2
This commit is contained in:
commit
761eace390
@ -37,6 +37,7 @@ export type AuthProfileFailureReason =
|
|||||||
| "rate_limit"
|
| "rate_limit"
|
||||||
| "billing"
|
| "billing"
|
||||||
| "timeout"
|
| "timeout"
|
||||||
|
| "provider_unavailable"
|
||||||
| "unknown";
|
| "unknown";
|
||||||
|
|
||||||
/** Per-profile usage statistics for round-robin and cooldown tracking */
|
/** Per-profile usage statistics for round-robin and cooldown tracking */
|
||||||
|
|||||||
@ -47,6 +47,38 @@ describe("failover-error", () => {
|
|||||||
expect(err?.status).toBe(400);
|
expect(err?.status).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("infers provider_unavailable from OpenRouter no-endpoints error", () => {
|
||||||
|
expect(
|
||||||
|
resolveFailoverReasonFromError({
|
||||||
|
status: 404,
|
||||||
|
message: "No endpoints found that support tool use",
|
||||||
|
}),
|
||||||
|
).toBe("provider_unavailable");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("infers provider_unavailable from model-unavailable messages", () => {
|
||||||
|
expect(
|
||||||
|
resolveFailoverReasonFromError({
|
||||||
|
message: "model is currently unavailable",
|
||||||
|
}),
|
||||||
|
).toBe("provider_unavailable");
|
||||||
|
expect(
|
||||||
|
resolveFailoverReasonFromError({
|
||||||
|
message: "model not available for this request",
|
||||||
|
}),
|
||||||
|
).toBe("provider_unavailable");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("coerces provider_unavailable errors with a 404 status", () => {
|
||||||
|
const err = coerceToFailoverError(
|
||||||
|
{ message: "No endpoints found that support tool use", status: 404 },
|
||||||
|
{ provider: "openrouter", model: "deepseek/deepseek-chat-v3-0324" },
|
||||||
|
);
|
||||||
|
expect(err?.reason).toBe("provider_unavailable");
|
||||||
|
expect(err?.status).toBe(404);
|
||||||
|
expect(err?.provider).toBe("openrouter");
|
||||||
|
});
|
||||||
|
|
||||||
it("describes non-Error values consistently", () => {
|
it("describes non-Error values consistently", () => {
|
||||||
const described = describeFailoverError(123);
|
const described = describeFailoverError(123);
|
||||||
expect(described.message).toBe("123");
|
expect(described.message).toBe("123");
|
||||||
|
|||||||
@ -50,6 +50,8 @@ export function resolveFailoverStatus(reason: FailoverReason): number | undefine
|
|||||||
return 408;
|
return 408;
|
||||||
case "format":
|
case "format":
|
||||||
return 400;
|
return 400;
|
||||||
|
case "provider_unavailable":
|
||||||
|
return 404;
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,4 +38,16 @@ describe("classifyFailoverReason", () => {
|
|||||||
"rate_limit",
|
"rate_limit",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it("classifies OpenRouter no-endpoints errors as provider_unavailable", () => {
|
||||||
|
expect(classifyFailoverReason("No endpoints found that support tool use")).toBe(
|
||||||
|
"provider_unavailable",
|
||||||
|
);
|
||||||
|
expect(classifyFailoverReason("No endpoints found that support this request")).toBe(
|
||||||
|
"provider_unavailable",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("classifies model-unavailable errors as provider_unavailable", () => {
|
||||||
|
expect(classifyFailoverReason("model is currently unavailable")).toBe("provider_unavailable");
|
||||||
|
expect(classifyFailoverReason("model not available")).toBe("provider_unavailable");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -396,6 +396,12 @@ const ERROR_PATTERNS = {
|
|||||||
"messages.1.content.1.tool_use.id",
|
"messages.1.content.1.tool_use.id",
|
||||||
"invalid request format",
|
"invalid request format",
|
||||||
],
|
],
|
||||||
|
providerUnavailable: [
|
||||||
|
"no endpoints found",
|
||||||
|
/no .* endpoints? available/,
|
||||||
|
"model is currently unavailable",
|
||||||
|
"model not available",
|
||||||
|
],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const IMAGE_DIMENSION_ERROR_RE =
|
const IMAGE_DIMENSION_ERROR_RE =
|
||||||
@ -496,6 +502,10 @@ export function isAuthAssistantError(msg: AssistantMessage | undefined): boolean
|
|||||||
return isAuthErrorMessage(msg.errorMessage ?? "");
|
return isAuthErrorMessage(msg.errorMessage ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isProviderUnavailableErrorMessage(raw: string): boolean {
|
||||||
|
return matchesErrorPatterns(raw, ERROR_PATTERNS.providerUnavailable);
|
||||||
|
}
|
||||||
|
|
||||||
export function classifyFailoverReason(raw: string): FailoverReason | null {
|
export function classifyFailoverReason(raw: string): FailoverReason | null {
|
||||||
if (isImageDimensionErrorMessage(raw)) return null;
|
if (isImageDimensionErrorMessage(raw)) return null;
|
||||||
if (isImageSizeError(raw)) return null;
|
if (isImageSizeError(raw)) return null;
|
||||||
@ -505,6 +515,7 @@ export function classifyFailoverReason(raw: string): FailoverReason | null {
|
|||||||
if (isBillingErrorMessage(raw)) return "billing";
|
if (isBillingErrorMessage(raw)) return "billing";
|
||||||
if (isTimeoutErrorMessage(raw)) return "timeout";
|
if (isTimeoutErrorMessage(raw)) return "timeout";
|
||||||
if (isAuthErrorMessage(raw)) return "auth";
|
if (isAuthErrorMessage(raw)) return "auth";
|
||||||
|
if (isProviderUnavailableErrorMessage(raw)) return "provider_unavailable";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,10 @@
|
|||||||
export type EmbeddedContextFile = { path: string; content: string };
|
export type EmbeddedContextFile = { path: string; content: string };
|
||||||
|
|
||||||
export type FailoverReason = "auth" | "format" | "rate_limit" | "billing" | "timeout" | "unknown";
|
export type FailoverReason =
|
||||||
|
| "auth"
|
||||||
|
| "format"
|
||||||
|
| "rate_limit"
|
||||||
|
| "billing"
|
||||||
|
| "timeout"
|
||||||
|
| "provider_unavailable"
|
||||||
|
| "unknown";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user