okengine 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/AGENTS.md +1 -1
  2. package/manifest.v1.schema.json +9 -1
  3. package/package.json +3 -3
  4. package/site/content/docs/console/runs.mdx +10 -0
  5. package/site/content/docs/console/traces.mdx +4 -0
  6. package/site/content/docs/elements/ai.mdx +54 -32
  7. package/site/content/docs/elements/channel.mdx +97 -9
  8. package/site/content/docs/elements/clock.mdx +1 -0
  9. package/site/content/docs/elements/flow.mdx +42 -8
  10. package/site/content/docs/get-started/basic-usage.mdx +5 -1
  11. package/site/content/docs/get-started/installation.mdx +9 -5
  12. package/site/content/docs/meta.json +2 -0
  13. package/site/content/docs/providers/azure-redis.mdx +85 -0
  14. package/site/content/docs/providers/cockroachdb.mdx +86 -0
  15. package/site/content/docs/providers/digitalocean-caching.mdx +88 -0
  16. package/site/content/docs/providers/dragonfly-cloud.mdx +82 -0
  17. package/site/content/docs/providers/elasticache.mdx +92 -0
  18. package/site/content/docs/providers/index.mdx +101 -0
  19. package/site/content/docs/providers/memorystore.mdx +89 -0
  20. package/site/content/docs/providers/meta.json +20 -0
  21. package/site/content/docs/providers/neon.mdx +115 -0
  22. package/site/content/docs/providers/redis-cloud.mdx +93 -0
  23. package/site/content/docs/providers/supabase.mdx +117 -0
  24. package/site/content/docs/providers/upstash.mdx +91 -0
  25. package/site/content/docs/providers/yugabytedb.mdx +95 -0
  26. package/site/content/docs/recipes/caddy.mdx +128 -0
  27. package/site/content/docs/recipes/cockroachdb.mdx +127 -0
  28. package/site/content/docs/recipes/dragonfly.mdx +125 -0
  29. package/site/content/docs/recipes/index.mdx +146 -0
  30. package/site/content/docs/recipes/llama-cpp.mdx +153 -0
  31. package/site/content/docs/recipes/mailpit.mdx +136 -0
  32. package/site/content/docs/recipes/meilisearch.mdx +139 -0
  33. package/site/content/docs/recipes/meta.json +31 -0
  34. package/site/content/docs/recipes/ollama.mdx +142 -0
  35. package/site/content/docs/recipes/openbao.mdx +141 -0
  36. package/site/content/docs/recipes/pgdog.mdx +137 -0
  37. package/site/content/docs/recipes/postgres.mdx +136 -0
  38. package/site/content/docs/recipes/redis.mdx +130 -0
  39. package/site/content/docs/recipes/rustfs.mdx +148 -0
  40. package/site/content/docs/recipes/sglang.mdx +105 -0
  41. package/site/content/docs/recipes/supabase-docker.mdx +145 -0
  42. package/site/content/docs/recipes/timescale.mdx +129 -0
  43. package/site/content/docs/recipes/traefik.mdx +136 -0
  44. package/site/content/docs/recipes/valkey.mdx +121 -0
  45. package/site/content/docs/recipes/vllm.mdx +106 -0
  46. package/site/content/docs/recipes/yugabytedb.mdx +135 -0
  47. package/site/content/docs/reference/cli.md +42 -6
  48. package/site/content/docs/reference/configuration.mdx +5 -3
  49. package/site/content/docs/reference/environment-variables.mdx +12 -12
  50. package/site/content/docs/reference/fx.mdx +38 -4
  51. package/src/cli/ai-setup/ai-setup.test.ts +32 -12
  52. package/src/cli/ai-setup/apply.ts +65 -23
  53. package/src/cli/ai-setup/catalog.ts +1316 -35
  54. package/src/cli/ai-setup/detect-ollama.ts +47 -0
  55. package/src/cli/ai-setup/index.ts +44 -7
  56. package/src/cli/ai-setup/prompts.ts +430 -486
  57. package/src/cli/ai-setup/recommend.test.ts +150 -50
  58. package/src/cli/ai-setup/recommend.ts +118 -101
  59. package/src/cli/ask-seed.test.ts +68 -0
  60. package/src/cli/ask-seed.ts +79 -0
  61. package/src/cli/db-seed-redact.test.ts +25 -0
  62. package/src/cli/db-seed.ts +36 -7
  63. package/src/cli/db.test.ts +1 -1
  64. package/src/cli/db.ts +2 -2
  65. package/src/cli/dev-controls.test.ts +107 -0
  66. package/src/cli/dev-controls.ts +354 -0
  67. package/src/cli/dev-schema-sync.ts +12 -4
  68. package/src/cli/dev.test.ts +50 -30
  69. package/src/cli/dev.ts +607 -66
  70. package/src/cli/drizzle-env.test.ts +40 -6
  71. package/src/cli/drizzle-env.ts +22 -1
  72. package/src/cli/ensure-drizzle-config.ts +6 -4
  73. package/src/cli/hero-meta.test.ts +13 -1
  74. package/src/cli/hero-meta.ts +28 -6
  75. package/src/cli/index.ts +5 -0
  76. package/src/cli/registry.ts +24 -0
  77. package/src/cli/replay.test.ts +105 -0
  78. package/src/cli/replay.ts +206 -0
  79. package/src/compiler/extract.ts +3 -3
  80. package/src/config/index.ts +3 -3
  81. package/src/console/server/claim.ts +3 -1
  82. package/src/console/ui/dist/assets/{index-ClX0ZCe5.js → index-D-9KFce5.js} +2 -2
  83. package/src/console/ui/dist/assets/panel-overview-CW0CQEc2.js +1 -0
  84. package/src/console/ui/dist/assets/panel-runs-BxuyDI3x.js +1 -0
  85. package/src/console/ui/dist/assets/{panel-signals-Dsxdu_AR.js → panel-signals-BXofbj7v.js} +1 -1
  86. package/src/console/ui/dist/assets/{panel-store-hy7O8HOs.js → panel-store-B8mH-arU.js} +1 -1
  87. package/src/console/ui/dist/assets/{panel-traces-CBiAe5go.js → panel-traces-DSA-ZWnf.js} +1 -1
  88. package/src/console/ui/dist/index.html +1 -1
  89. package/src/console/ui/overview/slo.ts +31 -3
  90. package/src/console/ui/overview/types.ts +6 -0
  91. package/src/console/ui/runs/errors.test.ts +53 -0
  92. package/src/console/ui/runs/errors.ts +88 -0
  93. package/src/console/ui/runs/index.ts +8 -0
  94. package/src/console/ui/runs/search.ts +16 -0
  95. package/src/console/ui/shell/panels/runs/RunsPanel.tsx +72 -1
  96. package/src/docker/ai-model-status.test.ts +101 -0
  97. package/src/docker/ai-model-status.ts +276 -0
  98. package/src/docker/compose-health.test.ts +115 -0
  99. package/src/docker/compose-health.ts +234 -0
  100. package/src/docker/compose.ts +24 -8
  101. package/src/docker/derive.ts +35 -9
  102. package/src/docker/docker.test.ts +304 -12
  103. package/src/docker/helpers.ts +56 -0
  104. package/src/docker/index.ts +52 -0
  105. package/src/docker/ollama-pull.ts +232 -0
  106. package/src/docker/recipes/cockroach.ts +24 -0
  107. package/src/docker/recipes/index.ts +32 -0
  108. package/src/docker/recipes/llama-cpp.ts +273 -0
  109. package/src/docker/recipes/ollama.ts +19 -23
  110. package/src/docker/recipes/pgdog.ts +4 -1
  111. package/src/docker/recipes/postgres.ts +5 -18
  112. package/src/docker/recipes/sglang.ts +55 -0
  113. package/src/docker/recipes/supabase.ts +21 -0
  114. package/src/docker/recipes/timescale.ts +18 -0
  115. package/src/docker/recipes/vllm.ts +44 -0
  116. package/src/docker/recipes/yugabyte.ts +23 -0
  117. package/src/docker/stack-id.test.ts +22 -0
  118. package/src/docker/stack-id.ts +38 -4
  119. package/src/docker/types.ts +15 -0
  120. package/src/drivers/ai-ollama.integration.test.ts +7 -4
  121. package/src/drivers/signal-engine.ts +10 -0
  122. package/src/drivers/signal-postgres.ts +24 -2
  123. package/src/drivers/signal-types.ts +10 -0
  124. package/src/elements/channel/fallback.test.ts +91 -0
  125. package/src/elements/channel/hard-bounce.test.ts +82 -0
  126. package/src/elements/channel/injection.test.ts +153 -0
  127. package/src/elements/channel/locale-ar.test.ts +102 -0
  128. package/src/elements/channel/otp-delivery.ts +5 -2
  129. package/src/elements/channel/receipts-ledger.test.ts +100 -0
  130. package/src/elements/channel/runtime.ts +24 -1
  131. package/src/elements/channel/test-helpers.ts +60 -0
  132. package/src/elements/channel/whatsapp-compliance.test.ts +124 -0
  133. package/src/elements/store/cache.ts +1 -1
  134. package/src/elements/store/emit-drizzle.ts +43 -9
  135. package/src/elements/store/runtime.ts +1 -1
  136. package/src/elements/store/schema-decl.test.ts +2 -0
  137. package/src/elements/store/schema-decl.ts +15 -0
  138. package/src/elements/store/seed.ts +1 -1
  139. package/src/index.ts +1 -0
  140. package/src/kernel/app.ts +117 -10
  141. package/src/kernel/boot-bind/signal.ts +6 -2
  142. package/src/kernel/boot-bind/store.ts +2 -1
  143. package/src/kernel/boot.test.ts +6 -5
  144. package/src/kernel/boot.ts +8 -3
  145. package/src/kernel/compensate.test.ts +131 -0
  146. package/src/kernel/correlation.test.ts +108 -0
  147. package/src/kernel/effect-timing.test.ts +82 -0
  148. package/src/kernel/flow.ts +38 -0
  149. package/src/kernel/fx-runs.test.ts +49 -0
  150. package/src/kernel/fx.test.ts +14 -0
  151. package/src/kernel/fx.ts +111 -1
  152. package/src/manifest/types.ts +8 -2
  153. package/src/runs/collect.ts +3 -0
  154. package/src/runs/export-otlp.ts +64 -0
  155. package/src/runs/index.ts +16 -0
  156. package/src/runs/parquet.ts +9 -0
  157. package/src/runs/types.ts +5 -0
  158. package/src/runs/window.test.ts +68 -0
  159. package/src/runs/window.ts +148 -0
  160. package/src/runtime/boot-warn.test.ts +45 -0
  161. package/src/runtime/boot-warn.ts +17 -0
  162. package/src/runtime/dev-request-log.ts +5 -5
  163. package/src/term.test.ts +95 -10
  164. package/src/term.ts +369 -14
  165. package/src/console/ui/dist/assets/panel-overview-DONDzBd2.js +0 -1
  166. package/src/console/ui/dist/assets/panel-runs-D5zf-D9c.js +0 -1
