fix(provider): classify HTTP 404 as retriable to trigger model fallback

HTTP 404 errors (e.g., "No endpoints found for model" from OpenRouter)
were previously unclassified, causing FallbackChain.Execute to return
"unclassified error" immediately without trying fallback candidates.

This change adds 404 to classifyByStatus() returning FailoverTimeout,
which is retriable, so the fallback chain proceeds to the next model.

Fixes #2334

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
merlinmiao 2026-04-05 18:13:51 +08:00
parent 15a70ac45c
commit 5e1fe65027
2 changed files with 19 additions and 0 deletions

View file

@ -177,6 +177,8 @@ func classifyByStatus(status int) FailoverReason {
return FailoverAuth
case status == 402:
return FailoverBilling
case status == 404:
return FailoverTimeout
case status == 408:
return FailoverTimeout
case status == 429:

View file

@ -39,6 +39,7 @@ func TestClassifyError_StatusCodes(t *testing.T) {
{401, FailoverAuth},
{403, FailoverAuth},
{402, FailoverBilling},
{404, FailoverTimeout},
{408, FailoverTimeout},
{429, FailoverRateLimit},
{400, FailoverFormat},
@ -264,6 +265,22 @@ func TestClassifyError_UnknownError(t *testing.T) {
}
}
func TestClassifyError_404TriggersFallback(t *testing.T) {
// OpenRouter returns 404 when model endpoint doesn't exist.
// This should be classified as FailoverTimeout (retriable) so fallback kicks in.
err := fmt.Errorf("fallback: unclassified error from openrouter/qwen3.6-plus-preview:free: API request failed:\n Status: 404\n Body: {\"error\":{\"message\":\"No endpoints found for qwen3.6-plus-preview:free.\",\"code\":404}}")
result := ClassifyError(err, "openrouter", "qwen3.6-plus-preview:free")
if result == nil {
t.Fatal("expected non-nil for 404 error")
}
if result.Reason != FailoverTimeout {
t.Errorf("reason = %q, want %q (retriable so fallback is triggered)", result.Reason, FailoverTimeout)
}
if !result.IsRetriable() {
t.Errorf("expected 404 to be retriable")
}
}
func TestClassifyError_ProviderModelPropagation(t *testing.T) {
err := errors.New("rate limit exceeded")
result := ClassifyError(err, "my-provider", "my-model")