v0.0.33 b9e7517

models.go RawHistoryFolder

1package provider
2
3import (
4	"fmt"
5	"sync"
6)
7
8// Model describes a single LLM we know about.
9type Model struct {
10	Provider      string // "anthropic" | "openai"
11	ID            string // API id
12	DisplayName   string
13	ContextWindow int
14	MaxOutput     int
15	Reasoning     bool // supports reasoning/thinking
16
17	// Prices are USD per 1M tokens.
18	PriceInput      float64
19	PriceOutput     float64
20	PriceCacheRead  float64
21	PriceCacheWrite float64
22
23	// Speculative marks models whose ids are known from the upstream
24	// vendor's CLI but not yet live on their public API. They'll 404
25	// today but start working the moment the provider flips the switch.
26	Speculative bool
27
28	// Source is where this model entry came from: "catalog" (baked in),
29	// "live" (discovered via /v1/models), or "cache" (loaded from the
30	// on-disk cache). Informational.
31	Source string
32}
33
34// Catalog is the hardcoded, read-only list of supported models.
35// Prices are USD per 1M tokens. The list is curated to what zot's
36// clients (Anthropic Messages + OpenAI Chat Completions) can actually
37// talk to; models that are only reachable through the OpenAI Responses
38// API (o1-pro, o3-pro, gpt-5-pro) are omitted.
39var Catalog = []Model{
40	// ---- Anthropic / Claude 4.x ----
41	{
42		Provider: "anthropic", ID: "claude-sonnet-4-5", DisplayName: "Claude Sonnet 4.5",
43		ContextWindow: 200000, MaxOutput: 64000, Reasoning: true,
44		PriceInput: 3.00, PriceOutput: 15.00, PriceCacheRead: 0.30, PriceCacheWrite: 3.75,
45	},
46	{
47		Provider: "anthropic", ID: "claude-opus-4-1", DisplayName: "Claude Opus 4.1",
48		ContextWindow: 200000, MaxOutput: 32000, Reasoning: true,
49		PriceInput: 15.00, PriceOutput: 75.00, PriceCacheRead: 1.50, PriceCacheWrite: 18.75,
50	},
51	{
52		Provider: "anthropic", ID: "claude-opus-4-0", DisplayName: "Claude Opus 4",
53		ContextWindow: 200000, MaxOutput: 32000, Reasoning: true,
54		PriceInput: 15.00, PriceOutput: 75.00, PriceCacheRead: 1.50, PriceCacheWrite: 18.75,
55	},
56	{
57		Provider: "anthropic", ID: "claude-sonnet-4-0", DisplayName: "Claude Sonnet 4",
58		ContextWindow: 200000, MaxOutput: 64000, Reasoning: true,
59		PriceInput: 3.00, PriceOutput: 15.00, PriceCacheRead: 0.30, PriceCacheWrite: 3.75,
60	},
61	{
62		Provider: "anthropic", ID: "claude-haiku-4-5", DisplayName: "Claude Haiku 4.5",
63		ContextWindow: 200000, MaxOutput: 64000, Reasoning: true,
64		PriceInput: 1.00, PriceOutput: 5.00, PriceCacheRead: 0.10, PriceCacheWrite: 1.25,
65	},
66
67	// ---- Anthropic / Claude 3.x (legacy) ----
68	{
69		Provider: "anthropic", ID: "claude-3-7-sonnet-20250219", DisplayName: "Claude Sonnet 3.7",
70		ContextWindow: 200000, MaxOutput: 64000, Reasoning: true,
71		PriceInput: 3.00, PriceOutput: 15.00, PriceCacheRead: 0.30, PriceCacheWrite: 3.75,
72	},
73	{
74		Provider: "anthropic", ID: "claude-3-5-sonnet-20241022", DisplayName: "Claude Sonnet 3.5 v2",
75		ContextWindow: 200000, MaxOutput: 8192, Reasoning: false,
76		PriceInput: 3.00, PriceOutput: 15.00, PriceCacheRead: 0.30, PriceCacheWrite: 3.75,
77	},
78	{
79		Provider: "anthropic", ID: "claude-3-5-haiku-latest", DisplayName: "Claude Haiku 3.5",
80		ContextWindow: 200000, MaxOutput: 8192, Reasoning: false,
81		PriceInput: 0.80, PriceOutput: 4.00, PriceCacheRead: 0.08, PriceCacheWrite: 1.00,
82	},
83	{
84		Provider: "anthropic", ID: "claude-3-opus-20240229", DisplayName: "Claude Opus 3",
85		ContextWindow: 200000, MaxOutput: 4096, Reasoning: false,
86		PriceInput: 15.00, PriceOutput: 75.00, PriceCacheRead: 1.50, PriceCacheWrite: 18.75,
87	},
88
89	// ---- OpenAI / GPT-5 family ----
90	{
91		Provider: "openai", ID: "gpt-5", DisplayName: "GPT-5",
92		ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
93		PriceInput: 1.25, PriceOutput: 10.00, PriceCacheRead: 0.125,
94	},
95	{
96		Provider: "openai", ID: "gpt-5-mini", DisplayName: "GPT-5 mini",
97		ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
98		PriceInput: 0.25, PriceOutput: 2.00, PriceCacheRead: 0.025,
99	},
100	{
101		Provider: "openai", ID: "gpt-5-nano", DisplayName: "GPT-5 nano",
102		ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
103		PriceInput: 0.05, PriceOutput: 0.40, PriceCacheRead: 0.005,
104	},
105
106	// ---- OpenAI / GPT-4.1 family ----
107	{
108		Provider: "openai", ID: "gpt-4.1", DisplayName: "GPT-4.1",
109		ContextWindow: 1047576, MaxOutput: 32768, Reasoning: false,
110		PriceInput: 2.00, PriceOutput: 8.00, PriceCacheRead: 0.50,
111	},
112	{
113		Provider: "openai", ID: "gpt-4.1-mini", DisplayName: "GPT-4.1 mini",
114		ContextWindow: 1047576, MaxOutput: 32768, Reasoning: false,
115		PriceInput: 0.40, PriceOutput: 1.60, PriceCacheRead: 0.10,
116	},
117	{
118		Provider: "openai", ID: "gpt-4.1-nano", DisplayName: "GPT-4.1 nano",
119		ContextWindow: 1047576, MaxOutput: 32768, Reasoning: false,
120		PriceInput: 0.10, PriceOutput: 0.40, PriceCacheRead: 0.03,
121	},
122
123	// ---- OpenAI / GPT-4o family ----
124	{
125		Provider: "openai", ID: "gpt-4o", DisplayName: "GPT-4o",
126		ContextWindow: 128000, MaxOutput: 16384, Reasoning: false,
127		PriceInput: 2.50, PriceOutput: 10.00, PriceCacheRead: 1.25,
128	},
129	{
130		Provider: "openai", ID: "gpt-4o-mini", DisplayName: "GPT-4o mini",
131		ContextWindow: 128000, MaxOutput: 16384, Reasoning: false,
132		PriceInput: 0.15, PriceOutput: 0.60, PriceCacheRead: 0.08,
133	},
134
135	// ---- OpenAI / reasoning models ----
136	{
137		Provider: "openai", ID: "o4-mini", DisplayName: "o4-mini",
138		ContextWindow: 200000, MaxOutput: 100000, Reasoning: true,
139		PriceInput: 1.10, PriceOutput: 4.40, PriceCacheRead: 0.275,
140	},
141	{
142		Provider: "openai", ID: "o3", DisplayName: "o3",
143		ContextWindow: 200000, MaxOutput: 100000, Reasoning: true,
144		PriceInput: 2.00, PriceOutput: 8.00, PriceCacheRead: 0.50,
145	},
146	{
147		Provider: "openai", ID: "o3-mini", DisplayName: "o3-mini",
148		ContextWindow: 200000, MaxOutput: 100000, Reasoning: true,
149		PriceInput: 1.10, PriceOutput: 4.40, PriceCacheRead: 0.55,
150	},
151	{
152		Provider: "openai", ID: "o1", DisplayName: "o1",
153		ContextWindow: 200000, MaxOutput: 100000, Reasoning: true,
154		PriceInput: 15.00, PriceOutput: 60.00, PriceCacheRead: 7.50,
155	},
156
157	// ---- Speculative: Anthropic ----
158	{
159		Provider: "anthropic", ID: "claude-opus-4-5", DisplayName: "Claude Opus 4.5",
160		ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true,
161		PriceInput: 5.00, PriceOutput: 25.00, PriceCacheRead: 0.50, PriceCacheWrite: 6.25,
162		Speculative: true,
163	},
164	{
165		Provider: "anthropic", ID: "claude-opus-4-6", DisplayName: "Claude Opus 4.6",
166		ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true,
167		PriceInput: 5.00, PriceOutput: 25.00, PriceCacheRead: 0.50, PriceCacheWrite: 6.25,
168		Speculative: true,
169	},
170	{
171		Provider: "anthropic", ID: "claude-opus-4-7", DisplayName: "Claude Opus 4.7",
172		ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true,
173		PriceInput: 5.00, PriceOutput: 25.00, PriceCacheRead: 0.50, PriceCacheWrite: 6.25,
174		Speculative: true,
175	},
176	{
177		Provider: "anthropic", ID: "claude-sonnet-4-6", DisplayName: "Claude Sonnet 4.6",
178		ContextWindow: 1000000, MaxOutput: 64000, Reasoning: true,
179		PriceInput: 3.00, PriceOutput: 15.00, PriceCacheRead: 0.30, PriceCacheWrite: 3.75,
180		Speculative: true,
181	},
182
183	// ---- Speculative: OpenAI ----
184	{
185		Provider: "openai", ID: "gpt-5.1", DisplayName: "GPT-5.1",
186		ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
187		PriceInput: 1.25, PriceOutput: 10.00, PriceCacheRead: 0.125,
188		Speculative: true,
189	},
190	{
191		Provider: "openai", ID: "gpt-5.2", DisplayName: "GPT-5.2",
192		ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
193		PriceInput: 1.75, PriceOutput: 14.00, PriceCacheRead: 0.175,
194		Speculative: true,
195	},
196	{
197		Provider: "openai", ID: "gpt-5.3", DisplayName: "GPT-5.3",
198		ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
199		PriceInput: 1.75, PriceOutput: 14.00, PriceCacheRead: 0.175,
200		Speculative: true,
201	},
202	{
203		Provider: "openai", ID: "gpt-5.4", DisplayName: "GPT-5.4",
204		ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
205		PriceInput: 2.50, PriceOutput: 15.00, PriceCacheRead: 0.25,
206		Speculative: true,
207	},
208	{
209		Provider: "openai", ID: "gpt-5.4-mini", DisplayName: "GPT-5.4 mini",
210		ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
211		PriceInput: 0.75, PriceOutput: 4.50, PriceCacheRead: 0.075,
212		Speculative: true,
213	},
214}
215
216// DefaultModel is used when the user does not specify one.
217var DefaultModel = Catalog[0] // claude-sonnet-4-5
218
219// ----- active (merged) catalog -----
220//
221// Callers should use Active() / FindModel / ModelsForProvider for
222// lookups. They return the baked-in Catalog merged with any live
223// models loaded via SetLiveModels.
224
225var (
226	activeMu sync.RWMutex
227	active   []Model = Catalog // default: just the static catalog
228)
229
230// SetLiveModels replaces the "live" overlay used by the active catalog.
231// Typically called after a successful /v1/models discovery or on load
232// from the on-disk cache.
233func SetLiveModels(live []Model) {
234	activeMu.Lock()
235	defer activeMu.Unlock()
236	if len(live) == 0 {
237		active = Catalog
238		return
239	}
240	active = MergeCatalog(live)
241}
242
243// Active returns the current merged catalog.
244func Active() []Model {
245	activeMu.RLock()
246	defer activeMu.RUnlock()
247	out := make([]Model, len(active))
248	copy(out, active)
249	return out
250}
251
252// FindModel returns a Model by id, optionally constrained by provider.
253// If provider is empty, the first matching id is returned. Looks up
254// against the merged active catalog.
255func FindModel(provider, id string) (Model, error) {
256	for _, m := range Active() {
257		if m.ID == id && (provider == "" || m.Provider == provider) {
258			return m, nil
259		}
260	}
261	return Model{}, fmt.Errorf("unknown model %q (provider=%q)", id, provider)
262}
263
264// ModelsForProvider returns all models for the given provider, from the
265// merged active catalog.
266func ModelsForProvider(provider string) []Model {
267	var out []Model
268	for _, m := range Active() {
269		if m.Provider == provider {
270			out = append(out, m)
271		}
272	}
273	return out
274}
275
276// ComputeCost returns the USD cost for the given usage on model m.
277func ComputeCost(m Model, u Usage) float64 {
278	const per = 1_000_000.0
279	return float64(u.InputTokens)*m.PriceInput/per +
280		float64(u.OutputTokens)*m.PriceOutput/per +
281		float64(u.CacheReadTokens)*m.PriceCacheRead/per +
282		float64(u.CacheWriteTokens)*m.PriceCacheWrite/per
283}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close