package routing import ( "context" "fmt" "log/slog" ) // CompleteFunc matches the signature used by every skill package's Config. type CompleteFunc func(ctx context.Context, model, system, user string) (string, int64, error) // RunInput captures the per-call inputs the dispatch wrapper needs. type RunInput struct { Skill string System string User string SessionID string ProjectRoot string } // Router composes a pass-rate fetcher, a decision policy, a session logger, // and a LiteLLM client. Skill packages receive Router.Run as their CompleteFunc. type Router struct { Fetcher *Fetcher Logger *Logger Policy Policy LocalModel string ClaudeModel string Complete CompleteFunc } // Run executes one skill call: decides local vs claude, calls LiteLLM, logs the // decision. On local-side error, falls open by retrying once on the Claude model. func (r *Router) Run(ctx context.Context, in RunInput) (string, int64, error) { pr, ferr := r.Fetcher.Get(ctx, in.Skill) if ferr != nil { slog.Warn("router: pass-rate unreachable, defaulting to local", "skill", in.Skill, "err", ferr) pr = nil } hash := CanonicalHash(in.System, in.User) decision := r.Policy.Decide(pr, hash) model := r.ClaudeModel if decision == DecideLocal { model = r.LocalModel } out, ms, err := r.Complete(ctx, model, in.System, in.User) _ = r.Logger.LogDecision(ctx, LogEntry{ SessionID: in.SessionID, Skill: in.Skill, Decision: decision.String(), Message: fmt.Sprintf("model=%s, pass_rate=%s", model, formatPassRate(pr)), ProjectRoot: in.ProjectRoot, DurationMs: ms, Failed: err != nil, }) if err != nil && decision == DecideLocal { slog.Warn("router: local failed, falling open to claude", "skill", in.Skill, "err", err) out, ms, err = r.Complete(ctx, r.ClaudeModel, in.System, in.User) _ = r.Logger.LogDecision(ctx, LogEntry{ SessionID: in.SessionID, Skill: in.Skill, Decision: "claude_fallback", Message: fmt.Sprintf("model=%s, after-local-error", r.ClaudeModel), ProjectRoot: in.ProjectRoot, DurationMs: ms, Failed: err != nil, }) } return out, ms, err } func formatPassRate(pr *float64) string { if pr == nil { return "null" } return fmt.Sprintf("%.2f", *pr) }