fix(agents): classify embedded provider business denials for fallback

This commit is contained in:
clawsweeper
2026-05-30 00:20:07 +00:00
parent a7b776932c
commit e266beac93
2 changed files with 36 additions and 15 deletions

View File

@@ -70,6 +70,31 @@ describe("classifyEmbeddedAgentRunResultForModelFallback", () => {
expect(result).toBeNull();
});
it("uses provider-scoped failover matching for business-denial payloads", () => {
const result = classifyEmbeddedAgentRunResultForModelFallback({
provider: "openrouter",
model: "claude-3.5-sonnet",
result: {
payloads: [
{
isError: true,
text: "Key limit exceeded",
},
],
meta: {
durationMs: 42,
},
},
});
expect(result).toEqual({
message: "openrouter/claude-3.5-sonnet ended with a provider error: Key limit exceeded",
reason: "billing",
code: "embedded_error_payload",
rawError: "Key limit exceeded",
});
});
it("does not retry unclassified non-GPT error payloads", () => {
const result = classifyEmbeddedAgentRunResultForModelFallback({
provider: "custom",

View File

@@ -1,9 +1,5 @@
import { isSilentReplyPayloadText } from "../../auto-reply/tokens.js";
import {
isAuthErrorMessage,
isAuthPermanentErrorMessage,
isBillingErrorMessage,
} from "../embedded-agent-helpers/failover-matches.js";
import { classifyFailoverReason } from "../embedded-agent-helpers/errors.js";
import type { FailoverReason } from "../embedded-agent-helpers/types.js";
import { isGpt5ModelId } from "../gpt5-prompt-overlay.js";
import type { ModelFallbackResultClassification } from "../model-fallback.js";
@@ -63,20 +59,20 @@ function classifyHarnessResult(params: {
function classifyBusinessDenialErrorPayloadReason(
errorText: string,
provider: string,
): Extract<FailoverReason, "auth" | "auth_permanent" | "billing"> | null {
if (!errorText.trim()) {
return null;
}
if (isBillingErrorMessage(errorText)) {
return "billing";
const failoverReason = classifyFailoverReason(errorText, { provider });
switch (failoverReason) {
case "auth":
case "auth_permanent":
case "billing":
return failoverReason;
default:
return null;
}
if (isAuthPermanentErrorMessage(errorText)) {
return "auth_permanent";
}
if (isAuthErrorMessage(errorText)) {
return "auth";
}
return null;
}
export function classifyEmbeddedAgentRunResultForModelFallback(params: {
@@ -128,7 +124,7 @@ export function classifyEmbeddedAgentRunResultForModelFallback(params: {
code: "incomplete_result",
};
}
const failoverReason = classifyBusinessDenialErrorPayloadReason(errorText);
const failoverReason = classifyBusinessDenialErrorPayloadReason(errorText, params.provider);
if (failoverReason) {
return {
message: `${params.provider}/${params.model} ended with a provider error: ${errorText}`,