fix(agents): surface OpenAI model capacity errors

This commit is contained in:
Vincent Koc
2026-04-23 14:03:05 -07:00
parent 76276b876d
commit f1ad5e27e0
6 changed files with 45 additions and 0 deletions

View File

@@ -64,6 +64,12 @@ describe("formatAssistantErrorText", () => {
"The AI service is temporarily overloaded. Please try again in a moment.",
);
});
it("returns a model-switch hint for OpenAI model capacity errors", () => {
const msg = makeAssistantError("Selected model is at capacity. Please try a different model.");
expect(formatAssistantErrorText(msg)).toBe(
"⚠️ Selected model is at capacity. Try a different model, or wait and retry.",
);
});
it("returns a recovery hint when tool call input is missing", () => {
const msg = makeAssistantError("tool_use.input: Field required");
const result = formatAssistantErrorText(msg);

View File

@@ -141,6 +141,17 @@ describe("sanitizeUserFacingText", () => {
);
});
it("returns a model-switch hint for OpenAI model capacity errors", () => {
expect(
sanitizeUserFacingText(
"OpenAI error: Selected model is at capacity. Please try a different model.",
{
errorContext: true,
},
),
).toBe("⚠️ Selected model is at capacity. Try a different model, or wait and retry.");
});
it("returns a transport-specific message for prefixed ECONNREFUSED errors", () => {
expect(
sanitizeUserFacingText("Error: connect ECONNREFUSED 127.0.0.1:443", {

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
isAuthErrorMessage,
isBillingErrorMessage,
isOverloadedErrorMessage,
isRateLimitErrorMessage,
} from "./failover-matches.js";
@@ -68,6 +69,12 @@ describe("Z.ai vendor error codes (#48988)", () => {
expect(isRateLimitErrorMessage("rate limit exceeded")).toBe(true);
});
it("OpenAI model-capacity text is classified as overloaded", () => {
expect(
isOverloadedErrorMessage("Selected model is at capacity. Please try a different model."),
).toBe(true);
});
it("billing still classified correctly", () => {
expect(isBillingErrorMessage("insufficient credits")).toBe(true);
});

View File

@@ -73,6 +73,7 @@ const ERROR_PATTERNS = {
overloaded: [
/overloaded_error|"type"\s*:\s*"overloaded_error"/i,
"overloaded",
/\b(?:selected\s+)?model\s+(?:is\s+)?at capacity\b/i,
// Match "service unavailable" only when combined with an explicit overload
// indicator — a generic 503 from a proxy/CDN should not be classified as
// provider-overload (#32828).

View File

@@ -34,6 +34,8 @@ export function formatBillingErrorMessage(provider?: string, model?: string): st
export const BILLING_ERROR_USER_MESSAGE = formatBillingErrorMessage();
const RATE_LIMIT_ERROR_USER_MESSAGE = "⚠️ API rate limit reached. Please try again later.";
const MODEL_CAPACITY_ERROR_USER_MESSAGE =
"⚠️ Selected model is at capacity. Try a different model, or wait and retry.";
const OVERLOADED_ERROR_USER_MESSAGE =
"The AI service is temporarily overloaded. Please try again in a moment.";
const FINAL_TAG_RE = /<\s*\/?\s*final\s*>/gi;
@@ -60,6 +62,7 @@ const HTTP_ERROR_HINTS = [
];
const RATE_LIMIT_SPECIFIC_HINT_RE =
/\bmin(ute)?s?\b|\bhours?\b|\bseconds?\b|\btry again in\b|\breset\b|\bplan\b|\bquota\b/i;
const MODEL_CAPACITY_ERROR_RE = /\b(?:selected\s+)?model\s+(?:is\s+)?at capacity\b/i;
const NON_ERROR_PROVIDER_PAYLOAD_MAX_LENGTH = 16_384;
const NON_ERROR_PROVIDER_PAYLOAD_PREFIX_RE = /^codex\s*error(?:\s+\d{3})?[:\s-]+/i;
@@ -93,6 +96,9 @@ export function formatRateLimitOrOverloadedErrorCopy(raw: string): string | unde
if (isRateLimitErrorMessage(raw)) {
return extractProviderRateLimitMessage(raw) ?? RATE_LIMIT_ERROR_USER_MESSAGE;
}
if (MODEL_CAPACITY_ERROR_RE.test(raw)) {
return MODEL_CAPACITY_ERROR_USER_MESSAGE;
}
if (isOverloadedErrorMessage(raw)) {
return OVERLOADED_ERROR_USER_MESSAGE;
}

View File

@@ -101,6 +101,20 @@ describe("buildEmbeddedRunPayloads", () => {
expect(payloads.some((payload) => payload.text?.includes("request_id"))).toBe(false);
});
it("surfaces OpenAI model capacity errors instead of generic empty-response copy", () => {
const payloads = buildPayloads({
lastAssistant: makeAssistant({
errorMessage: "Selected model is at capacity. Please try a different model.",
content: [],
}),
});
expectSinglePayloadSummary(payloads, {
text: "⚠️ Selected model is at capacity. Try a different model, or wait and retry.",
isError: true,
});
});
it("includes provider and model context for billing errors", () => {
const payloads = buildPayloads({
lastAssistant: makeAssistant({