@@ -1,65 +1,446 @@
1
1
  /**
2
- * Curated Ollama model catalog for `oke ai setup`.
2
+ * Curated model catalog for `oke ai setup` / create-oke AI wizard.
3
3
  *
4
- * Short lists with Recommended not the full Ollama library.
5
- * Tags verified against common 2026 library names; override freely.
4
+ * Tiers map to machine RAM classes (not download size). Manual lists show
5
+ * up to 10 entries with modalities.
6
6
  */
7
7
 
8
8
  /** Role a model can fill in the wizard. */
9
9
  export type CatalogRole = "chat" | "vision" | "embed";
10
10
 
11
- /** One curated model entry. */
11
+ /** Speed / quality tier for the Select model step. */
12
+ export type ModelTier = "ultra-fast" | "fast" | "balanced" | "smart";
13
+
14
+ /** Capability tags shown in manual model lists. */
15
+ export type ModelModality = "text" | "vision" | "code" | "reasoning";
16
+
17
+ /** One curated local model entry (Ollama tag or Docker Hub `ai/` id). */
12
18
  export type CatalogModel = {
13
19
  readonly id: string;
14
20
  readonly label: string;
15
21
  readonly hint: string;
16
22
  readonly role: CatalogRole;
17
23
  readonly recommended?: boolean;
18
- /** Approximate RAM (GB) needed to run comfortably. */
24
+ /** Approximate RAM (GB) needed to run comfortably (machine tier). */
19
25
  readonly ramGb: number;
26
+ readonly tier: ModelTier;
27
+ readonly modalities: readonly ModelModality[];
20
28
  };
21
29
 
