mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:10:52 +00:00
fix(reply): classify billing cooldown summaries
This commit is contained in:
@@ -912,6 +912,64 @@ describe("runAgentTurnWithFallback", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("surfaces billing guidance for pure billing cooldown fallback exhaustion", async () => {
|
||||
state.runWithModelFallbackMock.mockRejectedValueOnce(
|
||||
Object.assign(
|
||||
new Error(
|
||||
"All models failed (2): anthropic/claude-opus-4-6: Provider anthropic has billing issue (skipping all models) (billing) | anthropic/claude-sonnet-4-6: Provider anthropic has billing issue (skipping all models) (billing)",
|
||||
),
|
||||
{
|
||||
name: "FallbackSummaryError",
|
||||
attempts: [
|
||||
{
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-6",
|
||||
error: "Provider anthropic has billing issue (skipping all models)",
|
||||
reason: "billing",
|
||||
},
|
||||
{
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-6",
|
||||
error: "Provider anthropic has billing issue (skipping all models)",
|
||||
reason: "billing",
|
||||
},
|
||||
],
|
||||
soonestCooldownExpiry: Date.now() + 60_000,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const runAgentTurnWithFallback = await getRunAgentTurnWithFallback();
|
||||
const result = await runAgentTurnWithFallback({
|
||||
commandBody: "hello",
|
||||
followupRun: createFollowupRun(),
|
||||
sessionCtx: {
|
||||
Provider: "whatsapp",
|
||||
MessageSid: "msg",
|
||||
} as unknown as TemplateContext,
|
||||
opts: {},
|
||||
typingSignals: createMockTypingSignaler(),
|
||||
blockReplyPipeline: null,
|
||||
blockStreamingEnabled: false,
|
||||
resolvedBlockStreamingBreak: "message_end",
|
||||
applyReplyToMode: (payload) => payload,
|
||||
shouldEmitToolResult: () => true,
|
||||
shouldEmitToolOutput: () => false,
|
||||
pendingToolTasks: new Set(),
|
||||
resetSessionAfterCompactionFailure: async () => false,
|
||||
resetSessionAfterRoleOrderingConflict: async () => false,
|
||||
isHeartbeat: false,
|
||||
sessionKey: "main",
|
||||
getActiveSessionEntry: () => undefined,
|
||||
resolvedVerboseLevel: "off",
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("final");
|
||||
if (result.kind === "final") {
|
||||
expect(result.payload.text).toBe("billing");
|
||||
}
|
||||
});
|
||||
|
||||
it("surfaces gateway restart text when fallback exhaustion wraps a drain error", async () => {
|
||||
const { replyOperation, failMock } = createMockReplyOperation();
|
||||
state.runWithModelFallbackMock.mockRejectedValueOnce(
|
||||
|
||||
@@ -311,6 +311,14 @@ function isPureTransientRateLimitSummary(err: unknown): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isPureBillingSummary(err: unknown): boolean {
|
||||
return (
|
||||
isFallbackSummaryError(err) &&
|
||||
err.attempts.length > 0 &&
|
||||
err.attempts.every((attempt) => attempt.reason === "billing")
|
||||
);
|
||||
}
|
||||
|
||||
function isToolResultTurnMismatchError(message: string): boolean {
|
||||
const lower = normalizeLowercaseStringOrEmpty(message);
|
||||
return (
|
||||
@@ -1320,7 +1328,9 @@ export async function runAgentTurnWithFallback(params: {
|
||||
continue;
|
||||
}
|
||||
const message = formatErrorMessage(err);
|
||||
const isBilling = isBillingErrorMessage(message);
|
||||
const isBilling = isFallbackSummaryError(err)
|
||||
? isPureBillingSummary(err)
|
||||
: isBillingErrorMessage(message);
|
||||
const isContextOverflow = !isBilling && isLikelyContextOverflowError(message);
|
||||
const isCompactionFailure = !isBilling && isCompactionFailureError(message);
|
||||
const isSessionCorruption = /function call turn comes immediately after/i.test(message);
|
||||
|
||||
Reference in New Issue
Block a user