This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/routing/fallback.go
2026-03-02 10:31:18 +00:00

60 lines
1.8 KiB
Go

package routing
import (
"fmt"
"log"
)
// ── Fallback Runner ─────────────────────────
// DispatchFunc is called by the fallback runner to attempt a request
// against a specific candidate. Returns nil on success, error on failure.
// The caller is responsible for setting up SSE headers, streaming, etc.
// This is NOT the actual dispatch — it's the "try this candidate" callback.
type DispatchFunc func(candidate Candidate) error
// RunWithFallback tries candidates in order until one succeeds.
// Returns the candidate that succeeded, the decision (updated with fallback
// depth), and any error if all candidates fail.
//
// maxRetries controls how many fallback attempts are made beyond the first.
// maxRetries=0 means only try the first candidate (no fallback).
// maxRetries=-1 means try all candidates.
func RunWithFallback(candidates []Candidate, decision *Decision, maxRetries int, dispatch DispatchFunc) (Candidate, *Decision, error) {
if len(candidates) == 0 {
return Candidate{}, decision, fmt.Errorf("no routing candidates available")
}
// Determine how many candidates to try
limit := 1 // just the first
if maxRetries < 0 {
limit = len(candidates) // try all
} else if maxRetries > 0 {
limit = 1 + maxRetries
if limit > len(candidates) {
limit = len(candidates)
}
}
var lastErr error
for i := 0; i < limit; i++ {
c := candidates[i]
err := dispatch(c)
if err == nil {
// Success
if decision != nil {
decision.Selected = c.ConfigID
decision.FallbackDepth = i
}
return c, decision, nil
}
lastErr = err
if i < limit-1 {
log.Printf("[routing] candidate %s (%s) failed: %v — trying next", c.ConfigID, c.ProviderID, err)
}
}
return Candidate{}, decision, fmt.Errorf("all %d routing candidates failed; last error: %w", limit, lastErr)
}