22
- /** Chat models (short list). */
30
+ /** Tier menu rows (prompt labels). */
31
+ export const MODEL_TIERS: readonly {
32
+ readonly value: ModelTier;
33
+ readonly label: string;
34
+ readonly hint: string;
35
+ readonly minRamGb: number;
36
+ readonly maxRamGb: number;
37
+ }[] = [
38
+ {
39
+ value: "ultra-fast",
40
+ label: "Ultra Fast",
41
+ hint: "~1GB–~2GB RAM",
42
+ minRamGb: 1,
43
+ maxRamGb: 2,
44
+ },
45
+ {
46
+ value: "fast",
47
+ label: "Fast",
48
+ hint: "~4GB–~8GB RAM",
49
+ minRamGb: 4,
50
+ maxRamGb: 8,
51
+ },
52
+ {
53
+ value: "balanced",
54
+ label: "Balanced",
55
+ hint: "~8GB–~16GB RAM",
56
+ minRamGb: 8,
57
+ maxRamGb: 16,
58
+ },
59
+ {
60
+ value: "smart",
61
+ label: "Smart",
62
+ hint: "~24GB–~32GB RAM",
63
+ minRamGb: 24,
64
+ maxRamGb: 32,
65
+ },
66
+ ];
67
+
68
+ /** Chat / multimodal models (curated; manual pick shows ≤10 per tier). */
23
69
  export const CHAT_MODELS: readonly CatalogModel[] = [
70
+ // Ultra Fast (~1–2GB)
71
+ {
72
+ id: "smollm2:135m",
73
+ label: "SmolLM2 135M",
74
+ hint: "Tiny · on-device",
75
+ role: "chat",
76
+ ramGb: 1,
77
+ tier: "ultra-fast",
78
+ modalities: ["text"],
79
+ },
80
+ {
81
+ id: "qwen2.5:0.5b",
82
+ label: "Qwen2.5 0.5B",
83
+ hint: "Smallest Qwen",
84
+ role: "chat",
85
+ ramGb: 1,
86
+ tier: "ultra-fast",
87
+ modalities: ["text", "code"],
88
+ },
89
+ {
90
+ id: "llama3.2:1b",
91
+ label: "Llama 3.2 1B",
92
+ hint: "Meta tiny",
93
+ role: "chat",
94
+ ramGb: 2,
95
+ tier: "ultra-fast",
96
+ modalities: ["text"],
97
+ },
98
+ {
99
+ id: "gemma3:1b",
100
+ label: "Gemma 3 1B",
101
+ hint: "Google tiny",
102
+ role: "chat",
103
+ ramGb: 2,
104
+ tier: "ultra-fast",
105
+ modalities: ["text"],
106
+ },
107
+ {
108
+ id: "qwen2.5:1.5b",
109
+ label: "Qwen2.5 1.5B",
110
+ hint: "Small coding",
111
+ role: "chat",
112
+ recommended: true,
113
+ ramGb: 2,
114
+ tier: "ultra-fast",
115
+ modalities: ["text", "code"],
116
+ },
117
+ {
118
+ id: "deepseek-r1:1.5b",
119
+ label: "DeepSeek R1 1.5B",
120
+ hint: "Tiny reasoning",
121
+ role: "chat",
122
+ ramGb: 2,
123
+ tier: "ultra-fast",
124
+ modalities: ["text", "reasoning"],
125
+ },
126
+ {
127
+ id: "moondream",
128
+ label: "Moondream",
129
+ hint: "Tiny vision",
130
+ role: "chat",
131
+ ramGb: 2,
132
+ tier: "ultra-fast",
133
+ modalities: ["text", "vision"],
134
+ },
135
+ {
136
+ id: "tinydolphin",
137
+ label: "TinyDolphin",
138
+ hint: "Ultra-compact chat",
139
+ role: "chat",
140
+ ramGb: 1,
141
+ tier: "ultra-fast",
142
+ modalities: ["text"],
143
+ },
144
+ {
145
+ id: "phi3:mini",
146
+ label: "Phi-3 Mini",
147
+ hint: "Microsoft compact",
148
+ role: "chat",
149
+ ramGb: 2,
150
+ tier: "ultra-fast",
151
+ modalities: ["text", "code"],
152
+ },
153
+ {
154
+ id: "stable-code:3b",
155
+ label: "Stable Code 3B",
156
+ hint: "Compact coder",
157
+ role: "chat",
158
+ ramGb: 2,
159
+ tier: "ultra-fast",
160
+ modalities: ["text", "code"],
161
+ },
162
+
163
+ // Fast (~4–8GB)
164
+ {
165
+ id: "llama3.2:3b",
166
+ label: "Llama 3.2 3B",
167
+ hint: "Small general",
168
+ role: "chat",
169
+ ramGb: 4,
170
+ tier: "fast",
171
+ modalities: ["text"],
172
+ },
173
+ {
174
+ id: "phi4-mini",
175
+ label: "Phi-4 Mini",
176
+ hint: "Strong small",
177
+ role: "chat",
178
+ ramGb: 4,
179
+ tier: "fast",
180
+ modalities: ["text", "code"],
181
+ },
182
+ {
183
+ id: "qwen2.5:3b",
184
+ label: "Qwen2.5 3B",
185
+ hint: "Coding · small",
186
+ role: "chat",
187
+ ramGb: 4,
188
+ tier: "fast",
189
+ modalities: ["text", "code"],
190
+ },
24
191
  {
25
192
  id: "gemma4:e4b",
26
193
  label: "Gemma 4 4B",
27
- hint: "Fast · ≈8GB-class machine",
194
+ hint: "Fast default",
28
195
  role: "chat",
29
196
  recommended: true,
30
197
  ramGb: 8,
198
+ tier: "fast",
199
+ modalities: ["text"],
200
+ },
201
+ {
202
+ id: "qwen2.5:7b",
203
+ label: "Qwen2.5 7B",
204
+ hint: "Coding workhorse",
205
+ role: "chat",
206
+ ramGb: 8,
207
+ tier: "fast",
208
+ modalities: ["text", "code"],
209
+ },
210
+ {
211
+ id: "mistral:7b",
212
+ label: "Mistral 7B",
213
+ hint: "General purpose",
214
+ role: "chat",
215
+ ramGb: 8,
216
+ tier: "fast",
217
+ modalities: ["text"],
218
+ },
219
+ {
220
+ id: "deepseek-r1:7b",
221
+ label: "DeepSeek R1 7B",
222
+ hint: "Reasoning",
223
+ role: "chat",
224
+ ramGb: 8,
225
+ tier: "fast",
226
+ modalities: ["text", "reasoning"],
227
+ },
228
+ {
229
+ id: "llava:7b",
230
+ label: "LLaVA 7B",
231
+ hint: "Vision-language",
232
+ role: "chat",
233
+ ramGb: 8,
234
+ tier: "fast",
235
+ modalities: ["text", "vision"],
236
+ },
237
+ {
238
+ id: "qwen3-vl:4b",
239
+ label: "Qwen3-VL 4B",
240
+ hint: "Vision-language",
241
+ role: "chat",
242
+ ramGb: 8,
243
+ tier: "fast",
244
+ modalities: ["text", "vision"],
245
+ },
246
+ {
247
+ id: "granite3.3:8b",
248
+ label: "Granite 3.3 8B",
249
+ hint: "IBM enterprise",
250
+ role: "chat",
251
+ ramGb: 8,
252
+ tier: "fast",
253
+ modalities: ["text", "code"],
254
+ },
255
+
256
+ // Balanced (~8–16GB)
257
+ {
258
+ id: "deepseek-r1:8b",
259
+ label: "DeepSeek R1 8B",
260
+ hint: "Strong reasoning",
261
+ role: "chat",
262
+ ramGb: 10,
263
+ tier: "balanced",
264
+ modalities: ["text", "reasoning"],
265
+ },
266
+ {
267
+ id: "llama3.1:8b",
268
+ label: "Llama 3.1 8B",
269
+ hint: "General purpose",
270
+ role: "chat",
271
+ ramGb: 10,
272
+ tier: "balanced",
273
+ modalities: ["text"],
31
274
  },
32
275
  {
33
276
  id: "qwen3.5:9b",
34
277
  label: "Qwen3.5 9B",
35
- hint: "Better coding · ≈16GB-class",
278
+ hint: "Better coding",
279
+ role: "chat",
280
+ recommended: true,
281
+ ramGb: 16,
282
+ tier: "balanced",
283
+ modalities: ["text", "code"],
284
+ },
285
+ {
286
+ id: "llama4:scout",
287
+ label: "Llama 4 Scout",
288
+ hint: "General purpose",
289
+ role: "chat",
290
+ ramGb: 16,
291
+ tier: "balanced",
292
+ modalities: ["text"],
293
+ },
294
+ {
295
+ id: "qwen2.5:14b",
296
+ label: "Qwen2.5 14B",
297
+ hint: "Larger Qwen",
298
+ role: "chat",
299
+ ramGb: 16,
300
+ tier: "balanced",
301
+ modalities: ["text", "code"],
302
+ },
303
+ {
304
+ id: "qwen2.5-coder:14b",
305
+ label: "Qwen2.5 Coder 14B",
306
+ hint: "Coding specialist",
307
+ role: "chat",
308
+ ramGb: 16,
309
+ tier: "balanced",
310
+ modalities: ["text", "code"],
311
+ },
312
+ {
313
+ id: "mistral-nemo",
314
+ label: "Mistral Nemo",
315
+ hint: "12B-class general",
316
+ role: "chat",
317
+ ramGb: 12,
318
+ tier: "balanced",
319
+ modalities: ["text"],
320
+ },
321
+ {
322
+ id: "llava:13b",
323
+ label: "LLaVA 13B",
324
+ hint: "Vision-language",
325
+ role: "chat",
326
+ ramGb: 16,
327
+ tier: "balanced",
328
+ modalities: ["text", "vision"],
329
+ },
330
+ {
331
+ id: "gemma2:9b",
332
+ label: "Gemma 2 9B",
333
+ hint: "Google mid",
334
+ role: "chat",
335
+ ramGb: 12,
336
+ tier: "balanced",
337
+ modalities: ["text"],
338
+ },
339
+ {
340
+ id: "command-r",
341
+ label: "Command R",
342
+ hint: "Cohere RAG-friendly",
36
343
  role: "chat",
37
344
  ramGb: 16,
345
+ tier: "balanced",
346
+ modalities: ["text"],
347
+ },
348
+
349
+ // Smart (~24–32GB)
350
+ {
351
+ id: "gemma2:27b",
352
+ label: "Gemma 2 27B",
353
+ hint: "Large Gemma",
354
+ role: "chat",
355
+ ramGb: 24,
356
+ tier: "smart",
357
+ modalities: ["text"],
38
358
  },
39
359
  {
40
360
  id: "qwen3.5:27b",
41
361
  label: "Qwen3.5 27B",
42
- hint: "Best local quality · ≈32GB-class",
362
+ hint: "Best local quality",
43
363
  role: "chat",
364
+ recommended: true,
44
365
  ramGb: 32,
366
+ tier: "smart",
367
+ modalities: ["text", "code"],
45
368
  },
46
369
  {
47
- id: "llama4:scout",
48
- label: "Llama 4 Scout",
49
- hint: "General purpose · ≈16GB-class",
370
+ id: "qwen2.5:32b",
371
+ label: "Qwen2.5 32B",
372
+ hint: "Large Qwen",
50
373
  role: "chat",
51
- ramGb: 16,
374
+ ramGb: 32,
375
+ tier: "smart",
376
+ modalities: ["text", "code"],
52
377
  },
53
378
  {
54
- id: "deepseek-r1:8b",
55
- label: "DeepSeek R1 8B",
56
- hint: "Strong reasoning · ≈10GB-class",
379
+ id: "qwen2.5-coder:32b",
380
+ label: "Qwen2.5 Coder 32B",
381
+ hint: "Large coder",
57
382
  role: "chat",
58
- ramGb: 10,
383
+ ramGb: 32,
384
+ tier: "smart",
385
+ modalities: ["text", "code"],
386
+ },
387
+ {
388
+ id: "deepseek-r1:32b",
389
+ label: "DeepSeek R1 32B",
390
+ hint: "Large reasoning",
391
+ role: "chat",
392
+ ramGb: 32,
393
+ tier: "smart",
394
+ modalities: ["text", "reasoning"],
395
+ },
396
+ {
397
+ id: "mixtral:8x7b",
398
+ label: "Mixtral 8x7B",
399
+ hint: "MoE general",
400
+ role: "chat",
401
+ ramGb: 26,
402
+ tier: "smart",
403
+ modalities: ["text"],
404
+ },
405
+ {
406
+ id: "command-r-plus",
407
+ label: "Command R+",
408
+ hint: "Cohere large",
409
+ role: "chat",
410
+ ramGb: 32,
411
+ tier: "smart",
412
+ modalities: ["text"],
413
+ },
414
+ {
415
+ id: "mistral-small:24b",
416
+ label: "Mistral Small 24B",
417
+ hint: "Mistral mid-large",
418
+ role: "chat",
419
+ ramGb: 24,
420
+ tier: "smart",
421
+ modalities: ["text"],
422
+ },
423
+ {
424
+ id: "llama3.1:70b",
425
+ label: "Llama 3.1 70B",
426
+ hint: "Needs ~40GB+; listed for reference",
427
+ role: "chat",
428
+ ramGb: 40,
429
+ tier: "smart",
430
+ modalities: ["text"],
431
+ },
432
+ {
433
+ id: "qwen2.5:72b",
434
+ label: "Qwen2.5 72B",
435
+ hint: "Needs ~48GB+; listed for reference",
436
+ role: "chat",
437
+ ramGb: 48,
438
+ tier: "smart",
439
+ modalities: ["text", "code"],
59
440
  },
60
441
  ];
