Documentation
¶
Overview ¶
Package modelsetup holds what kvit-coder-ui needs to add a model without the person editing a file: the providers it knows, the models.dev catalog that says how each model is served, the endpoint's own list of models, the model row built from those, and a test request that checks a row works. It draws nothing on the terminal; internal/tui asks the questions.
The design is spec/configing.md. The rows built here are written to ~/.kvit-coder/models.yaml by internal/config, which is where the agent finds them on its next turn.
Index ¶
- Constants
- Variables
- func BackendForNPM(npm string) (string, bool)
- func CanOpenBrowser() bool
- func CatalogIDs() []string
- func EffortSummary(opts []config.EffortOption) string
- func Efforts(m *CatalogModel) []config.EffortOption
- func KeyEnvFor(name string) string
- func OpenBrowser(url string) error
- func ProtocolLabel(backend string) string
- func Row(p Provider, c Candidate, taken func(string) bool) config.ModelEntry
- func ServerContext(ctx context.Context, client *http.Client, baseURL string) int
- func SetBackend(p Provider, e *config.ModelEntry, backend string)
- func Slug(s string) string
- func Tokens(n int) string
- type Candidate
- type Catalog
- type CatalogModel
- type CatalogProvider
- type Kind
- type ListedModel
- type ProbeResult
- type Provider
- type ReasoningOption
Constants ¶
const CatalogTTL = 24 * time.Hour
CatalogTTL is how long a fetched catalog is used before it is fetched again. models.dev changes a few times a week; a day keeps a new model a day away at most.
Variables ¶
var CatalogURL = "https://models.dev/api.json"
CatalogURL is where the catalog is fetched from. Tests point it elsewhere.
var Providers = []Provider{ { Key: "zen", Name: "OpenCode Zen", Short: "Zen", Kind: Hosted, BaseURL: "https://opencode.ai/zen/v1", MessagesURL: "https://opencode.ai/zen", KeyEnv: "OPENCODE_API_KEY", KeyPage: "https://opencode.ai/zen", CatalogID: "opencode", EffortField: llm.EffortFieldReasoningEffort, Headers: openCodeHeaders, }, { Key: "go", Name: "OpenCode Go", Short: "Go", Kind: Hosted, BaseURL: "https://opencode.ai/zen/go/v1", MessagesURL: "https://opencode.ai/zen/go", KeyEnv: "OPENCODE_API_KEY", KeyPage: "https://opencode.ai/zen", CatalogID: "opencode-go", EffortField: llm.EffortFieldReasoningEffort, Headers: openCodeHeaders, }, { Key: "local", Name: "Local server", Short: "local", Kind: Local, BaseURL: "http://localhost:8080/v1", }, { Key: "other", Name: "Other endpoint", Kind: Other, }, }
Providers is the list :setup offers, in the order shown. Only endpoints kvit-coder has been used with are listed by name; "Other endpoint" covers the rest. Anthropic's own API is not among them because it expects the key in an x-api-key header, and the messages client sends Authorization: Bearer, which OpenCode accepts.
Functions ¶
func BackendForNPM ¶
BackendForNPM maps the client library models.dev names for a model to the protocol kvit-coder speaks to it. OpenCode serves one gateway over three protocols and models.dev records which by naming the library: the OpenAI library speaks the responses protocol, the Anthropic one the messages protocol, and the generic OpenAI-compatible one (or none named) chat completions. Google's library speaks a protocol kvit-coder does not have.
func CanOpenBrowser ¶
func CanOpenBrowser() bool
CanOpenBrowser reports whether a browser opened here would appear in front of the person: not over SSH, and on Linux only with a display to draw on.
func CatalogIDs ¶
func CatalogIDs() []string
CatalogIDs are the models.dev providers the list above uses, which is all of models.dev that is kept in the cache.
func EffortSummary ¶
func EffortSummary(opts []config.EffortOption) string
EffortSummary is a menu as one phrase: "minimal–xhigh", "max", or "" for none.
func Efforts ¶
func Efforts(m *CatalogModel) []config.EffortOption
Efforts is the :eN menu for a model from its models.dev effort levels, in kvit-coder's order from none to max, starting at high when it is offered and at the highest level otherwise. A model that does not reason, or whose reasoning is only switched on and off, gets no menu.
func KeyEnvFor ¶
KeyEnvFor suggests a variable name for an endpoint's key: "My Proxy" gives MY_PROXY_API_KEY.
func OpenBrowser ¶
OpenBrowser opens url in the default browser and does not wait for it.
func ProtocolLabel ¶
ProtocolLabel is how a protocol is written for a person.
func Row ¶
Row builds the models: row for a candidate. taken reports whether an id is already in use, so the row gets one that is not ("kimi-k3-go", then "kimi-k3-go-2").
func ServerContext ¶
ServerContext asks a llama.cpp server for the context size it was started with, from GET /props at the address without its /v1. Any other server, or any failure, gives 0, and the row is written without a context size.
func SetBackend ¶
func SetBackend(p Provider, e *config.ModelEntry, backend string)
SetBackend points a row at another protocol of the same provider, as when a test request found the one that answers.
Types ¶
type Candidate ¶
type Candidate struct {
ID string
Listed ListedModel
Catalog *CatalogModel // nil when models.dev does not describe the model
// Backend is the protocol the model is served over, from models.dev; for
// a model models.dev does not describe it is the provider's usual one,
// and a test request finds the real one (see Probe).
Backend string
// Unsupported says why the model cannot be used, such as a protocol
// kvit-coder does not speak. Empty when it can.
Unsupported string
}
Candidate is one model :setup can offer: an id the endpoint listed, joined with what models.dev says about it.
func Candidates ¶
func Candidates(p Provider, listed []ListedModel, cat *Catalog) []Candidate
Candidates joins an endpoint's model list with the catalog, in the endpoint's order.
type Catalog ¶
type Catalog struct {
FetchedAt time.Time `json:"fetched_at"`
Providers map[string]CatalogProvider `json:"providers"`
}
Catalog is the part of models.dev kvit-coder-ui uses: the providers in Providers, keyed by their models.dev id.
func FetchCatalog ¶
FetchCatalog downloads models.dev and keeps the providers in Providers.
func LoadCatalog ¶
func LoadCatalog(ctx context.Context, client *http.Client, dir string, now time.Time) (*Catalog, string, error)
LoadCatalog returns the catalog from the cache in dir when it is younger than CatalogTTL, and fetches it otherwise. When the fetch fails and an older cache exists, that cache is returned with a warning; when there is no cache either, the error is returned and :setup carries on without the catalog, asking for what it would have said.
func ParseCatalog ¶
ParseCatalog reads models.dev's api.json, keeping only the providers in Providers. The other two hundred or so are decoded no further than their raw bytes.
func (*Catalog) Model ¶
func (c *Catalog) Model(providerID, modelID string) *CatalogModel
Model looks a model up; nil when the catalog does not describe it.
func (*Catalog) ProviderNPM ¶
ProviderNPM is the client library a provider uses for models that name none of their own.
type CatalogModel ¶
type CatalogModel struct {
ID string `json:"id"`
Name string `json:"name"`
Reasoning bool `json:"reasoning"`
ReasoningOptions []ReasoningOption `json:"reasoning_options,omitempty"`
ToolCall bool `json:"tool_call"`
Status string `json:"status,omitempty"`
Limit struct {
Context int `json:"context"`
Output int `json:"output"`
} `json:"limit"`
Cost *struct {
Input float64 `json:"input"`
Output float64 `json:"output"`
} `json:"cost,omitempty"`
// Provider overrides the provider's client library for this model, which
// is how one gateway serving three protocols is described.
Provider *struct {
NPM string `json:"npm"`
} `json:"provider,omitempty"`
}
CatalogModel is one model of a models.dev provider.
type CatalogProvider ¶
type CatalogProvider struct {
ID string `json:"id"`
Name string `json:"name"`
NPM string `json:"npm"`
API string `json:"api"`
Env []string `json:"env"`
Models map[string]CatalogModel `json:"models"`
}
CatalogProvider is one models.dev provider.
type Kind ¶
type Kind int
Kind is how a provider is set up.
const ( // Hosted is a gateway with a fixed address, a key, and an entry on // models.dev that describes its models. Hosted Kind = iota // Local is a server the person runs, such as llama.cpp, vLLM, Ollama or // LM Studio: its address is asked, it needs no key, and models.dev knows // nothing about what it serves. Local // Other is any endpoint that speaks one of the three protocols. Its name, // address, protocol and key variable are all asked. Other )
type ListedModel ¶
type ListedModel struct {
ID string
// Context is the context size the endpoint reports, or 0: vLLM puts
// max_model_len in its model list, and for llama.cpp ServerContext asks
// /props. Hosted gateways report nothing here; models.dev has it.
Context int
}
ListedModel is one model an endpoint says it serves.
func ListModels ¶
func ListModels(ctx context.Context, client *http.Client, baseURL, key string, headers map[string]string) ([]ListedModel, error)
ListModels asks an OpenAI-compatible endpoint which models it serves: GET <baseURL>/models, the request llama.cpp, vLLM, Ollama, LM Studio and the hosted gateways all answer. The list is sorted by id.
type ProbeResult ¶
type ProbeResult struct {
// Backend is the protocol that answered; it differs from the row's when
// the row's own was refused and another one answered.
Backend string
// Err is why no protocol answered; nil when one did.
Err error
// KeyRefused is set when the endpoint answered 401 or 403.
KeyRefused bool
}
ProbeResult is what a test request found out about a row.
func Probe ¶
func Probe(ctx context.Context, p Provider, entry config.ModelEntry, key string, headers map[string]string, tryOthers bool) ProbeResult
Probe sends the model in entry one short question through the same client the agent uses, with key and headers, at the lowest effort the row offers.
When tryOthers is set and the endpoint refuses the row's protocol for any reason other than the key, a rate limit or the network, the other two protocols are tried and the first that answers is reported. That is how the protocol of a model models.dev does not describe is found: a 200 from one of them is good evidence, where a guess is not.
type Provider ¶
type Provider struct {
// Key is the short name put at the end of a row's id ("kimi-k3-go"), so
// the same model from two providers gets two ids.
Key string
// Name is what the list shows, and Short is what goes in brackets after
// a model's name ("Kimi K3 (Go)").
Name, Short string
Kind Kind
// BaseURL is where requests go. MessagesURL is the address for models
// served over Claude's messages protocol, for which kvit-coder appends
// /v1/messages itself, so it stops before /v1.
BaseURL, MessagesURL string
// KeyEnv is the environment variable a row reads its key from; a key
// typed into :setup is saved under the same name in credentials.json.
KeyEnv string
// KeyPage is the web page where a key is issued.
KeyPage string
// CatalogID is the provider's id on models.dev.
CatalogID string
// EffortField is where a chat-completions request carries the effort for
// this provider; see llm.effort_field in docs/configuration.md.
EffortField string
// Headers are put on every row for this provider.
Headers []string
}
Provider is one entry of the list :setup opens with.
func LocalProvider ¶
LocalProvider is the local server entry pointed at baseURL.
func OtherProvider ¶
OtherProvider describes an endpoint the person named in :setup. name is what they typed, baseURL the address, and keyEnv the variable for its key (empty when it needs none).
func (Provider) BaseURLFor ¶
BaseURLFor is where a row for this provider sends requests over backend: the messages address for the messages protocol, the usual one otherwise.
type ReasoningOption ¶
ReasoningOption is one way a model's reasoning is controlled. Only the "effort" kind, with its list of accepted levels, maps onto kvit-coder's efforts; models.dev also has "toggle" and "budget_tokens".