// Package agentmodel holds the ONE place that knows how pansy turns a model spec // into a majordomo model: which provider to register and under which token. // // It exists as a leaf so both internal/agent (which builds the run loop) and // internal/service (which validates a spec before storing it as a setting) can // share that knowledge without an import cycle — agent imports service, so the // shared bit can live in neither of them. package agentmodel import ( "errors" "fmt" "strings" "gitea.stevedudenhoeffer.com/steve/majordomo" "gitea.stevedudenhoeffer.com/steve/majordomo/llm" "gitea.stevedudenhoeffer.com/steve/majordomo/provider/ollama" ) // registry builds the private majordomo registry pansy uses. // // Private, not the package-level default: pansy passes the key it was configured // with rather than depending on ambient environment, and majordomo's own // ollama-cloud preset reads OLLAMA_API_KEY while pansy (like gadfly) is // configured with OLLAMA_CLOUD_API_KEY. Registering the provider explicitly // makes that bridge visible instead of a mysterious empty token. func registry(apiKey string) *majordomo.Registry { reg := majordomo.New() reg.RegisterProvider(ollama.Cloud(ollama.WithToken(apiKey))) return reg } // Resolve parses a model spec against pansy's registry into a live model. The // spec goes to Parse VERBATIM — the grammar, including comma-separated failover // chains, is majordomo's, and re-implementing any of it here would only mean two // places to update when it grows. func Resolve(apiKey, spec string) (llm.Model, error) { if strings.TrimSpace(spec) == "" { return nil, errors.New("agentmodel: empty model spec") } m, err := registry(apiKey).Parse(spec) if err != nil { return nil, fmt.Errorf("agentmodel: resolve %q: %w", spec, err) } return m, nil } // Validate reports whether a spec resolves, without building anything the caller // keeps — the cheap, deterministic check a settings save runs to reject a typo. // // It does NOT make a live call, so it needs no working key and won't catch a // model that is merely absent upstream; that surfaces on first use. Parse // resolving (known provider, well-formed spec) is the half worth doing eagerly. func Validate(apiKey, spec string) error { _, err := Resolve(apiKey, spec) return err }