61
442
 
62
- /** Vision models (short list + skip handled in prompts). */
443
+ /** Vision-only short list (legacy / optional). */
63
444
  export const VISION_MODELS: readonly CatalogModel[] = [
64
445
  {
65
446
  id: "qwen3-vl:4b",
@@ -68,17 +449,21 @@ export const VISION_MODELS: readonly CatalogModel[] = [
68
449
  role: "vision",
69
450
  recommended: true,
70
451
  ramGb: 8,
452
+ tier: "fast",
453
+ modalities: ["text", "vision"],
71
454
  },
72
455
  {
73
- id: "gemma4:e4b",
74
- label: "Gemma Vision",
75
- hint: "Multimodal Gemma 4",
456
+ id: "llava:7b",
457
+ label: "LLaVA 7B",
458
+ hint: "Vision-language",
76
459
  role: "vision",
77
460
  ramGb: 8,
461
+ tier: "fast",
462
+ modalities: ["text", "vision"],
78
463
  },
79
464
  ];
80
465
 
81
- /** Embedding models. */
466
+ /** Embedding models (applied automatically — no download prompt). */
82
467
  export const EMBED_MODELS: readonly CatalogModel[] = [
83
468
  {
84
469
  id: "nomic-embed-text",
@@ -87,6 +472,8 @@ export const EMBED_MODELS: readonly CatalogModel[] = [
87
472
  role: "embed",
88
473
  recommended: true,
89
474
  ramGb: 1,
475
+ tier: "ultra-fast",
476
+ modalities: ["text"],
90
477
  },
91
478
  {
92
479
  id: "bge-m3",
@@ -94,6 +481,8 @@ export const EMBED_MODELS: readonly CatalogModel[] = [
94
481
  hint: "Multilingual + long docs",
95
482
  role: "embed",
96
483
  ramGb: 2,
484
+ tier: "ultra-fast",
485
+ modalities: ["text"],
97
486
  },
98
487
  {
99
488
  id: "jina-embeddings-v4",
@@ -101,6 +490,8 @@ export const EMBED_MODELS: readonly CatalogModel[] = [
101
490
  hint: "High-quality embedder",
102
491
  role: "embed",
103
492
  ramGb: 2,
493
+ tier: "ultra-fast",
494
+ modalities: ["text"],
104
495
  },
105
496
  ];
106
497
 
@@ -112,25 +503,56 @@ export const ALL_CURATED: readonly CatalogModel[] = [
112
503
  ];
113
504
 
114
505
  /**
115
- * Recommend a chat model for the host RAM (comfortable headroom).
506
+ * Sort catalog rows by RAM ascending, then label (stable pick lists).
507
+ *
508
+ * @param models - Catalog entries
509
+ */
510
+ function byRamThenLabel(models: readonly CatalogModel[]): CatalogModel[] {
511
+ return [...models].sort((a, b) => a.ramGb - b.ramGb || a.label.localeCompare(b.label));
512
+ }
513
+
514
+ /**
515
+ * Chat models in a tier (≤10 for manual pick), ordered by RAM.
516
+ *
517
+ * @param tier - Speed / quality tier
518
+ */
519
+ export function modelsForTier(tier: ModelTier): readonly CatalogModel[] {
520
+ return byRamThenLabel(CHAT_MODELS.filter((m) => m.tier === tier)).slice(0, 10);
521
+ }
522
+
523
+ /**
524
+ * Recommended chat model for a tier (falls back to first in tier).
116
525
  *
117
- * Prefer {@link recommendChatForNeeds} when use-case answers are available.
526
+ * @param tier - Selected tier
527
+ * @param totalRamGb - Prefer models that fit when RAM is known
528
+ */
529
+ export function recommendForTier(tier: ModelTier, totalRamGb: number | null = null): CatalogModel {
530
+ const list = modelsForTier(tier);
531
+ const headroom = 4;
532
+ const fits =
533
+ totalRamGb !== null && Number.isFinite(totalRamGb)
534
+ ? list.filter((m) => m.ramGb + headroom <= totalRamGb || m.ramGb <= totalRamGb)
535
+ : list;
536
+ const pool = fits.length > 0 ? fits : list;
537
+ return pool.find((m) => m.recommended) ?? pool[pool.length - 1] ?? list[0]!;
538
+ }
539
+
540
+ /**
541
+ * Recommend a chat model for the host RAM (comfortable headroom).
118
542
  *
119
543
  * @param totalRamGb - Detected total system RAM in GB (or null)
120
544
  */
121
545
  export function recommendChatModel(totalRamGb: number | null): CatalogModel {
122
- // Keep catalog free of recommend.ts cycles — same rules as comfortableChatModels.
123
546
  const HEADROOM = 4;
124
547
  const sorted = [...CHAT_MODELS].sort((a, b) => a.ramGb - b.ramGb);
125
548
  const smallest = sorted[0]!;
126
549
  if (totalRamGb === null || !Number.isFinite(totalRamGb)) {
127
- return CHAT_MODELS.find((m) => m.recommended) ?? smallest;
550
+ return CHAT_MODELS.find((m) => m.recommended && m.tier === "fast") ?? smallest;
128
551
  }
129
552
  const comfortable = sorted.filter(
130
553
  (m) => (m.id === smallest.id && m.ramGb <= totalRamGb) || m.ramGb + HEADROOM <= totalRamGb,
131
554
  );
132
555
  if (comfortable.length > 0) return comfortable[comfortable.length - 1]!;
133
- // Fall back to largest that meets the machine tier (tight but runnable).
134
556
  const tierOk = sorted.filter((m) => m.ramGb <= totalRamGb);
135
557
  return tierOk.length > 0 ? tierOk[tierOk.length - 1]! : smallest;
136
558
  }
@@ -145,6 +567,11 @@ export function recommendForRole(role: "vision" | "embed"): CatalogModel {
145
567
  return list.find((m) => m.recommended) ?? list[0]!;
146
568
  }
147
569
 
570
+ /** Format modalities for a list row. */
571
+ export function formatModalities(modalities: readonly ModelModality[]): string {
572
+ return modalities.join(" · ");
573
+ }
574
+
148
575
  /** Thin cloud provider menu (non-Ollama). */
149
576
  export const CLOUD_PROVIDERS = [
150
577
  {
@@ -152,31 +579,42 @@ export const CLOUD_PROVIDERS = [
152
579
  label: "OpenAI",
153
580
  driver: "openai-compatible" as const,
154
581
  baseUrl: "https://api.openai.com/v1",
582
+ apiKeyEnv: "OPENAI_API_KEY",
583
+ },
584
+ {
585
+ value: "anthropic",
586
+ label: "Anthropic",
587
+ driver: "anthropic" as const,
588
+ baseUrl: undefined,
589
+ apiKeyEnv: "ANTHROPIC_API_KEY",
155
590
  },
156
- { value: "anthropic", label: "Anthropic", driver: "anthropic" as const, baseUrl: undefined },
157
591
  {
158
592
  value: "gemini",
159
593
  label: "Gemini",
160
594
  driver: "openai-compatible" as const,
161
595
  baseUrl: undefined,
596
+ apiKeyEnv: "OPENAI_API_KEY",
162
597
  },
163
598
  {
164
599
  value: "lmstudio",
165
600
  label: "LM Studio",
166
601
  driver: "openai-compatible" as const,
167
602
  baseUrl: "http://127.0.0.1:1234/v1",
603
+ apiKeyEnv: undefined,
168
604
  },
169
605
  {
170
606
  value: "openrouter",
171
607
  label: "OpenRouter",
172
608
  driver: "openai-compatible" as const,
173
609
  baseUrl: "https://openrouter.ai/api/v1",
610
+ apiKeyEnv: "OPENAI_API_KEY",
174
611
  },
175
612
  {
176
613
  value: "custom",
177
614
  label: "Custom OpenAI Compatible",
178
615
  driver: "openai-compatible" as const,
179
616
  baseUrl: undefined,
617
+ apiKeyEnv: "OPENAI_API_KEY",
180
618
  },
181
619
  ] as const;
182
620
 
@@ -188,25 +626,79 @@ export type CloudModel = {
188
626
  readonly recommended?: boolean;
189
627
  };
190
628
 
191
- /** Short curated chat models per cloud / openai-compatible provider. */
629
+ /** Up to 10 curated / “latest” chat models per cloud provider. */
192
630
  export const CLOUD_CHAT_MODELS: Readonly<Record<string, readonly CloudModel[]>> = {
193
631
  openai: [
194
- { id: "gpt-4o-mini", label: "GPT-4o mini", hint: "Fast · cheap default", recommended: true },
195
- { id: "gpt-4o", label: "GPT-4o", hint: "Strong general / vision" },
632
+ { id: "gpt-4.1-nano", label: "GPT-4.1 nano", hint: "Fastest · cheapest" },
633
+ { id: "gpt-4.1-mini", label: "GPT-4.1 mini", hint: "Fast · cheap" },
634
+ { id: "gpt-4o-mini", label: "GPT-4o mini", hint: "Fast default", recommended: true },
196
635
  { id: "gpt-4.1", label: "GPT-4.1", hint: "Latest flagship" },
636
+ { id: "gpt-4o", label: "GPT-4o", hint: "Strong general / vision" },
197
637
  { id: "o4-mini", label: "o4-mini", hint: "Reasoning · lower cost" },
638
+ { id: "o3-mini", label: "o3-mini", hint: "Reasoning" },
639
+ { id: "o3", label: "o3", hint: "Deep reasoning" },
640
+ { id: "gpt-4.1-mini-2025-04-14", label: "GPT-4.1 mini (dated)", hint: "Pinned snapshot" },
641
+ { id: "chatgpt-4o-latest", label: "ChatGPT-4o latest", hint: "ChatGPT-aligned" },
198
642
  ],
199
643
  anthropic: [
644
+ {
645
+ id: "claude-haiku-4-20250514",
646
+ label: "Claude Haiku 4",
647
+ hint: "Fast · cheap",
648
+ },
200
649
  {
201
650
  id: "claude-sonnet-4-20250514",
202
651
  label: "Claude Sonnet 4",
203
652
  hint: "Balanced default",
204
653
  recommended: true,
205
654
  },
206
- { id: "claude-opus-4-20250514", label: "Claude Opus 4", hint: "Highest quality" },
207
- { id: "claude-haiku-4-20250514", label: "Claude Haiku 4", hint: "Fast · cheap" },
655
+ {
656
+ id: "claude-opus-4-20250514",
657
+ label: "Claude Opus 4",
658
+ hint: "Highest quality",
659
+ },
660
+ {
661
+ id: "claude-3-7-sonnet-20250219",
662
+ label: "Claude 3.7 Sonnet",
663
+ hint: "Prior mid-tier",
664
+ },
665
+ {
666
+ id: "claude-3-5-haiku-20241022",
667
+ label: "Claude 3.5 Haiku",
668
+ hint: "Prior Haiku",
669
+ },
670
+ {
671
+ id: "claude-3-5-sonnet-20241022",
672
+ label: "Claude 3.5 Sonnet",
673
+ hint: "Prior Sonnet",
674
+ },
675
+ {
676
+ id: "claude-3-opus-20240229",
677
+ label: "Claude 3 Opus",
678
+ hint: "Legacy Opus",
679
+ },
680
+ {
681
+ id: "claude-3-haiku-20240307",
682
+ label: "Claude 3 Haiku",
683
+ hint: "Legacy Haiku",
684
+ },
685
+ {
686
+ id: "claude-3-sonnet-20240229",
687
+ label: "Claude 3 Sonnet",
688
+ hint: "Legacy Sonnet",
689
+ },
690
+ {
691
+ id: "claude-2.1",
692
+ label: "Claude 2.1",
693
+ hint: "Legacy",
694
+ },
208
695
  ],
209
696
  gemini: [
697
+ {
698
+ id: "gemini-2.5-flash-lite",
699
+ label: "Gemini 2.5 Flash-Lite",
700
+ hint: "Fastest",
701
+ },
210
702
  {
211
703
  id: "gemini-2.5-flash",
212
704
  label: "Gemini 2.5 Flash",
@@ -214,6 +706,13 @@ export const CLOUD_CHAT_MODELS: Readonly<Record<string, readonly CloudModel[]>>
214
706
  recommended: true,
215
707
  },
216
708
  { id: "gemini-2.5-pro", label: "Gemini 2.5 Pro", hint: "Higher quality" },
709
+ { id: "gemini-2.0-flash", label: "Gemini 2.0 Flash", hint: "Prior Flash" },
710
+ { id: "gemini-2.0-flash-lite", label: "Gemini 2.0 Flash-Lite", hint: "Prior Lite" },
711
+ { id: "gemini-1.5-flash", label: "Gemini 1.5 Flash", hint: "Legacy Flash" },
712
+ { id: "gemini-1.5-pro", label: "Gemini 1.5 Pro", hint: "Legacy Pro" },
713
+ { id: "gemini-2.5-flash-preview", label: "Gemini 2.5 Flash preview", hint: "Preview" },
714
+ { id: "gemini-2.5-pro-preview", label: "Gemini 2.5 Pro preview", hint: "Preview" },
715
+ { id: "gemma-3-27b-it", label: "Gemma 3 27B IT", hint: "Open weights via API" },
217
716
  ],
218
717
  openrouter: [
219
718
  {
@@ -222,8 +721,15 @@ export const CLOUD_CHAT_MODELS: Readonly<Record<string, readonly CloudModel[]>>
222
721
  hint: "via OpenRouter",
223
722
  recommended: true,
224
723
  },
724
+ { id: "openai/gpt-4.1", label: "GPT-4.1", hint: "via OpenRouter" },
725
+ { id: "openai/o4-mini", label: "o4-mini", hint: "via OpenRouter" },
225
726
  { id: "anthropic/claude-sonnet-4", label: "Claude Sonnet 4", hint: "via OpenRouter" },
727
+ { id: "anthropic/claude-opus-4", label: "Claude Opus 4", hint: "via OpenRouter" },
728
+ { id: "anthropic/claude-haiku-4", label: "Claude Haiku 4", hint: "via OpenRouter" },
226
729
  { id: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash", hint: "via OpenRouter" },
730
+ { id: "google/gemini-2.5-pro", label: "Gemini 2.5 Pro", hint: "via OpenRouter" },
731
+ { id: "meta-llama/llama-4-scout", label: "Llama 4 Scout", hint: "via OpenRouter" },
732
+ { id: "deepseek/deepseek-r1", label: "DeepSeek R1", hint: "via OpenRouter" },
227
733
  ],
228
734
  lmstudio: [
229
735
  {
@@ -244,12 +750,21 @@ export const CLOUD_CHAT_MODELS: Readonly<Record<string, readonly CloudModel[]>>
244
750
  };
245
751
 
246
752
  /**
247
- * Curated chat models for a cloud provider (empty → Other-only).
753
+ * Curated chat models for a cloud provider (deduped, ≤10).
248
754
  *
249
755
  * @param provider - Menu provider id
250
756
  */
251
757
  export function cloudChatModels(provider: string): readonly CloudModel[] {
252
- return CLOUD_CHAT_MODELS[provider] ?? [];
758
+ const list = CLOUD_CHAT_MODELS[provider] ?? [];
759
+ const seen = new Set<string>();
760
+ const out: CloudModel[] = [];
761
+ for (const m of list) {
762
+ if (seen.has(m.id)) continue;
763
+ seen.add(m.id);
764
+ out.push(m);
765
+ if (out.length >= 10) break;
766
+ }
767
+ return out;
253
768
  }
254
769
 
255
770
  /**
@@ -261,3 +776,769 @@ export function recommendCloudChat(provider: string): string {
261
776
  const list = cloudChatModels(provider);
262
777
  return list.find((m) => m.recommended)?.id ?? list[0]?.id ?? "gpt-4o-mini";
263
778
  }
779
+
780
+ /**
781
+ * Curated Docker Hub `ai/` chat models for llama.cpp (`LLAMA_ARG_DOCKER_REPO`).
782
+ * Ids omit the `ai/` org prefix (llama.cpp default). ≥10 per tier; manual pick
783
+ * shows up to 20. HF-origin weights are only listed when published under
784
+ * Docker Hub [`ai/`](https://hub.docker.com/u/ai) (recipe cannot load raw HF ids).
785
+ */
786
+ export const LLAMA_CPP_CHAT_MODELS: readonly CatalogModel[] = [
787
+ // Ultra Fast (~1–2GB)
788
+ {
789
+ id: "smollm2",
790
+ label: "SmolLM2",
791
+ hint: "Default · lightest",
792
+ role: "chat",
793
+ recommended: true,
794
+ ramGb: 1,
795
+ tier: "ultra-fast",
796
+ modalities: ["text"],
797
+ },
798
+ {
799
+ id: "smollm2:135M-Q4_K_M",
800
+ label: "SmolLM2 135M",
801
+ hint: "Tiny smoke test",
802
+ role: "chat",
803
+ ramGb: 1,
804
+ tier: "ultra-fast",
805
+ modalities: ["text"],
806
+ },
807
+ {
808
+ id: "functiongemma",
809
+ label: "FunctionGemma 270M",
810
+ hint: "Tool calling",
811
+ role: "chat",
812
+ ramGb: 1,
813
+ tier: "ultra-fast",
814
+ modalities: ["text", "code"],
815
+ },
816
+ {
817
+ id: "gemma3:270m",
818
+ label: "Gemma 3 270M",
819
+ hint: "Google tiny",
820
+ role: "chat",
821
+ ramGb: 1,
822
+ tier: "ultra-fast",
823
+ modalities: ["text"],
824
+ },
825
+ {
826
+ id: "granite-4.0-h-nano:350M-Q8_0",
827
+ label: "Granite 4.0 H Nano 350M",
828
+ hint: "IBM tiny",
829
+ role: "chat",
830
+ ramGb: 1,
831
+ tier: "ultra-fast",
832
+ modalities: ["text"],
833
+ },
834
+ {
835
+ id: "qwen3:0.6B-Q4_K_M",
836
+ label: "Qwen3 0.6B",
837
+ hint: "Small coding",
838
+ role: "chat",
839
+ ramGb: 2,
840
+ tier: "ultra-fast",
841
+ modalities: ["text", "code"],
842
+ },
843
+ {
844
+ id: "smolvlm:500M-Q8_0",
845
+ label: "SmolVLM 500M",
846
+ hint: "Tiny vision",
847
+ role: "chat",
848
+ ramGb: 2,
849
+ tier: "ultra-fast",
850
+ modalities: ["text", "vision"],
851
+ },
852
+ {
853
+ id: "llama3.2:1B-Q4_0",
854
+ label: "Llama 3.2 1B",
855
+ hint: "Meta tiny",
856
+ role: "chat",
857
+ ramGb: 2,
858
+ tier: "ultra-fast",
859
+ modalities: ["text"],
860
+ },
861
+ {
862
+ id: "granite-4.0-h-nano",
863
+ label: "Granite 4.0 H Nano 1B",
864
+ hint: "IBM nano",
865
+ role: "chat",
866
+ ramGb: 2,
867
+ tier: "ultra-fast",
868
+ modalities: ["text"],
869
+ },
870
+ {
871
+ id: "qwen2.5:0.5B-F16",
872
+ label: "Qwen2.5 0.5B",
873
+ hint: "Small coding",
874
+ role: "chat",
875
+ ramGb: 2,
876
+ tier: "ultra-fast",
877
+ modalities: ["text", "code"],
878
+ },
879
+ {
880
+ id: "smollm2:360M-Q4_K_M",
881
+ label: "SmolLM2 360M",
882
+ hint: "Small instruct",
883
+ role: "chat",
884
+ ramGb: 1,
885
+ tier: "ultra-fast",
886
+ modalities: ["text"],
887
+ },
888
+ {
889
+ id: "smollm2:135M-Q4_0",
890
+ label: "SmolLM2 135M Q4_0",
891
+ hint: "Tiny Q4_0",
892
+ role: "chat",
893
+ ramGb: 1,
894
+ tier: "ultra-fast",
895
+ modalities: ["text"],
896
+ },
897
+ {
898
+ id: "gemma3:270m-q4_K_M",
899
+ label: "Gemma 3 270M Q4",
900
+ hint: "Google tiny Q4",
901
+ role: "chat",
902
+ ramGb: 1,
903
+ tier: "ultra-fast",
904
+ modalities: ["text"],
905
+ },
906
+ {
907
+ id: "functiongemma:q4_K_M",
908
+ label: "FunctionGemma Q4",
909
+ hint: "Tool calling Q4",
910
+ role: "chat",
911
+ ramGb: 1,
912
+ tier: "ultra-fast",
913
+ modalities: ["text", "code"],
914
+ },
915
+ {
916
+ id: "qwen3:0.6B-Q4_0",
917
+ label: "Qwen3 0.6B Q4_0",
918
+ hint: "Small coding",
919
+ role: "chat",
920
+ ramGb: 2,
921
+ tier: "ultra-fast",
922
+ modalities: ["text", "code"],
923
+ },
924
+ {
925
+ id: "llama3.2:1B-Q8_0",
926
+ label: "Llama 3.2 1B Q8",
927
+ hint: "Meta tiny Q8",
928
+ role: "chat",
929
+ ramGb: 2,
930
+ tier: "ultra-fast",
931
+ modalities: ["text"],
932
+ },
933
+ {
934
+ id: "granite-4.0-nano:350M-BF16",
935
+ label: "Granite 4.0 Nano 350M",
936
+ hint: "IBM nano",
937
+ role: "chat",
938
+ ramGb: 2,
939
+ tier: "ultra-fast",
940
+ modalities: ["text"],
941
+ },
942
+ {
943
+ id: "smolvlm",
944
+ label: "SmolVLM",
945
+ hint: "Tiny vision default",
946
+ role: "chat",
947
+ ramGb: 2,
948
+ tier: "ultra-fast",
949
+ modalities: ["text", "vision"],
950
+ },
951
+ {
952
+ id: "granite3.3:2b",
953
+ label: "Granite 3.3 2B",
954
+ hint: "IBM 2B",
955
+ role: "chat",
956
+ ramGb: 2,
957
+ tier: "ultra-fast",
958
+ modalities: ["text", "code"],
959
+ },
960
+ {
961
+ id: "gemma3:270m-q8_0",
962
+ label: "Gemma 3 270M Q8",
963
+ hint: "Google tiny Q8",
964
+ role: "chat",
965
+ ramGb: 1,
966
+ tier: "ultra-fast",
967
+ modalities: ["text"],
968
+ },
969
+ // Fast (~4–8GB) — Gemma 4 · Qwen near the top
970
+ {
971
+ id: "llama3.2",
972
+ label: "Llama 3.2 3B",
973
+ hint: "Balanced local chat",
974
+ role: "chat",
975
+ recommended: true,
976
+ ramGb: 4,
977
+ tier: "fast",
978
+ modalities: ["text"],
979
+ },
980
+ {
981
+ id: "gemma4:e2b-q4_K_M",
982
+ label: "Gemma 4 E2B",
983
+ hint: "Popular · multimodal",
984
+ role: "chat",
985
+ ramGb: 8,
986
+ tier: "fast",
987
+ modalities: ["text", "vision"],
988
+ },
989
+ {
990
+ id: "qwen3:4B-UD-Q4_K_XL",
991
+ label: "Qwen3 4B",
992
+ hint: "Popular · coding · agents",
993
+ role: "chat",
994
+ ramGb: 6,
995
+ tier: "fast",
996
+ modalities: ["text", "code"],
997
+ },
998
+ {
999
+ id: "qwen2.5:3B-Q4_K_M",
1000
+ label: "Qwen2.5 3B",
1001
+ hint: "Coding",
1002
+ role: "chat",
1003
+ ramGb: 4,
1004
+ tier: "fast",
1005
+ modalities: ["text", "code"],
1006
+ },
1007
+ {
1008
+ id: "gemma3:4b",
1009
+ label: "Gemma 3 4B",
1010
+ hint: "Reasoning",
1011
+ role: "chat",
1012
+ ramGb: 8,
1013
+ tier: "fast",
1014
+ modalities: ["text", "reasoning"],
1015
+ },
1016
+ {
1017
+ id: "gemma3n:e2b",
1018
+ label: "Gemma 3n E2B",
1019
+ hint: "On-device multimodal",
1020
+ role: "chat",
1021
+ ramGb: 8,
1022
+ tier: "fast",
1023
+ modalities: ["text", "vision"],
1024
+ },
1025
+ {
1026
+ id: "qwen3-vl:2B-UD-Q4_K_XL",
1027
+ label: "Qwen3-VL 2B",
1028
+ hint: "Vision",
1029
+ role: "chat",
1030
+ ramGb: 6,
1031
+ tier: "fast",
1032
+ modalities: ["text", "vision"],
1033
+ },
1034
+ {
1035
+ id: "smollm3",
1036
+ label: "SmolLM3",
1037
+ hint: "On-device chat",
1038
+ role: "chat",
1039
+ ramGb: 4,
1040
+ tier: "fast",
1041
+ modalities: ["text"],
1042
+ },
1043
+ {
1044
+ id: "ministral3:3B-Q4_K_M",
1045
+ label: "Ministral 3B",
1046
+ hint: "Mistral small",
1047
+ role: "chat",
1048
+ ramGb: 4,
1049
+ tier: "fast",
1050
+ modalities: ["text", "code"],
1051
+ },
1052
+ {
1053
+ id: "nemotron-3-nano:4b",
1054
+ label: "Nemotron 3 Nano 4B",
1055
+ hint: "NVIDIA nano",
1056
+ role: "chat",
1057
+ ramGb: 6,
1058
+ tier: "fast",
1059
+ modalities: ["text", "code"],
1060
+ },
1061
+ {
1062
+ id: "granite-4.0-micro",
1063
+ label: "Granite 4.0 Micro 3B",
1064
+ hint: "IBM micro",
1065
+ role: "chat",
1066
+ ramGb: 4,
1067
+ tier: "fast",
1068
+ modalities: ["text", "code"],
1069
+ },
1070
+ {
1071
+ id: "granite4:micro",
1072
+ label: "Granite 4 Micro",
1073
+ hint: "IBM micro Q4",
1074
+ role: "chat",
1075
+ ramGb: 4,
1076
+ tier: "fast",
1077
+ modalities: ["text"],
1078
+ },
1079
+ {
1080
+ id: "moondream2",
1081
+ label: "Moondream2 1.5B",
1082
+ hint: "Vision",
1083
+ role: "chat",
1084
+ ramGb: 6,
1085
+ tier: "fast",
1086
+ modalities: ["text", "vision"],
1087
+ },
1088
+ {
1089
+ id: "medgemma:4b",
1090
+ label: "MedGemma 4B",
1091
+ hint: "Medical",
1092
+ role: "chat",
1093
+ ramGb: 8,
1094
+ tier: "fast",
1095
+ modalities: ["text", "vision"],
1096
+ },
1097
+ {
1098
+ id: "granite4.1:3b",
1099
+ label: "Granite 4.1 3B",
1100
+ hint: "IBM 3B",
1101
+ role: "chat",
1102
+ ramGb: 4,
1103
+ tier: "fast",
1104
+ modalities: ["text", "code"],
1105
+ },
1106
+ {
1107
+ id: "llama3.2:3B-Q4_K_M",
1108
+ label: "Llama 3.2 3B Q4",
1109
+ hint: "Meta 3B Q4",
1110
+ role: "chat",
1111
+ ramGb: 4,
1112
+ tier: "fast",
1113
+ modalities: ["text"],
1114
+ },
1115
+ {
1116
+ id: "gemma4:e2b",
1117
+ label: "Gemma 4 E2B default",
1118
+ hint: "Popular · multimodal",
1119
+ role: "chat",
1120
+ ramGb: 8,
1121
+ tier: "fast",
1122
+ modalities: ["text", "vision"],
1123
+ },
1124
+ {
1125
+ id: "gemma3:4b-q4_K_M",
1126
+ label: "Gemma 3 4B Q4",
1127
+ hint: "Reasoning Q4",
1128
+ role: "chat",
1129
+ ramGb: 8,
1130
+ tier: "fast",
1131
+ modalities: ["text", "reasoning"],
1132
+ },
1133
+ {
1134
+ id: "smollm3:Q4_K_M",
1135
+ label: "SmolLM3 Q4",
1136
+ hint: "On-device Q4",
1137
+ role: "chat",
1138
+ ramGb: 4,
1139
+ tier: "fast",
1140
+ modalities: ["text"],
1141
+ },
1142
+ {
1143
+ id: "granite3.3:2b-q4_K_M",
1144
+ label: "Granite 3.3 2B Q4",
1145
+ hint: "IBM 2B Q4",
1146
+ role: "chat",
1147
+ ramGb: 4,
1148
+ tier: "fast",
1149
+ modalities: ["text", "code"],
1150
+ },
1151
+ // Balanced (~8–16GB) — Gemma 4 E4B · Qwen near the top
1152
+ {
1153
+ id: "qwen3:8B-Q4_K_M",
1154
+ label: "Qwen3 8B",
1155
+ hint: "Popular · coding · agents",
1156
+ role: "chat",
1157
+ recommended: true,
1158
+ ramGb: 12,
1159
+ tier: "balanced",
1160
+ modalities: ["text", "code"],
1161
+ },
1162
+ {
1163
+ id: "gemma4:e4b",
1164
+ label: "Gemma 4 E4B",
1165
+ hint: "Popular · multimodal",
1166
+ role: "chat",
1167
+ ramGb: 16,
1168
+ tier: "balanced",
1169
+ modalities: ["text", "vision", "reasoning"],
1170
+ },
1171
+ {
1172
+ id: "qwen2.5:7B-Q4_K_M",
1173
+ label: "Qwen2.5 7B",
1174
+ hint: "Coding",
1175
+ role: "chat",
1176
+ ramGb: 12,
1177
+ tier: "balanced",
1178
+ modalities: ["text", "code"],
1179
+ },
1180
+ {
1181
+ id: "qwen3-vl:8B",
1182
+ label: "Qwen3-VL 8B",
1183
+ hint: "Vision",
1184
+ role: "chat",
1185
+ ramGb: 16,
1186
+ tier: "balanced",
1187
+ modalities: ["text", "vision", "code"],
1188
+ },
1189
+ {
1190
+ id: "llama3.1:8B-Q4_K_M",
1191
+ label: "Llama 3.1 8B",
1192
+ hint: "Meta general",
1193
+ role: "chat",
1194
+ ramGb: 12,
1195
+ tier: "balanced",
1196
+ modalities: ["text"],
1197
+ },
1198
+ {
1199
+ id: "gemma3n:e4b",
1200
+ label: "Gemma 3n E4B",
1201
+ hint: "On-device multimodal",
1202
+ role: "chat",
1203
+ ramGb: 16,
1204
+ tier: "balanced",
1205
+ modalities: ["text", "vision"],
1206
+ },
1207
+ {
1208
+ id: "mistral",
1209
+ label: "Mistral 7B",
1210
+ hint: "General + code",
1211
+ role: "chat",
1212
+ ramGb: 12,
1213
+ tier: "balanced",
1214
+ modalities: ["text", "code"],
1215
+ },
1216
+ {
1217
+ id: "deepseek-r1-distill-llama:8B-Q4_K_M",
1218
+ label: "DeepSeek R1 8B",
1219
+ hint: "Reasoning distill",
1220
+ role: "chat",
1221
+ ramGb: 12,
1222
+ tier: "balanced",
1223
+ modalities: ["text", "reasoning"],
1224
+ },
1225
+ {
1226
+ id: "mistral-nemo",
1227
+ label: "Mistral Nemo 12B",
1228
+ hint: "General",
1229
+ role: "chat",
1230
+ ramGb: 16,
1231
+ tier: "balanced",
1232
+ modalities: ["text", "code"],
1233
+ },
1234
+ {
1235
+ id: "ministral3:8B-Q4_K_M",
1236
+ label: "Ministral 8B",
1237
+ hint: "Mistral instruct",
1238
+ role: "chat",
1239
+ ramGb: 12,
1240
+ tier: "balanced",
1241
+ modalities: ["text", "code"],
1242
+ },
1243
+ {
1244
+ id: "granite-4.0-h-tiny",
1245
+ label: "Granite 4.0 H Tiny 7B",
1246
+ hint: "IBM tiny",
1247
+ role: "chat",
1248
+ ramGb: 12,
1249
+ tier: "balanced",
1250
+ modalities: ["text", "code"],
1251
+ },
1252
+ {
1253
+ id: "ministral-3:8b-instruct",
1254
+ label: "Ministral 3 8B Instruct",
1255
+ hint: "Instruct",
1256
+ role: "chat",
1257
+ ramGb: 12,
1258
+ tier: "balanced",
1259
+ modalities: ["text", "code"],
1260
+ },
1261
+ {
1262
+ id: "granite3.3:8b",
1263
+ label: "Granite 3.3 8B",
1264
+ hint: "IBM 8B",
1265
+ role: "chat",
1266
+ ramGb: 12,
1267
+ tier: "balanced",
1268
+ modalities: ["text", "code"],
1269
+ },
1270
+ {
1271
+ id: "granite4.1:8b",
1272
+ label: "Granite 4.1 8B",
1273
+ hint: "IBM 8B",
1274
+ role: "chat",
1275
+ ramGb: 12,
1276
+ tier: "balanced",
1277
+ modalities: ["text", "code"],
1278
+ },
1279
+ {
1280
+ id: "qwen3:8B-Q4_0",
1281
+ label: "Qwen3 8B Q4_0",
1282
+ hint: "Coding Q4_0",
1283
+ role: "chat",
1284
+ ramGb: 12,
1285
+ tier: "balanced",
1286
+ modalities: ["text", "code"],
1287
+ },
1288
+ {
1289
+ id: "gemma4:e4b-q4_K_M",
1290
+ label: "Gemma 4 E4B Q4",
1291
+ hint: "Popular · multimodal Q4",
1292
+ role: "chat",
1293
+ ramGb: 16,
1294
+ tier: "balanced",
1295
+ modalities: ["text", "vision", "reasoning"],
1296
+ },
1297
+ {
1298
+ id: "ministral-3:8b-reasoning",
1299
+ label: "Ministral 3 8B Reasoning",
1300
+ hint: "Reasoning",
1301
+ role: "chat",
1302
+ ramGb: 12,
1303
+ tier: "balanced",
1304
+ modalities: ["text", "reasoning", "code"],
1305
+ },
1306
+ {
1307
+ id: "llama3.1",
1308
+ label: "Llama 3.1 8B",
1309
+ hint: "Meta default",
1310
+ role: "chat",
1311
+ ramGb: 12,
1312
+ tier: "balanced",
1313
+ modalities: ["text"],
1314
+ },
1315
+ {
1316
+ id: "qwen2.5",
1317
+ label: "Qwen2.5 7B",
1318
+ hint: "Coding default",
1319
+ role: "chat",
1320
+ ramGb: 12,
1321
+ tier: "balanced",
1322
+ modalities: ["text", "code"],
1323
+ },
1324
+ {
1325
+ id: "gemma3n",
1326
+ label: "Gemma 3n",
1327
+ hint: "On-device default",
1328
+ role: "chat",
1329
+ ramGb: 16,
1330
+ tier: "balanced",
1331
+ modalities: ["text", "vision"],
1332
+ },
1333
+ // Smart (~24–32GB) — Gemma 4 31B · Qwen family near the top
1334
+ {
1335
+ id: "phi4",
1336
+ label: "Phi-4 14B",
1337
+ hint: "Reasoning",
1338
+ role: "chat",
1339
+ recommended: true,
1340
+ ramGb: 24,
1341
+ tier: "smart",
1342
+ modalities: ["text", "reasoning", "code"],
1343
+ },
1344
+ {
1345
+ id: "gemma4:31b",
1346
+ label: "Gemma 4 31B",
1347
+ hint: "Popular · large multimodal",
1348
+ role: "chat",
1349
+ ramGb: 32,
1350
+ tier: "smart",
1351
+ modalities: ["text", "vision", "reasoning"],
1352
+ },
1353
+ {
1354
+ id: "qwen3.5:27b",
1355
+ label: "Qwen3.5 27B",
1356
+ hint: "Popular · general · coding",
1357
+ role: "chat",
1358
+ ramGb: 32,
1359
+ tier: "smart",
1360
+ modalities: ["text", "code"],
1361
+ },
1362
+ {
1363
+ id: "qwen3:30B-A3B-Q4_K_M",
1364
+ label: "Qwen3 30B-A3B",
1365
+ hint: "MoE · coding",
1366
+ role: "chat",
1367
+ ramGb: 24,
1368
+ tier: "smart",
1369
+ modalities: ["text", "code"],
1370
+ },
1371
+ {
1372
+ id: "qwen3-coder:30B",
1373
+ label: "Qwen3-Coder 30B",
1374
+ hint: "Coding agent",
1375
+ role: "chat",
1376
+ ramGb: 24,
1377
+ tier: "smart",
1378
+ modalities: ["text", "code"],
1379
+ },
1380
+ {
1381
+ id: "qwen3-vl:32B-UD-Q4_K_XL",
1382
+ label: "Qwen3-VL 32B",
1383
+ hint: "Vision",
1384
+ role: "chat",
1385
+ ramGb: 32,
1386
+ tier: "smart",
1387
+ modalities: ["text", "vision", "code"],
1388
+ },
1389
+ {
1390
+ id: "gemma3:27b",
1391
+ label: "Gemma 3 27B",
1392
+ hint: "Large reasoning",
1393
+ role: "chat",
1394
+ ramGb: 32,
1395
+ tier: "smart",
1396
+ modalities: ["text", "reasoning"],
1397
+ },
1398
+ {
1399
+ id: "qwq:32B-Q4_K_M",
1400
+ label: "QwQ 32B",
1401
+ hint: "Reasoning",
1402
+ role: "chat",
1403
+ ramGb: 32,
1404
+ tier: "smart",
1405
+ modalities: ["text", "reasoning"],
1406
+ },
1407
+ {
1408
+ id: "gpt-oss:20b",
1409
+ label: "GPT-OSS 20B",
1410
+ hint: "Open weights",
1411
+ role: "chat",
1412
+ ramGb: 24,
1413
+ tier: "smart",
1414
+ modalities: ["text", "reasoning"],
1415
+ },
1416
+ {
1417
+ id: "magistral-small-3.2",
1418
+ label: "Magistral Small 24B",
1419
+ hint: "Reasoning",
1420
+ role: "chat",
1421
+ ramGb: 24,
1422
+ tier: "smart",
1423
+ modalities: ["text", "reasoning"],
1424
+ },
1425
+ {
1426
+ id: "ministral3:14B",
1427
+ label: "Ministral 14B",
1428
+ hint: "Mistral mid",
1429
+ role: "chat",
1430
+ ramGb: 24,
1431
+ tier: "smart",
1432
+ modalities: ["text", "code"],
1433
+ },
1434
+ {
1435
+ id: "nemotron-3-nano:30b-a3b",
1436
+ label: "Nemotron 3 Nano 30B-A3B",
1437
+ hint: "NVIDIA MoE",
1438
+ role: "chat",
1439
+ ramGb: 24,
1440
+ tier: "smart",
1441
+ modalities: ["text", "code"],
1442
+ },
1443
+ {
1444
+ id: "devstral-small-2",
1445
+ label: "Devstral Small 2",
1446
+ hint: "Coding",
1447
+ role: "chat",
1448
+ ramGb: 24,
1449
+ tier: "smart",
1450
+ modalities: ["text", "code"],
1451
+ },
1452
+ {
1453
+ id: "medgemma:27b-text",
1454
+ label: "MedGemma 27B Text",
1455
+ hint: "Medical",
1456
+ role: "chat",
1457
+ ramGb: 32,
1458
+ tier: "smart",
1459
+ modalities: ["text"],
1460
+ },
1461
+ {
1462
+ id: "gemma4:26b-a4b-q4_K_M",
1463
+ label: "Gemma 4 26B-A4B",
1464
+ hint: "Popular · MoE multimodal",
1465
+ role: "chat",
1466
+ ramGb: 32,
1467
+ tier: "smart",
1468
+ modalities: ["text", "vision", "reasoning"],
1469
+ },
1470
+ {
1471
+ id: "glm-4.7-flash",
1472
+ label: "GLM-4.7 Flash",
1473
+ hint: "MoE flash",
1474
+ role: "chat",
1475
+ ramGb: 24,
1476
+ tier: "smart",
1477
+ modalities: ["text", "code"],
1478
+ },
1479
+ {
1480
+ id: "granite4.1:30b",
1481
+ label: "Granite 4.1 30B",
1482
+ hint: "IBM 30B",
1483
+ role: "chat",
1484
+ ramGb: 32,
1485
+ tier: "smart",
1486
+ modalities: ["text", "code"],
1487
+ },
1488
+ {
1489
+ id: "deepcoder-preview",
1490
+ label: "DeepCoder Preview 14B",
1491
+ hint: "Coding",
1492
+ role: "chat",
1493
+ ramGb: 24,
1494
+ tier: "smart",
1495
+ modalities: ["text", "code"],
1496
+ },
1497
+ {
1498
+ id: "qwen3:14B-Q6_K",
1499
+ label: "Qwen3 14B",
1500
+ hint: "Coding mid",
1501
+ role: "chat",
1502
+ ramGb: 24,
1503
+ tier: "smart",
1504
+ modalities: ["text", "code"],
1505
+ },
1506
+ {
1507
+ id: "nemotron3",
1508
+ label: "Nemotron 3 30B",
1509
+ hint: "NVIDIA",
1510
+ role: "chat",
1511
+ ramGb: 32,
1512
+ tier: "smart",
1513
+ modalities: ["text", "code"],
1514
+ },
1515
+ ];
1516
+
1517
+ /**
1518
+ * llama.cpp chat models in a tier (manual pick shows up to 20), ordered by RAM.
1519
+ *
1520
+ * @param tier - Speed / quality tier
1521
+ */
1522
+ export function llamaCppModelsForTier(tier: ModelTier): readonly CatalogModel[] {
1523
+ return byRamThenLabel(LLAMA_CPP_CHAT_MODELS.filter((m) => m.tier === tier)).slice(0, 20);
1524
+ }
1525
+
1526
+ /**
1527
+ * Recommended Docker Hub `ai/` model for a tier (RAM-aware).
1528
+ *
1529
+ * @param tier - Selected tier
1530
+ * @param totalRamGb - Prefer models that fit when RAM is known
1531
+ */
1532
+ export function recommendLlamaCppForTier(
1533
+ tier: ModelTier,
1534
+ totalRamGb: number | null = null,
1535
+ ): CatalogModel {
1536
+ const list = llamaCppModelsForTier(tier);
1537
+ const headroom = 4;
1538
+ const fits =
1539
+ totalRamGb !== null && Number.isFinite(totalRamGb)
1540
+ ? list.filter((m) => m.ramGb + headroom <= totalRamGb || m.ramGb <= totalRamGb)
1541
+ : list;
1542
+ const pool = fits.length > 0 ? fits : list;
1543
+ return pool.find((m) => m.recommended) ?? pool[pool.length - 1] ?? list[0]!;
1544
+ }