1use serde::Serialize;
2
3use crate::inference::{
4 ModelDescriptor, ModelHarnessProfile, ModelInstructionOverlay, ModelProfileReasoning,
5 ModelSchemaPolicy, ProviderFamily, ReasoningEffortDescriptor,
6};
7
8mod anthropic;
9mod deepseek;
10pub mod image_models;
11mod openai_codex;
12mod openrouter;
13mod synthetic;
14mod xiaomi_mimo;
15
16pub use deepseek::{DEEPSEEK_DEFAULT_BASE_URL, DEEPSEEK_DEFAULT_MODEL, DEEPSEEK_ENV_ALIASES};
17pub use image_models::{
18 IMAGE_PROVIDER_GOOGLE, IMAGE_PROVIDER_OPENAI, ImageModelCatalogEntry,
19 ImageProviderCatalogEntry, built_in_image_providers, image_model_descriptors,
20 image_models_for_provider, lookup_image_model, lookup_image_provider,
21};
22pub use synthetic::{SYNTHETIC_DEFAULT_BASE_URL, SYNTHETIC_DEFAULT_MODEL, SYNTHETIC_ENV_ALIASES};
23pub use xiaomi_mimo::{XIAOMI_MIMO_ENV_ALIASES, XIAOMI_MIMO_TOKEN_PLAN_ENV_ALIASES};
24
25pub const PROVIDER_MOCK: &str = "mock";
26pub const PROVIDER_OPENAI: &str = "openai";
27pub const PROVIDER_CODEX: &str = "codex";
28pub const PROVIDER_ANTHROPIC: &str = "anthropic";
29pub const PROVIDER_CLAUDE_CODE: &str = "claude-code";
30pub const PROVIDER_GEMINI: &str = "gemini";
31pub const PROVIDER_VERTEX: &str = "vertex";
32pub const PROVIDER_GOOGLE: &str = "google";
33pub const PROVIDER_ZEROENTROPY: &str = "zeroentropy";
34pub const PROVIDER_XAI: &str = "xai";
35pub const PROVIDER_SUPERGROK: &str = "supergrok";
36pub const PROVIDER_OPENCODE: &str = "opencode";
37pub const PROVIDER_OPENCODE_GO: &str = "opencode-go";
38pub const PROVIDER_OPENROUTER: &str = "openrouter";
39pub const PROVIDER_FIREWORKS: &str = "fireworks";
40pub const PROVIDER_RODER_CLOUD: &str = "roder-cloud";
41pub const PROVIDER_POOLSIDE: &str = "poolside";
42pub const PROVIDER_CURSOR: &str = "cursor";
43pub const PROVIDER_XIAOMI_MIMO: &str = "xiaomi-mimo";
44pub const PROVIDER_XIAOMI_MIMO_TOKEN_PLAN: &str = "xiaomi-mimo-token-plan";
45pub const PROVIDER_KIMI_CODE: &str = "kimi-code";
46pub const PROVIDER_SYNTHETIC: &str = "synthetic";
47pub const PROVIDER_DEEPSEEK: &str = "deepseek";
48
49pub const PROVIDER_KIND_MOCK: &str = "mock";
50pub const PROVIDER_KIND_OPENAI: &str = "openai";
51pub const PROVIDER_KIND_CHAT_COMPLETIONS: &str = "chat_completions";
52pub const PROVIDER_KIND_ANTHROPIC: &str = "anthropic";
53pub const PROVIDER_KIND_CLAUDE_CODE: &str = "claude_code";
54pub const PROVIDER_KIND_GEMINI: &str = "gemini";
55pub const PROVIDER_KIND_VERTEX: &str = "vertex";
56pub const PROVIDER_KIND_XAI: &str = "xai";
57pub const PROVIDER_KIND_OPENCODE: &str = "opencode";
58pub const PROVIDER_KIND_OPENROUTER: &str = "openrouter";
59pub const PROVIDER_KIND_FIREWORKS: &str = "fireworks";
60pub const PROVIDER_KIND_RODER_CLOUD: &str = "roder_cloud";
61pub const PROVIDER_KIND_POOLSIDE: &str = "poolside";
62pub const PROVIDER_KIND_CURSOR: &str = "cursor";
63pub const PROVIDER_KIND_XIAOMI_MIMO: &str = PROVIDER_KIND_CHAT_COMPLETIONS;
64pub const PROVIDER_KIND_SYNTHETIC: &str = PROVIDER_KIND_CHAT_COMPLETIONS;
65pub const PROVIDER_KIND_DEEPSEEK: &str = PROVIDER_KIND_CHAT_COMPLETIONS;
66
67pub const REASONING_NONE: &str = "none";
68pub const REASONING_MINIMAL: &str = "minimal";
69pub const REASONING_LOW: &str = "low";
70pub const REASONING_MEDIUM: &str = "medium";
71pub const REASONING_HIGH: &str = "high";
72pub const REASONING_XHIGH: &str = "xhigh";
73pub const REASONING_MAX: &str = "max";
74pub const REASONING_ULTRA: &str = "ultra";
75
76pub const DEFAULT_MODEL_ID: &str = "gpt-6-sol";
77pub const EDIT_TOOL_PATCH: &str = "patch";
78pub const EDIT_TOOL_EDIT: &str = "edit";
79
80#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
81pub struct ProviderCatalogEntry {
82 pub id: &'static str,
83 pub name: &'static str,
84 pub kind: &'static str,
85 pub default_model: &'static str,
86 pub base_url: Option<&'static str>,
87 pub env_key: Option<&'static str>,
88 pub env_aliases: &'static [&'static str],
89 pub requires_auth: bool,
90 pub supports_websockets: bool,
91}
92
93#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
94pub struct ReasoningOption {
95 pub effort: &'static str,
96 pub description: &'static str,
97}
98
99#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
100pub struct ModelCatalogEntry {
101 pub id: &'static str,
102 pub display_name: &'static str,
103 pub description: &'static str,
104 pub provider: &'static str,
105 pub default_reasoning: &'static str,
106 pub supported_reasoning: &'static [ReasoningOption],
107 pub context_window: u32,
108 pub max_context_window: u32,
109 pub auto_compact_token_limit: u32,
110 pub supports_compaction: bool,
111 pub supports_images: bool,
112 pub supports_tools: bool,
113 pub supports_structured: bool,
114 pub edit_tool: Option<&'static str>,
115 pub hidden: bool,
116}
117
118pub const STANDARD_REASONING: &[ReasoningOption] = &[
119 ReasoningOption {
120 effort: REASONING_LOW,
121 description: "Fast responses with lighter reasoning",
122 },
123 ReasoningOption {
124 effort: REASONING_MEDIUM,
125 description: "Balances speed and reasoning depth for everyday tasks",
126 },
127 ReasoningOption {
128 effort: REASONING_HIGH,
129 description: "Greater reasoning depth for complex problems",
130 },
131 ReasoningOption {
132 effort: REASONING_XHIGH,
133 description: "Extra high reasoning depth for complex problems",
134 },
135];
136
137pub const OPUS_REASONING: &[ReasoningOption] = &[
141 ReasoningOption {
142 effort: REASONING_LOW,
143 description: "Most efficient; best for short, scoped tasks",
144 },
145 ReasoningOption {
146 effort: REASONING_MEDIUM,
147 description: "Balanced reasoning depth for cost-sensitive workflows",
148 },
149 ReasoningOption {
150 effort: REASONING_HIGH,
151 description: "High capability for complex reasoning and agentic tasks",
152 },
153 ReasoningOption {
154 effort: REASONING_XHIGH,
155 description: "Extended capability for long-horizon coding and agentic work",
156 },
157 ReasoningOption {
158 effort: REASONING_MAX,
159 description: "Absolute maximum capability with no constraints on token spending",
160 },
161];
162
163pub const SONNET_REASONING: &[ReasoningOption] = &[
165 ReasoningOption {
166 effort: REASONING_LOW,
167 description: "Most efficient; lowest latency and cost",
168 },
169 ReasoningOption {
170 effort: REASONING_MEDIUM,
171 description: "Balances speed, cost, and performance for most tasks",
172 },
173 ReasoningOption {
174 effort: REASONING_HIGH,
175 description: "Greater reasoning depth for complex problems",
176 },
177 ReasoningOption {
178 effort: REASONING_MAX,
179 description: "Absolute maximum capability with no constraints on token spending",
180 },
181];
182
183pub const GPT_52_REASONING: &[ReasoningOption] = &[
184 ReasoningOption {
185 effort: REASONING_LOW,
186 description: "Balances speed with some reasoning; useful for straightforward queries and short explanations",
187 },
188 ReasoningOption {
189 effort: REASONING_MEDIUM,
190 description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks",
191 },
192 ReasoningOption {
193 effort: REASONING_HIGH,
194 description: "Maximizes reasoning depth for complex or ambiguous problems",
195 },
196 ReasoningOption {
197 effort: REASONING_XHIGH,
198 description: "Extra high reasoning for complex problems",
199 },
200];
201
202pub const HAIKU_REASONING: &[ReasoningOption] = &[
203 ReasoningOption {
204 effort: REASONING_LOW,
205 description: "Fast responses with lighter reasoning",
206 },
207 ReasoningOption {
208 effort: REASONING_MEDIUM,
209 description: "Balances speed and reasoning depth for everyday tasks",
210 },
211];
212
213pub const GEMINI_REASONING: &[ReasoningOption] = &[
214 ReasoningOption {
215 effort: REASONING_MINIMAL,
216 description: "Minimal Gemini thinking",
217 },
218 ReasoningOption {
219 effort: REASONING_LOW,
220 description: "Low Gemini thinking",
221 },
222 ReasoningOption {
223 effort: REASONING_MEDIUM,
224 description: "Medium Gemini thinking",
225 },
226 ReasoningOption {
227 effort: REASONING_HIGH,
228 description: "High Gemini thinking",
229 },
230];
231
232pub const MOCK_REASONING: &[ReasoningOption] = &[ReasoningOption {
233 effort: REASONING_NONE,
234 description: "No model-side reasoning",
235}];
236
237pub const POOLSIDE_REASONING: &[ReasoningOption] = &[
238 ReasoningOption {
239 effort: REASONING_NONE,
240 description: "Disable Poolside thinking for lower latency",
241 },
242 ReasoningOption {
243 effort: REASONING_MEDIUM,
244 description: "Enable Poolside thinking",
245 },
246];
247
248pub const GEMINI_ENV_ALIASES: &[&str] = &[
249 "GEMINI_API_KEY",
250 "GOOGLE_API_KEY",
251 "GOOGLE_GENAI_API_KEY",
252 "GOOGLE_AI_API_KEY",
253];
254
255pub const VERTEX_ENV_ALIASES: &[&str] = &["VERTEX_CREDENTIALS_JSON"];
256
257pub const XAI_ENV_ALIASES: &[&str] = &["RODER_XAI_API_KEY"];
258
259pub const XAI_CONFIGURABLE_REASONING: &[ReasoningOption] = &[
260 ReasoningOption {
261 effort: REASONING_NONE,
262 description: "No xAI reasoning effort",
263 },
264 ReasoningOption {
265 effort: REASONING_LOW,
266 description: "Low xAI reasoning effort",
267 },
268 ReasoningOption {
269 effort: REASONING_MEDIUM,
270 description: "Medium xAI reasoning effort",
271 },
272 ReasoningOption {
273 effort: REASONING_HIGH,
274 description: "High xAI reasoning effort",
275 },
276 ReasoningOption {
277 effort: REASONING_XHIGH,
278 description: "Extra-high xAI reasoning effort",
279 },
280];
281
282pub const XAI_REASONING: &[ReasoningOption] = &[
283 ReasoningOption {
284 effort: REASONING_LOW,
285 description: "Low xAI reasoning effort",
286 },
287 ReasoningOption {
288 effort: REASONING_MEDIUM,
289 description: "Medium xAI reasoning effort",
290 },
291 ReasoningOption {
292 effort: REASONING_HIGH,
293 description: "High xAI reasoning effort",
294 },
295 ReasoningOption {
296 effort: REASONING_XHIGH,
297 description: "Extra-high xAI reasoning effort",
298 },
299];
300
301pub const XAI_NO_REASONING: &[ReasoningOption] = &[ReasoningOption {
302 effort: REASONING_NONE,
303 description: "No xAI reasoning effort",
304}];
305
306pub const OPENROUTER_REASONING: &[ReasoningOption] = &[
307 ReasoningOption {
308 effort: REASONING_NONE,
309 description: "Disable OpenRouter reasoning controls",
310 },
311 ReasoningOption {
312 effort: REASONING_LOW,
313 description: "Low OpenRouter reasoning effort",
314 },
315 ReasoningOption {
316 effort: REASONING_MEDIUM,
317 description: "Medium OpenRouter reasoning effort",
318 },
319 ReasoningOption {
320 effort: REASONING_HIGH,
321 description: "High OpenRouter reasoning effort",
322 },
323];
324
325pub const RODER_CLOUD_REASONING: &[ReasoningOption] = &[ReasoningOption {
332 effort: REASONING_NONE,
333 description: "roder.cloud forwards no reasoning controls",
334}];
335
336pub const BUILT_IN_PROVIDERS: &[ProviderCatalogEntry] = &[
337 ProviderCatalogEntry {
338 id: PROVIDER_MOCK,
339 name: "Mock",
340 kind: PROVIDER_KIND_MOCK,
341 default_model: "mock",
342 base_url: None,
343 env_key: None,
344 env_aliases: &[],
345 requires_auth: false,
346 supports_websockets: false,
347 },
348 ProviderCatalogEntry {
349 id: PROVIDER_OPENAI,
350 name: "OpenAI",
351 kind: PROVIDER_KIND_OPENAI,
352 default_model: DEFAULT_MODEL_ID,
353 base_url: Some("https://api.openai.com/v1"),
354 env_key: Some("OPENAI_API_KEY"),
355 env_aliases: &[],
356 requires_auth: true,
357 supports_websockets: true,
358 },
359 ProviderCatalogEntry {
360 id: PROVIDER_CODEX,
361 name: "Codex",
362 kind: PROVIDER_KIND_OPENAI,
363 default_model: DEFAULT_MODEL_ID,
364 base_url: Some("https://api.openai.com/v1"),
365 env_key: Some("OPENAI_API_KEY"),
366 env_aliases: &[],
367 requires_auth: true,
368 supports_websockets: true,
369 },
370 ProviderCatalogEntry {
371 id: PROVIDER_ANTHROPIC,
372 name: "Anthropic",
373 kind: PROVIDER_KIND_ANTHROPIC,
374 default_model: "claude-sonnet-5",
375 base_url: Some("https://api.anthropic.com"),
376 env_key: Some("ANTHROPIC_API_KEY"),
377 env_aliases: &[],
378 requires_auth: true,
379 supports_websockets: false,
380 },
381 ProviderCatalogEntry {
382 id: PROVIDER_CLAUDE_CODE,
383 name: "Claude Code",
384 kind: PROVIDER_KIND_CLAUDE_CODE,
385 default_model: "sonnet",
386 base_url: None,
387 env_key: None,
388 env_aliases: &["CLAUDE_CODE_CLI_PATH", "RODER_CLAUDE_CODE_CLI_PATH"],
389 requires_auth: false,
390 supports_websockets: false,
391 },
392 ProviderCatalogEntry {
393 id: PROVIDER_GEMINI,
394 name: "Gemini",
395 kind: PROVIDER_KIND_GEMINI,
396 default_model: "gemini-3.5-flash",
397 base_url: None,
398 env_key: Some("GEMINI_API_TOKEN"),
399 env_aliases: GEMINI_ENV_ALIASES,
400 requires_auth: true,
401 supports_websockets: false,
402 },
403 ProviderCatalogEntry {
404 id: PROVIDER_VERTEX,
405 name: "Vertex AI",
406 kind: PROVIDER_KIND_VERTEX,
407 default_model: "gemini-3.5-flash",
408 base_url: None,
409 env_key: Some("GOOGLE_APPLICATION_CREDENTIALS"),
410 env_aliases: VERTEX_ENV_ALIASES,
411 requires_auth: true,
412 supports_websockets: false,
413 },
414 ProviderCatalogEntry {
415 id: PROVIDER_XAI,
416 name: "xAI",
417 kind: PROVIDER_KIND_XAI,
418 default_model: "grok-4.7",
419 base_url: Some("https://api.x.ai/v1"),
420 env_key: Some("XAI_API_KEY"),
421 env_aliases: XAI_ENV_ALIASES,
422 requires_auth: true,
423 supports_websockets: false,
424 },
425 ProviderCatalogEntry {
426 id: PROVIDER_SUPERGROK,
427 name: "SuperGrok",
428 kind: PROVIDER_KIND_XAI,
429 default_model: "grok-4.7",
430 base_url: Some("https://api.x.ai/v1"),
431 env_key: None,
432 env_aliases: &[],
433 requires_auth: true,
434 supports_websockets: false,
435 },
436 ProviderCatalogEntry {
437 id: PROVIDER_OPENCODE,
438 name: "OpenCode Zen",
439 kind: PROVIDER_KIND_OPENCODE,
440 default_model: "gpt-6-sol",
441 base_url: Some("https://opencode.ai/zen/v1"),
442 env_key: Some("OPENCODE_API_KEY"),
443 env_aliases: &["OPENCODE_ZEN_API_KEY", "RODER_OPENCODE_API_KEY"],
444 requires_auth: true,
445 supports_websockets: false,
446 },
447 ProviderCatalogEntry {
448 id: PROVIDER_OPENCODE_GO,
449 name: "OpenCode Go",
450 kind: PROVIDER_KIND_OPENCODE,
451 default_model: "kimi-k2.6",
452 base_url: Some("https://opencode.ai/zen/go/v1"),
453 env_key: Some("OPENCODE_GO_API_KEY"),
454 env_aliases: &["RODER_OPENCODE_GO_API_KEY", "OPENCODE_API_KEY"],
455 requires_auth: true,
456 supports_websockets: false,
457 },
458 ProviderCatalogEntry {
459 id: PROVIDER_OPENROUTER,
460 name: "OpenRouter",
461 kind: PROVIDER_KIND_OPENROUTER,
462 default_model: "x-ai/grok-4.6",
463 base_url: Some("https://openrouter.ai/api/v1"),
464 env_key: Some("OPENROUTER_API_KEY"),
465 env_aliases: &["RODER_OPENROUTER_API_KEY"],
466 requires_auth: true,
467 supports_websockets: false,
468 },
469 ProviderCatalogEntry {
470 id: PROVIDER_FIREWORKS,
471 name: "Fireworks AI",
472 kind: PROVIDER_KIND_FIREWORKS,
473 default_model: "accounts/fireworks/models/qwen3-235b-a22b",
474 base_url: Some("https://api.fireworks.ai/inference/v1"),
475 env_key: Some("FIREWORKS_API_KEY"),
476 env_aliases: &["RODER_FIREWORKS_API_KEY"],
477 requires_auth: true,
478 supports_websockets: false,
479 },
480 ProviderCatalogEntry {
481 id: PROVIDER_RODER_CLOUD,
482 name: "Roder Cloud",
483 kind: PROVIDER_KIND_RODER_CLOUD,
484 default_model: "roder.cloud/free",
485 base_url: None,
489 env_key: Some("RODER_CLOUD_API_KEY"),
490 env_aliases: &["RODER_CLOUD_TOKEN"],
491 requires_auth: true,
492 supports_websockets: false,
493 },
494 ProviderCatalogEntry {
495 id: PROVIDER_POOLSIDE,
496 name: "Poolside",
497 kind: PROVIDER_KIND_POOLSIDE,
498 default_model: "poolside/laguna-m.1",
499 base_url: Some("https://inference.poolside.ai/v1"),
500 env_key: Some("POOLSIDE_API_KEY"),
501 env_aliases: &["RODER_POOLSIDE_API_KEY"],
502 requires_auth: true,
503 supports_websockets: false,
504 },
505 ProviderCatalogEntry {
506 id: PROVIDER_CURSOR,
507 name: "Cursor",
508 kind: PROVIDER_KIND_CURSOR,
509 default_model: "composer-2.5",
510 base_url: Some("https://agentn.global.api5.cursor.sh"),
511 env_key: Some("CURSOR_API_KEY"),
512 env_aliases: &["RODER_CURSOR_API_KEY"],
513 requires_auth: true,
514 supports_websockets: false,
515 },
516 xiaomi_mimo::PAY_AS_YOU_GO_PROVIDER,
517 xiaomi_mimo::TOKEN_PLAN_PROVIDER,
518 synthetic::SYNTHETIC_PROVIDER,
519 deepseek::DEEPSEEK_PROVIDER,
520 ProviderCatalogEntry {
521 id: PROVIDER_KIMI_CODE,
522 name: "Kimi Code",
523 kind: PROVIDER_KIND_CHAT_COMPLETIONS,
524 default_model: "kimi-for-coding",
525 base_url: Some("https://api.kimi.com/coding/v1"),
526 env_key: Some("KIMI_CODE_API_KEY"),
527 env_aliases: &["RODER_KIMI_CODE_API_KEY"],
528 requires_auth: true,
529 supports_websockets: false,
530 },
531];
532
533const fn retired(mut model: ModelCatalogEntry) -> ModelCatalogEntry {
536 model.hidden = true;
537 model
538}
539
540pub const BUILT_IN_MODELS: &[ModelCatalogEntry] = &[
541 openai_codex::GPT_6_ASTRA,
542 openai_codex::GPT_6_SOL,
543 openai_codex::GPT_61_SOL,
544 openai_codex::GPT_6_LUNA,
545 retired(openai_codex::GPT_56_SOL),
546 retired(openai_codex::GPT_56_TERRA),
547 retired(openai_codex::GPT_56_LUNA),
548 retired(openai_model(
549 "gpt-5.5",
550 "GPT-5.5",
551 "Frontier model for complex coding, research, and real-world work.",
552 1_050_000,
553 945_000,
554 true,
555 STANDARD_REASONING,
556 )),
557 retired(openai_codex::GPT_54),
558 retired(openai_model(
559 "gpt-5.4-mini",
560 "GPT-5.4-Mini",
561 "Small, fast, and cost-efficient model for simpler coding tasks.",
562 400_000,
563 360_000,
564 true,
565 STANDARD_REASONING,
566 )),
567 retired(ModelCatalogEntry {
568 id: "gpt-5.3-codex-spark",
569 display_name: "GPT-5.3-Codex-Spark",
570 description: "Ultra-fast coding model optimized for low-latency Codex workflows.",
571 provider: PROVIDER_CODEX,
572 default_reasoning: REASONING_HIGH,
573 supported_reasoning: STANDARD_REASONING,
574 context_window: 128_000,
575 max_context_window: 128_000,
576 auto_compact_token_limit: 115_200,
577 supports_compaction: true,
578 supports_images: false,
579 supports_tools: true,
580 supports_structured: false,
581 edit_tool: Some("patch"),
582 hidden: false,
583 }),
584 ModelCatalogEntry {
585 id: "codex-auto-review",
586 display_name: "Codex Auto Review",
587 description: "Automatic approval review model for Codex.",
588 provider: PROVIDER_OPENAI,
589 default_reasoning: REASONING_MEDIUM,
590 supported_reasoning: STANDARD_REASONING,
591 context_window: 272_000,
592 max_context_window: 272_000,
593 auto_compact_token_limit: 244_800,
594 supports_compaction: false,
595 supports_images: false,
596 supports_tools: true,
597 supports_structured: false,
598 edit_tool: Some("patch"),
599 hidden: true,
600 },
601 anthropic::OPUS_55,
602 anthropic::SONNET_5,
603 anthropic::SONNET_55,
604 anthropic_model(
605 "claude-fable-5-1",
606 "Claude Fable 5.1",
607 "Anthropic's most capable widely released model; successor to Fable 5 for frontier reasoning and long-horizon agentic work.",
608 1_000_000,
609 900_000,
610 REASONING_HIGH,
611 OPUS_REASONING,
612 true,
613 ),
614 anthropic_model(
615 "claude-fable-5",
616 "Claude Fable 5",
617 "Anthropic's most powerful, most intelligent model; a new tier above Opus for frontier reasoning and agentic work.",
618 1_000_000,
619 900_000,
620 REASONING_HIGH,
621 OPUS_REASONING,
622 true,
623 ),
624 anthropic_model(
625 "claude-opus-4-8",
626 "Claude Opus 4.8",
627 "Anthropic's most capable Opus-tier model for complex reasoning, long-horizon agentic coding, and high-autonomy work.",
628 1_000_000,
629 900_000,
630 REASONING_HIGH,
631 OPUS_REASONING,
632 true,
633 ),
634 anthropic_model(
635 "claude-opus-4-7",
636 "Claude Opus 4.7",
637 "Most capable Claude model for complex reasoning and agentic coding.",
638 1_000_000,
639 900_000,
640 REASONING_HIGH,
641 OPUS_REASONING,
642 true,
643 ),
644 anthropic_model(
645 "claude-sonnet-4-6",
646 "Claude Sonnet 4.6",
647 "Balanced Claude model for coding, tool use, and everyday agent workflows.",
648 1_000_000,
649 900_000,
650 REASONING_MEDIUM,
651 SONNET_REASONING,
652 true,
653 ),
654 anthropic_model(
655 "claude-haiku-4-5-20251001",
656 "Claude Haiku 4.5",
657 "Fast Claude model for lower-latency tool workflows.",
658 200_000,
659 180_000,
660 REASONING_NONE,
661 &[],
662 false,
664 ),
665 anthropic::HAIKU_55,
666 anthropic::CLAUDE_CODE_OPUS_55,
667 anthropic::CLAUDE_CODE_SONNET_5,
668 anthropic::CLAUDE_CODE_SONNET_55,
669 claude_code_model(
670 "fable",
671 "Claude Code Fable",
672 "Claude Code harness Fable alias for the most powerful frontier model.",
673 1_000_000,
674 900_000,
675 REASONING_HIGH,
676 OPUS_REASONING,
677 ),
678 claude_code_model(
679 "sonnet",
680 "Claude Code Sonnet",
681 "Claude Code harness Sonnet alias for coding and tool workflows.",
682 1_000_000,
683 900_000,
684 REASONING_MEDIUM,
685 SONNET_REASONING,
686 ),
687 claude_code_model(
688 "opus",
689 "Claude Code Opus",
690 "Claude Code harness Opus alias for complex long-horizon agentic work.",
691 1_000_000,
692 900_000,
693 REASONING_HIGH,
694 OPUS_REASONING,
695 ),
696 claude_code_model(
697 "haiku",
698 "Claude Code Haiku",
699 "Claude Code harness Haiku alias for fast lower-latency coding turns.",
700 200_000,
701 180_000,
702 REASONING_NONE,
703 &[],
704 ),
705 anthropic::CLAUDE_CODE_HAIKU_55,
706 claude_code_model(
707 "claude-sonnet-4-6",
708 "Claude Code Sonnet 4.6",
709 "Claude Sonnet 4.6 through the local Claude Code harness.",
710 1_000_000,
711 900_000,
712 REASONING_MEDIUM,
713 SONNET_REASONING,
714 ),
715 claude_code_model(
716 "claude-opus-4-8",
717 "Claude Code Opus 4.8",
718 "Claude Opus 4.8 through the local Claude Code harness.",
719 1_000_000,
720 900_000,
721 REASONING_HIGH,
722 OPUS_REASONING,
723 ),
724 claude_code_model(
725 "claude-fable-5",
726 "Claude Code Fable 5",
727 "Claude Fable 5 through the local Claude Code harness.",
728 1_000_000,
729 900_000,
730 REASONING_HIGH,
731 OPUS_REASONING,
732 ),
733 claude_code_model(
734 "claude-fable-5-1",
735 "Claude Code Fable 5.1",
736 "Claude Fable 5.1 through the local Claude Code harness.",
737 1_000_000,
738 900_000,
739 REASONING_HIGH,
740 OPUS_REASONING,
741 ),
742 gemini_model(
743 PROVIDER_GEMINI,
744 "gemini-3.8-flash",
745 "Gemini 3.8 Flash",
746 "Google's most intelligent Flash model for long-horizon software engineering, autonomous agents, and complex workflows.",
747 REASONING_MEDIUM,
748 ),
749 gemini_model(
750 PROVIDER_GEMINI,
751 "gemini-3.5-flash",
752 "Gemini 3.5 Flash",
753 "Stable Gemini Flash model for agentic coding, tool use, and long-horizon workflows.",
754 REASONING_MEDIUM,
755 ),
756 gemini_model(
757 PROVIDER_GEMINI,
758 "gemini-3.7-flash",
759 "Gemini 3.7 Flash",
760 "Google's latest speed-tier Gemini model for high-throughput agentic coding, tool use, and long-context workflows.",
761 REASONING_HIGH,
762 ),
763 gemini_model(
764 PROVIDER_GEMINI,
765 "gemini-3.1-pro-preview",
766 "Gemini 3.1 Pro Preview",
767 "Gemini model for complex coding, long context, and tool-heavy agent workflows.",
768 REASONING_HIGH,
769 ),
770 gemini_model(
771 PROVIDER_GEMINI,
772 "gemini-3.1-pro-preview-customtools",
773 "Gemini 3.1 Pro Preview Custom Tools",
774 "Gemini preview variant exposed for custom tool validation and tool-heavy coding workflows.",
775 REASONING_HIGH,
776 ),
777 gemini_model(
778 PROVIDER_GEMINI,
779 "gemini-3-flash-preview",
780 "Gemini 3 Flash Preview",
781 "Fast Gemini model for everyday coding, tool use, and multimodal prompts.",
782 REASONING_MEDIUM,
783 ),
784 gemini_model(
785 PROVIDER_GEMINI,
786 "gemini-3.1-flash-lite-preview",
787 "Gemini 3.1 Flash-Lite Preview",
788 "Lightweight Gemini model for low-latency coding and agent interactions.",
789 REASONING_LOW,
790 ),
791 gemini_model(
792 PROVIDER_VERTEX,
793 "gemini-3.8-flash",
794 "Gemini 3.8 Flash",
795 "Google's most intelligent Flash model on Vertex AI for long-horizon software engineering and autonomous agents.",
796 REASONING_MEDIUM,
797 ),
798 gemini_model(
799 PROVIDER_VERTEX,
800 "gemini-3.5-flash",
801 "Gemini 3.5 Flash",
802 "Stable Gemini Flash model on Vertex AI for agentic coding, tool use, and long-horizon workflows.",
803 REASONING_MEDIUM,
804 ),
805 gemini_model(
806 PROVIDER_VERTEX,
807 "gemini-3.7-flash",
808 "Gemini 3.7 Flash",
809 "Google's latest speed-tier Gemini model on Vertex AI for high-throughput agentic coding, tool use, and long-context workflows.",
810 REASONING_HIGH,
811 ),
812 gemini_model(
813 PROVIDER_VERTEX,
814 "gemini-3.1-pro-preview",
815 "Gemini 3.1 Pro Preview",
816 "Gemini model on Vertex AI for complex coding, long context, and tool-heavy agent workflows.",
817 REASONING_HIGH,
818 ),
819 gemini_model(
820 PROVIDER_VERTEX,
821 "gemini-3-flash-preview",
822 "Gemini 3 Flash Preview",
823 "Fast Gemini model on Vertex AI for everyday coding, tool use, and multimodal prompts.",
824 REASONING_MEDIUM,
825 ),
826 gemini_model(
827 PROVIDER_VERTEX,
828 "gemini-3.1-flash-lite-preview",
829 "Gemini 3.1 Flash-Lite Preview",
830 "Lightweight Gemini model on Vertex AI for low-latency coding and agent interactions.",
831 REASONING_LOW,
832 ),
833 xai_model(
834 PROVIDER_XAI,
835 "grok-4.7",
836 "Grok 4.7",
837 "xAI's most capable model for coding, chat, long-running agents, and configurable reasoning.",
838 500_000,
839 REASONING_HIGH,
840 XAI_REASONING,
841 true,
842 false,
843 ),
844 xai_model(
845 PROVIDER_XAI,
846 "grok-4.6",
847 "Grok 4.6",
848 "xAI's flagship model for coding, long-running agents, knowledge work, and configurable reasoning.",
849 500_000,
850 REASONING_HIGH,
851 XAI_REASONING,
852 true,
853 false,
854 ),
855 xai_model(
856 PROVIDER_XAI,
857 "grok-4.3",
858 "Grok 4.3",
859 "xAI flagship model for chat, coding, tool use, and configurable reasoning.",
860 1_000_000,
861 REASONING_LOW,
862 XAI_CONFIGURABLE_REASONING,
863 true,
864 false,
865 ),
866 xai_model(
867 PROVIDER_XAI,
868 "grok-4.20-multi-agent-0309",
869 "Grok 4.20 Multi-Agent",
870 "xAI long-context model with agentic tool-calling and reasoning.",
871 2_000_000,
872 REASONING_LOW,
873 XAI_REASONING,
874 true,
875 false,
876 ),
877 xai_model(
878 PROVIDER_XAI,
879 "grok-4.20-0309-reasoning",
880 "Grok 4.20 Reasoning",
881 "xAI long-context reasoning model for complex tool-heavy workflows.",
882 2_000_000,
883 REASONING_LOW,
884 XAI_REASONING,
885 true,
886 false,
887 ),
888 xai_model(
889 PROVIDER_XAI,
890 "grok-4.20-0309-non-reasoning",
891 "Grok 4.20 Non-Reasoning",
892 "xAI long-context model for lower-latency non-reasoning workflows.",
893 2_000_000,
894 REASONING_NONE,
895 XAI_NO_REASONING,
896 true,
897 false,
898 ),
899 xai_model(
900 PROVIDER_SUPERGROK,
901 "grok-4.7",
902 "Grok 4.7",
903 "SuperGrok OAuth access to xAI's most capable coding and long-running agent model.",
904 500_000,
905 REASONING_HIGH,
906 XAI_REASONING,
907 true,
908 false,
909 ),
910 xai_model(
911 PROVIDER_SUPERGROK,
912 "grok-4.6",
913 "Grok 4.6",
914 "SuperGrok OAuth access to xAI's flagship coding and long-running agent model.",
915 500_000,
916 REASONING_HIGH,
917 XAI_REASONING,
918 true,
919 false,
920 ),
921 xai_model(
922 PROVIDER_SUPERGROK,
923 "grok-composer-2.5-fast",
924 "Grok Composer 2.5 Fast",
925 "SuperGrok OAuth access to xAI Composer 2.5 Fast for lower-latency agentic coding.",
926 200_000,
927 REASONING_NONE,
928 &[],
929 false,
930 false,
931 ),
932 xai_model(
933 PROVIDER_SUPERGROK,
934 "grok-4.3",
935 "Grok 4.3",
936 "SuperGrok OAuth access to xAI Grok 4.3.",
937 1_000_000,
938 REASONING_LOW,
939 XAI_CONFIGURABLE_REASONING,
940 true,
941 true,
942 ),
943 xai_model(
944 PROVIDER_SUPERGROK,
945 "grok-4.20-multi-agent-0309",
946 "Grok 4.20 Multi-Agent",
947 "SuperGrok OAuth access to xAI's long-context multi-agent model.",
948 2_000_000,
949 REASONING_LOW,
950 XAI_REASONING,
951 true,
952 true,
953 ),
954 xai_model(
955 PROVIDER_SUPERGROK,
956 "grok-4.20-0309-reasoning",
957 "Grok 4.20 Reasoning",
958 "SuperGrok OAuth access to xAI's long-context reasoning model.",
959 2_000_000,
960 REASONING_LOW,
961 XAI_REASONING,
962 true,
963 true,
964 ),
965 xai_model(
966 PROVIDER_SUPERGROK,
967 "grok-4.20-0309-non-reasoning",
968 "Grok 4.20 Non-Reasoning",
969 "SuperGrok OAuth access to xAI's long-context non-reasoning model.",
970 2_000_000,
971 REASONING_NONE,
972 XAI_NO_REASONING,
973 true,
974 true,
975 ),
976 opencode_model(
977 PROVIDER_OPENCODE,
978 "gpt-6-sol",
979 "GPT 6 Sol",
980 "OpenCode Zen GPT 6 Sol gateway model.",
981 1_050_000,
982 REASONING_MEDIUM,
983 STANDARD_REASONING,
984 ),
985 retired(opencode_model(
986 PROVIDER_OPENCODE,
987 "gpt-5.5",
988 "GPT 5.5",
989 "OpenCode Zen GPT 5.5 gateway model.",
990 1_050_000,
991 REASONING_MEDIUM,
992 STANDARD_REASONING,
993 )),
994 retired(opencode_model(
995 PROVIDER_OPENCODE,
996 "gpt-5.3-codex-spark",
997 "GPT 5.3 Codex Spark",
998 "OpenCode Zen low-latency Codex model.",
999 128_000,
1000 REASONING_HIGH,
1001 STANDARD_REASONING,
1002 )),
1003 opencode_model(
1004 PROVIDER_OPENCODE,
1005 "big-pickle",
1006 "Big Pickle",
1007 "OpenCode Zen free coding model.",
1008 256_000,
1009 REASONING_NONE,
1010 &[],
1011 ),
1012 opencode_model(
1013 PROVIDER_OPENCODE,
1014 "mimo-v2.5-free",
1015 "MiMo V2.5 Free",
1016 "OpenCode Zen free Xiaomi MiMo coding model.",
1017 256_000,
1018 REASONING_NONE,
1019 &[],
1020 ),
1021 opencode_model(
1022 PROVIDER_OPENCODE,
1023 "nemotron-3-ultra-free",
1024 "Nemotron 3 Ultra Free",
1025 "OpenCode Zen free Nemotron coding model.",
1026 128_000,
1027 REASONING_NONE,
1028 &[],
1029 ),
1030 opencode_model(
1031 PROVIDER_OPENCODE,
1032 "north-mini-code-free",
1033 "North Mini Code Free",
1034 "OpenCode Zen free North Mini coding model.",
1035 128_000,
1036 REASONING_NONE,
1037 &[],
1038 ),
1039 opencode_model(
1040 PROVIDER_OPENCODE,
1041 "deepseek-v4-flash",
1042 "DeepSeek V4 Flash",
1043 "OpenCode Zen DeepSeek coding model.",
1044 128_000,
1045 REASONING_HIGH,
1046 deepseek::DEEPSEEK_REASONING,
1047 ),
1048 opencode_model(
1049 PROVIDER_OPENCODE,
1050 "deepseek-v4-pro",
1051 "DeepSeek V4 Pro",
1052 "OpenCode Zen DeepSeek Pro coding model.",
1053 128_000,
1054 REASONING_HIGH,
1055 deepseek::DEEPSEEK_REASONING,
1056 ),
1057 opencode_model(
1058 PROVIDER_OPENCODE_GO,
1059 "kimi-k2.6",
1060 "Kimi K2.6",
1061 "OpenCode Go Kimi coding model.",
1062 256_000,
1063 REASONING_NONE,
1064 &[],
1065 ),
1066 opencode_model(
1067 PROVIDER_OPENCODE_GO,
1068 "qwen3.6-plus",
1069 "Qwen3.6 Plus",
1070 "OpenCode Go Qwen coding model.",
1071 256_000,
1072 REASONING_NONE,
1073 &[],
1074 ),
1075 opencode_model(
1076 PROVIDER_OPENCODE_GO,
1077 "glm-5.1",
1078 "GLM-5.1",
1079 "OpenCode Go GLM coding model.",
1080 256_000,
1081 REASONING_NONE,
1082 &[],
1083 ),
1084 opencode_model(
1085 PROVIDER_OPENCODE_GO,
1086 "deepseek-v4-flash",
1087 "DeepSeek V4 Flash",
1088 "OpenCode Go DeepSeek coding model.",
1089 128_000,
1090 REASONING_HIGH,
1091 deepseek::DEEPSEEK_REASONING,
1092 ),
1093 opencode_model(
1094 PROVIDER_OPENCODE_GO,
1095 "deepseek-v4-pro",
1096 "DeepSeek V4 Pro",
1097 "OpenCode Go DeepSeek Pro coding model.",
1098 128_000,
1099 REASONING_HIGH,
1100 deepseek::DEEPSEEK_REASONING,
1101 ),
1102 opencode_model(
1103 PROVIDER_KIMI_CODE,
1104 "kimi-for-coding",
1105 "K2.7 Code",
1106 "Kimi Code subscription coding model (OAuth via api.kimi.com/coding/v1).",
1107 262_144,
1108 REASONING_NONE,
1109 &[],
1110 ),
1111 openrouter::X_AI_GROK_4_6,
1112 openrouter::X_AI_GROK_4_7,
1113 openrouter::MOONSHOTAI_KIMI_K3,
1114 openrouter::MOONSHOTAI_KIMI_K2_7_CODE,
1115 openrouter::MOONSHOTAI_KIMI_K2_6,
1116 openrouter::ANTHROPIC_CLAUDE_OPUS_5_5,
1117 openrouter::ANTHROPIC_CLAUDE_SONNET_5_5,
1118 openrouter::ANTHROPIC_CLAUDE_HAIKU_5_5,
1119 openrouter::ANTHROPIC_CLAUDE_FABLE_5_1,
1120 openrouter::ANTHROPIC_CLAUDE_OPUS_5,
1121 openrouter::OPENAI_GPT_6_1_SOL,
1122 openrouter::OPENAI_GPT_6_SOL,
1123 openrouter::OPENAI_GPT_6_LUNA,
1124 openrouter::OPENAI_GPT_6_ASTRA,
1125 openrouter::GOOGLE_GEMINI_3_8_FLASH,
1126 openrouter::GOOGLE_GEMINI_3_7_FLASH,
1127 openrouter::DEEPSEEK_DEEPSEEK_V4_PRO_0813,
1128 openrouter::DEEPSEEK_DEEPSEEK_V4_1_FLASH,
1129 openrouter::QWEN_QWEN3_8_MAX_0902,
1130 openrouter::QWEN_QWEN3_8_FLASH,
1131 openrouter::Z_AI_GLM_5_3,
1132 openrouter::Z_AI_GLM_5_3_FLASH,
1133 openrouter::XIAOMI_MIMO_V2_6_PRO,
1134 openrouter::MISTRALAI_MISTRAL_LARGE_4_0,
1135 openrouter::META_MUSE_SPARK_1_3,
1136 ModelCatalogEntry {
1137 id: "accounts/fireworks/models/qwen3-235b-a22b",
1138 display_name: "Qwen3 235B A22B",
1139 description: "Fireworks Responses-capable serverless model with client-executed function tool support.",
1140 provider: PROVIDER_FIREWORKS,
1141 default_reasoning: REASONING_NONE,
1142 supported_reasoning: &[],
1143 context_window: 131_072,
1144 max_context_window: 131_072,
1145 auto_compact_token_limit: 0,
1146 supports_compaction: false,
1147 supports_images: false,
1148 supports_tools: true,
1149 supports_structured: true,
1150 edit_tool: Some(EDIT_TOOL_PATCH),
1151 hidden: false,
1152 },
1153 roder_cloud_model(
1154 "roder.cloud/free",
1155 "Roder Free",
1156 "Free hosted model on roder.cloud.",
1157 32_768,
1158 ),
1159 retired(roder_cloud_model(
1160 "roder.cloud/openai/gpt-5.5",
1161 "GPT-5.5 (Roder Cloud)",
1162 "roder.cloud hosted route for OpenAI GPT-5.5.",
1163 400_000,
1164 )),
1165 roder_cloud_model(
1166 "roder.cloud/anthropic/claude-opus-4-7",
1167 "Claude Opus 4.7 (Roder Cloud)",
1168 "roder.cloud hosted route for Anthropic Claude Opus 4.7.",
1169 200_000,
1170 ),
1171 roder_cloud_model(
1172 "roder.cloud/google/gemini-3.1-pro-preview",
1173 "Gemini 3.1 Pro (Roder Cloud)",
1174 "roder.cloud hosted route for Google Gemini 3.1 Pro Preview.",
1175 200_000,
1176 ),
1177 poolside_model(
1178 "poolside/laguna-m.1",
1179 "Laguna M.1",
1180 "Poolside flagship agentic coding model.",
1181 REASONING_MEDIUM,
1182 ),
1183 poolside_model(
1184 "poolside/laguna-xs.2",
1185 "Laguna XS.2",
1186 "Poolside lightweight agentic coding model.",
1187 REASONING_MEDIUM,
1188 ),
1189 xiaomi_mimo::PAYG_V25_PRO,
1190 xiaomi_mimo::PAYG_V2_PRO,
1191 xiaomi_mimo::PAYG_V25,
1192 xiaomi_mimo::PAYG_V2_OMNI,
1193 xiaomi_mimo::PAYG_V2_FLASH,
1194 xiaomi_mimo::TOKEN_PLAN_V25_PRO,
1195 xiaomi_mimo::TOKEN_PLAN_V2_PRO,
1196 xiaomi_mimo::TOKEN_PLAN_V25,
1197 xiaomi_mimo::TOKEN_PLAN_V2_OMNI,
1198 xiaomi_mimo::TOKEN_PLAN_V2_FLASH,
1199 synthetic::SYN_LARGE_TEXT,
1200 synthetic::SYN_SMALL_TEXT,
1201 synthetic::SYN_LARGE_VISION,
1202 synthetic::SYN_SMALL_VISION,
1203 synthetic::HF_MINIMAX_M3,
1204 synthetic::HF_QWEN3_6_27B,
1205 synthetic::HF_KIMI_K2_6,
1206 synthetic::HF_NEMOTRON_3_SUPER,
1207 synthetic::HF_GLM_4_7,
1208 synthetic::HF_GLM_4_7_FLASH,
1209 synthetic::HF_GLM_5_1,
1210 synthetic::HF_GLM_5_2,
1211 synthetic::HF_GPT_OSS_120B,
1212 synthetic::HF_QWEN3_5_397B_A17B,
1213 deepseek::DEEPSEEK_CHAT,
1214 deepseek::DEEPSEEK_REASONER,
1215 deepseek::DEEPSEEK_V4_FLASH,
1216 deepseek::DEEPSEEK_V4_PRO,
1217 ModelCatalogEntry {
1218 id: "composer-2.5",
1219 display_name: "Composer 2.5",
1220 description: "Cursor Composer model exposed through direct AgentService inference.",
1221 provider: PROVIDER_CURSOR,
1222 default_reasoning: REASONING_NONE,
1223 supported_reasoning: &[],
1224 context_window: 200_000,
1225 max_context_window: 200_000,
1226 auto_compact_token_limit: 180_000,
1227 supports_compaction: true,
1228 supports_images: false,
1229 supports_tools: false,
1230 supports_structured: false,
1231 edit_tool: None,
1232 hidden: false,
1233 },
1234 cursor_model(
1235 "composer-2.5-fast",
1236 "Composer 2.5 Fast",
1237 "Cursor Composer 2.5 fast variant for lower-latency agent turns.",
1238 200_000,
1239 180_000,
1240 REASONING_NONE,
1241 &[],
1242 ),
1243 cursor_model(
1244 "claude-fable-5",
1245 "Claude Fable 5",
1246 "Anthropic Claude Fable 5, Anthropic's most powerful frontier model, routed through Cursor's AgentService.",
1247 1_000_000,
1248 900_000,
1249 REASONING_HIGH,
1250 OPUS_REASONING,
1251 ),
1252 cursor_model(
1253 "claude-opus-4-8",
1254 "Claude Opus 4.8",
1255 "Anthropic Claude Opus 4.8 routed through Cursor's AgentService.",
1256 1_000_000,
1257 900_000,
1258 REASONING_HIGH,
1259 OPUS_REASONING,
1260 ),
1261 cursor_model(
1262 "claude-sonnet-4-6",
1263 "Claude Sonnet 4.6",
1264 "Anthropic Claude Sonnet 4.6 routed through Cursor's AgentService.",
1265 1_000_000,
1266 900_000,
1267 REASONING_MEDIUM,
1268 SONNET_REASONING,
1269 ),
1270 retired(cursor_model(
1271 "gpt-5.5",
1272 "GPT-5.5",
1273 "OpenAI GPT-5.5 routed through Cursor's AgentService.",
1274 1_050_000,
1275 945_000,
1276 REASONING_MEDIUM,
1277 STANDARD_REASONING,
1278 )),
1279 retired(cursor_model(
1280 "gpt-5.5-fast",
1281 "GPT-5.5 Fast",
1282 "OpenAI GPT-5.5 fast variant routed through Cursor's AgentService.",
1283 1_050_000,
1284 945_000,
1285 REASONING_MEDIUM,
1286 STANDARD_REASONING,
1287 )),
1288 cursor_model(
1289 "gemini-3.1-pro-preview",
1290 "Gemini 3.1 Pro",
1291 "Google Gemini 3.1 Pro routed through Cursor's AgentService.",
1292 1_048_576,
1293 943_718,
1294 REASONING_MEDIUM,
1295 GEMINI_REASONING,
1296 ),
1297 cursor_model(
1298 "grok-4.6",
1299 "Grok 4.6",
1300 "xAI Grok 4.6 routed through Cursor's AgentService for long-running coding and knowledge-work agents.",
1301 256_000,
1302 230_400,
1303 REASONING_HIGH,
1304 STANDARD_REASONING,
1305 ),
1306 cursor_model(
1307 "gemini-3.7-flash",
1308 "Gemini 3.7 Flash",
1309 "Google Gemini 3.7 Flash routed through Cursor's AgentService for high-throughput agentic coding.",
1310 1_000_000,
1311 900_000,
1312 REASONING_HIGH,
1313 GEMINI_REASONING,
1314 ),
1315 cursor_model(
1316 "grok-4.3",
1317 "Grok 4.3",
1318 "xAI Grok 4.3 routed through Cursor's AgentService.",
1319 1_000_000,
1320 900_000,
1321 REASONING_MEDIUM,
1322 STANDARD_REASONING,
1323 ),
1324 ModelCatalogEntry {
1325 id: "text-embedding-3-large",
1326 display_name: "Text Embedding 3 Large",
1327 description: "OpenAI embedding model for local semantic memories.",
1328 provider: PROVIDER_OPENAI,
1329 default_reasoning: REASONING_NONE,
1330 supported_reasoning: &[],
1331 context_window: 0,
1332 max_context_window: 0,
1333 auto_compact_token_limit: 0,
1334 supports_compaction: false,
1335 supports_images: false,
1336 supports_tools: true,
1337 supports_structured: false,
1338 edit_tool: None,
1339 hidden: true,
1340 },
1341 ModelCatalogEntry {
1342 id: "gemini-embedding-2",
1343 display_name: "Gemini Embedding 2",
1344 description: "Google Gemini embedding model for local semantic memories.",
1345 provider: PROVIDER_GOOGLE,
1346 default_reasoning: REASONING_NONE,
1347 supported_reasoning: &[],
1348 context_window: 0,
1349 max_context_window: 0,
1350 auto_compact_token_limit: 0,
1351 supports_compaction: false,
1352 supports_images: false,
1353 supports_tools: false,
1354 supports_structured: false,
1355 edit_tool: None,
1356 hidden: true,
1357 },
1358 ModelCatalogEntry {
1359 id: "zembed-1",
1360 display_name: "ZeroEntropy zembed-1",
1361 description: "ZeroEntropy embedding model for local semantic memories.",
1362 provider: PROVIDER_ZEROENTROPY,
1363 default_reasoning: REASONING_NONE,
1364 supported_reasoning: &[],
1365 context_window: 0,
1366 max_context_window: 0,
1367 auto_compact_token_limit: 0,
1368 supports_compaction: false,
1369 supports_images: false,
1370 supports_tools: false,
1371 supports_structured: false,
1372 edit_tool: None,
1373 hidden: true,
1374 },
1375 ModelCatalogEntry {
1376 id: "mock",
1377 display_name: "Mock",
1378 description: "Local deterministic mock provider for tests and offline development.",
1379 provider: PROVIDER_MOCK,
1380 default_reasoning: REASONING_NONE,
1381 supported_reasoning: MOCK_REASONING,
1382 context_window: 128_000,
1383 max_context_window: 128_000,
1384 auto_compact_token_limit: 115_200,
1385 supports_compaction: false,
1386 supports_images: false,
1387 supports_tools: true,
1388 supports_structured: false,
1389 edit_tool: None,
1390 hidden: true,
1391 },
1392];
1393
1394const fn openai_model(
1395 id: &'static str,
1396 display_name: &'static str,
1397 description: &'static str,
1398 context_window: u32,
1399 auto_compact_token_limit: u32,
1400 supports_compaction: bool,
1401 supported_reasoning: &'static [ReasoningOption],
1402) -> ModelCatalogEntry {
1403 ModelCatalogEntry {
1404 id,
1405 display_name,
1406 description,
1407 provider: PROVIDER_OPENAI,
1408 default_reasoning: REASONING_MEDIUM,
1409 supported_reasoning,
1410 context_window,
1411 max_context_window: context_window,
1412 auto_compact_token_limit,
1413 supports_compaction,
1414 supports_images: false,
1415 supports_tools: true,
1416 supports_structured: false,
1417 edit_tool: Some("patch"),
1418 hidden: false,
1419 }
1420}
1421
1422#[allow(clippy::too_many_arguments)]
1423const fn anthropic_model(
1424 id: &'static str,
1425 display_name: &'static str,
1426 description: &'static str,
1427 context_window: u32,
1428 auto_compact_token_limit: u32,
1429 default_reasoning: &'static str,
1430 supported_reasoning: &'static [ReasoningOption],
1431 supports_compaction: bool,
1442) -> ModelCatalogEntry {
1443 ModelCatalogEntry {
1444 id,
1445 display_name,
1446 description,
1447 provider: PROVIDER_ANTHROPIC,
1448 default_reasoning,
1449 supported_reasoning,
1450 context_window,
1451 max_context_window: context_window,
1452 auto_compact_token_limit,
1453 supports_compaction,
1454 supports_images: false,
1455 supports_tools: true,
1456 supports_structured: false,
1457 edit_tool: Some("edit"),
1458 hidden: false,
1459 }
1460}
1461
1462const fn claude_code_model(
1463 id: &'static str,
1464 display_name: &'static str,
1465 description: &'static str,
1466 context_window: u32,
1467 auto_compact_token_limit: u32,
1468 default_reasoning: &'static str,
1469 supported_reasoning: &'static [ReasoningOption],
1470) -> ModelCatalogEntry {
1471 ModelCatalogEntry {
1472 id,
1473 display_name,
1474 description,
1475 provider: PROVIDER_CLAUDE_CODE,
1476 default_reasoning,
1477 supported_reasoning,
1478 context_window,
1479 max_context_window: context_window,
1480 auto_compact_token_limit,
1481 supports_compaction: false,
1487 supports_images: true,
1488 supports_tools: true,
1489 supports_structured: false,
1490 edit_tool: Some(EDIT_TOOL_EDIT),
1491 hidden: false,
1492 }
1493}
1494
1495const fn gemini_model(
1496 provider: &'static str,
1497 id: &'static str,
1498 display_name: &'static str,
1499 description: &'static str,
1500 default_reasoning: &'static str,
1501) -> ModelCatalogEntry {
1502 ModelCatalogEntry {
1503 id,
1504 display_name,
1505 description,
1506 provider,
1507 default_reasoning,
1508 supported_reasoning: GEMINI_REASONING,
1509 context_window: 1_048_576,
1510 max_context_window: 1_048_576,
1511 auto_compact_token_limit: 943_718,
1512 supports_compaction: false,
1513 supports_images: true,
1514 supports_tools: true,
1515 supports_structured: true,
1516 edit_tool: Some("edit"),
1517 hidden: false,
1518 }
1519}
1520
1521const fn xai_model(
1522 provider: &'static str,
1523 id: &'static str,
1524 display_name: &'static str,
1525 description: &'static str,
1526 context_window: u32,
1527 default_reasoning: &'static str,
1528 supported_reasoning: &'static [ReasoningOption],
1529 supports_images: bool,
1530 hidden: bool,
1531) -> ModelCatalogEntry {
1532 ModelCatalogEntry {
1533 id,
1534 display_name,
1535 description,
1536 provider,
1537 default_reasoning,
1538 supported_reasoning,
1539 context_window,
1540 max_context_window: context_window,
1541 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1542 supports_compaction: false,
1543 supports_images,
1544 supports_tools: true,
1545 supports_structured: true,
1546 edit_tool: Some("edit"),
1547 hidden,
1548 }
1549}
1550
1551const fn opencode_model(
1552 provider: &'static str,
1553 id: &'static str,
1554 display_name: &'static str,
1555 description: &'static str,
1556 context_window: u32,
1557 default_reasoning: &'static str,
1558 supported_reasoning: &'static [ReasoningOption],
1559) -> ModelCatalogEntry {
1560 ModelCatalogEntry {
1561 id,
1562 display_name,
1563 description,
1564 provider,
1565 default_reasoning,
1566 supported_reasoning,
1567 context_window,
1568 max_context_window: context_window,
1569 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1570 supports_compaction: false,
1571 supports_images: false,
1572 supports_tools: true,
1573 supports_structured: true,
1574 edit_tool: Some("edit"),
1575 hidden: false,
1576 }
1577}
1578
1579const fn roder_cloud_model(
1580 id: &'static str,
1581 display_name: &'static str,
1582 description: &'static str,
1583 context_window: u32,
1584) -> ModelCatalogEntry {
1585 ModelCatalogEntry {
1586 id,
1587 display_name,
1588 description,
1589 provider: PROVIDER_RODER_CLOUD,
1590 default_reasoning: REASONING_NONE,
1591 supported_reasoning: RODER_CLOUD_REASONING,
1592 context_window,
1593 max_context_window: context_window,
1594 auto_compact_token_limit: 0,
1595 supports_compaction: false,
1596 supports_images: false,
1597 supports_tools: false,
1598 supports_structured: false,
1599 edit_tool: None,
1600 hidden: false,
1601 }
1602}
1603
1604const fn poolside_model(
1605 id: &'static str,
1606 display_name: &'static str,
1607 description: &'static str,
1608 default_reasoning: &'static str,
1609) -> ModelCatalogEntry {
1610 ModelCatalogEntry {
1611 id,
1612 display_name,
1613 description,
1614 provider: PROVIDER_POOLSIDE,
1615 default_reasoning,
1616 supported_reasoning: POOLSIDE_REASONING,
1617 context_window: 131_072,
1618 max_context_window: 131_072,
1619 auto_compact_token_limit: 117_964,
1620 supports_compaction: false,
1621 supports_images: false,
1622 supports_tools: true,
1623 supports_structured: true,
1624 edit_tool: Some("edit"),
1625 hidden: false,
1626 }
1627}
1628
1629const fn cursor_model(
1630 id: &'static str,
1631 display_name: &'static str,
1632 description: &'static str,
1633 context_window: u32,
1634 auto_compact_token_limit: u32,
1635 default_reasoning: &'static str,
1636 supported_reasoning: &'static [ReasoningOption],
1637) -> ModelCatalogEntry {
1638 ModelCatalogEntry {
1639 id,
1640 display_name,
1641 description,
1642 provider: PROVIDER_CURSOR,
1643 default_reasoning,
1644 supported_reasoning,
1645 context_window,
1646 max_context_window: context_window,
1647 auto_compact_token_limit,
1648 supports_compaction: true,
1649 supports_images: true,
1653 supports_tools: false,
1654 supports_structured: false,
1655 edit_tool: None,
1656 hidden: false,
1657 }
1658}
1659
1660pub fn built_in_providers() -> &'static [ProviderCatalogEntry] {
1661 BUILT_IN_PROVIDERS
1662}
1663
1664pub fn built_in_models(include_hidden: bool) -> Vec<&'static ModelCatalogEntry> {
1665 BUILT_IN_MODELS
1666 .iter()
1667 .filter(|model| include_hidden || !model.hidden)
1668 .collect()
1669}
1670
1671pub fn models_for_provider(provider: &str, include_hidden: bool) -> Vec<ModelDescriptor> {
1672 built_in_models(include_hidden)
1673 .into_iter()
1674 .filter(|model| model.provider == provider)
1675 .map(ModelDescriptor::from)
1676 .collect()
1677}
1678
1679pub fn models_for_codex(include_hidden: bool) -> Vec<ModelDescriptor> {
1680 built_in_models(include_hidden)
1681 .into_iter()
1682 .filter(|model| model.provider == PROVIDER_OPENAI || model.provider == PROVIDER_CODEX)
1683 .map(ModelDescriptor::from)
1684 .collect()
1685}
1686
1687pub fn lookup_model(id: &str) -> Option<&'static ModelCatalogEntry> {
1688 BUILT_IN_MODELS.iter().find(|model| model.id == id)
1689}
1690
1691pub fn lookup_model_for_provider(provider: &str, id: &str) -> Option<&'static ModelCatalogEntry> {
1701 BUILT_IN_MODELS
1702 .iter()
1703 .find(|model| model.provider == provider && model.id == id)
1704 .or_else(|| lookup_model(id))
1705}
1706
1707pub fn built_in_model_profile(id: &str) -> Option<ModelHarnessProfile> {
1708 lookup_model(id).map(model_harness_profile_from_catalog)
1709}
1710
1711pub fn built_in_model_profile_for_provider(
1717 provider: &str,
1718 id: &str,
1719) -> Option<ModelHarnessProfile> {
1720 lookup_model_for_provider(provider, id).map(model_harness_profile_from_catalog)
1721}
1722
1723pub fn built_in_model_profiles() -> Vec<ModelHarnessProfile> {
1724 built_in_models(true)
1725 .into_iter()
1726 .map(model_harness_profile_from_catalog)
1727 .collect()
1728}
1729
1730fn model_harness_profile_from_catalog(model: &ModelCatalogEntry) -> ModelHarnessProfile {
1731 let provider_family = provider_family_for_provider(model.provider);
1732 ModelHarnessProfile {
1733 model: model.id.to_string(),
1734 provider: model.provider.to_string(),
1735 provider_family,
1736 edit_tool: model.edit_tool.map(str::to_string),
1737 schema_policy: schema_policy_for_family(provider_family),
1738 instruction_overlay: instruction_overlay_for_family(provider_family),
1739 reasoning: ModelProfileReasoning {
1740 orientation: Some(model.default_reasoning.to_string()),
1741 execution: Some(default_execution_reasoning(model)),
1742 verification: Some(model.default_reasoning.to_string()),
1743 recovery: Some(model.default_reasoning.to_string()),
1744 },
1745 parallel_tool_calls: Some(
1746 model.supports_tools
1747 && matches!(
1748 provider_family,
1749 ProviderFamily::OpenAi | ProviderFamily::Xai | ProviderFamily::Opencode
1750 ),
1751 ),
1752 auto_compact_token_limit: (model.auto_compact_token_limit > 0)
1753 .then_some(model.auto_compact_token_limit),
1754 }
1755}
1756
1757pub fn provider_family_for_provider(provider: &str) -> ProviderFamily {
1758 match provider {
1759 PROVIDER_OPENAI | PROVIDER_CODEX => ProviderFamily::OpenAi,
1760 PROVIDER_ANTHROPIC | PROVIDER_CLAUDE_CODE => ProviderFamily::Anthropic,
1761 PROVIDER_GEMINI | PROVIDER_VERTEX => ProviderFamily::Gemini,
1762 PROVIDER_XAI | PROVIDER_SUPERGROK => ProviderFamily::Xai,
1763 PROVIDER_OPENCODE | PROVIDER_OPENCODE_GO => ProviderFamily::Opencode,
1764 PROVIDER_OPENROUTER | PROVIDER_FIREWORKS | PROVIDER_RODER_CLOUD => ProviderFamily::OpenAi,
1765 PROVIDER_POOLSIDE => ProviderFamily::Poolside,
1766 PROVIDER_CURSOR => ProviderFamily::Cursor,
1767 PROVIDER_XIAOMI_MIMO | PROVIDER_XIAOMI_MIMO_TOKEN_PLAN => ProviderFamily::OpenAi,
1768 PROVIDER_KIMI_CODE => ProviderFamily::OpenAi,
1769 PROVIDER_SYNTHETIC => ProviderFamily::OpenAi,
1770 PROVIDER_DEEPSEEK => ProviderFamily::OpenAi,
1771 _ => ProviderFamily::Mock,
1772 }
1773}
1774
1775fn schema_policy_for_family(family: ProviderFamily) -> ModelSchemaPolicy {
1776 match family {
1777 ProviderFamily::OpenAi => ModelSchemaPolicy::RequiredFirstFlat,
1778 _ => ModelSchemaPolicy::StandardRequiredFirst,
1779 }
1780}
1781
1782fn instruction_overlay_for_family(family: ProviderFamily) -> ModelInstructionOverlay {
1783 match family {
1784 ProviderFamily::OpenAi => ModelInstructionOverlay::LiteralToolOutputs,
1785 ProviderFamily::Anthropic | ProviderFamily::Gemini => {
1786 ModelInstructionOverlay::IntuitiveContext
1787 }
1788 _ => ModelInstructionOverlay::Standard,
1789 }
1790}
1791
1792fn default_execution_reasoning(model: &ModelCatalogEntry) -> String {
1793 if model
1794 .supported_reasoning
1795 .iter()
1796 .any(|option| option.effort == REASONING_LOW)
1797 {
1798 REASONING_LOW.to_string()
1799 } else {
1800 model.default_reasoning.to_string()
1801 }
1802}
1803
1804pub fn model_supports_reasoning_effort(model: &str, effort: &str) -> bool {
1805 lookup_model(model)
1806 .map(|entry| {
1807 entry
1808 .supported_reasoning
1809 .iter()
1810 .any(|option| option.effort == effort)
1811 })
1812 .unwrap_or(false)
1813}
1814
1815pub fn normalize_provider_id(provider: &str) -> String {
1816 match provider.trim().to_ascii_lowercase().as_str() {
1817 "grok" | "x-ai" | "x.ai" => PROVIDER_XAI.to_string(),
1818 "grok-oauth" | "xai-oauth" | "x-ai-oauth" | "xai-grok-oauth" => {
1819 PROVIDER_SUPERGROK.to_string()
1820 }
1821 "opencode" => PROVIDER_OPENCODE.to_string(),
1822 "go" | "opencode_go" | "opencode-go" => PROVIDER_OPENCODE_GO.to_string(),
1823 "openrouter" => PROVIDER_OPENROUTER.to_string(),
1824 "fireworks" | "fireworks-ai" | "fireworks_ai" => PROVIDER_FIREWORKS.to_string(),
1825 "roder-cloud" | "roder_cloud" | "rodercloud" | "roder.cloud" => {
1826 PROVIDER_RODER_CLOUD.to_string()
1827 }
1828 "laguna" | "poolside" => PROVIDER_POOLSIDE.to_string(),
1829 "composer" | "cursor-composer" => PROVIDER_CURSOR.to_string(),
1830 "claude_code" | "claudecode" => PROVIDER_CLAUDE_CODE.to_string(),
1831 "kimi" | "kimi-code" | "kimi_code" | "moonshot" => PROVIDER_KIMI_CODE.to_string(),
1832 "synthetic" | "synthetic-ai" | "synthetic_ai" | "synthetic.new" => {
1833 PROVIDER_SYNTHETIC.to_string()
1834 }
1835 "deepseek" | "deepseek-platform" | "deepseek_platform" => PROVIDER_DEEPSEEK.to_string(),
1836 provider => provider.to_string(),
1837 }
1838}
1839
1840impl From<&ModelCatalogEntry> for ModelDescriptor {
1841 fn from(model: &ModelCatalogEntry) -> Self {
1842 let supported_reasoning = model
1843 .supported_reasoning
1844 .iter()
1845 .map(|option| ReasoningEffortDescriptor {
1846 effort: option.effort.to_string(),
1847 description: option.description.to_string(),
1848 })
1849 .collect::<Vec<_>>();
1850 Self {
1851 id: model.id.to_string(),
1852 name: model.display_name.to_string(),
1853 context_window: (model.context_window > 0).then_some(model.context_window),
1854 default_reasoning: (!supported_reasoning.is_empty())
1855 .then(|| model.default_reasoning.to_string()),
1856 supported_reasoning,
1857 }
1858 }
1859}
1860
1861#[cfg(test)]
1862mod tests {
1863 use super::*;
1864
1865 #[test]
1866 fn openrouter_catalog_entries_are_well_formed() {
1867 let entries = BUILT_IN_MODELS
1868 .iter()
1869 .filter(|model| model.provider == PROVIDER_OPENROUTER)
1870 .collect::<Vec<_>>();
1871 assert!(!entries.is_empty());
1872 for model in entries {
1873 assert!(
1874 model.id.contains('/'),
1875 "{} is not an OpenRouter slug",
1876 model.id
1877 );
1878 assert!(
1879 model.context_window > 0,
1880 "{} has no context window",
1881 model.id
1882 );
1883 assert_eq!(
1884 model.max_context_window, model.context_window,
1885 "{}",
1886 model.id
1887 );
1888 assert!(
1889 model.auto_compact_token_limit > 0
1890 && model.auto_compact_token_limit < model.context_window,
1891 "{} compaction threshold must sit inside its window",
1892 model.id
1893 );
1894 assert!(
1897 !model.supports_compaction,
1898 "{} must compact client-side",
1899 model.id
1900 );
1901 assert!(model.supports_tools, "{} must support tools", model.id);
1902 }
1903 }
1904
1905 #[test]
1906 fn openrouter_catalog_resolves_kimi_k3_with_live_limits() {
1907 let k3 = lookup_model_for_provider(PROVIDER_OPENROUTER, "moonshotai/kimi-k3")
1908 .expect("kimi k3 is catalogued");
1909 assert_eq!(k3.context_window, 1_048_576);
1910 assert!(k3.supports_images);
1911 assert!(!k3.supported_reasoning.is_empty());
1912 assert_eq!(k3.auto_compact_token_limit, 943_718);
1913 }
1914
1915 #[test]
1916 fn catalog_contains_gode_providers() {
1917 let ids = BUILT_IN_PROVIDERS
1918 .iter()
1919 .map(|provider| provider.id)
1920 .collect::<Vec<_>>();
1921 assert_eq!(
1922 ids,
1923 vec![
1924 "mock",
1925 "openai",
1926 "codex",
1927 "anthropic",
1928 "claude-code",
1929 "gemini",
1930 "vertex",
1931 "xai",
1932 "supergrok",
1933 "opencode",
1934 "opencode-go",
1935 "openrouter",
1936 "fireworks",
1937 "roder-cloud",
1938 "poolside",
1939 "cursor",
1940 "xiaomi-mimo",
1941 "xiaomi-mimo-token-plan",
1942 "synthetic",
1943 "deepseek",
1944 "kimi-code"
1945 ]
1946 );
1947 }
1948
1949 #[test]
1950 fn gemini_provider_defaults_to_stable_35_flash() {
1951 let provider = BUILT_IN_PROVIDERS
1952 .iter()
1953 .find(|provider| provider.id == PROVIDER_GEMINI)
1954 .unwrap();
1955
1956 assert_eq!(provider.default_model, "gemini-3.5-flash");
1957
1958 let model = lookup_model("gemini-3.5-flash").unwrap();
1959 assert_eq!(model.display_name, "Gemini 3.5 Flash");
1960 assert_eq!(model.provider, PROVIDER_GEMINI);
1961 assert_eq!(model.context_window, 1_048_576);
1962 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
1963 assert!(model.supports_tools);
1964 assert!(model.supports_structured);
1965 assert_eq!(
1966 model
1967 .supported_reasoning
1968 .iter()
1969 .map(|option| option.effort)
1970 .collect::<Vec<_>>(),
1971 vec![
1972 REASONING_MINIMAL,
1973 REASONING_LOW,
1974 REASONING_MEDIUM,
1975 REASONING_HIGH
1976 ]
1977 );
1978 }
1979
1980 #[test]
1981 fn vertex_provider_mirrors_gemini_models_under_vertex_id() {
1982 let provider = BUILT_IN_PROVIDERS
1983 .iter()
1984 .find(|provider| provider.id == PROVIDER_VERTEX)
1985 .unwrap();
1986
1987 assert_eq!(provider.default_model, "gemini-3.5-flash");
1988 assert_eq!(provider.env_key, Some("GOOGLE_APPLICATION_CREDENTIALS"));
1989 assert_eq!(provider.env_aliases, &["VERTEX_CREDENTIALS_JSON"]);
1990
1991 let model = lookup_model_for_provider(PROVIDER_VERTEX, "gemini-3.5-flash").unwrap();
1992 assert_eq!(model.provider, PROVIDER_VERTEX);
1993 assert_eq!(model.context_window, 1_048_576);
1994 assert!(model.supports_tools);
1995 assert_eq!(
1996 provider_family_for_provider(PROVIDER_VERTEX),
1997 ProviderFamily::Gemini
1998 );
1999 }
2000
2001 #[test]
2002 fn gemini_38_flash_is_offered_on_both_google_providers() {
2003 for provider in [PROVIDER_GEMINI, PROVIDER_VERTEX] {
2004 let model = lookup_model_for_provider(provider, "gemini-3.8-flash").unwrap();
2005 assert_eq!(model.provider, provider);
2006 assert_eq!(model.display_name, "Gemini 3.8 Flash");
2007 assert_eq!(model.context_window, 1_048_576);
2008 assert_eq!(model.auto_compact_token_limit, 943_718);
2009 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
2010 assert!(model.supports_tools);
2011 assert!(model.supports_structured);
2012 assert!(model.supports_images);
2013 assert!(!model.hidden);
2014 }
2015 }
2016
2017 #[test]
2018 fn catalog_contains_gode_visible_models() {
2019 let ids = built_in_models(false)
2020 .into_iter()
2021 .map(|model| model.id)
2022 .collect::<Vec<_>>();
2023 assert_eq!(
2024 ids,
2025 vec![
2026 "gpt-6-astra",
2027 "gpt-6-sol",
2028 "gpt-6.1-sol",
2029 "gpt-6-luna",
2030 "claude-opus-5-5",
2031 "claude-sonnet-5",
2032 "claude-sonnet-5-5",
2033 "claude-fable-5-1",
2034 "claude-fable-5",
2035 "claude-opus-4-8",
2036 "claude-opus-4-7",
2037 "claude-sonnet-4-6",
2038 "claude-haiku-4-5-20251001",
2039 "claude-haiku-5-5",
2040 "claude-opus-5-5",
2041 "claude-sonnet-5",
2042 "claude-sonnet-5-5",
2043 "fable",
2044 "sonnet",
2045 "opus",
2046 "haiku",
2047 "claude-haiku-5-5",
2048 "claude-sonnet-4-6",
2049 "claude-opus-4-8",
2050 "claude-fable-5",
2051 "claude-fable-5-1",
2052 "gemini-3.8-flash",
2053 "gemini-3.5-flash",
2054 "gemini-3.7-flash",
2055 "gemini-3.1-pro-preview",
2056 "gemini-3.1-pro-preview-customtools",
2057 "gemini-3-flash-preview",
2058 "gemini-3.1-flash-lite-preview",
2059 "gemini-3.8-flash",
2060 "gemini-3.5-flash",
2061 "gemini-3.7-flash",
2062 "gemini-3.1-pro-preview",
2063 "gemini-3-flash-preview",
2064 "gemini-3.1-flash-lite-preview",
2065 "grok-4.7",
2066 "grok-4.6",
2067 "grok-4.3",
2068 "grok-4.20-multi-agent-0309",
2069 "grok-4.20-0309-reasoning",
2070 "grok-4.20-0309-non-reasoning",
2071 "grok-4.7",
2072 "grok-4.6",
2073 "grok-composer-2.5-fast",
2074 "gpt-6-sol",
2075 "big-pickle",
2076 "mimo-v2.5-free",
2077 "nemotron-3-ultra-free",
2078 "north-mini-code-free",
2079 "deepseek-v4-flash",
2080 "deepseek-v4-pro",
2081 "kimi-k2.6",
2082 "qwen3.6-plus",
2083 "glm-5.1",
2084 "deepseek-v4-flash",
2085 "deepseek-v4-pro",
2086 "kimi-for-coding",
2087 "x-ai/grok-4.6",
2088 "x-ai/grok-4.7",
2089 "moonshotai/kimi-k3",
2090 "moonshotai/kimi-k2.7-code",
2091 "moonshotai/kimi-k2.6",
2092 "anthropic/claude-opus-5.5",
2093 "anthropic/claude-sonnet-5.5",
2094 "anthropic/claude-haiku-5.5",
2095 "anthropic/claude-fable-5.1",
2096 "anthropic/claude-opus-5",
2097 "openai/gpt-6.1-sol",
2098 "openai/gpt-6-sol",
2099 "openai/gpt-6-luna",
2100 "openai/gpt-6-astra",
2101 "google/gemini-3.8-flash",
2102 "google/gemini-3.7-flash",
2103 "deepseek/deepseek-v4-pro-0813",
2104 "deepseek/deepseek-v4.1-flash",
2105 "qwen/qwen3.8-max-0902",
2106 "qwen/qwen3.8-flash",
2107 "z-ai/glm-5.3",
2108 "z-ai/glm-5.3-flash",
2109 "xiaomi/mimo-v2.6-pro",
2110 "mistralai/mistral-large-4-0",
2111 "meta/muse-spark-1.3",
2112 "accounts/fireworks/models/qwen3-235b-a22b",
2113 "roder.cloud/free",
2114 "roder.cloud/anthropic/claude-opus-4-7",
2115 "roder.cloud/google/gemini-3.1-pro-preview",
2116 "poolside/laguna-m.1",
2117 "poolside/laguna-xs.2",
2118 "mimo-v2.5-pro",
2119 "mimo-v2-pro",
2120 "mimo-v2.5",
2121 "mimo-v2-omni",
2122 "mimo-v2-flash",
2123 "mimo-v2.5-pro",
2124 "mimo-v2-pro",
2125 "mimo-v2.5",
2126 "mimo-v2-omni",
2127 "mimo-v2-flash",
2128 "syn:large:text",
2129 "syn:small:text",
2130 "syn:large:vision",
2131 "syn:small:vision",
2132 "hf:MiniMaxAI/MiniMax-M3",
2133 "hf:Qwen/Qwen3.6-27B",
2134 "hf:moonshotai/Kimi-K2.6",
2135 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
2136 "hf:zai-org/GLM-4.7",
2137 "hf:zai-org/GLM-4.7-Flash",
2138 "hf:zai-org/GLM-5.1",
2139 "hf:zai-org/GLM-5.2",
2140 "hf:openai/gpt-oss-120b",
2141 "hf:Qwen/Qwen3.5-397B-A17B",
2142 "deepseek-chat",
2143 "deepseek-reasoner",
2144 "deepseek-v4-flash",
2145 "deepseek-v4-pro",
2146 "composer-2.5",
2147 "composer-2.5-fast",
2148 "claude-fable-5",
2149 "claude-opus-4-8",
2150 "claude-sonnet-4-6",
2151 "gemini-3.1-pro-preview",
2152 "grok-4.6",
2153 "gemini-3.7-flash",
2154 "grok-4.3",
2155 ]
2156 );
2157 }
2158
2159 #[test]
2160 fn provider_model_lists_match_gode_catalog() {
2161 assert_eq!(models_for_provider(PROVIDER_OPENAI, false).len(), 4);
2162 assert_eq!(models_for_codex(false).len(), 4);
2163 assert_eq!(models_for_provider(PROVIDER_ANTHROPIC, false).len(), 10);
2164 assert_eq!(models_for_provider(PROVIDER_CLAUDE_CODE, false).len(), 12);
2165 assert_eq!(models_for_provider(PROVIDER_GEMINI, false).len(), 7);
2166 assert_eq!(models_for_provider(PROVIDER_VERTEX, false).len(), 6);
2167 assert_eq!(models_for_provider(PROVIDER_XAI, false).len(), 6);
2168 assert_eq!(models_for_provider(PROVIDER_SUPERGROK, false).len(), 3);
2169 assert_eq!(models_for_provider(PROVIDER_OPENCODE, false).len(), 7);
2170 assert_eq!(models_for_provider(PROVIDER_OPENCODE_GO, false).len(), 5);
2171 assert_eq!(models_for_provider(PROVIDER_OPENROUTER, false).len(), 25);
2172 assert_eq!(models_for_provider(PROVIDER_FIREWORKS, false).len(), 1);
2173 assert_eq!(models_for_provider(PROVIDER_RODER_CLOUD, false).len(), 3);
2174 assert_eq!(models_for_provider(PROVIDER_POOLSIDE, false).len(), 2);
2175 assert_eq!(models_for_provider(PROVIDER_CURSOR, false).len(), 9);
2176 assert_eq!(models_for_provider(PROVIDER_XIAOMI_MIMO, false).len(), 5);
2177 assert_eq!(
2178 models_for_provider(PROVIDER_XIAOMI_MIMO_TOKEN_PLAN, false).len(),
2179 5
2180 );
2181 assert_eq!(models_for_provider(PROVIDER_KIMI_CODE, false).len(), 1);
2182 assert_eq!(models_for_provider(PROVIDER_SYNTHETIC, false).len(), 14);
2183 assert_eq!(models_for_provider(PROVIDER_DEEPSEEK, false).len(), 4);
2184 assert_eq!(models_for_provider(PROVIDER_MOCK, true).len(), 1);
2185 }
2186
2187 #[test]
2188 fn codex_model_list_matches_current_subscription_roster() {
2189 let codex_provider = built_in_providers()
2190 .iter()
2191 .find(|provider| provider.id == PROVIDER_CODEX)
2192 .expect("codex provider");
2193 assert_eq!(codex_provider.default_model, "gpt-6-sol");
2194
2195 let ids = models_for_codex(false)
2196 .into_iter()
2197 .map(|model| model.id)
2198 .collect::<Vec<_>>();
2199
2200 assert_eq!(
2201 ids,
2202 vec!["gpt-6-astra", "gpt-6-sol", "gpt-6.1-sol", "gpt-6-luna"]
2203 );
2204 }
2205
2206 #[test]
2207 fn new_codex_models_match_current_subscription_metadata() {
2208 let assert_model = |id: &str,
2209 name: &str,
2210 description: &str,
2211 default_reasoning: &str,
2212 efforts: &[&str],
2213 context_window: u32,
2214 max_context_window: u32| {
2215 let model = lookup_model_for_provider(PROVIDER_OPENAI, id).unwrap();
2216
2217 assert_eq!(model.display_name, name, "{id} display name");
2218 assert_eq!(model.description, description, "{id} description");
2219 assert_eq!(model.provider, PROVIDER_OPENAI, "{id} provider");
2220 assert_eq!(
2221 model.default_reasoning, default_reasoning,
2222 "{id} default reasoning"
2223 );
2224 assert_eq!(
2225 model
2226 .supported_reasoning
2227 .iter()
2228 .map(|option| option.effort)
2229 .collect::<Vec<_>>(),
2230 efforts,
2231 "{id} efforts"
2232 );
2233 assert_eq!(model.context_window, context_window, "{id} context window");
2234 assert_eq!(
2235 model.max_context_window, max_context_window,
2236 "{id} max context window"
2237 );
2238 assert_eq!(
2239 model.auto_compact_token_limit,
2240 context_window.saturating_mul(9) / 10,
2241 "{id} auto compact limit"
2242 );
2243 assert!(model.supports_compaction, "{id} compaction support");
2244 assert!(model.supports_images, "{id} image support");
2245 assert!(model.supports_tools, "{id} tool support");
2246 let offered = matches!(
2248 id,
2249 "gpt-6-astra" | "gpt-6-sol" | "gpt-6-luna" | "gpt-6.1-sol"
2250 );
2251 assert_eq!(model.hidden, !offered, "{id} visibility");
2252 };
2253
2254 assert_model(
2255 "gpt-6-astra",
2256 "GPT-6-Astra",
2257 "OpenAI's most capable model, built for the hardest end-to-end work.",
2258 REASONING_HIGH,
2259 &[
2260 REASONING_LOW,
2261 REASONING_MEDIUM,
2262 REASONING_HIGH,
2263 REASONING_XHIGH,
2264 REASONING_MAX,
2265 ],
2266 1_050_000,
2267 1_050_000,
2268 );
2269 assert_model(
2270 "gpt-6-sol",
2271 "GPT-6 Sol",
2272 "Agentic coding model balancing intelligence and cost.",
2273 REASONING_MEDIUM,
2274 &[
2275 REASONING_NONE,
2276 REASONING_LOW,
2277 REASONING_MEDIUM,
2278 REASONING_HIGH,
2279 REASONING_XHIGH,
2280 REASONING_MAX,
2281 ],
2282 1_050_000,
2283 1_050_000,
2284 );
2285 assert_model(
2286 "gpt-6.1-sol",
2287 "GPT-6.1-Sol",
2288 "Agentic coding model balancing intelligence and cost.",
2289 REASONING_MEDIUM,
2290 &[
2291 REASONING_NONE,
2292 REASONING_LOW,
2293 REASONING_MEDIUM,
2294 REASONING_HIGH,
2295 REASONING_XHIGH,
2296 REASONING_MAX,
2297 ],
2298 1_050_000,
2299 1_050_000,
2300 );
2301 assert_model(
2302 "gpt-6-luna",
2303 "GPT-6 Luna",
2304 "Efficient model for focused, high-volume tasks.",
2305 REASONING_MEDIUM,
2306 &[
2307 REASONING_NONE,
2308 REASONING_LOW,
2309 REASONING_MEDIUM,
2310 REASONING_HIGH,
2311 REASONING_XHIGH,
2312 REASONING_MAX,
2313 ],
2314 1_050_000,
2315 1_050_000,
2316 );
2317 assert_model(
2318 "gpt-5.6-sol",
2319 "GPT-5.6-Sol",
2320 "Latest frontier agentic coding model.",
2321 REASONING_LOW,
2322 &[
2323 REASONING_LOW,
2324 REASONING_MEDIUM,
2325 REASONING_HIGH,
2326 REASONING_XHIGH,
2327 REASONING_MAX,
2328 REASONING_ULTRA,
2329 ],
2330 372_000,
2331 372_000,
2332 );
2333 assert_model(
2334 "gpt-5.6-terra",
2335 "GPT-5.6-Terra",
2336 "Balanced agentic coding model for everyday work.",
2337 REASONING_MEDIUM,
2338 &[
2339 REASONING_LOW,
2340 REASONING_MEDIUM,
2341 REASONING_HIGH,
2342 REASONING_XHIGH,
2343 REASONING_MAX,
2344 REASONING_ULTRA,
2345 ],
2346 372_000,
2347 372_000,
2348 );
2349 assert_model(
2350 "gpt-5.6-luna",
2351 "GPT-5.6-Luna",
2352 "Fast and affordable agentic coding model.",
2353 REASONING_MEDIUM,
2354 &[
2355 REASONING_LOW,
2356 REASONING_MEDIUM,
2357 REASONING_HIGH,
2358 REASONING_XHIGH,
2359 REASONING_MAX,
2360 ],
2361 372_000,
2362 372_000,
2363 );
2364 assert_model(
2365 "gpt-5.4",
2366 "GPT-5.4",
2367 "Strong model for everyday coding.",
2368 REASONING_MEDIUM,
2369 &[
2370 REASONING_LOW,
2371 REASONING_MEDIUM,
2372 REASONING_HIGH,
2373 REASONING_XHIGH,
2374 ],
2375 272_000,
2376 1_000_000,
2377 );
2378 }
2379
2380 #[test]
2381 fn deepseek_catalog_defaults_to_chat_model() {
2382 let provider = BUILT_IN_PROVIDERS
2383 .iter()
2384 .find(|provider| provider.id == PROVIDER_DEEPSEEK)
2385 .expect("deepseek provider registered");
2386 assert_eq!(provider.name, "DeepSeek Platform");
2387 assert_eq!(provider.default_model, "deepseek-chat");
2388 assert_eq!(provider.base_url, Some("https://api.deepseek.com/v1"));
2389 assert_eq!(provider.env_key, Some("DEEPSEEK_API_KEY"));
2390 assert_eq!(
2391 normalize_provider_id("deepseek-platform"),
2392 PROVIDER_DEEPSEEK
2393 );
2394 assert_eq!(
2395 provider_family_for_provider(PROVIDER_DEEPSEEK),
2396 ProviderFamily::OpenAi
2397 );
2398
2399 let models = models_for_provider(PROVIDER_DEEPSEEK, false);
2400 assert_eq!(models.len(), 4);
2401 assert!(models.iter().any(|model| model.id == "deepseek-chat"));
2402 assert!(models.iter().any(|model| model.id == "deepseek-reasoner"));
2403 assert!(models.iter().any(|model| model.id == "deepseek-v4-flash"));
2404 assert!(models.iter().any(|model| model.id == "deepseek-v4-pro"));
2405
2406 let flash = models
2407 .iter()
2408 .find(|model| model.id == "deepseek-v4-flash")
2409 .expect("flash model");
2410 assert_eq!(flash.default_reasoning, Some(REASONING_HIGH.to_string()));
2411 assert_eq!(
2412 flash
2413 .supported_reasoning
2414 .iter()
2415 .map(|option| option.effort.as_str())
2416 .collect::<Vec<_>>(),
2417 vec![
2418 REASONING_NONE,
2419 REASONING_LOW,
2420 REASONING_HIGH,
2421 REASONING_XHIGH,
2422 REASONING_MAX,
2423 ]
2424 );
2425
2426 let chat = models
2427 .iter()
2428 .find(|model| model.id == "deepseek-chat")
2429 .expect("chat model");
2430 assert_eq!(chat.default_reasoning, Some(REASONING_NONE.to_string()));
2431 assert!(
2432 chat.supported_reasoning
2433 .iter()
2434 .any(|option| option.effort == REASONING_HIGH)
2435 );
2436 }
2437
2438 #[test]
2439 fn synthetic_catalog_defaults_to_large_text_alias() {
2440 let provider = built_in_providers()
2441 .iter()
2442 .find(|provider| provider.id == PROVIDER_SYNTHETIC)
2443 .expect("synthetic provider registered");
2444 assert_eq!(provider.name, "Synthetic");
2445 assert_eq!(provider.default_model, "syn:large:text");
2446 assert_eq!(
2447 provider.base_url,
2448 Some("https://api.synthetic.new/openai/v1")
2449 );
2450 assert_eq!(provider.env_key, Some("SYNTHETIC_API_KEY"));
2451 assert!(provider.env_aliases.contains(&"RODER_SYNTHETIC_API_KEY"));
2452 assert!(!provider.supports_websockets);
2453
2454 let models = models_for_provider(PROVIDER_SYNTHETIC, false);
2455 let default = models
2456 .iter()
2457 .find(|model| model.id == provider.default_model)
2458 .expect("default synthetic model present");
2459 assert_eq!(default.name, "Synthetic Large (Text)");
2460 assert!(models.iter().any(|model| model.id == "syn:small:text"));
2461 let vision = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:vision")
2462 .expect("vision alias present");
2463 assert!(vision.supports_images);
2464 assert_eq!(
2465 provider_family_for_provider(PROVIDER_SYNTHETIC),
2466 ProviderFamily::OpenAi
2467 );
2468 }
2469
2470 #[test]
2471 fn synthetic_model_ids_preserve_alias_and_hf_segments() {
2472 assert_eq!(normalize_provider_id("synthetic"), PROVIDER_SYNTHETIC);
2473 assert_eq!(normalize_provider_id("synthetic.new"), PROVIDER_SYNTHETIC);
2474 let alias = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:text")
2476 .expect("syn alias resolves");
2477 assert_eq!(alias.id, "syn:large:text");
2478 assert_eq!(alias.provider, PROVIDER_SYNTHETIC);
2479 let label = "synthetic/hf:zai-org/GLM-5.2";
2483 let (provider, model) = label.split_once('/').unwrap();
2484 assert_eq!(provider, PROVIDER_SYNTHETIC);
2485 assert_eq!(model, "hf:zai-org/GLM-5.2");
2486 }
2487
2488 #[test]
2489 fn synthetic_always_on_models_are_pinned_with_documented_context_windows() {
2490 let glm_5_2 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-5.2")
2491 .expect("GLM-5.2 pinned");
2492 assert_eq!(glm_5_2.provider, PROVIDER_SYNTHETIC);
2493 assert_eq!(glm_5_2.context_window, 524_288);
2494 assert!(!glm_5_2.supports_images);
2495
2496 let minimax = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:MiniMaxAI/MiniMax-M3")
2497 .expect("MiniMax-M3 pinned");
2498 assert_eq!(minimax.context_window, 524_288);
2499
2500 let glm_4_7 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-4.7")
2501 .expect("GLM-4.7 pinned");
2502 assert_eq!(glm_4_7.context_window, 202_752);
2503
2504 let gpt_oss = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:openai/gpt-oss-120b")
2505 .expect("gpt-oss-120b pinned");
2506 assert_eq!(gpt_oss.context_window, 131_072);
2507
2508 let qwen_3_5 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:Qwen/Qwen3.5-397B-A17B")
2509 .expect("Qwen3.5 397B pinned");
2510 assert_eq!(qwen_3_5.context_window, 262_144);
2511
2512 let always_on = [
2514 "hf:MiniMaxAI/MiniMax-M3",
2515 "hf:Qwen/Qwen3.6-27B",
2516 "hf:moonshotai/Kimi-K2.6",
2517 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
2518 "hf:zai-org/GLM-4.7",
2519 "hf:zai-org/GLM-4.7-Flash",
2520 "hf:zai-org/GLM-5.1",
2521 "hf:zai-org/GLM-5.2",
2522 "hf:openai/gpt-oss-120b",
2523 "hf:Qwen/Qwen3.5-397B-A17B",
2524 ];
2525 for id in always_on {
2526 let entry = lookup_model_for_provider(PROVIDER_SYNTHETIC, id)
2527 .unwrap_or_else(|| panic!("{id} should be pinned in the synthetic catalog"));
2528 assert_eq!(entry.provider, PROVIDER_SYNTHETIC);
2529 assert!(entry.supports_tools);
2530 assert!(entry.supports_structured);
2531 }
2532 }
2533
2534 #[test]
2535 fn claude_code_catalog_uses_long_context_windows() {
2536 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-sonnet-4-6").unwrap();
2537 let claude_code =
2538 lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-sonnet-4-6").unwrap();
2539
2540 assert_eq!(direct.context_window, 1_000_000);
2541 assert_eq!(claude_code.context_window, 1_000_000);
2542 assert_eq!(claude_code.auto_compact_token_limit, 900_000);
2543 assert!(!claude_code.supports_compaction);
2546 assert!(direct.supports_compaction);
2550 assert_eq!(direct.auto_compact_token_limit, 900_000);
2551 }
2552
2553 #[test]
2554 fn claude_haiku_does_not_advertise_server_side_compaction() {
2555 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
2556
2557 assert!(!haiku.supports_compaction);
2562 assert_eq!(haiku.auto_compact_token_limit, 180_000);
2563 }
2564
2565 #[test]
2566 fn claude_fable_5_1_is_offered_directly_and_through_the_claude_code_harness() {
2567 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-fable-5-1").unwrap();
2568 assert_eq!(direct.display_name, "Claude Fable 5.1");
2569 assert_eq!(direct.context_window, 1_000_000);
2570 assert_eq!(direct.auto_compact_token_limit, 900_000);
2571 assert_eq!(direct.default_reasoning, REASONING_HIGH);
2572 assert!(direct.supports_compaction);
2573 assert_eq!(
2574 direct
2575 .supported_reasoning
2576 .iter()
2577 .map(|option| option.effort)
2578 .collect::<Vec<_>>(),
2579 vec![
2580 REASONING_LOW,
2581 REASONING_MEDIUM,
2582 REASONING_HIGH,
2583 REASONING_XHIGH,
2584 REASONING_MAX
2585 ]
2586 );
2587
2588 let harness = lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-fable-5-1").unwrap();
2589 assert_eq!(harness.provider, PROVIDER_CLAUDE_CODE);
2590 assert_eq!(harness.context_window, 1_000_000);
2591 assert!(!harness.supports_compaction);
2594 }
2595
2596 #[test]
2597 fn google_embedding_model_is_hidden_from_chat_lists() {
2598 assert!(lookup_model("gemini-embedding-2").is_some());
2599 assert!(
2600 models_for_provider(PROVIDER_GOOGLE, false)
2601 .iter()
2602 .all(|model| model.id != "gemini-embedding-2")
2603 );
2604 let model = lookup_model("gemini-embedding-2").unwrap();
2605 assert!(model.hidden);
2606 assert!(!model.supports_tools);
2607 }
2608
2609 #[test]
2610 fn zeroentropy_embedding_model_is_hidden_from_chat_lists() {
2611 assert!(lookup_model("zembed-1").is_some());
2612 assert!(
2613 models_for_provider(PROVIDER_ZEROENTROPY, false)
2614 .iter()
2615 .all(|model| model.id != "zembed-1")
2616 );
2617 let model = lookup_model("zembed-1").unwrap();
2618 assert!(model.hidden);
2619 assert!(!model.supports_tools);
2620 }
2621
2622 #[test]
2623 fn catalog_model_profile_derives_openai_defaults() {
2624 let profile = built_in_model_profile("gpt-5.5").unwrap();
2625
2626 assert_eq!(profile.provider_family, ProviderFamily::OpenAi);
2627 assert_eq!(profile.edit_tool.as_deref(), Some(EDIT_TOOL_PATCH));
2628 assert_eq!(profile.schema_policy, ModelSchemaPolicy::RequiredFirstFlat);
2629 assert_eq!(
2630 profile.instruction_overlay,
2631 ModelInstructionOverlay::LiteralToolOutputs
2632 );
2633 assert_eq!(profile.reasoning.execution.as_deref(), Some(REASONING_LOW));
2634 assert_eq!(profile.parallel_tool_calls, Some(true));
2635 }
2636
2637 #[test]
2638 fn poolside_catalog_defaults_to_thinking_enabled() {
2639 let laguna = lookup_model("poolside/laguna-m.1").unwrap();
2640 assert_eq!(laguna.default_reasoning, REASONING_MEDIUM);
2641 assert_eq!(
2642 laguna
2643 .supported_reasoning
2644 .iter()
2645 .map(|option| option.effort)
2646 .collect::<Vec<_>>(),
2647 vec![REASONING_NONE, REASONING_MEDIUM]
2648 );
2649 }
2650
2651 #[test]
2652 fn xiaomi_mimo_catalog_uses_chat_completions_kind_and_exact_model_ids() {
2653 let provider = BUILT_IN_PROVIDERS
2654 .iter()
2655 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO)
2656 .unwrap();
2657 let token_plan = BUILT_IN_PROVIDERS
2658 .iter()
2659 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO_TOKEN_PLAN)
2660 .unwrap();
2661
2662 assert_eq!(provider.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2663 assert_eq!(token_plan.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2664 assert_eq!(provider.env_key, Some("MIMO_API_KEY"));
2665 assert_eq!(token_plan.env_key, Some("MIMO_TOKEN_PLAN_API_KEY"));
2666
2667 let ids = models_for_provider(PROVIDER_XIAOMI_MIMO, false)
2668 .into_iter()
2669 .map(|model| model.id)
2670 .collect::<Vec<_>>();
2671 assert_eq!(
2672 ids,
2673 vec![
2674 "mimo-v2.5-pro",
2675 "mimo-v2-pro",
2676 "mimo-v2.5",
2677 "mimo-v2-omni",
2678 "mimo-v2-flash"
2679 ]
2680 );
2681 assert!(lookup_model("out-of-v2-flash").is_none());
2682 }
2683
2684 #[test]
2685 fn supergrok_catalog_exposes_grok_47_46_and_composer_with_expected_context_windows() {
2686 let grok47 = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-4.7").unwrap();
2687 assert_eq!(grok47.display_name, "Grok 4.7");
2688 assert_eq!(grok47.context_window, 500_000);
2689 assert_eq!(grok47.auto_compact_token_limit, 450_000);
2690 assert_eq!(grok47.default_reasoning, REASONING_HIGH);
2691
2692 let grok46 = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-4.6").unwrap();
2693 assert_eq!(grok46.display_name, "Grok 4.6");
2694 assert_eq!(grok46.context_window, 500_000);
2695 assert_eq!(grok46.auto_compact_token_limit, 450_000);
2696 assert_eq!(grok46.default_reasoning, REASONING_HIGH);
2697
2698 let composer =
2699 lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-composer-2.5-fast").unwrap();
2700 assert_eq!(composer.display_name, "Grok Composer 2.5 Fast");
2701 assert_eq!(composer.context_window, 200_000);
2702 assert_eq!(composer.auto_compact_token_limit, 180_000);
2703 assert!(composer.supported_reasoning.is_empty());
2704 assert!(!composer.supports_images);
2705
2706 let visible = models_for_provider(PROVIDER_SUPERGROK, false)
2707 .into_iter()
2708 .map(|model| model.id)
2709 .collect::<Vec<_>>();
2710 assert_eq!(
2711 visible,
2712 vec![
2713 "grok-4.7".to_string(),
2714 "grok-4.6".to_string(),
2715 "grok-composer-2.5-fast".to_string(),
2716 ]
2717 );
2718 }
2719
2720 #[test]
2721 fn xai_catalog_entries_match_current_grok_contract() {
2722 let grok46 = models_for_provider(PROVIDER_XAI, false)
2723 .into_iter()
2724 .find(|model| model.id == "grok-4.6")
2725 .unwrap();
2726 assert_eq!(grok46.context_window, Some(500_000));
2727 assert_eq!(grok46.default_reasoning.as_deref(), Some(REASONING_HIGH));
2728 assert_eq!(
2729 grok46
2730 .supported_reasoning
2731 .iter()
2732 .map(|option| option.effort.as_str())
2733 .collect::<Vec<_>>(),
2734 vec![
2735 REASONING_LOW,
2736 REASONING_MEDIUM,
2737 REASONING_HIGH,
2738 REASONING_XHIGH
2739 ]
2740 );
2741
2742 let grok43 = models_for_provider(PROVIDER_XAI, false)
2743 .into_iter()
2744 .find(|model| model.id == "grok-4.3")
2745 .unwrap();
2746 assert_eq!(grok43.context_window, Some(1_000_000));
2747 assert_eq!(grok43.default_reasoning.as_deref(), Some(REASONING_LOW));
2748 assert_eq!(
2749 grok43
2750 .supported_reasoning
2751 .iter()
2752 .map(|option| option.effort.as_str())
2753 .collect::<Vec<_>>(),
2754 vec![
2755 REASONING_NONE,
2756 REASONING_LOW,
2757 REASONING_MEDIUM,
2758 REASONING_HIGH,
2759 REASONING_XHIGH
2760 ]
2761 );
2762
2763 let grok420 = lookup_model("grok-4.20-multi-agent-0309").unwrap();
2764 assert_eq!(grok420.context_window, 2_000_000);
2765 assert_eq!(grok420.auto_compact_token_limit, 1_800_000);
2766 assert_eq!(grok420.provider, PROVIDER_XAI);
2767 }
2768
2769 #[test]
2770 fn provider_aliases_normalize_xai_and_supergrok() {
2771 assert_eq!(normalize_provider_id("grok"), PROVIDER_XAI);
2772 assert_eq!(normalize_provider_id("x.ai"), PROVIDER_XAI);
2773 assert_eq!(normalize_provider_id("x-ai"), PROVIDER_XAI);
2774 assert_eq!(normalize_provider_id("xai-oauth"), PROVIDER_SUPERGROK);
2775 assert_eq!(normalize_provider_id("grok-oauth"), PROVIDER_SUPERGROK);
2776 assert_eq!(normalize_provider_id("supergrok"), PROVIDER_SUPERGROK);
2777 assert_eq!(normalize_provider_id("laguna"), PROVIDER_POOLSIDE);
2778 assert_eq!(normalize_provider_id("composer"), PROVIDER_CURSOR);
2779 }
2780
2781 #[test]
2782 fn fireworks_catalog_preserves_account_scoped_default_model() {
2783 let provider = BUILT_IN_PROVIDERS
2784 .iter()
2785 .find(|provider| provider.id == PROVIDER_FIREWORKS)
2786 .unwrap();
2787
2788 assert_eq!(
2789 provider.default_model,
2790 "accounts/fireworks/models/qwen3-235b-a22b"
2791 );
2792 assert_eq!(provider.env_key, Some("FIREWORKS_API_KEY"));
2793 assert_eq!(provider.env_aliases, &["RODER_FIREWORKS_API_KEY"]);
2794
2795 let model = lookup_model_for_provider(PROVIDER_FIREWORKS, provider.default_model).unwrap();
2796 assert_eq!(model.provider, PROVIDER_FIREWORKS);
2797 assert!(model.supports_tools);
2798 assert!(model.supports_structured);
2799 assert_eq!(
2800 provider_family_for_provider(PROVIDER_FIREWORKS),
2801 ProviderFamily::OpenAi
2802 );
2803 }
2804
2805 #[test]
2806 fn cursor_catalog_profile_is_text_only_agentservice() {
2807 let composer = lookup_model("composer-2.5").unwrap();
2808 assert_eq!(composer.provider, PROVIDER_CURSOR);
2809 assert!(!composer.supports_tools);
2810 assert!(!composer.supports_structured);
2811
2812 let profile = built_in_model_profile("composer-2.5").unwrap();
2813 assert_eq!(profile.provider_family, ProviderFamily::Cursor);
2814 assert_eq!(profile.parallel_tool_calls, Some(false));
2815 }
2816
2817 #[test]
2818 fn provider_aware_lookup_resolves_cursor_proxied_models_to_cursor_family() {
2819 let id_only = built_in_model_profile("claude-opus-4-8").unwrap();
2821 assert_eq!(id_only.provider_family, ProviderFamily::Anthropic);
2822
2823 let cursor =
2825 built_in_model_profile_for_provider(PROVIDER_CURSOR, "claude-opus-4-8").unwrap();
2826 assert_eq!(cursor.provider_family, ProviderFamily::Cursor);
2827 assert_eq!(cursor.provider, PROVIDER_CURSOR);
2828 assert_eq!(cursor.parallel_tool_calls, Some(false));
2829
2830 let anthropic =
2831 built_in_model_profile_for_provider(PROVIDER_ANTHROPIC, "claude-opus-4-8").unwrap();
2832 assert_eq!(anthropic.provider_family, ProviderFamily::Anthropic);
2833
2834 let fallback =
2836 built_in_model_profile_for_provider("does-not-exist", "claude-opus-4-8").unwrap();
2837 assert_eq!(fallback.provider_family, ProviderFamily::Anthropic);
2838 }
2839
2840 #[test]
2841 fn opencode_default_model_is_a_listed_opencode_model() {
2842 let provider = built_in_providers()
2843 .iter()
2844 .find(|provider| provider.id == PROVIDER_OPENCODE)
2845 .expect("opencode provider");
2846 assert!(
2847 models_for_provider(PROVIDER_OPENCODE, false)
2848 .iter()
2849 .any(|model| model.id == provider.default_model),
2850 "opencode default {} should be listed for opencode",
2851 provider.default_model
2852 );
2853 }
2854
2855 #[test]
2856 fn retired_gpt_5_models_are_hidden_but_still_resolvable() {
2857 let visible = built_in_models(false)
2858 .into_iter()
2859 .map(|model| model.id)
2860 .collect::<Vec<_>>();
2861 for id in [
2862 "gpt-5.6-sol",
2863 "gpt-5.6-terra",
2864 "gpt-5.6-luna",
2865 "gpt-5.5",
2866 "gpt-5.5-fast",
2867 "gpt-5.4",
2868 "gpt-5.4-mini",
2869 "gpt-5.3-codex-spark",
2870 "roder.cloud/openai/gpt-5.5",
2871 ] {
2872 assert!(
2873 !visible.contains(&id),
2874 "{id} should be hidden from model lists"
2875 );
2876 assert!(lookup_model(id).is_some(), "{id} should still resolve");
2878 }
2879 assert!(visible.contains(&"gpt-6-sol") && visible.contains(&"gpt-6.1-sol"));
2880 }
2881
2882 #[test]
2883 fn retired_cursor_gpt55_keeps_standard_reasoning_metadata() {
2884 let gpt55 = ModelDescriptor::from(
2885 lookup_model_for_provider(PROVIDER_CURSOR, "gpt-5.5")
2886 .expect("cursor catalog should still resolve retired gpt-5.5"),
2887 );
2888
2889 assert_eq!(gpt55.default_reasoning.as_deref(), Some(REASONING_MEDIUM));
2890 assert_eq!(
2891 gpt55
2892 .supported_reasoning
2893 .iter()
2894 .map(|option| option.effort.as_str())
2895 .collect::<Vec<_>>(),
2896 vec![
2897 REASONING_LOW,
2898 REASONING_MEDIUM,
2899 REASONING_HIGH,
2900 REASONING_XHIGH
2901 ]
2902 );
2903
2904 let gpt55_fast = ModelDescriptor::from(
2905 lookup_model_for_provider(PROVIDER_CURSOR, "gpt-5.5-fast")
2906 .expect("cursor catalog should still resolve retired gpt-5.5-fast"),
2907 );
2908 assert_eq!(
2909 gpt55_fast.default_reasoning.as_deref(),
2910 Some(REASONING_MEDIUM)
2911 );
2912 assert_eq!(gpt55_fast.supported_reasoning.len(), 4);
2913 }
2914
2915 #[test]
2916 fn cursor_opus_advertises_configurable_reasoning_effort() {
2917 let opus = models_for_provider(PROVIDER_CURSOR, false)
2918 .into_iter()
2919 .find(|model| model.id == "claude-opus-4-8")
2920 .expect("cursor catalog should expose claude-opus-4-8");
2921
2922 assert_eq!(opus.default_reasoning.as_deref(), Some(REASONING_HIGH));
2923 assert_eq!(
2924 opus.supported_reasoning
2925 .iter()
2926 .map(|option| option.effort.as_str())
2927 .collect::<Vec<_>>(),
2928 vec![
2929 REASONING_LOW,
2930 REASONING_MEDIUM,
2931 REASONING_HIGH,
2932 REASONING_XHIGH,
2933 REASONING_MAX
2934 ]
2935 );
2936
2937 let sonnet = models_for_provider(PROVIDER_CURSOR, false)
2940 .into_iter()
2941 .find(|model| model.id == "claude-sonnet-4-6")
2942 .expect("cursor catalog should expose claude-sonnet-4-6");
2943 assert_eq!(sonnet.default_reasoning.as_deref(), Some(REASONING_MEDIUM));
2944 assert_eq!(
2945 sonnet
2946 .supported_reasoning
2947 .iter()
2948 .map(|option| option.effort.as_str())
2949 .collect::<Vec<_>>(),
2950 vec![
2951 REASONING_LOW,
2952 REASONING_MEDIUM,
2953 REASONING_HIGH,
2954 REASONING_MAX
2955 ]
2956 );
2957 }
2958
2959 #[test]
2960 fn claude_opus_and_sonnet_advertise_max_effort() {
2961 let efforts = |id: &str| {
2962 lookup_model(id)
2963 .unwrap()
2964 .supported_reasoning
2965 .iter()
2966 .map(|option| option.effort)
2967 .collect::<Vec<_>>()
2968 };
2969
2970 for id in ["claude-opus-4-8", "claude-opus-4-7"] {
2972 assert_eq!(
2973 efforts(id),
2974 vec![
2975 REASONING_LOW,
2976 REASONING_MEDIUM,
2977 REASONING_HIGH,
2978 REASONING_XHIGH,
2979 REASONING_MAX
2980 ],
2981 "{id} effort levels"
2982 );
2983 }
2984
2985 assert_eq!(
2987 efforts("claude-sonnet-4-6"),
2988 vec![
2989 REASONING_LOW,
2990 REASONING_MEDIUM,
2991 REASONING_HIGH,
2992 REASONING_MAX
2993 ]
2994 );
2995
2996 assert!(!efforts("gpt-5.5").contains(&REASONING_MAX));
2998 }
2999
3000 #[test]
3001 fn claude_haiku_does_not_advertise_reasoning_effort() {
3002 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
3003
3004 assert_eq!(haiku.default_reasoning, REASONING_NONE);
3005 assert!(haiku.supported_reasoning.is_empty());
3006
3007 let descriptor = ModelDescriptor::from(haiku);
3008 assert_eq!(descriptor.default_reasoning, None);
3009 assert!(descriptor.supported_reasoning.is_empty());
3010 }
3011
3012 #[test]
3013 fn openai_context_windows_match_current_catalog_values() {
3014 let gpt55 = lookup_model("gpt-5.5").unwrap();
3015 assert_eq!(gpt55.context_window, 1_050_000);
3016 assert_eq!(gpt55.max_context_window, 1_050_000);
3017 assert_eq!(gpt55.auto_compact_token_limit, 945_000);
3018
3019 let mini = lookup_model("gpt-5.4-mini").unwrap();
3020 assert_eq!(mini.context_window, 400_000);
3021 assert_eq!(mini.max_context_window, 400_000);
3022 assert_eq!(mini.auto_compact_token_limit, 360_000);
3023
3024 let spark = lookup_model("gpt-5.3-codex-spark").unwrap();
3025 assert_eq!(spark.provider, PROVIDER_CODEX);
3026 assert_eq!(spark.context_window, 128_000);
3027 assert_eq!(spark.max_context_window, 128_000);
3028 assert_eq!(spark.auto_compact_token_limit, 115_200);
3029 }
3030
3031 #[test]
3032 fn auto_compact_defaults_to_ninety_percent_of_context_window() {
3033 for model in BUILT_IN_MODELS {
3034 if model.context_window == 0 || model.auto_compact_token_limit == 0 {
3035 continue;
3036 }
3037 assert_eq!(
3038 model.auto_compact_token_limit,
3039 model.context_window.saturating_mul(9) / 10,
3040 "{} should compact at 90% of its context window",
3041 model.id
3042 );
3043 }
3044 }
3045}