// Package models implements MODEL ROUTING v0: a static, config-only map from // task class -> tier alias -> concrete proxy model. There is deliberately no // heuristic code: the tier map in harness.toml is the whole contract, and an // unknown class is a hard error so routing stays auditable. package models import ( "fmt" "sort" ) // Router resolves task classes to concrete model names. type Router struct { tiers map[string]string // tier alias -> concrete model classes map[string]string // task class -> tier alias defaultTier string } // Decision records one routing outcome for the REPORT trail. type Decision struct { Class string Tier string Model string } // NewRouter validates the config maps and returns a Router. func NewRouter(tiers, classes map[string]string, defaultTier string) (*Router, error) { if len(tiers) == 0 { return nil, fmt.Errorf("models: tier map is empty") } if _, ok := tiers[defaultTier]; !ok { return nil, fmt.Errorf("models: default tier %q has no model entry", defaultTier) } for class, tier := range classes { if _, ok := tiers[tier]; !ok { return nil, fmt.Errorf("models: class %q points at unknown tier %q", class, tier) } } return &Router{tiers: tiers, classes: classes, defaultTier: defaultTier}, nil } // Resolve maps a task class to its tier and concrete model. An empty class // falls back to the default tier; an unknown class is an error. func (r *Router) Resolve(class string) (Decision, error) { tier := r.defaultTier if class != "" { t, ok := r.classes[class] if !ok { return Decision{}, fmt.Errorf("models: unknown task class %q (add it to [models.classes] in harness.toml)", class) } tier = t } model, ok := r.tiers[tier] if !ok { return Decision{}, fmt.Errorf("models: tier %q has no model entry", tier) } return Decision{Class: class, Tier: tier, Model: model}, nil } // Tiers returns the sorted tier aliases known to the router. func (r *Router) Tiers() []string { out := make([]string, 0, len(r.tiers)) for k := range r.tiers { out = append(out, k) } sort.Strings(out) return out } // Classes returns the sorted task classes known to the router (the // `harness serve` model catalog is derived from it: one model per class). func (r *Router) Classes() []string { out := make([]string, 0, len(r.classes)) for k := range r.classes { out = append(out, k) } sort.Strings(out) return out }