modelsetup

package
v0.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 19 Imported by: 0

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

View Source
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

View Source
var CatalogURL = "https://models.dev/api.json"

CatalogURL is where the catalog is fetched from. Tests point it elsewhere.

View Source
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

func BackendForNPM(npm string) (string, bool)

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

func KeyEnvFor(name string) string

KeyEnvFor suggests a variable name for an endpoint's key: "My Proxy" gives MY_PROXY_API_KEY.

func OpenBrowser

func OpenBrowser(url string) error

OpenBrowser opens url in the default browser and does not wait for it.

func ProtocolLabel

func ProtocolLabel(backend string) string

ProtocolLabel is how a protocol is written for a person.

func Row

func Row(p Provider, c Candidate, taken func(string) bool) config.ModelEntry

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

func ServerContext(ctx context.Context, client *http.Client, baseURL string) int

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.

func Slug

func Slug(s string) string

Slug turns a name or model id into the lowercase letters, digits, dots and dashes a row id is made of: "hf:zai-org/GLM-4.7" gives "hf-zai-org-glm-4.7".

func Tokens

func Tokens(n int) string

Tokens writes a context size the way people say it: 1048576 is "1.05M", 262144 is "262K".

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.

func (Candidate) Described

func (c Candidate) Described() bool

Described reports whether models.dev describes the model, which is what makes its protocol, context size and effort levels known without asking.

func (Candidate) Detail

func (c Candidate) Detail() string

Detail is the line the model list shows beside a candidate's id.

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

func FetchCatalog(ctx context.Context, client *http.Client, now time.Time) (*Catalog, error)

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

func ParseCatalog(r io.Reader, now time.Time) (*Catalog, error)

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

func (c *Catalog) ProviderNPM(providerID string) string

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.

func (ProbeResult) OK

func (r ProbeResult) OK() bool

OK reports whether the model answered.

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

func LocalProvider(baseURL string) Provider

LocalProvider is the local server entry pointed at baseURL.

func OtherProvider

func OtherProvider(name, baseURL, keyEnv string) Provider

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

func (p Provider) BaseURLFor(backend string) string

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

type ReasoningOption struct {
	Type   string   `json:"type"`
	Values []string `json:"values,omitempty"`
}

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".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL