@raingor/pi-web-switch 0.3.2 → 0.4.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.
@@ -0,0 +1,967 @@
1
+ // ─── Model Catalog ─────────────────────────────────────────
2
+ // A curated database of known models with their full capabilities,
3
+ // so users don't have to look up context window / max tokens / reasoning / vision / cost
4
+ // every time they add a model to a custom provider.
5
+ //
6
+ // Costs are in USD per 1M tokens. For providers whose pricing differs
7
+ // from the vendor (e.g. resellers), users can override cost in the form.
8
+
9
+ import type { Model } from "@/types";
10
+
11
+ export interface CatalogEntry {
12
+ // Match patterns: tried in order, first match wins on id (case-insensitive)
13
+ patterns: string[];
14
+ // Filled-in model meta
15
+ name?: string;
16
+ reasoning?: boolean;
17
+ input?: Model["input"];
18
+ contextWindow: number;
19
+ maxTokens: number;
20
+ cost?: Model["cost"];
21
+ // Optional tag shown in the catalog picker
22
+ family?: string;
23
+ }
24
+
25
+ // ─── Helpers ────────────────────────────────────────────────
26
+
27
+ const P = (input: number, output: number, cacheRead = 0, cacheWrite = 0) => ({
28
+ input,
29
+ output,
30
+ cacheRead,
31
+ cacheWrite,
32
+ });
33
+
34
+ // ─── Catalog ────────────────────────────────────────────────
35
+ // Ordered roughly by popularity; more specific patterns first.
36
+
37
+ export const MODEL_CATALOG: CatalogEntry[] = [
38
+ // ── Anthropic Claude ────────────────────────────────────
39
+ {
40
+ patterns: ["claude-opus-4-5", "claude-4-5-opus"],
41
+ name: "Claude 4.5 Opus",
42
+ reasoning: true,
43
+ input: ["text", "image"],
44
+ contextWindow: 200_000,
45
+ maxTokens: 32_000,
46
+ cost: P(15, 75, 1.5, 18.75),
47
+ family: "Anthropic",
48
+ },
49
+ {
50
+ patterns: ["claude-sonnet-4-5", "claude-4-5-sonnet"],
51
+ name: "Claude 4.5 Sonnet",
52
+ reasoning: true,
53
+ input: ["text", "image"],
54
+ contextWindow: 200_000,
55
+ maxTokens: 16_384,
56
+ cost: P(3, 15, 0.3, 3.75),
57
+ family: "Anthropic",
58
+ },
59
+ {
60
+ patterns: ["claude-opus-4", "claude-4-opus"],
61
+ name: "Claude 4 Opus",
62
+ reasoning: true,
63
+ input: ["text", "image"],
64
+ contextWindow: 200_000,
65
+ maxTokens: 32_000,
66
+ cost: P(15, 75, 1.5, 18.75),
67
+ family: "Anthropic",
68
+ },
69
+ {
70
+ patterns: ["claude-sonnet-4", "claude-4-sonnet"],
71
+ name: "Claude 4 Sonnet",
72
+ reasoning: true,
73
+ input: ["text", "image"],
74
+ contextWindow: 200_000,
75
+ maxTokens: 16_384,
76
+ cost: P(3, 15, 0.3, 3.75),
77
+ family: "Anthropic",
78
+ },
79
+ {
80
+ patterns: ["claude-3-7-sonnet", "claude-3.7-sonnet"],
81
+ name: "Claude 3.7 Sonnet",
82
+ reasoning: true,
83
+ input: ["text", "image"],
84
+ contextWindow: 200_000,
85
+ maxTokens: 64_000,
86
+ cost: P(3, 15, 0.3, 3.75),
87
+ family: "Anthropic",
88
+ },
89
+ {
90
+ patterns: ["claude-3-5-sonnet", "claude-3.5-sonnet"],
91
+ name: "Claude 3.5 Sonnet",
92
+ reasoning: false,
93
+ input: ["text", "image"],
94
+ contextWindow: 200_000,
95
+ maxTokens: 8192,
96
+ cost: P(3, 15, 0.3, 3.75),
97
+ family: "Anthropic",
98
+ },
99
+ {
100
+ patterns: ["claude-3-5-haiku", "claude-3.5-haiku"],
101
+ name: "Claude 3.5 Haiku",
102
+ reasoning: false,
103
+ input: ["text", "image"],
104
+ contextWindow: 200_000,
105
+ maxTokens: 8192,
106
+ cost: P(0.8, 4, 0.08, 1),
107
+ family: "Anthropic",
108
+ },
109
+ {
110
+ patterns: ["claude-haiku-3-5"],
111
+ name: "Claude 3.5 Haiku",
112
+ reasoning: false,
113
+ input: ["text", "image"],
114
+ contextWindow: 200_000,
115
+ maxTokens: 8192,
116
+ cost: P(0.8, 4, 0.08, 1),
117
+ family: "Anthropic",
118
+ },
119
+
120
+ // ── OpenAI / GPT ────────────────────────────────────────
121
+ {
122
+ patterns: ["gpt-5", "gpt5"],
123
+ name: "GPT-5",
124
+ reasoning: true,
125
+ input: ["text", "image"],
126
+ contextWindow: 256_000,
127
+ maxTokens: 65_536,
128
+ cost: P(10, 40, 5, 10),
129
+ family: "OpenAI",
130
+ },
131
+ {
132
+ patterns: ["gpt-5.1", "gpt-5-1"],
133
+ name: "GPT-5.1",
134
+ reasoning: true,
135
+ input: ["text", "image"],
136
+ contextWindow: 256_000,
137
+ maxTokens: 65_536,
138
+ cost: P(10, 40, 5, 10),
139
+ family: "OpenAI",
140
+ },
141
+ {
142
+ patterns: ["gpt-4o-mini"],
143
+ name: "GPT-4o Mini",
144
+ reasoning: false,
145
+ input: ["text", "image"],
146
+ contextWindow: 128_000,
147
+ maxTokens: 16_384,
148
+ cost: P(0.15, 0.6, 0.075, 0.225),
149
+ family: "OpenAI",
150
+ },
151
+ {
152
+ patterns: ["gpt-4o"],
153
+ name: "GPT-4o",
154
+ reasoning: false,
155
+ input: ["text", "image"],
156
+ contextWindow: 128_000,
157
+ maxTokens: 16_384,
158
+ cost: P(2.5, 10, 1.25, 3.75),
159
+ family: "OpenAI",
160
+ },
161
+ {
162
+ patterns: ["gpt-4.1-mini", "gpt-4-1-mini"],
163
+ name: "GPT-4.1 Mini",
164
+ reasoning: false,
165
+ input: ["text", "image"],
166
+ contextWindow: 1_048_576,
167
+ maxTokens: 65_536,
168
+ cost: P(0.4, 1.6, 0.2, 0.4),
169
+ family: "OpenAI",
170
+ },
171
+ {
172
+ patterns: ["gpt-4.1", "gpt-4-1"],
173
+ name: "GPT-4.1",
174
+ reasoning: false,
175
+ input: ["text", "image"],
176
+ contextWindow: 1_048_576,
177
+ maxTokens: 32_768,
178
+ cost: P(2, 8, 0.5, 2),
179
+ family: "OpenAI",
180
+ },
181
+ {
182
+ patterns: ["o3-pro"],
183
+ name: "o3-pro",
184
+ reasoning: true,
185
+ input: ["text", "image"],
186
+ contextWindow: 200_000,
187
+ maxTokens: 100_000,
188
+ cost: P(11, 44),
189
+ family: "OpenAI",
190
+ },
191
+ {
192
+ patterns: ["o3"],
193
+ name: "o3",
194
+ reasoning: true,
195
+ input: ["text", "image"],
196
+ contextWindow: 200_000,
197
+ maxTokens: 100_000,
198
+ cost: P(2, 8, 1, 2),
199
+ family: "OpenAI",
200
+ },
201
+ {
202
+ patterns: ["o4-mini"],
203
+ name: "o4-mini",
204
+ reasoning: true,
205
+ input: ["text", "image"],
206
+ contextWindow: 200_000,
207
+ maxTokens: 100_000,
208
+ cost: P(1.1, 4.4, 0.55, 1.65),
209
+ family: "OpenAI",
210
+ },
211
+ {
212
+ patterns: ["o3-mini"],
213
+ name: "o3-mini",
214
+ reasoning: true,
215
+ input: ["text"],
216
+ contextWindow: 200_000,
217
+ maxTokens: 100_000,
218
+ cost: P(1.1, 4.4, 0.55, 1.65),
219
+ family: "OpenAI",
220
+ },
221
+ {
222
+ patterns: ["o1"],
223
+ name: "o1",
224
+ reasoning: true,
225
+ input: ["text"],
226
+ contextWindow: 200_000,
227
+ maxTokens: 100_000,
228
+ cost: P(15, 60),
229
+ family: "OpenAI",
230
+ },
231
+
232
+ // ── DeepSeek ────────────────────────────────────────────
233
+ {
234
+ patterns: ["deepseek-v4", "deepseek-v4-flash", "deepseek-v4-chat"],
235
+ name: "DeepSeek V4 Flash",
236
+ reasoning: false,
237
+ input: ["text"],
238
+ contextWindow: 128_000,
239
+ maxTokens: 8192,
240
+ cost: P(0.3, 0.6, 0.15, 0.3),
241
+ family: "DeepSeek",
242
+ },
243
+ {
244
+ patterns: ["deepseek-v4-pro"],
245
+ name: "DeepSeek V4 Pro",
246
+ reasoning: true,
247
+ input: ["text"],
248
+ contextWindow: 128_000,
249
+ maxTokens: 8192,
250
+ cost: P(2, 8, 1, 2),
251
+ family: "DeepSeek",
252
+ },
253
+ {
254
+ patterns: ["deepseek-reasoner", "deepseek-r1"],
255
+ name: "DeepSeek R1",
256
+ reasoning: true,
257
+ input: ["text"],
258
+ contextWindow: 128_000,
259
+ maxTokens: 8192,
260
+ cost: P(0.55, 2.19, 0.14, 0.55),
261
+ family: "DeepSeek",
262
+ },
263
+ {
264
+ patterns: ["deepseek-chat", "deepseek-v3"],
265
+ name: "DeepSeek V3",
266
+ reasoning: false,
267
+ input: ["text"],
268
+ contextWindow: 128_000,
269
+ maxTokens: 8192,
270
+ cost: P(0.27, 1.1, 0.07, 0.27),
271
+ family: "DeepSeek",
272
+ },
273
+
274
+ // ── Google Gemini ───────────────────────────────────────
275
+ {
276
+ patterns: ["gemini-2.5-pro"],
277
+ name: "Gemini 2.5 Pro",
278
+ reasoning: true,
279
+ input: ["text", "image"],
280
+ contextWindow: 1_048_576,
281
+ maxTokens: 65_536,
282
+ cost: P(1.25, 10, 0.625, 1.25),
283
+ family: "Google",
284
+ },
285
+ {
286
+ patterns: ["gemini-2.5-flash"],
287
+ name: "Gemini 2.5 Flash",
288
+ reasoning: false,
289
+ input: ["text", "image"],
290
+ contextWindow: 1_048_576,
291
+ maxTokens: 65_536,
292
+ cost: P(0.15, 0.6, 0.075, 0.15),
293
+ family: "Google",
294
+ },
295
+ {
296
+ patterns: ["gemini-2.0-flash"],
297
+ name: "Gemini 2.0 Flash",
298
+ reasoning: false,
299
+ input: ["text", "image"],
300
+ contextWindow: 1_048_576,
301
+ maxTokens: 8192,
302
+ cost: P(0.1, 0.4, 0.025, 0.1),
303
+ family: "Google",
304
+ },
305
+
306
+ // ── Alibaba Qwen (通义千问) ─────────────────────────────
307
+ {
308
+ patterns: ["qwen3-max", "qwen-3-max", "qwen3.7-max", "qwen-3.7-max"],
309
+ name: "Qwen 3 Max",
310
+ reasoning: true,
311
+ input: ["text"],
312
+ contextWindow: 128_000,
313
+ maxTokens: 8192,
314
+ cost: P(1.5, 6, 0.75, 1.5),
315
+ family: "Qwen",
316
+ },
317
+ {
318
+ patterns: ["qwen3-coder"],
319
+ name: "Qwen 3 Coder",
320
+ reasoning: true,
321
+ input: ["text"],
322
+ contextWindow: 256_000,
323
+ maxTokens: 65_536,
324
+ family: "Qwen",
325
+ },
326
+ {
327
+ patterns: ["qwen3-vl"],
328
+ name: "Qwen 3 VL",
329
+ reasoning: false,
330
+ input: ["text", "image"],
331
+ contextWindow: 128_000,
332
+ maxTokens: 8192,
333
+ family: "Qwen",
334
+ },
335
+ {
336
+ patterns: ["qwen3-"],
337
+ name: "Qwen 3",
338
+ reasoning: true,
339
+ input: ["text"],
340
+ contextWindow: 128_000,
341
+ maxTokens: 8192,
342
+ family: "Qwen",
343
+ },
344
+ {
345
+ patterns: ["qwen2.5-vl", "qwen-vl-max", "qwen-vl-plus"],
346
+ name: "Qwen VL",
347
+ reasoning: false,
348
+ input: ["text", "image"],
349
+ contextWindow: 128_000,
350
+ maxTokens: 8192,
351
+ family: "Qwen",
352
+ },
353
+ {
354
+ patterns: ["qwen-max"],
355
+ name: "Qwen Max",
356
+ reasoning: false,
357
+ input: ["text"],
358
+ contextWindow: 32_768,
359
+ maxTokens: 8192,
360
+ cost: P(1.6, 6.4),
361
+ family: "Qwen",
362
+ },
363
+ {
364
+ patterns: ["qwen-plus"],
365
+ name: "Qwen Plus",
366
+ reasoning: false,
367
+ input: ["text"],
368
+ contextWindow: 128_000,
369
+ maxTokens: 8192,
370
+ cost: P(0.8, 2),
371
+ family: "Qwen",
372
+ },
373
+ {
374
+ patterns: ["qwen-turbo"],
375
+ name: "Qwen Turbo",
376
+ reasoning: false,
377
+ input: ["text"],
378
+ contextWindow: 1_000_000,
379
+ maxTokens: 8192,
380
+ cost: P(0.3, 0.6),
381
+ family: "Qwen",
382
+ },
383
+ {
384
+ patterns: ["qwen2.5-coder", "qwen-coder"],
385
+ name: "Qwen 2.5 Coder",
386
+ reasoning: false,
387
+ input: ["text"],
388
+ contextWindow: 128_000,
389
+ maxTokens: 8192,
390
+ family: "Qwen",
391
+ },
392
+ {
393
+ patterns: ["qwen2.5-72b"],
394
+ name: "Qwen 2.5 72B",
395
+ reasoning: false,
396
+ input: ["text"],
397
+ contextWindow: 128_000,
398
+ maxTokens: 8192,
399
+ family: "Qwen",
400
+ },
401
+ {
402
+ patterns: ["qwen-long"],
403
+ name: "Qwen Long",
404
+ reasoning: false,
405
+ input: ["text"],
406
+ contextWindow: 10_000_000,
407
+ maxTokens: 8192,
408
+ cost: P(0.5, 2),
409
+ family: "Qwen",
410
+ },
411
+
412
+ // ── Zhipu GLM (智谱) ────────────────────────────────────
413
+ {
414
+ patterns: ["glm-5.1", "glm-5-plus"],
415
+ name: "GLM 5.1",
416
+ reasoning: false,
417
+ input: ["text"],
418
+ contextWindow: 128_000,
419
+ maxTokens: 8192,
420
+ cost: P(0.5, 2, 0.25, 0.5),
421
+ family: "GLM",
422
+ },
423
+ {
424
+ patterns: ["glm-z1", "glm-4.5"],
425
+ name: "GLM-Z1 (Reasoning)",
426
+ reasoning: true,
427
+ input: ["text"],
428
+ contextWindow: 128_000,
429
+ maxTokens: 8192,
430
+ family: "GLM",
431
+ },
432
+ {
433
+ patterns: ["glm-4-plus"],
434
+ name: "GLM-4 Plus",
435
+ reasoning: false,
436
+ input: ["text"],
437
+ contextWindow: 128_000,
438
+ maxTokens: 4096,
439
+ cost: P(5, 5),
440
+ family: "GLM",
441
+ },
442
+ {
443
+ patterns: ["glm-4-air"],
444
+ name: "GLM-4 Air",
445
+ reasoning: false,
446
+ input: ["text"],
447
+ contextWindow: 128_000,
448
+ maxTokens: 4096,
449
+ cost: P(0.1, 0.1),
450
+ family: "GLM",
451
+ },
452
+ {
453
+ patterns: ["glm-4v", "glm-4-vision"],
454
+ name: "GLM-4V (Vision)",
455
+ reasoning: false,
456
+ input: ["text", "image"],
457
+ contextWindow: 8192,
458
+ maxTokens: 4096,
459
+ cost: P(1, 1),
460
+ family: "GLM",
461
+ },
462
+ {
463
+ patterns: ["glm-4-long"],
464
+ name: "GLM-4 Long",
465
+ reasoning: false,
466
+ input: ["text"],
467
+ contextWindow: 1_000_000,
468
+ maxTokens: 4096,
469
+ cost: P(1, 1),
470
+ family: "GLM",
471
+ },
472
+
473
+ // ── Moonshot Kimi (月之暗面) ────────────────────────────
474
+ {
475
+ patterns: ["moonshot-v1-128k", "kimi-128k"],
476
+ name: "Kimi 128K",
477
+ reasoning: false,
478
+ input: ["text"],
479
+ contextWindow: 128_000,
480
+ maxTokens: 8192,
481
+ cost: P(1.2, 1.2),
482
+ family: "Moonshot",
483
+ },
484
+ {
485
+ patterns: ["moonshot-v1-32k", "kimi-32k"],
486
+ name: "Kimi 32K",
487
+ reasoning: false,
488
+ input: ["text"],
489
+ contextWindow: 32_768,
490
+ maxTokens: 8192,
491
+ cost: P(0.6, 0.6),
492
+ family: "Moonshot",
493
+ },
494
+ {
495
+ patterns: ["moonshot-v1-8k", "kimi-8k", "moonshot-v1"],
496
+ name: "Kimi 8K",
497
+ reasoning: false,
498
+ input: ["text"],
499
+ contextWindow: 8192,
500
+ maxTokens: 4096,
501
+ cost: P(0.3, 0.3),
502
+ family: "Moonshot",
503
+ },
504
+ {
505
+ patterns: ["kimi-latest"],
506
+ name: "Kimi Latest",
507
+ reasoning: false,
508
+ input: ["text"],
509
+ contextWindow: 256_000,
510
+ maxTokens: 8192,
511
+ family: "Moonshot",
512
+ },
513
+
514
+ // ── ByteDance Doubao (豆包) ─────────────────────────────
515
+ {
516
+ patterns: ["doubao-1.5-pro"],
517
+ name: "Doubao 1.5 Pro",
518
+ reasoning: false,
519
+ input: ["text"],
520
+ contextWindow: 256_000,
521
+ maxTokens: 16_384,
522
+ cost: P(0.8, 2),
523
+ family: "Doubao",
524
+ },
525
+ {
526
+ patterns: ["doubao-pro"],
527
+ name: "Doubao Pro",
528
+ reasoning: false,
529
+ input: ["text"],
530
+ contextWindow: 256_000,
531
+ maxTokens: 4096,
532
+ cost: P(0.8, 2),
533
+ family: "Doubao",
534
+ },
535
+ {
536
+ patterns: ["doubao-lite"],
537
+ name: "Doubao Lite",
538
+ reasoning: false,
539
+ input: ["text"],
540
+ contextWindow: 256_000,
541
+ maxTokens: 4096,
542
+ cost: P(0.3, 0.6),
543
+ family: "Doubao",
544
+ },
545
+ {
546
+ patterns: ["doubao-vision"],
547
+ name: "Doubao Vision",
548
+ reasoning: false,
549
+ input: ["text", "image"],
550
+ contextWindow: 32_768,
551
+ maxTokens: 4096,
552
+ family: "Doubao",
553
+ },
554
+ {
555
+ patterns: ["seed-1-6", "doubao-seed"],
556
+ name: "Doubao Seed 1.6",
557
+ reasoning: true,
558
+ input: ["text"],
559
+ contextWindow: 256_000,
560
+ maxTokens: 16_384,
561
+ family: "Doubao",
562
+ },
563
+
564
+ // ── Baidu ERNIE (文心) ──────────────────────────────────
565
+ {
566
+ patterns: ["ernie-4", "ernie-4.0", "wenxin-4"],
567
+ name: "ERNIE 4.0",
568
+ reasoning: false,
569
+ input: ["text"],
570
+ contextWindow: 128_000,
571
+ maxTokens: 4096,
572
+ cost: P(12, 12),
573
+ family: "ERNIE",
574
+ },
575
+ {
576
+ patterns: ["ernie-3.5", "ernie-35"],
577
+ name: "ERNIE 3.5",
578
+ reasoning: false,
579
+ input: ["text"],
580
+ contextWindow: 128_000,
581
+ maxTokens: 4096,
582
+ cost: P(1.2, 1.2),
583
+ family: "ERNIE",
584
+ },
585
+ {
586
+ patterns: ["ernie-x1"],
587
+ name: "ERNIE X1 (Reasoning)",
588
+ reasoning: true,
589
+ input: ["text"],
590
+ contextWindow: 128_000,
591
+ maxTokens: 8192,
592
+ family: "ERNIE",
593
+ },
594
+
595
+ // ── SenseNova (商汤) ────────────────────────────────────
596
+ {
597
+ patterns: ["glm-5.2"],
598
+ name: "GLM 5.2",
599
+ reasoning: false,
600
+ input: ["text"],
601
+ contextWindow: 128_000,
602
+ maxTokens: 8192,
603
+ family: "SenseNova",
604
+ },
605
+
606
+ // ── MiniMax ─────────────────────────────────────────────
607
+ {
608
+ patterns: ["minimax-abab6.5", "abab6.5"],
609
+ name: "MiniMax abab 6.5",
610
+ reasoning: false,
611
+ input: ["text"],
612
+ contextWindow: 245_760,
613
+ maxTokens: 8192,
614
+ family: "MiniMax",
615
+ },
616
+ {
617
+ patterns: ["minimax-text-01"],
618
+ name: "MiniMax Text-01",
619
+ reasoning: false,
620
+ input: ["text"],
621
+ contextWindow: 1_000_000,
622
+ maxTokens: 8192,
623
+ family: "MiniMax",
624
+ },
625
+
626
+ // ── 01.AI Yi (零一万物) ─────────────────────────────────
627
+ {
628
+ patterns: ["yi-lightning"],
629
+ name: "Yi Lightning",
630
+ reasoning: false,
631
+ input: ["text"],
632
+ contextWindow: 128_000,
633
+ maxTokens: 4096,
634
+ family: "Yi",
635
+ },
636
+ {
637
+ patterns: ["yi-large"],
638
+ name: "Yi Large",
639
+ reasoning: false,
640
+ input: ["text"],
641
+ contextWindow: 32_768,
642
+ maxTokens: 4096,
643
+ cost: P(1, 2),
644
+ family: "Yi",
645
+ },
646
+ {
647
+ patterns: ["yi-vision"],
648
+ name: "Yi Vision",
649
+ reasoning: false,
650
+ input: ["text", "image"],
651
+ contextWindow: 16_384,
652
+ maxTokens: 4096,
653
+ family: "Yi",
654
+ },
655
+ {
656
+ patterns: ["yi-34b"],
657
+ name: "Yi 34B",
658
+ reasoning: false,
659
+ input: ["text"],
660
+ contextWindow: 4096,
661
+ maxTokens: 4096,
662
+ family: "Yi",
663
+ },
664
+
665
+ // ── MiMo ────────────────────────────────────────────────
666
+ {
667
+ patterns: ["mimo-v2.5", "mimo-v2"],
668
+ name: "MiMo V2.5",
669
+ reasoning: true,
670
+ input: ["text"],
671
+ contextWindow: 128_000,
672
+ maxTokens: 8192,
673
+ cost: P(1.2, 4.8, 0.6, 1.2),
674
+ family: "MiMo",
675
+ },
676
+
677
+ // ── StepFun (阶跃星辰) ──────────────────────────────────
678
+ {
679
+ patterns: ["step-2"],
680
+ name: "Step 2",
681
+ reasoning: true,
682
+ input: ["text"],
683
+ contextWindow: 128_000,
684
+ maxTokens: 8192,
685
+ family: "StepFun",
686
+ },
687
+ {
688
+ patterns: ["step-1v"],
689
+ name: "Step 1V (Vision)",
690
+ reasoning: false,
691
+ input: ["text", "image"],
692
+ contextWindow: 8192,
693
+ maxTokens: 4096,
694
+ family: "StepFun",
695
+ },
696
+
697
+ // ── Mistral ─────────────────────────────────────────────
698
+ {
699
+ patterns: ["mistral-large"],
700
+ name: "Mistral Large",
701
+ reasoning: false,
702
+ input: ["text"],
703
+ contextWindow: 128_000,
704
+ maxTokens: 8192,
705
+ cost: P(2, 6, 1, 2),
706
+ family: "Mistral",
707
+ },
708
+ {
709
+ patterns: ["mistral-medium"],
710
+ name: "Mistral Medium",
711
+ reasoning: false,
712
+ input: ["text"],
713
+ contextWindow: 32_000,
714
+ maxTokens: 4096,
715
+ cost: P(0.4, 2),
716
+ family: "Mistral",
717
+ },
718
+ {
719
+ patterns: ["mistral-small"],
720
+ name: "Mistral Small",
721
+ reasoning: false,
722
+ input: ["text"],
723
+ contextWindow: 32_000,
724
+ maxTokens: 4096,
725
+ cost: P(0.2, 0.6),
726
+ family: "Mistral",
727
+ },
728
+ {
729
+ patterns: ["codestral"],
730
+ name: "Codestral",
731
+ reasoning: false,
732
+ input: ["text"],
733
+ contextWindow: 32_000,
734
+ maxTokens: 4096,
735
+ cost: P(0.3, 0.9),
736
+ family: "Mistral",
737
+ },
738
+
739
+ // ── Llama (Meta) ────────────────────────────────────────
740
+ {
741
+ patterns: ["llama-4-maverick"],
742
+ name: "Llama 4 Maverick",
743
+ reasoning: true,
744
+ input: ["text", "image"],
745
+ contextWindow: 1_000_000,
746
+ maxTokens: 16_384,
747
+ family: "Llama",
748
+ },
749
+ {
750
+ patterns: ["llama-4-scout"],
751
+ name: "Llama 4 Scout",
752
+ reasoning: false,
753
+ input: ["text", "image"],
754
+ contextWindow: 10_000_000,
755
+ maxTokens: 16_384,
756
+ family: "Llama",
757
+ },
758
+ {
759
+ patterns: ["llama-3.3-70b", "llama-3-3-70b"],
760
+ name: "Llama 3.3 70B",
761
+ reasoning: false,
762
+ input: ["text"],
763
+ contextWindow: 128_000,
764
+ maxTokens: 8192,
765
+ cost: P(0.59, 0.79),
766
+ family: "Llama",
767
+ },
768
+ {
769
+ patterns: ["llama-3.1-405b", "llama-3-1-405b"],
770
+ name: "Llama 3.1 405B",
771
+ reasoning: false,
772
+ input: ["text"],
773
+ contextWindow: 128_000,
774
+ maxTokens: 4096,
775
+ family: "Llama",
776
+ },
777
+ {
778
+ patterns: ["llama-3.1-70b", "llama-3-1-70b"],
779
+ name: "Llama 3.1 70B",
780
+ reasoning: false,
781
+ input: ["text"],
782
+ contextWindow: 128_000,
783
+ maxTokens: 4096,
784
+ family: "Llama",
785
+ },
786
+ {
787
+ patterns: ["llama-3.2-90b-vision", "llama-3-2-90b-vision"],
788
+ name: "Llama 3.2 90B Vision",
789
+ reasoning: false,
790
+ input: ["text", "image"],
791
+ contextWindow: 128_000,
792
+ maxTokens: 4096,
793
+ family: "Llama",
794
+ },
795
+ {
796
+ patterns: ["llama-3.2-11b-vision", "llama-3-2-11b-vision"],
797
+ name: "Llama 3.2 11B Vision",
798
+ reasoning: false,
799
+ input: ["text", "image"],
800
+ contextWindow: 128_000,
801
+ maxTokens: 4096,
802
+ family: "Llama",
803
+ },
804
+
805
+ // ── Grok (xAI) ─────────────────────────────────────────
806
+ {
807
+ patterns: ["grok-3-mini"],
808
+ name: "Grok 3 Mini",
809
+ reasoning: true,
810
+ input: ["text"],
811
+ contextWindow: 131_072,
812
+ maxTokens: 16_384,
813
+ cost: P(0.3, 0.5),
814
+ family: "Grok",
815
+ },
816
+ {
817
+ patterns: ["grok-3"],
818
+ name: "Grok 3",
819
+ reasoning: true,
820
+ input: ["text"],
821
+ contextWindow: 131_072,
822
+ maxTokens: 16_384,
823
+ cost: P(3, 15),
824
+ family: "Grok",
825
+ },
826
+
827
+ // ── Cohere Command ──────────────────────────────────────
828
+ {
829
+ patterns: ["command-r-plus"],
830
+ name: "Command R Plus",
831
+ reasoning: false,
832
+ input: ["text"],
833
+ contextWindow: 128_000,
834
+ maxTokens: 4096,
835
+ cost: P(2.5, 10),
836
+ family: "Cohere",
837
+ },
838
+ {
839
+ patterns: ["command-r"],
840
+ name: "Command R",
841
+ reasoning: false,
842
+ input: ["text"],
843
+ contextWindow: 128_000,
844
+ maxTokens: 4096,
845
+ cost: P(0.15, 0.6),
846
+ family: "Cohere",
847
+ },
848
+
849
+ // ── GitHub Copilot ──────────────────────────────────────
850
+ {
851
+ patterns: ["copilot-gpt-4o"],
852
+ name: "Copilot GPT-4o",
853
+ reasoning: false,
854
+ input: ["text"],
855
+ contextWindow: 128_000,
856
+ maxTokens: 4096,
857
+ cost: P(0, 0),
858
+ family: "GitHub",
859
+ },
860
+
861
+ // ── OpenCode / free providers ───────────────────────────
862
+ {
863
+ patterns: ["deepseek-v4-flash-free"],
864
+ name: "DeepSeek V4 Flash (Free)",
865
+ reasoning: false,
866
+ input: ["text"],
867
+ contextWindow: 128_000,
868
+ maxTokens: 8192,
869
+ cost: P(0, 0, 0, 0),
870
+ family: "OpenCode",
871
+ },
872
+ ];
873
+
874
+ // ─── Heuristic guess by id (pattern + keyword matching) ────
875
+ // Used as a last-resort fallthrough for unknown model IDs.
876
+
877
+ const KW_REASONING = /\b(r1|o1|o3|o4|z1|reasoner|reasoning|think\b|deepseek-r|qwq)\b/i;
878
+ const KW_VISION = /\b(vision|vl|multimodal|4o|gpt-4o|gemini-.*-vision|claude-.*opu|claude-.*sonnet|claude-.*haiku|llama-.*vision|qwen-.*vl|glm-.*v\b)\b/i;
879
+ const KW_CONTEXT_8K = /[-_](8k)\b/i;
880
+ const KW_CONTEXT_16K = /[-_](16k)\b/i;
881
+ const KW_CONTEXT_32K = /[-_](32k)\b/i;
882
+ const KW_CONTEXT_64K = /[-_](64k)\b/i;
883
+ const KW_CONTEXT_128K = /[-_](128k)\b/i;
884
+ const KW_CONTEXT_256K = /[-_](256k)\b/i;
885
+ const KW_CONTEXT_1M = /[-_](1m|1024k|1048576)\b/i;
886
+ const KW_AUDIO = /\b(audio|whisper|tts|speech)\b/i;
887
+
888
+ export interface GuessResult {
889
+ reasoning?: boolean;
890
+ input?: Model["input"];
891
+ contextWindow?: number;
892
+ maxTokens?: number;
893
+ source: "catalog" | "heuristic" | "default";
894
+ matched?: string; // pattern that matched
895
+ }
896
+
897
+ export function guessModelMeta(id: string): GuessResult {
898
+ const key = (id ?? "").toLowerCase();
899
+
900
+ // 1. Exact/prefix/suffix catalog match
901
+ for (const entry of MODEL_CATALOG) {
902
+ for (const p of entry.patterns) {
903
+ const pat = p.toLowerCase();
904
+ if (key === pat || key.startsWith(pat + "-") || key.endsWith("/" + pat) ||
905
+ key.includes("/" + pat + "-") || key.includes("/" + pat + ":")) {
906
+ return {
907
+ reasoning: entry.reasoning,
908
+ input: entry.input,
909
+ contextWindow: entry.contextWindow,
910
+ maxTokens: entry.maxTokens,
911
+ source: "catalog",
912
+ matched: entry.name ?? p,
913
+ };
914
+ }
915
+ }
916
+ }
917
+
918
+ // 2. Heuristic keyword match
919
+ const reasoning = KW_REASONING.test(key);
920
+ const input: Model["input"] = ["text"];
921
+ if (KW_VISION.test(key)) input.push("image");
922
+ if (KW_AUDIO.test(key)) input.push("audio");
923
+
924
+ let contextWindow: number | undefined;
925
+ if (KW_CONTEXT_1M.test(key)) contextWindow = 1_048_576;
926
+ else if (KW_CONTEXT_256K.test(key)) contextWindow = 262_144;
927
+ else if (KW_CONTEXT_128K.test(key)) contextWindow = 131_072;
928
+ else if (KW_CONTEXT_64K.test(key)) contextWindow = 65_536;
929
+ else if (KW_CONTEXT_32K.test(key)) contextWindow = 32_768;
930
+ else if (KW_CONTEXT_16K.test(key)) contextWindow = 16_384;
931
+ else if (KW_CONTEXT_8K.test(key)) contextWindow = 8192;
932
+
933
+ if (reasoning || input.length > 1 || contextWindow) {
934
+ return { reasoning, input, contextWindow, source: "heuristic" };
935
+ }
936
+
937
+ // 3. Nothing matched — caller falls back to defaults
938
+ return { source: "default" };
939
+ }
940
+
941
+ // ─── Search the catalog for the picker UI ───────────────────
942
+
943
+ export function searchCatalog(query: string, limit = 40): CatalogEntry[] {
944
+ const q = query.trim().toLowerCase();
945
+ if (!q) return MODEL_CATALOG.slice(0, limit);
946
+ const out: CatalogEntry[] = [];
947
+ for (const e of MODEL_CATALOG) {
948
+ const hay = [e.name ?? "", e.family ?? "", ...e.patterns].join(" ").toLowerCase();
949
+ if (hay.includes(q)) out.push(e);
950
+ if (out.length >= limit) break;
951
+ }
952
+ return out;
953
+ }
954
+
955
+ // ─── Convert a catalog entry to a Partial<Model> for prefill ─
956
+
957
+ export function catalogToModel(e: CatalogEntry, idOverride?: string): Partial<Model> {
958
+ return {
959
+ id: idOverride ?? e.patterns[0],
960
+ name: e.name,
961
+ reasoning: e.reasoning ?? false,
962
+ input: e.input ?? ["text"],
963
+ contextWindow: e.contextWindow,
964
+ maxTokens: e.maxTokens,
965
+ cost: e.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
966
+ };
967
+ }