@inixiative/foundry-core 0.2.0 → 0.3.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,1587 @@
1
+ /**
2
+ * The model registry: the vocabulary of what models are worth using for, the
3
+ * table of providers and models, and the lookups over it.
4
+ *
5
+ * It lives in core rather than beside the adapters because consumers that
6
+ * depend on core alone — Oracle among them — must be able to reason about which
7
+ * model suits a piece of work without taking a dependency on Foundry's HTTP and
8
+ * process handling. Adapters stay in `packages/foundry`; this file is data and
9
+ * pure functions only, and adds no runtime dependency.
10
+ */
11
+
12
+ export type ModelTier = 'fast' | 'standard' | 'powerful';
13
+ export type CostTier = 'low' | 'medium' | 'high';
14
+ export type ProviderType =
15
+ | 'anthropic'
16
+ | 'openai'
17
+ | 'openai-compatible'
18
+ | 'gemini'
19
+ | 'claude-code'
20
+ | 'codex'
21
+ | 'xai'
22
+ | 'typesafe'
23
+ | 'custom';
24
+ export type RuntimeKind = 'api' | 'native-harness' | 'typed-decision';
25
+
26
+ /**
27
+ * What a model is worth using for. Every model is a judgment client — that is
28
+ * not a provider category, it is a tag every entry carries. The other tags name
29
+ * work Foundry already distinguishes:
30
+ *
31
+ * - judgment classifier/router/decider agent kinds and the decision
32
+ * provider: which archive, which middleware, which domain.
33
+ * - execution the executor kind (Artificer): tools on, multi-turn.
34
+ * - domain-advising the `domain-advising` flow role: read an owned domain layer
35
+ * and advise/guard from it.
36
+ * - learning-review the background `learning.review` pass over finished work.
37
+ * - tool-use CompletionOpts.tools / toolDefinitions.
38
+ * - reasoning CompletionOpts.thinking (reasoning effort / think budget).
39
+ */
40
+ export type ModelCapability =
41
+ | 'judgment'
42
+ | 'execution'
43
+ | 'domain-advising'
44
+ | 'learning-review'
45
+ | 'tool-use'
46
+ | 'reasoning';
47
+
48
+ export const MODEL_CAPABILITIES: readonly ModelCapability[] = [
49
+ 'judgment',
50
+ 'execution',
51
+ 'domain-advising',
52
+ 'learning-review',
53
+ 'tool-use',
54
+ 'reasoning',
55
+ ] as const;
56
+
57
+ /** How the provider is paid for. `envKey` is present exactly when this is "api-key". */
58
+ export type ProviderCredential = 'api-key' | 'subscription' | 'local';
59
+
60
+ /** Effort levels hosts publish. A superset of CompletionOpts.thinking. */
61
+ export type ReasoningEffort = 'none' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'ultra';
62
+
63
+ /**
64
+ * How one model takes its reasoning controls. This is the map that replaced a
65
+ * model-name regex: naming is not behaviour, and the next generation's names
66
+ * are not knowable in advance.
67
+ */
68
+ export interface ModelReasoning {
69
+ /**
70
+ * Where the effort goes. Only the first two reach the OpenAI-compatible
71
+ * adapter; Anthropic and Gemini have their own adapters and their own fields,
72
+ * recorded here so the map describes every model, not just the compatible ones.
73
+ */
74
+ param: 'reasoning_effort' | 'reasoning.effort' | 'output_config.effort' | 'thinking_level';
75
+ /** Accepted levels. Without "none", thinking cannot be switched off. */
76
+ efforts: readonly ReasoningEffort[];
77
+ /** Sent when the caller asks for nothing, or asks for a level this model rejects. */
78
+ fallback: ReasoningEffort;
79
+ /** OpenAI reasoning models cap output with max_completion_tokens, not max_tokens. */
80
+ outputField?: 'max_completion_tokens';
81
+ /** xAI reasoning models reject `stop` and the penalty parameters. */
82
+ rejectsStop?: boolean;
83
+ }
84
+
85
+ export interface FoundryModelInfo {
86
+ id: string;
87
+ label: string;
88
+ tier: ModelTier;
89
+ costTier?: CostTier;
90
+ contextWindow?: number;
91
+ maxOutputTokens?: number;
92
+ runtimeKind: RuntimeKind;
93
+ nativeAlias?: boolean;
94
+ /** Always includes "judgment". Includes "reasoning" exactly when `reasoning` is set. */
95
+ capabilities: ModelCapability[];
96
+ /** Request shape for reasoning. Absent = plain chat completions with max_tokens. */
97
+ reasoning?: ModelReasoning;
98
+ /**
99
+ * The vendor trains on prompts and completions sent to this model, by its own
100
+ * documentation. Set only where the vendor says so. This is a term of use, not
101
+ * a capability: it says nothing about what the model is fit for, so it must not
102
+ * be a `ModelCapability` — `modelOptionsByCapability` selects models for work,
103
+ * and a commercial term has no business changing that selection. Every entry
104
+ * carrying it also says so in its `label`, because a `<select>` renders nothing
105
+ * but the label.
106
+ */
107
+ trainsOnInput?: boolean;
108
+ notes?: string;
109
+ }
110
+
111
+ /** OpenAI's API reasoning models: effort can be switched off, output caps differ. */
112
+ const OPENAI_REASONING: ModelReasoning = {
113
+ param: 'reasoning_effort',
114
+ efforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
115
+ fallback: 'none',
116
+ outputField: 'max_completion_tokens',
117
+ };
118
+ /** Codex CLI catalogue (~/.codex/models_cache.json): no "none", medium by default. */
119
+ const CODEX_REASONING: ModelReasoning = {
120
+ param: 'reasoning_effort',
121
+ efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
122
+ fallback: 'medium',
123
+ };
124
+ const CODEX_REASONING_ULTRA: ModelReasoning = {
125
+ ...CODEX_REASONING,
126
+ efforts: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
127
+ };
128
+ /** Anthropic: adaptive thinking, steered by output_config.effort, never by reasoning_effort. */
129
+ const CLAUDE_REASONING: ModelReasoning = {
130
+ param: 'output_config.effort',
131
+ efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
132
+ fallback: 'high',
133
+ };
134
+ /** Grok nests effort and cannot be switched off; reasoning models reject stop. */
135
+ const GROK_REASONING: ModelReasoning = {
136
+ param: 'reasoning.effort',
137
+ efforts: ['low', 'medium', 'high'],
138
+ fallback: 'high',
139
+ rejectsStop: true,
140
+ };
141
+ const GROK_REASONING_XHIGH: ModelReasoning = {
142
+ ...GROK_REASONING,
143
+ efforts: ['low', 'medium', 'high', 'xhigh'],
144
+ };
145
+ /** Compatible hosts whose thinking is a plain effort with no "none" and no output-field change. */
146
+ const EFFORT_ONLY: ModelReasoning = {
147
+ param: 'reasoning_effort',
148
+ efforts: ['low', 'medium', 'high'],
149
+ fallback: 'medium',
150
+ };
151
+ /** Gemini takes thinking_level, and rejects "minimal". */
152
+ const GEMINI_REASONING: ModelReasoning = {
153
+ param: 'thinking_level',
154
+ efforts: ['low', 'medium', 'high'],
155
+ fallback: 'medium',
156
+ };
157
+ /** DeepSeek publishes its own ladder, including "none" and "max". */
158
+ const DEEPSEEK_REASONING: ModelReasoning = {
159
+ param: 'reasoning_effort',
160
+ efforts: ['none', 'low', 'high', 'max'],
161
+ fallback: 'none',
162
+ };
163
+ /**
164
+ * Meta's Model API. Muse Spark always reasons: `reasoning_effort: "none"` is a 400.
165
+ * The docs also accept "minimal", which this vocabulary does not name, so it is not offered.
166
+ * Output is capped with max_completion_tokens; max_tokens survives as a deprecated alias.
167
+ */
168
+ const MUSE_REASONING: ModelReasoning = {
169
+ param: 'reasoning_effort',
170
+ efforts: ['low', 'medium', 'high', 'xhigh'],
171
+ fallback: 'medium',
172
+ outputField: 'max_completion_tokens',
173
+ };
174
+
175
+ export interface FoundryProviderInfo {
176
+ id: string;
177
+ type: ProviderType;
178
+ label: string;
179
+ description: string;
180
+ envKey?: string;
181
+ credential: ProviderCredential;
182
+ /**
183
+ * Versioned API root for OpenAI-compatible hosts, used verbatim: requests go
184
+ * to `${apiRoot}/chat/completions`. Hosts disagree about the version segment
185
+ * (/v1, /api/paas/v4, /compatible-mode/v1, none at all), so this is not
186
+ * normalized. A user's `providers[id].baseUrl` override is.
187
+ */
188
+ apiRoot?: string;
189
+ enabledByDefault: boolean;
190
+ models: FoundryModelInfo[];
191
+ }
192
+
193
+ export interface ModelSweepOption {
194
+ provider: string;
195
+ model: string;
196
+ label: string;
197
+ }
198
+
199
+ export const MODEL_REGISTRY_UPDATED_AT = '2026-09-24';
200
+
201
+ /**
202
+ * Decision default: the cheapest current model tagged `judgment`. Held here
203
+ * rather than in an adapter so one map answers "what runs decisions".
204
+ */
205
+ export const DECISION_PROVIDER = 'openai';
206
+ export const DECISION_MODEL = 'gpt-6-luna';
207
+
208
+ /** Tag rule: every model judges. Cheap models review and route; capable models execute and advise. */
209
+ const FAST = ['judgment', 'learning-review', 'tool-use'] as ModelCapability[];
210
+ const FAST_REASONING = [...FAST, 'reasoning'] as ModelCapability[];
211
+ const STANDARD = [
212
+ 'judgment',
213
+ 'learning-review',
214
+ 'execution',
215
+ 'domain-advising',
216
+ 'tool-use',
217
+ ] as ModelCapability[];
218
+ const STANDARD_REASONING = [...STANDARD, 'reasoning'] as ModelCapability[];
219
+ const POWERFUL = ['judgment', 'execution', 'domain-advising', 'tool-use'] as ModelCapability[];
220
+ const POWERFUL_REASONING = [...POWERFUL, 'reasoning'] as ModelCapability[];
221
+
222
+ export const MODEL_REGISTRY: Record<string, FoundryProviderInfo> = {
223
+ 'claude-code': {
224
+ id: 'claude-code',
225
+ type: 'claude-code',
226
+ label: 'Claude Code (CLI subscription)',
227
+ description: "Native Claude Code harness using the user's authenticated subscription.",
228
+ credential: 'subscription',
229
+ enabledByDefault: true,
230
+ models: [
231
+ {
232
+ id: 'fable',
233
+ label: 'Fable 5.1',
234
+ tier: 'powerful',
235
+ costTier: 'high',
236
+ contextWindow: 1_000_000,
237
+ maxOutputTokens: 128_000,
238
+ runtimeKind: 'native-harness',
239
+ nativeAlias: true,
240
+ capabilities: POWERFUL_REASONING,
241
+ reasoning: CLAUDE_REASONING,
242
+ notes: 'Claude Code alias for claude-fable-5-1.',
243
+ },
244
+ {
245
+ id: 'opus',
246
+ label: 'Opus 5',
247
+ tier: 'powerful',
248
+ costTier: 'high',
249
+ contextWindow: 1_000_000,
250
+ runtimeKind: 'native-harness',
251
+ nativeAlias: true,
252
+ capabilities: POWERFUL_REASONING,
253
+ reasoning: CLAUDE_REASONING,
254
+ },
255
+ {
256
+ id: 'sonnet',
257
+ label: 'Sonnet 5',
258
+ tier: 'standard',
259
+ costTier: 'medium',
260
+ contextWindow: 1_000_000,
261
+ runtimeKind: 'native-harness',
262
+ nativeAlias: true,
263
+ capabilities: STANDARD_REASONING,
264
+ reasoning: CLAUDE_REASONING,
265
+ },
266
+ {
267
+ id: 'haiku',
268
+ label: 'Haiku 4.5',
269
+ tier: 'fast',
270
+ costTier: 'low',
271
+ contextWindow: 200_000,
272
+ runtimeKind: 'native-harness',
273
+ nativeAlias: true,
274
+ capabilities: FAST_REASONING,
275
+ reasoning: { ...CLAUDE_REASONING, efforts: ['low', 'medium', 'high'] },
276
+ },
277
+ ],
278
+ },
279
+ anthropic: {
280
+ id: 'anthropic',
281
+ type: 'anthropic',
282
+ label: 'Anthropic (API key)',
283
+ description: 'Direct Anthropic API access without the Claude Code native harness.',
284
+ envKey: 'ANTHROPIC_API_KEY',
285
+ credential: 'api-key',
286
+ enabledByDefault: true,
287
+ models: [
288
+ {
289
+ id: 'claude-fable-5-1',
290
+ label: 'Claude Fable 5.1',
291
+ tier: 'powerful',
292
+ costTier: 'high',
293
+ contextWindow: 1_000_000,
294
+ maxOutputTokens: 128_000,
295
+ runtimeKind: 'api',
296
+ capabilities: POWERFUL_REASONING,
297
+ reasoning: CLAUDE_REASONING,
298
+ notes: 'Thinking is always on; steer with effort.',
299
+ },
300
+ {
301
+ id: 'claude-opus-5',
302
+ label: 'Claude Opus 5',
303
+ tier: 'powerful',
304
+ costTier: 'high',
305
+ contextWindow: 1_000_000,
306
+ maxOutputTokens: 128_000,
307
+ runtimeKind: 'api',
308
+ capabilities: POWERFUL_REASONING,
309
+ reasoning: CLAUDE_REASONING,
310
+ },
311
+ {
312
+ id: 'claude-sonnet-5',
313
+ label: 'Claude Sonnet 5',
314
+ tier: 'standard',
315
+ costTier: 'medium',
316
+ contextWindow: 1_000_000,
317
+ maxOutputTokens: 128_000,
318
+ runtimeKind: 'api',
319
+ capabilities: STANDARD_REASONING,
320
+ reasoning: CLAUDE_REASONING,
321
+ },
322
+ {
323
+ id: 'claude-haiku-4-5',
324
+ label: 'Claude Haiku 4.5',
325
+ tier: 'fast',
326
+ costTier: 'low',
327
+ contextWindow: 200_000,
328
+ runtimeKind: 'api',
329
+ capabilities: FAST_REASONING,
330
+ reasoning: { ...CLAUDE_REASONING, efforts: ['low', 'medium', 'high'] },
331
+ notes: 'Takes a thinking budget, not an effort ladder.',
332
+ },
333
+ // Previous generation, still served.
334
+ {
335
+ id: 'claude-fable-5',
336
+ label: 'Claude Fable 5',
337
+ tier: 'powerful',
338
+ costTier: 'high',
339
+ contextWindow: 1_000_000,
340
+ maxOutputTokens: 128_000,
341
+ runtimeKind: 'api',
342
+ capabilities: POWERFUL_REASONING,
343
+ reasoning: CLAUDE_REASONING,
344
+ },
345
+ {
346
+ id: 'claude-opus-4-8',
347
+ label: 'Claude Opus 4.8',
348
+ tier: 'powerful',
349
+ costTier: 'high',
350
+ contextWindow: 1_000_000,
351
+ maxOutputTokens: 128_000,
352
+ runtimeKind: 'api',
353
+ capabilities: POWERFUL_REASONING,
354
+ reasoning: CLAUDE_REASONING,
355
+ },
356
+ {
357
+ id: 'claude-opus-4-7',
358
+ label: 'Claude Opus 4.7',
359
+ tier: 'powerful',
360
+ costTier: 'high',
361
+ contextWindow: 1_000_000,
362
+ maxOutputTokens: 128_000,
363
+ runtimeKind: 'api',
364
+ capabilities: POWERFUL_REASONING,
365
+ reasoning: CLAUDE_REASONING,
366
+ },
367
+ {
368
+ id: 'claude-opus-4-6',
369
+ label: 'Claude Opus 4.6',
370
+ tier: 'powerful',
371
+ costTier: 'high',
372
+ contextWindow: 1_000_000,
373
+ maxOutputTokens: 128_000,
374
+ runtimeKind: 'api',
375
+ capabilities: POWERFUL_REASONING,
376
+ reasoning: { ...CLAUDE_REASONING, efforts: ['low', 'medium', 'high', 'max'] },
377
+ },
378
+ {
379
+ id: 'claude-sonnet-4-6',
380
+ label: 'Claude Sonnet 4.6',
381
+ tier: 'standard',
382
+ costTier: 'medium',
383
+ contextWindow: 1_000_000,
384
+ maxOutputTokens: 128_000,
385
+ runtimeKind: 'api',
386
+ capabilities: STANDARD_REASONING,
387
+ reasoning: { ...CLAUDE_REASONING, efforts: ['low', 'medium', 'high', 'max'] },
388
+ },
389
+ ],
390
+ },
391
+ codex: {
392
+ id: 'codex',
393
+ type: 'codex',
394
+ label: 'Codex CLI (OpenAI subscription)',
395
+ description: "Native Codex harness using the user's authenticated OpenAI/Codex subscription.",
396
+ credential: 'subscription',
397
+ enabledByDefault: true,
398
+ models: [
399
+ // Slugs, effort ladders and the 272k session window come from the local
400
+ // Codex catalogue (~/.codex/models_cache.json); the same models carry a
401
+ // 1,050,000 window on the platform API, which the openai entry records.
402
+ {
403
+ id: 'gpt-6-astra',
404
+ label: 'GPT-6 Astra',
405
+ tier: 'powerful',
406
+ costTier: 'high',
407
+ contextWindow: 272_000,
408
+ runtimeKind: 'native-harness',
409
+ capabilities: POWERFUL_REASONING,
410
+ reasoning: CODEX_REASONING_ULTRA,
411
+ notes: 'The Codex CLI default.',
412
+ },
413
+ {
414
+ id: 'gpt-6-sol',
415
+ label: 'GPT-6 Sol',
416
+ tier: 'powerful',
417
+ costTier: 'high',
418
+ contextWindow: 272_000,
419
+ runtimeKind: 'native-harness',
420
+ capabilities: POWERFUL_REASONING,
421
+ reasoning: CODEX_REASONING_ULTRA,
422
+ notes: 'Workhorse for coding and everyday work.',
423
+ },
424
+ {
425
+ id: 'gpt-6-luna',
426
+ label: 'GPT-6 Luna',
427
+ tier: 'fast',
428
+ costTier: 'low',
429
+ contextWindow: 272_000,
430
+ runtimeKind: 'native-harness',
431
+ capabilities: FAST_REASONING,
432
+ reasoning: CODEX_REASONING,
433
+ notes: "Foundry's decision model.",
434
+ },
435
+ {
436
+ id: 'gpt-5.6-sol',
437
+ label: 'GPT-5.6 Sol',
438
+ tier: 'powerful',
439
+ costTier: 'high',
440
+ contextWindow: 272_000,
441
+ runtimeKind: 'native-harness',
442
+ capabilities: POWERFUL_REASONING,
443
+ reasoning: CODEX_REASONING_ULTRA,
444
+ },
445
+ {
446
+ id: 'gpt-5.6-terra',
447
+ label: 'GPT-5.6 Terra',
448
+ tier: 'standard',
449
+ costTier: 'medium',
450
+ contextWindow: 272_000,
451
+ runtimeKind: 'native-harness',
452
+ capabilities: STANDARD_REASONING,
453
+ reasoning: CODEX_REASONING_ULTRA,
454
+ },
455
+ {
456
+ id: 'gpt-5.6-luna',
457
+ label: 'GPT-5.6 Luna',
458
+ tier: 'fast',
459
+ costTier: 'low',
460
+ contextWindow: 272_000,
461
+ runtimeKind: 'native-harness',
462
+ capabilities: FAST_REASONING,
463
+ reasoning: CODEX_REASONING,
464
+ },
465
+ {
466
+ id: 'gpt-5.5',
467
+ label: 'GPT-5.5',
468
+ tier: 'standard',
469
+ costTier: 'medium',
470
+ contextWindow: 272_000,
471
+ runtimeKind: 'native-harness',
472
+ capabilities: STANDARD_REASONING,
473
+ reasoning: {
474
+ ...CODEX_REASONING,
475
+ efforts: ['low', 'medium', 'high', 'xhigh'],
476
+ fallback: 'xhigh',
477
+ },
478
+ },
479
+ ],
480
+ },
481
+ openai: {
482
+ id: 'openai',
483
+ type: 'openai',
484
+ label: 'OpenAI',
485
+ description: 'OpenAI API models for classification, routing, and execution.',
486
+ envKey: 'OPENAI_API_KEY',
487
+ credential: 'api-key',
488
+ apiRoot: 'https://api.openai.com/v1',
489
+ enabledByDefault: true,
490
+ models: [
491
+ {
492
+ id: 'gpt-6-astra',
493
+ label: 'GPT-6 Astra',
494
+ tier: 'powerful',
495
+ costTier: 'high',
496
+ contextWindow: 1_050_000,
497
+ maxOutputTokens: 128_000,
498
+ runtimeKind: 'api',
499
+ capabilities: POWERFUL_REASONING,
500
+ reasoning: {
501
+ ...OPENAI_REASONING,
502
+ efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
503
+ fallback: 'low',
504
+ },
505
+ notes: 'Rejects effort "none" with a 400; tool calling needs the Responses API.',
506
+ },
507
+ {
508
+ id: 'gpt-6-sol',
509
+ label: 'GPT-6 Sol',
510
+ tier: 'powerful',
511
+ costTier: 'high',
512
+ contextWindow: 1_050_000,
513
+ maxOutputTokens: 128_000,
514
+ runtimeKind: 'api',
515
+ capabilities: POWERFUL_REASONING,
516
+ reasoning: OPENAI_REASONING,
517
+ },
518
+ {
519
+ id: 'gpt-6-luna',
520
+ label: 'GPT-6 Luna',
521
+ tier: 'fast',
522
+ costTier: 'low',
523
+ contextWindow: 1_050_000,
524
+ maxOutputTokens: 128_000,
525
+ runtimeKind: 'api',
526
+ capabilities: FAST_REASONING,
527
+ reasoning: OPENAI_REASONING,
528
+ notes: "Foundry's decision model.",
529
+ },
530
+ {
531
+ id: 'gpt-5.6-sol',
532
+ label: 'GPT-5.6 Sol',
533
+ tier: 'powerful',
534
+ costTier: 'high',
535
+ contextWindow: 1_050_000,
536
+ maxOutputTokens: 128_000,
537
+ runtimeKind: 'api',
538
+ capabilities: POWERFUL_REASONING,
539
+ reasoning: OPENAI_REASONING,
540
+ },
541
+ {
542
+ id: 'gpt-5.6-terra',
543
+ label: 'GPT-5.6 Terra',
544
+ tier: 'standard',
545
+ costTier: 'medium',
546
+ contextWindow: 1_050_000,
547
+ maxOutputTokens: 128_000,
548
+ runtimeKind: 'api',
549
+ capabilities: STANDARD_REASONING,
550
+ reasoning: OPENAI_REASONING,
551
+ },
552
+ {
553
+ id: 'gpt-5.6-luna',
554
+ label: 'GPT-5.6 Luna',
555
+ tier: 'fast',
556
+ costTier: 'low',
557
+ contextWindow: 1_050_000,
558
+ maxOutputTokens: 128_000,
559
+ runtimeKind: 'api',
560
+ capabilities: FAST_REASONING,
561
+ reasoning: OPENAI_REASONING,
562
+ },
563
+ {
564
+ id: 'gpt-5.5',
565
+ label: 'GPT-5.5',
566
+ tier: 'standard',
567
+ costTier: 'medium',
568
+ contextWindow: 1_050_000,
569
+ maxOutputTokens: 128_000,
570
+ runtimeKind: 'api',
571
+ capabilities: STANDARD_REASONING,
572
+ reasoning: { ...OPENAI_REASONING, efforts: ['none', 'low', 'medium', 'high', 'xhigh'] },
573
+ },
574
+ ],
575
+ },
576
+ gemini: {
577
+ id: 'gemini',
578
+ type: 'gemini',
579
+ label: 'Google Gemini',
580
+ description: 'Gemini API models for high-volume agent work.',
581
+ envKey: 'GEMINI_API_KEY',
582
+ credential: 'api-key',
583
+ enabledByDefault: true,
584
+ models: [
585
+ // The Pro and Flash lines carry different version numbers; Pro has no GA
586
+ // successor to 3.1 yet. thinking_level takes low/medium/high, not minimal.
587
+ {
588
+ id: 'gemini-3.1-pro-preview',
589
+ label: 'Gemini 3.1 Pro',
590
+ tier: 'powerful',
591
+ costTier: 'high',
592
+ contextWindow: 1_048_576,
593
+ maxOutputTokens: 65_536,
594
+ runtimeKind: 'api',
595
+ capabilities: POWERFUL_REASONING,
596
+ reasoning: GEMINI_REASONING,
597
+ },
598
+ {
599
+ id: 'gemini-3.8-flash',
600
+ label: 'Gemini 3.8 Flash',
601
+ tier: 'standard',
602
+ costTier: 'medium',
603
+ contextWindow: 1_048_576,
604
+ maxOutputTokens: 65_536,
605
+ runtimeKind: 'api',
606
+ capabilities: STANDARD_REASONING,
607
+ reasoning: GEMINI_REASONING,
608
+ },
609
+ {
610
+ id: 'gemini-3.7-flash',
611
+ label: 'Gemini 3.7 Flash',
612
+ tier: 'standard',
613
+ costTier: 'medium',
614
+ contextWindow: 1_048_576,
615
+ maxOutputTokens: 65_536,
616
+ runtimeKind: 'api',
617
+ capabilities: STANDARD_REASONING,
618
+ reasoning: GEMINI_REASONING,
619
+ },
620
+ {
621
+ id: 'gemini-3.5-flash-lite',
622
+ label: 'Gemini 3.5 Flash Lite',
623
+ tier: 'fast',
624
+ costTier: 'low',
625
+ runtimeKind: 'api',
626
+ capabilities: FAST_REASONING,
627
+ reasoning: GEMINI_REASONING,
628
+ },
629
+ {
630
+ id: 'gemini-3.1-flash-lite',
631
+ label: 'Gemini 3.1 Flash Lite',
632
+ tier: 'fast',
633
+ costTier: 'low',
634
+ contextWindow: 1_048_576,
635
+ maxOutputTokens: 65_536,
636
+ runtimeKind: 'api',
637
+ capabilities: FAST_REASONING,
638
+ reasoning: GEMINI_REASONING,
639
+ },
640
+ ],
641
+ },
642
+ meta: {
643
+ id: 'meta',
644
+ type: 'openai-compatible',
645
+ // Llama is not Meta's frontier line any more; Muse Spark replaced it in April 2026
646
+ // and there is no "Llama 5". The open-weight Llama entries under openrouter,
647
+ // together, fireworks and ollama are the older line, and stay where they are.
648
+ label: 'Meta (Muse Spark)',
649
+ description:
650
+ "Meta's Model API. OpenAI-compatible; it also serves an Anthropic-compatible /v1/messages. The documented key variable is the unprefixed MODEL_API_KEY.",
651
+ envKey: 'MODEL_API_KEY',
652
+ credential: 'api-key',
653
+ apiRoot: 'https://api.meta.ai/v1',
654
+ enabledByDefault: true,
655
+ models: [
656
+ // No max output is documented for any Muse Spark model, so none is recorded;
657
+ // a guess here would be sent as max_completion_tokens and truncate real replies.
658
+ // The -contributor ids are the same models at $0.10/$0.20 per M instead of
659
+ // $1.25/$4.25, bought with permission to train on the traffic. That is priced
660
+ // into the label as well as `trainsOnInput`, so no surface can show one without the other.
661
+ {
662
+ id: 'muse-spark-1.3',
663
+ label: 'Muse Spark 1.3',
664
+ tier: 'powerful',
665
+ costTier: 'high',
666
+ contextWindow: 1_048_576,
667
+ runtimeKind: 'api',
668
+ capabilities: POWERFUL_REASONING,
669
+ reasoning: MUSE_REASONING,
670
+ },
671
+ {
672
+ id: 'muse-spark-1.3-contributor',
673
+ label: 'Muse Spark 1.3 (Contributor — Meta trains on your data)',
674
+ tier: 'powerful',
675
+ costTier: 'low',
676
+ contextWindow: 1_048_576,
677
+ runtimeKind: 'api',
678
+ capabilities: POWERFUL_REASONING,
679
+ reasoning: MUSE_REASONING,
680
+ trainsOnInput: true,
681
+ notes:
682
+ '$0.10/$0.20 per M instead of $1.25/$4.25, in exchange for Meta training future models on your prompts and completions.',
683
+ },
684
+ {
685
+ id: 'muse-spark-1.2',
686
+ label: 'Muse Spark 1.2',
687
+ tier: 'powerful',
688
+ costTier: 'high',
689
+ contextWindow: 1_048_576,
690
+ runtimeKind: 'api',
691
+ capabilities: POWERFUL_REASONING,
692
+ reasoning: MUSE_REASONING,
693
+ },
694
+ {
695
+ id: 'muse-spark-1.2-contributor',
696
+ label: 'Muse Spark 1.2 (Contributor — Meta trains on your data)',
697
+ tier: 'powerful',
698
+ costTier: 'low',
699
+ contextWindow: 1_048_576,
700
+ runtimeKind: 'api',
701
+ capabilities: POWERFUL_REASONING,
702
+ reasoning: MUSE_REASONING,
703
+ trainsOnInput: true,
704
+ notes:
705
+ '$0.10/$0.20 per M instead of $1.25/$4.25, in exchange for Meta training future models on your prompts and completions.',
706
+ },
707
+ {
708
+ id: 'muse-spark-1.1',
709
+ label: 'Muse Spark 1.1',
710
+ tier: 'powerful',
711
+ costTier: 'high',
712
+ contextWindow: 1_048_576,
713
+ runtimeKind: 'api',
714
+ capabilities: POWERFUL_REASONING,
715
+ reasoning: MUSE_REASONING,
716
+ notes: 'No contributor tier is published for 1.1.',
717
+ },
718
+ // Not registered: muse-image, muse-voice-transcribe-1.0 and sam-3.1 are not
719
+ // chat models and do not answer /v1/chat/completions.
720
+ ],
721
+ },
722
+ xai: {
723
+ id: 'xai',
724
+ type: 'xai',
725
+ label: 'xAI (Grok)',
726
+ description:
727
+ 'Grok models. Near-OpenAI-compatible; reasoning effort is a nested object and cannot be turned off.',
728
+ envKey: 'XAI_API_KEY',
729
+ credential: 'api-key',
730
+ apiRoot: 'https://api.x.ai/v1',
731
+ enabledByDefault: true,
732
+ models: [
733
+ {
734
+ id: 'grok-4.7',
735
+ label: 'Grok 4.7',
736
+ tier: 'powerful',
737
+ costTier: 'high',
738
+ contextWindow: 500_000,
739
+ runtimeKind: 'api',
740
+ capabilities: POWERFUL_REASONING,
741
+ reasoning: GROK_REASONING_XHIGH,
742
+ notes: 'Reasoning effort low/medium/high/xhigh; cannot be disabled.',
743
+ },
744
+ {
745
+ id: 'grok-4.6',
746
+ label: 'Grok 4.6',
747
+ tier: 'powerful',
748
+ costTier: 'high',
749
+ contextWindow: 500_000,
750
+ runtimeKind: 'api',
751
+ capabilities: POWERFUL_REASONING,
752
+ reasoning: GROK_REASONING_XHIGH,
753
+ },
754
+ {
755
+ id: 'grok-4.5',
756
+ label: 'Grok 4.5',
757
+ tier: 'standard',
758
+ costTier: 'medium',
759
+ contextWindow: 500_000,
760
+ runtimeKind: 'api',
761
+ capabilities: STANDARD_REASONING,
762
+ reasoning: GROK_REASONING,
763
+ notes: 'xhigh effort is served as high.',
764
+ },
765
+ {
766
+ id: 'grok-4.3',
767
+ label: 'Grok 4.3',
768
+ tier: 'standard',
769
+ costTier: 'medium',
770
+ contextWindow: 1_000_000,
771
+ runtimeKind: 'api',
772
+ capabilities: STANDARD,
773
+ },
774
+ {
775
+ id: 'grok-4.20-0309-reasoning',
776
+ label: 'Grok 4.20 (reasoning)',
777
+ tier: 'powerful',
778
+ costTier: 'high',
779
+ contextWindow: 1_000_000,
780
+ runtimeKind: 'api',
781
+ capabilities: POWERFUL_REASONING,
782
+ reasoning: GROK_REASONING,
783
+ },
784
+ {
785
+ id: 'grok-4.20-0309-non-reasoning',
786
+ label: 'Grok 4.20 (non-reasoning)',
787
+ tier: 'fast',
788
+ costTier: 'low',
789
+ contextWindow: 1_000_000,
790
+ runtimeKind: 'api',
791
+ capabilities: FAST,
792
+ },
793
+ {
794
+ id: 'grok-build-0.1',
795
+ label: 'Grok Build 0.1',
796
+ tier: 'fast',
797
+ costTier: 'low',
798
+ contextWindow: 256_000,
799
+ runtimeKind: 'api',
800
+ capabilities: FAST,
801
+ },
802
+ ],
803
+ },
804
+ deepseek: {
805
+ id: 'deepseek',
806
+ type: 'openai-compatible',
807
+ label: 'DeepSeek',
808
+ description:
809
+ 'DeepSeek API. OpenAI-compatible; penalties are silently ignored and output defaults differ.',
810
+ envKey: 'DEEPSEEK_API_KEY',
811
+ credential: 'api-key',
812
+ apiRoot: 'https://api.deepseek.com',
813
+ enabledByDefault: true,
814
+ models: [
815
+ {
816
+ id: 'deepseek-flash',
817
+ label: 'DeepSeek Flash (V4.1)',
818
+ tier: 'fast',
819
+ costTier: 'low',
820
+ contextWindow: 1_000_000,
821
+ maxOutputTokens: 393_216,
822
+ runtimeKind: 'api',
823
+ capabilities: FAST_REASONING,
824
+ reasoning: { ...DEEPSEEK_REASONING, fallback: 'low' },
825
+ notes: 'Thinking on by default; the legacy deepseek-v4-flash ids route here.',
826
+ },
827
+ {
828
+ id: 'deepseek-v4-pro',
829
+ label: 'DeepSeek V4 Pro',
830
+ tier: 'powerful',
831
+ costTier: 'high',
832
+ contextWindow: 1_000_000,
833
+ maxOutputTokens: 393_216,
834
+ runtimeKind: 'api',
835
+ capabilities: POWERFUL_REASONING,
836
+ reasoning: DEEPSEEK_REASONING,
837
+ },
838
+ ],
839
+ },
840
+ kimi: {
841
+ id: 'kimi',
842
+ type: 'openai-compatible',
843
+ label: 'Kimi (Moonshot AI)',
844
+ description: 'Moonshot Kimi models. OpenAI-compatible; temperature range is 0-1, not 0-2.',
845
+ envKey: 'MOONSHOT_API_KEY',
846
+ credential: 'api-key',
847
+ apiRoot: 'https://api.moonshot.ai/v1',
848
+ enabledByDefault: true,
849
+ models: [
850
+ {
851
+ id: 'kimi-k3',
852
+ label: 'Kimi K3',
853
+ tier: 'powerful',
854
+ costTier: 'high',
855
+ contextWindow: 1_048_576,
856
+ maxOutputTokens: 131_072,
857
+ runtimeKind: 'api',
858
+ capabilities: POWERFUL_REASONING,
859
+ reasoning: EFFORT_ONLY,
860
+ notes: 'Thinking is always on and cannot be disabled.',
861
+ },
862
+ {
863
+ id: 'kimi-k2.6',
864
+ label: 'Kimi K2.6',
865
+ tier: 'standard',
866
+ costTier: 'medium',
867
+ contextWindow: 262_144,
868
+ runtimeKind: 'api',
869
+ capabilities: STANDARD_REASONING,
870
+ reasoning: EFFORT_ONLY,
871
+ notes: 'Thinking requires temperature 1.0; non-thinking requires 0.6.',
872
+ },
873
+ {
874
+ id: 'kimi-k2.7-code',
875
+ label: 'Kimi K2.7 Code',
876
+ tier: 'standard',
877
+ costTier: 'medium',
878
+ contextWindow: 262_144,
879
+ runtimeKind: 'api',
880
+ capabilities: STANDARD,
881
+ notes: 'tool_choice required is unsupported.',
882
+ },
883
+ {
884
+ id: 'kimi-k2.7-code-highspeed',
885
+ label: 'Kimi K2.7 Code (high speed)',
886
+ tier: 'fast',
887
+ costTier: 'medium',
888
+ contextWindow: 262_144,
889
+ runtimeKind: 'api',
890
+ capabilities: FAST,
891
+ },
892
+ ],
893
+ },
894
+ glm: {
895
+ id: 'glm',
896
+ type: 'openai-compatible',
897
+ label: 'GLM (Z.ai / Zhipu)',
898
+ description:
899
+ "GLM over Z.ai's OpenAI-compatible v4 endpoint. Mainland Zhipu is https://open.bigmodel.cn/api/paas/v4.",
900
+ envKey: 'ZAI_API_KEY',
901
+ credential: 'api-key',
902
+ apiRoot: 'https://api.z.ai/api/paas/v4',
903
+ enabledByDefault: true,
904
+ models: [
905
+ {
906
+ id: 'glm-4.6',
907
+ label: 'GLM-4.6',
908
+ tier: 'standard',
909
+ costTier: 'medium',
910
+ contextWindow: 204_800,
911
+ maxOutputTokens: 128_000,
912
+ runtimeKind: 'api',
913
+ capabilities: STANDARD_REASONING,
914
+ reasoning: EFFORT_ONLY,
915
+ notes: 'Thinking is opt-in via the thinking parameter.',
916
+ },
917
+ {
918
+ id: 'glm-4.7',
919
+ label: 'GLM-4.7',
920
+ tier: 'standard',
921
+ costTier: 'medium',
922
+ contextWindow: 200_000,
923
+ maxOutputTokens: 128_000,
924
+ runtimeKind: 'api',
925
+ capabilities: STANDARD_REASONING,
926
+ reasoning: EFFORT_ONLY,
927
+ },
928
+ {
929
+ id: 'glm-4.7-flash',
930
+ label: 'GLM-4.7 Flash',
931
+ tier: 'fast',
932
+ costTier: 'low',
933
+ contextWindow: 200_000,
934
+ maxOutputTokens: 128_000,
935
+ runtimeKind: 'api',
936
+ capabilities: FAST_REASONING,
937
+ reasoning: EFFORT_ONLY,
938
+ notes: "Free on Z.ai's pricing page.",
939
+ },
940
+ {
941
+ id: 'glm-5.3',
942
+ label: 'GLM-5.3',
943
+ tier: 'powerful',
944
+ costTier: 'high',
945
+ contextWindow: 1_000_000,
946
+ maxOutputTokens: 128_000,
947
+ runtimeKind: 'api',
948
+ capabilities: POWERFUL_REASONING,
949
+ reasoning: EFFORT_ONLY,
950
+ },
951
+ {
952
+ id: 'glm-5.3-flash',
953
+ label: 'GLM-5.3 Flash',
954
+ tier: 'fast',
955
+ costTier: 'low',
956
+ contextWindow: 1_000_000,
957
+ maxOutputTokens: 128_000,
958
+ runtimeKind: 'api',
959
+ capabilities: FAST_REASONING,
960
+ reasoning: EFFORT_ONLY,
961
+ },
962
+ // The 4.x and 5.x lines run in parallel; neither is sunset.
963
+ {
964
+ id: 'glm-5.2',
965
+ label: 'GLM-5.2',
966
+ tier: 'powerful',
967
+ costTier: 'high',
968
+ contextWindow: 1_000_000,
969
+ maxOutputTokens: 128_000,
970
+ runtimeKind: 'api',
971
+ capabilities: POWERFUL_REASONING,
972
+ reasoning: EFFORT_ONLY,
973
+ },
974
+ {
975
+ id: 'glm-5.1',
976
+ label: 'GLM-5.1',
977
+ tier: 'standard',
978
+ costTier: 'medium',
979
+ contextWindow: 200_000,
980
+ maxOutputTokens: 128_000,
981
+ runtimeKind: 'api',
982
+ capabilities: STANDARD_REASONING,
983
+ reasoning: EFFORT_ONLY,
984
+ },
985
+ {
986
+ id: 'glm-5',
987
+ label: 'GLM-5',
988
+ tier: 'standard',
989
+ costTier: 'medium',
990
+ contextWindow: 200_000,
991
+ maxOutputTokens: 128_000,
992
+ runtimeKind: 'api',
993
+ capabilities: STANDARD_REASONING,
994
+ reasoning: EFFORT_ONLY,
995
+ },
996
+ {
997
+ id: 'glm-4.5',
998
+ label: 'GLM-4.5',
999
+ tier: 'standard',
1000
+ costTier: 'medium',
1001
+ contextWindow: 128_000,
1002
+ maxOutputTokens: 96_000,
1003
+ runtimeKind: 'api',
1004
+ capabilities: STANDARD_REASONING,
1005
+ reasoning: EFFORT_ONLY,
1006
+ },
1007
+ {
1008
+ id: 'glm-4.5-air',
1009
+ label: 'GLM-4.5 Air',
1010
+ tier: 'fast',
1011
+ costTier: 'low',
1012
+ contextWindow: 128_000,
1013
+ maxOutputTokens: 96_000,
1014
+ runtimeKind: 'api',
1015
+ capabilities: FAST_REASONING,
1016
+ reasoning: EFFORT_ONLY,
1017
+ },
1018
+ {
1019
+ id: 'glm-4.5-flash',
1020
+ label: 'GLM-4.5 Flash',
1021
+ tier: 'fast',
1022
+ costTier: 'low',
1023
+ contextWindow: 128_000,
1024
+ maxOutputTokens: 96_000,
1025
+ runtimeKind: 'api',
1026
+ capabilities: FAST_REASONING,
1027
+ reasoning: EFFORT_ONLY,
1028
+ },
1029
+ ],
1030
+ },
1031
+ qwen: {
1032
+ id: 'qwen',
1033
+ type: 'openai-compatible',
1034
+ label: 'Qwen (Alibaba Model Studio)',
1035
+ // The legacy static domain still works; Alibaba now recommends the per-workspace
1036
+ // domain (https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1),
1037
+ // which has no static form. Keys are region-bound: a wrong-region key reads as a bad key.
1038
+ description:
1039
+ 'DashScope compatible mode, Singapore. Set baseUrl to your workspace or region domain; mainland China is https://dashscope.aliyuncs.com/compatible-mode/v1.',
1040
+ envKey: 'DASHSCOPE_API_KEY',
1041
+ credential: 'api-key',
1042
+ apiRoot: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
1043
+ enabledByDefault: true,
1044
+ models: [
1045
+ // Alibaba's own grouping: 3.8 and 3.7-plus/flash are "recommended"; 3.7-max
1046
+ // and the 3.6 line are "legacy"; the undotted names are older still.
1047
+ {
1048
+ id: 'qwen3.8-max',
1049
+ label: 'Qwen3.8 Max',
1050
+ tier: 'powerful',
1051
+ costTier: 'high',
1052
+ contextWindow: 1_000_000,
1053
+ maxOutputTokens: 131_072,
1054
+ runtimeKind: 'api',
1055
+ capabilities: POWERFUL_REASONING,
1056
+ reasoning: EFFORT_ONLY,
1057
+ },
1058
+ {
1059
+ id: 'qwen3.8-flash',
1060
+ label: 'Qwen3.8 Flash',
1061
+ tier: 'fast',
1062
+ costTier: 'low',
1063
+ contextWindow: 1_000_000,
1064
+ maxOutputTokens: 131_072,
1065
+ runtimeKind: 'api',
1066
+ capabilities: FAST_REASONING,
1067
+ reasoning: EFFORT_ONLY,
1068
+ },
1069
+ {
1070
+ id: 'qwen3.7-plus',
1071
+ label: 'Qwen3.7 Plus',
1072
+ tier: 'standard',
1073
+ costTier: 'medium',
1074
+ runtimeKind: 'api',
1075
+ capabilities: STANDARD_REASONING,
1076
+ reasoning: EFFORT_ONLY,
1077
+ },
1078
+ {
1079
+ id: 'qwen3.7-flash',
1080
+ label: 'Qwen3.7 Flash',
1081
+ tier: 'fast',
1082
+ costTier: 'low',
1083
+ runtimeKind: 'api',
1084
+ capabilities: FAST_REASONING,
1085
+ reasoning: EFFORT_ONLY,
1086
+ },
1087
+ {
1088
+ id: 'qwen3.7-max',
1089
+ label: 'Qwen3.7 Max',
1090
+ tier: 'powerful',
1091
+ costTier: 'high',
1092
+ contextWindow: 991_808,
1093
+ maxOutputTokens: 131_072,
1094
+ runtimeKind: 'api',
1095
+ capabilities: POWERFUL_REASONING,
1096
+ reasoning: EFFORT_ONLY,
1097
+ },
1098
+ {
1099
+ id: 'qwen3.6-plus',
1100
+ label: 'Qwen3.6 Plus',
1101
+ tier: 'standard',
1102
+ costTier: 'medium',
1103
+ contextWindow: 1_000_000,
1104
+ maxOutputTokens: 65_536,
1105
+ runtimeKind: 'api',
1106
+ capabilities: STANDARD_REASONING,
1107
+ reasoning: EFFORT_ONLY,
1108
+ },
1109
+ {
1110
+ id: 'qwen3.6-flash',
1111
+ label: 'Qwen3.6 Flash',
1112
+ tier: 'fast',
1113
+ costTier: 'low',
1114
+ contextWindow: 1_000_000,
1115
+ maxOutputTokens: 65_536,
1116
+ runtimeKind: 'api',
1117
+ capabilities: FAST_REASONING,
1118
+ reasoning: EFFORT_ONLY,
1119
+ },
1120
+ {
1121
+ id: 'qwen-plus',
1122
+ label: 'Qwen Plus (legacy)',
1123
+ tier: 'standard',
1124
+ costTier: 'low',
1125
+ contextWindow: 1_000_000,
1126
+ maxOutputTokens: 32_768,
1127
+ runtimeKind: 'api',
1128
+ capabilities: STANDARD,
1129
+ },
1130
+ {
1131
+ id: 'qwen-flash',
1132
+ label: 'Qwen Flash (legacy)',
1133
+ tier: 'fast',
1134
+ costTier: 'low',
1135
+ contextWindow: 1_000_000,
1136
+ maxOutputTokens: 32_768,
1137
+ runtimeKind: 'api',
1138
+ capabilities: FAST,
1139
+ },
1140
+ ],
1141
+ },
1142
+ openrouter: {
1143
+ id: 'openrouter',
1144
+ type: 'openai-compatible',
1145
+ label: 'OpenRouter',
1146
+ description:
1147
+ 'One key, many vendors. Model ids are namespaced vendor/model; a :free suffix marks free models.',
1148
+ envKey: 'OPENROUTER_API_KEY',
1149
+ credential: 'api-key',
1150
+ apiRoot: 'https://openrouter.ai/api/v1',
1151
+ enabledByDefault: true,
1152
+ models: [
1153
+ {
1154
+ id: 'meta-llama/llama-3.1-8b-instruct',
1155
+ label: 'Llama 3.1 8B Instruct',
1156
+ tier: 'fast',
1157
+ costTier: 'low',
1158
+ contextWindow: 131_072,
1159
+ runtimeKind: 'api',
1160
+ capabilities: FAST,
1161
+ notes: 'Cheapest Llama on OpenRouter.',
1162
+ },
1163
+ {
1164
+ id: 'meta-llama/llama-3.3-70b-instruct',
1165
+ label: 'Llama 3.3 70B Instruct',
1166
+ tier: 'standard',
1167
+ costTier: 'medium',
1168
+ contextWindow: 131_072,
1169
+ runtimeKind: 'api',
1170
+ capabilities: STANDARD,
1171
+ },
1172
+ {
1173
+ id: 'meta-llama/llama-4-scout',
1174
+ label: 'Llama 4 Scout',
1175
+ tier: 'standard',
1176
+ costTier: 'low',
1177
+ contextWindow: 1_310_720,
1178
+ runtimeKind: 'api',
1179
+ capabilities: STANDARD,
1180
+ },
1181
+ {
1182
+ id: 'meta-llama/llama-4-maverick',
1183
+ label: 'Llama 4 Maverick',
1184
+ tier: 'powerful',
1185
+ costTier: 'medium',
1186
+ contextWindow: 1_048_576,
1187
+ runtimeKind: 'api',
1188
+ capabilities: POWERFUL,
1189
+ },
1190
+ {
1191
+ id: 'mistralai/mistral-nemo',
1192
+ label: 'Mistral Nemo',
1193
+ tier: 'fast',
1194
+ costTier: 'low',
1195
+ runtimeKind: 'api',
1196
+ capabilities: FAST,
1197
+ notes: 'Cheapest model in the OpenRouter catalogue.',
1198
+ },
1199
+ {
1200
+ id: 'z-ai/glm-5.2:free',
1201
+ label: 'GLM-5.2 (free)',
1202
+ tier: 'standard',
1203
+ costTier: 'low',
1204
+ runtimeKind: 'api',
1205
+ capabilities: STANDARD,
1206
+ notes: 'Free tier; the cheapest hosted judgment model that needs no local runtime.',
1207
+ },
1208
+ {
1209
+ id: 'qwen/qwen3.8-27b:free',
1210
+ label: 'Qwen3.8 27B (free)',
1211
+ tier: 'standard',
1212
+ costTier: 'low',
1213
+ runtimeKind: 'api',
1214
+ capabilities: STANDARD,
1215
+ notes: 'Free tier.',
1216
+ },
1217
+ ],
1218
+ },
1219
+ groq: {
1220
+ id: 'groq',
1221
+ type: 'openai-compatible',
1222
+ label: 'Groq',
1223
+ description:
1224
+ 'Groq LPU inference. Meta Llama chat models are enterprise-contract only here; GPT-OSS is the self-serve open-weight line.',
1225
+ envKey: 'GROQ_API_KEY',
1226
+ credential: 'api-key',
1227
+ apiRoot: 'https://api.groq.com/openai/v1',
1228
+ enabledByDefault: true,
1229
+ models: [
1230
+ {
1231
+ id: 'openai/gpt-oss-20b',
1232
+ label: 'GPT-OSS 20B',
1233
+ tier: 'fast',
1234
+ costTier: 'low',
1235
+ contextWindow: 131_072,
1236
+ runtimeKind: 'api',
1237
+ capabilities: FAST_REASONING,
1238
+ reasoning: EFFORT_ONLY,
1239
+ },
1240
+ {
1241
+ id: 'openai/gpt-oss-120b',
1242
+ label: 'GPT-OSS 120B',
1243
+ tier: 'standard',
1244
+ costTier: 'low',
1245
+ contextWindow: 131_072,
1246
+ runtimeKind: 'api',
1247
+ capabilities: STANDARD_REASONING,
1248
+ reasoning: EFFORT_ONLY,
1249
+ },
1250
+ ],
1251
+ },
1252
+ together: {
1253
+ id: 'together',
1254
+ type: 'openai-compatible',
1255
+ label: 'Together AI',
1256
+ description:
1257
+ 'Hosted open-weight models. Llama ids are not uniform across versions; check each one.',
1258
+ envKey: 'TOGETHER_API_KEY',
1259
+ credential: 'api-key',
1260
+ apiRoot: 'https://api.together.ai/v1',
1261
+ enabledByDefault: true,
1262
+ models: [
1263
+ {
1264
+ id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
1265
+ label: 'Llama 3.3 70B Instruct Turbo',
1266
+ tier: 'standard',
1267
+ costTier: 'medium',
1268
+ contextWindow: 131_072,
1269
+ runtimeKind: 'api',
1270
+ capabilities: STANDARD,
1271
+ },
1272
+ {
1273
+ id: 'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo',
1274
+ label: 'Llama 3.1 405B Instruct Turbo',
1275
+ tier: 'powerful',
1276
+ costTier: 'high',
1277
+ runtimeKind: 'api',
1278
+ capabilities: POWERFUL,
1279
+ notes: 'The 3.1 line keeps the Meta- prefix; 3.3 and 4 drop it.',
1280
+ },
1281
+ {
1282
+ id: 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8',
1283
+ label: 'Llama 4 Maverick 17B 128E FP8',
1284
+ tier: 'powerful',
1285
+ costTier: 'medium',
1286
+ runtimeKind: 'api',
1287
+ capabilities: POWERFUL,
1288
+ },
1289
+ ],
1290
+ },
1291
+ fireworks: {
1292
+ id: 'fireworks',
1293
+ type: 'openai-compatible',
1294
+ label: 'Fireworks AI',
1295
+ description: 'Hosted open-weight models. Model ids are full account paths, not bare names.',
1296
+ envKey: 'FIREWORKS_API_KEY',
1297
+ credential: 'api-key',
1298
+ apiRoot: 'https://api.fireworks.ai/inference/v1',
1299
+ enabledByDefault: true,
1300
+ models: [
1301
+ {
1302
+ id: 'accounts/fireworks/models/llama-v3p1-8b-instruct',
1303
+ label: 'Llama 3.1 8B Instruct',
1304
+ tier: 'fast',
1305
+ costTier: 'low',
1306
+ runtimeKind: 'api',
1307
+ capabilities: FAST,
1308
+ },
1309
+ {
1310
+ id: 'accounts/fireworks/models/llama-v3p3-70b-instruct',
1311
+ label: 'Llama 3.3 70B Instruct',
1312
+ tier: 'standard',
1313
+ costTier: 'medium',
1314
+ runtimeKind: 'api',
1315
+ capabilities: STANDARD,
1316
+ },
1317
+ {
1318
+ id: 'accounts/fireworks/models/llama-v3p1-405b-instruct',
1319
+ label: 'Llama 3.1 405B Instruct',
1320
+ tier: 'powerful',
1321
+ costTier: 'high',
1322
+ runtimeKind: 'api',
1323
+ capabilities: POWERFUL,
1324
+ },
1325
+ {
1326
+ id: 'accounts/fireworks/models/llama4-scout-instruct-basic',
1327
+ label: 'Llama 4 Scout',
1328
+ tier: 'standard',
1329
+ costTier: 'low',
1330
+ runtimeKind: 'api',
1331
+ capabilities: STANDARD,
1332
+ },
1333
+ {
1334
+ id: 'accounts/fireworks/models/llama4-maverick-instruct-basic',
1335
+ label: 'Llama 4 Maverick',
1336
+ tier: 'powerful',
1337
+ costTier: 'medium',
1338
+ runtimeKind: 'api',
1339
+ capabilities: POWERFUL,
1340
+ },
1341
+ ],
1342
+ },
1343
+ ollama: {
1344
+ id: 'ollama',
1345
+ type: 'openai-compatible',
1346
+ label: 'Ollama (local)',
1347
+ description:
1348
+ 'Local Ollama server. No credential: clients must send a bearer, the server ignores it.',
1349
+ credential: 'local',
1350
+ apiRoot: 'http://localhost:11434/v1',
1351
+ enabledByDefault: true,
1352
+ models: [
1353
+ {
1354
+ id: 'llama3.2:3b',
1355
+ label: 'Llama 3.2 3B',
1356
+ tier: 'fast',
1357
+ costTier: 'low',
1358
+ contextWindow: 128_000,
1359
+ runtimeKind: 'api',
1360
+ capabilities: FAST,
1361
+ notes: 'ollama pull llama3.2:3b — the cheapest working judgment model.',
1362
+ },
1363
+ {
1364
+ id: 'llama3.1:8b',
1365
+ label: 'Llama 3.1 8B',
1366
+ tier: 'fast',
1367
+ costTier: 'low',
1368
+ contextWindow: 128_000,
1369
+ runtimeKind: 'api',
1370
+ capabilities: FAST,
1371
+ },
1372
+ {
1373
+ id: 'llama3.3:70b',
1374
+ label: 'Llama 3.3 70B',
1375
+ tier: 'standard',
1376
+ costTier: 'low',
1377
+ contextWindow: 128_000,
1378
+ runtimeKind: 'api',
1379
+ capabilities: STANDARD,
1380
+ },
1381
+ {
1382
+ id: 'llama4:16x17b',
1383
+ label: 'Llama 4 Scout',
1384
+ tier: 'standard',
1385
+ costTier: 'low',
1386
+ contextWindow: 10_000_000,
1387
+ runtimeKind: 'api',
1388
+ capabilities: STANDARD,
1389
+ },
1390
+ {
1391
+ id: 'llama4:128x17b',
1392
+ label: 'Llama 4 Maverick',
1393
+ tier: 'powerful',
1394
+ costTier: 'low',
1395
+ contextWindow: 1_000_000,
1396
+ runtimeKind: 'api',
1397
+ capabilities: POWERFUL,
1398
+ },
1399
+ {
1400
+ id: 'qwen3:8b',
1401
+ label: 'Qwen3 8B',
1402
+ tier: 'fast',
1403
+ costTier: 'low',
1404
+ runtimeKind: 'api',
1405
+ capabilities: FAST,
1406
+ },
1407
+ {
1408
+ id: 'gpt-oss:20b',
1409
+ label: 'GPT-OSS 20B',
1410
+ tier: 'fast',
1411
+ costTier: 'low',
1412
+ runtimeKind: 'api',
1413
+ capabilities: FAST_REASONING,
1414
+ reasoning: EFFORT_ONLY,
1415
+ },
1416
+ ],
1417
+ },
1418
+ vllm: {
1419
+ id: 'vllm',
1420
+ type: 'openai-compatible',
1421
+ label: 'vLLM (local)',
1422
+ description:
1423
+ 'Local vLLM server on port 8000. Keyless unless started with --api-key; the model id is whatever --served-model-name says.',
1424
+ credential: 'local',
1425
+ apiRoot: 'http://localhost:8000/v1',
1426
+ enabledByDefault: true,
1427
+ models: [
1428
+ {
1429
+ id: 'NousResearch/Meta-Llama-3-8B-Instruct',
1430
+ label: 'vLLM served model',
1431
+ tier: 'standard',
1432
+ costTier: 'low',
1433
+ runtimeKind: 'api',
1434
+ capabilities: STANDARD,
1435
+ notes:
1436
+ "vLLM's own quickstart example. Replace with this server's --served-model-name; vLLM has no fixed catalogue.",
1437
+ },
1438
+ ],
1439
+ },
1440
+ mistral: {
1441
+ id: 'mistral',
1442
+ type: 'openai-compatible',
1443
+ label: 'Mistral AI',
1444
+ description:
1445
+ 'Mistral API. Wire-compatible with OpenAI; JSON mode needs the word JSON in the prompt.',
1446
+ envKey: 'MISTRAL_API_KEY',
1447
+ credential: 'api-key',
1448
+ apiRoot: 'https://api.mistral.ai/v1',
1449
+ enabledByDefault: true,
1450
+ models: [
1451
+ {
1452
+ id: 'ministral-3b-2512',
1453
+ label: 'Ministral 3 3B',
1454
+ tier: 'fast',
1455
+ costTier: 'low',
1456
+ runtimeKind: 'api',
1457
+ capabilities: FAST,
1458
+ },
1459
+ {
1460
+ id: 'ministral-8b-2512',
1461
+ label: 'Ministral 3 8B',
1462
+ tier: 'fast',
1463
+ costTier: 'low',
1464
+ runtimeKind: 'api',
1465
+ capabilities: FAST,
1466
+ },
1467
+ {
1468
+ id: 'ministral-14b-2512',
1469
+ label: 'Ministral 3 14B',
1470
+ tier: 'fast',
1471
+ costTier: 'low',
1472
+ runtimeKind: 'api',
1473
+ capabilities: FAST,
1474
+ },
1475
+ {
1476
+ id: 'mistral-small-2603',
1477
+ label: 'Mistral Small 4',
1478
+ tier: 'standard',
1479
+ costTier: 'low',
1480
+ contextWindow: 256_000,
1481
+ runtimeKind: 'api',
1482
+ capabilities: STANDARD_REASONING,
1483
+ reasoning: EFFORT_ONLY,
1484
+ notes: 'Unifies instruct, reasoning and coding.',
1485
+ },
1486
+ {
1487
+ id: 'mistral-medium-3-5',
1488
+ label: 'Mistral Medium 3.5',
1489
+ tier: 'powerful',
1490
+ costTier: 'medium',
1491
+ contextWindow: 256_000,
1492
+ runtimeKind: 'api',
1493
+ capabilities: POWERFUL_REASONING,
1494
+ reasoning: EFFORT_ONLY,
1495
+ notes: 'Takes reasoning_effort.',
1496
+ },
1497
+ {
1498
+ id: 'mistral-large-2512',
1499
+ label: 'Mistral Large 3',
1500
+ tier: 'powerful',
1501
+ costTier: 'high',
1502
+ contextWindow: 256_000,
1503
+ runtimeKind: 'api',
1504
+ capabilities: POWERFUL,
1505
+ },
1506
+ ],
1507
+ },
1508
+ typesafe: {
1509
+ id: 'typesafe',
1510
+ type: 'typesafe',
1511
+ label: 'TypeSafe (Jev)',
1512
+ description:
1513
+ 'Typed decisions — choice, score and noul with calibrated confidence. Judgment only: it answers typed questions, it does not complete chat.',
1514
+ envKey: 'TYPESAFE_API_KEY',
1515
+ credential: 'api-key',
1516
+ apiRoot: 'https://api.typesafe.ai/v1',
1517
+ enabledByDefault: false,
1518
+ models: [
1519
+ {
1520
+ id: 'jev-latest',
1521
+ label: 'Jev (latest)',
1522
+ tier: 'fast',
1523
+ costTier: 'low',
1524
+ runtimeKind: 'typed-decision',
1525
+ capabilities: ['judgment'],
1526
+ notes:
1527
+ 'Not an LLMProvider: TypeSafeDecisionClient.evaluate takes typed questions, so it cannot back a chat agent.',
1528
+ },
1529
+ ],
1530
+ },
1531
+ };
1532
+
1533
+ /** Every registered model carrying all of the requested capabilities. */
1534
+ export function modelOptionsByCapability(...capabilities: ModelCapability[]): ModelSweepOption[] {
1535
+ return Object.values(MODEL_REGISTRY).flatMap((provider) =>
1536
+ provider.models
1537
+ .filter((model) =>
1538
+ capabilities.every((capability) => model.capabilities.includes(capability)),
1539
+ )
1540
+ .map((model) => ({ provider: provider.id, model: model.id, label: model.id })),
1541
+ );
1542
+ }
1543
+
1544
+ export function registryModel(providerId: string, modelId: string): FoundryModelInfo | undefined {
1545
+ return MODEL_REGISTRY[providerId]?.models.find((model) => model.id === modelId);
1546
+ }
1547
+
1548
+ /** Unknown provider/model pairs report no capabilities rather than throwing. */
1549
+ export function modelCapabilities(providerId: string, modelId: string): ModelCapability[] {
1550
+ return [...(registryModel(providerId, modelId)?.capabilities ?? [])];
1551
+ }
1552
+
1553
+ export function modelHasCapability(
1554
+ providerId: string,
1555
+ modelId: string,
1556
+ capability: ModelCapability,
1557
+ ): boolean {
1558
+ return modelCapabilities(providerId, modelId).includes(capability);
1559
+ }
1560
+
1561
+ /** Providers with at least one model carrying the capability. */
1562
+ export function providersWithCapability(capability: ModelCapability): FoundryProviderInfo[] {
1563
+ return Object.values(MODEL_REGISTRY).filter((provider) =>
1564
+ provider.models.some((model) => model.capabilities.includes(capability)),
1565
+ );
1566
+ }
1567
+
1568
+ /** The part of a Foundry configuration `resolveDecisionModel` reads. */
1569
+ export interface DecisionModelDefaults {
1570
+ defaults: {
1571
+ classifierProvider?: string;
1572
+ classifierModel?: string;
1573
+ };
1574
+ }
1575
+
1576
+ /** What the API-token path would construct, before any credential is read. */
1577
+ export function resolveDecisionModel(config: DecisionModelDefaults): {
1578
+ provider: string;
1579
+ model: string;
1580
+ } {
1581
+ const configured = config.defaults.classifierProvider;
1582
+ // A classifier provider outside the registry — the subscription decision
1583
+ // profile — belongs to the native path, which constructs its own client.
1584
+ const provider = configured && MODEL_REGISTRY[configured] ? configured : DECISION_PROVIDER;
1585
+ const model = (provider === configured && config.defaults.classifierModel) || DECISION_MODEL;
1586
+ return { provider, model };
1587
+ }