diff --git a/changelog.d/fixes/9303-recovery-hint-all-targets-skipped.md b/changelog.d/fixes/9303-recovery-hint-all-targets-skipped.md new file mode 100644 index 0000000000..78649d651f --- /dev/null +++ b/changelog.d/fixes/9303-recovery-hint-all-targets-skipped.md @@ -0,0 +1 @@ +- fix(combo): recovery hint for all_targets_skipped now points at provider quota/availability instead of 'transient, just retry' (#9303) diff --git a/open-sse/services/combo/pinRecovery.ts b/open-sse/services/combo/pinRecovery.ts index 6531edc15a..d8cd728443 100644 --- a/open-sse/services/combo/pinRecovery.ts +++ b/open-sse/services/combo/pinRecovery.ts @@ -53,6 +53,12 @@ export function buildRecoveryHint( next_step: "Strict context requirements removed every target (known context windows are below minContextWindow). Lower minContextWindow, switch contextFilterMode to lenient, or add larger-context models.", }; + case "all_targets_skipped": + return { + action: "switch-combo", + next_step: + "Every target was skipped before dispatch (capability pre-filter narrowed the pool and the remaining targets were all quota-exhausted/unavailable). Check the provider's quota in /dashboard/providers, reconnect or top up the account, or switch to a combo/model that has a healthy capability-matching target.", + }; default: return { action: "retry", diff --git a/tests/unit/9303-recovery-hint-all-targets-skipped.test.ts b/tests/unit/9303-recovery-hint-all-targets-skipped.test.ts new file mode 100644 index 0000000000..7b7319eab1 --- /dev/null +++ b/tests/unit/9303-recovery-hint-all-targets-skipped.test.ts @@ -0,0 +1,31 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { buildRecoveryHint } = await import("../../open-sse/services/combo/pinRecovery.ts"); + +test( + "#9303: buildRecoveryHint('all_targets_skipped') must return an actionable " + + "hint, not the generic 'transient, just retry' default", + () => { + const hint = buildRecoveryHint("all_targets_skipped"); + + assert.notEqual( + hint.action, + "retry", + "the pre-dispatch full-exhaustion terminal reason must not be classified as a " + + "generically 'retry'-able transient failure — the reporter's log shows the " + + "identical exhaustion recurring across ~9 consecutive requests with no recovery" + ); + assert.doesNotMatch( + hint.next_step, + /failed transiently/i, + "must not tell the client this was transient when the whole target pool was " + + "pre-filtered/quota-exhausted before a single dispatch attempt was made" + ); + assert.match( + hint.next_step, + /quota|availability|provider/s, + "the hint must point at the provider quota/availability as the actionable next step" + ); + } +); \ No newline at end of file