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.1-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.1-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_61_TERRA,
542 openai_codex::GPT_6_ASTRA,
543 retired(openai_codex::GPT_6_SOL),
544 openai_codex::GPT_61_SOL,
545 openai_codex::GPT_6_LUNA,
546 retired(openai_codex::GPT_56_SOL),
547 retired(openai_codex::GPT_56_TERRA),
548 retired(openai_codex::GPT_56_LUNA),
549 retired(openai_model(
550 "gpt-5.5",
551 "GPT-5.5",
552 "Frontier model for complex coding, research, and real-world work.",
553 1_050_000,
554 945_000,
555 true,
556 STANDARD_REASONING,
557 )),
558 retired(openai_codex::GPT_54),
559 retired(openai_model(
560 "gpt-5.4-mini",
561 "GPT-5.4-Mini",
562 "Small, fast, and cost-efficient model for simpler coding tasks.",
563 400_000,
564 360_000,
565 true,
566 STANDARD_REASONING,
567 )),
568 retired(ModelCatalogEntry {
569 id: "gpt-5.3-codex-spark",
570 display_name: "GPT-5.3-Codex-Spark",
571 description: "Ultra-fast coding model optimized for low-latency Codex workflows.",
572 provider: PROVIDER_CODEX,
573 default_reasoning: REASONING_HIGH,
574 supported_reasoning: STANDARD_REASONING,
575 context_window: 128_000,
576 max_context_window: 128_000,
577 auto_compact_token_limit: 115_200,
578 supports_compaction: true,
579 supports_images: false,
580 supports_tools: true,
581 supports_structured: false,
582 edit_tool: Some("patch"),
583 hidden: false,
584 }),
585 ModelCatalogEntry {
586 id: "codex-auto-review",
587 display_name: "Codex Auto Review",
588 description: "Automatic approval review model for Codex.",
589 provider: PROVIDER_OPENAI,
590 default_reasoning: REASONING_MEDIUM,
591 supported_reasoning: STANDARD_REASONING,
592 context_window: 272_000,
593 max_context_window: 272_000,
594 auto_compact_token_limit: 244_800,
595 supports_compaction: false,
596 supports_images: false,
597 supports_tools: true,
598 supports_structured: false,
599 edit_tool: Some("patch"),
600 hidden: true,
601 },
602 anthropic::OPUS_55,
603 anthropic::SONNET_5,
604 anthropic::SONNET_55,
605 anthropic_model(
606 "claude-fable-5-1",
607 "Claude Fable 5.1",
608 "Anthropic's most capable widely released model; successor to Fable 5 for frontier reasoning and long-horizon agentic work.",
609 1_000_000,
610 900_000,
611 REASONING_HIGH,
612 OPUS_REASONING,
613 true,
614 ),
615 anthropic_model(
616 "claude-fable-5",
617 "Claude Fable 5",
618 "Anthropic's most powerful, most intelligent model; a new tier above Opus for frontier reasoning and agentic work.",
619 1_000_000,
620 900_000,
621 REASONING_HIGH,
622 OPUS_REASONING,
623 true,
624 ),
625 anthropic_model(
626 "claude-opus-4-8",
627 "Claude Opus 4.8",
628 "Anthropic's most capable Opus-tier model for complex reasoning, long-horizon agentic coding, and high-autonomy work.",
629 1_000_000,
630 900_000,
631 REASONING_HIGH,
632 OPUS_REASONING,
633 true,
634 ),
635 anthropic_model(
636 "claude-opus-4-7",
637 "Claude Opus 4.7",
638 "Most capable Claude model for complex reasoning and agentic coding.",
639 1_000_000,
640 900_000,
641 REASONING_HIGH,
642 OPUS_REASONING,
643 true,
644 ),
645 anthropic_model(
646 "claude-sonnet-4-6",
647 "Claude Sonnet 4.6",
648 "Balanced Claude model for coding, tool use, and everyday agent workflows.",
649 1_000_000,
650 900_000,
651 REASONING_MEDIUM,
652 SONNET_REASONING,
653 true,
654 ),
655 anthropic_model(
656 "claude-haiku-4-5-20251001",
657 "Claude Haiku 4.5",
658 "Fast Claude model for lower-latency tool workflows.",
659 200_000,
660 180_000,
661 REASONING_NONE,
662 &[],
663 false,
665 ),
666 anthropic::HAIKU_55,
667 anthropic::CLAUDE_CODE_OPUS_55,
668 anthropic::CLAUDE_CODE_SONNET_5,
669 anthropic::CLAUDE_CODE_SONNET_55,
670 claude_code_model(
671 "fable",
672 "Claude Code Fable",
673 "Claude Code harness Fable alias for the most powerful frontier model.",
674 1_000_000,
675 900_000,
676 REASONING_HIGH,
677 OPUS_REASONING,
678 ),
679 claude_code_model(
680 "sonnet",
681 "Claude Code Sonnet",
682 "Claude Code harness Sonnet alias for coding and tool workflows.",
683 1_000_000,
684 900_000,
685 REASONING_MEDIUM,
686 SONNET_REASONING,
687 ),
688 claude_code_model(
689 "opus",
690 "Claude Code Opus",
691 "Claude Code harness Opus alias for complex long-horizon agentic work.",
692 1_000_000,
693 900_000,
694 REASONING_HIGH,
695 OPUS_REASONING,
696 ),
697 claude_code_model(
698 "haiku",
699 "Claude Code Haiku",
700 "Claude Code harness Haiku alias for fast lower-latency coding turns.",
701 200_000,
702 180_000,
703 REASONING_NONE,
704 &[],
705 ),
706 anthropic::CLAUDE_CODE_HAIKU_55,
707 claude_code_model(
708 "claude-sonnet-4-6",
709 "Claude Code Sonnet 4.6",
710 "Claude Sonnet 4.6 through the local Claude Code harness.",
711 1_000_000,
712 900_000,
713 REASONING_MEDIUM,
714 SONNET_REASONING,
715 ),
716 claude_code_model(
717 "claude-opus-4-8",
718 "Claude Code Opus 4.8",
719 "Claude Opus 4.8 through the local Claude Code harness.",
720 1_000_000,
721 900_000,
722 REASONING_HIGH,
723 OPUS_REASONING,
724 ),
725 claude_code_model(
726 "claude-fable-5",
727 "Claude Code Fable 5",
728 "Claude Fable 5 through the local Claude Code harness.",
729 1_000_000,
730 900_000,
731 REASONING_HIGH,
732 OPUS_REASONING,
733 ),
734 claude_code_model(
735 "claude-fable-5-1",
736 "Claude Code Fable 5.1",
737 "Claude Fable 5.1 through the local Claude Code harness.",
738 1_000_000,
739 900_000,
740 REASONING_HIGH,
741 OPUS_REASONING,
742 ),
743 gemini_model(
744 PROVIDER_GEMINI,
745 "gemini-3.8-flash",
746 "Gemini 3.8 Flash",
747 "Google's most intelligent Flash model for long-horizon software engineering, autonomous agents, and complex workflows.",
748 REASONING_MEDIUM,
749 ),
750 gemini_model(
751 PROVIDER_GEMINI,
752 "gemini-3.5-flash",
753 "Gemini 3.5 Flash",
754 "Stable Gemini Flash model for agentic coding, tool use, and long-horizon workflows.",
755 REASONING_MEDIUM,
756 ),
757 gemini_model(
758 PROVIDER_GEMINI,
759 "gemini-3.7-flash",
760 "Gemini 3.7 Flash",
761 "Google's latest speed-tier Gemini model for high-throughput agentic coding, tool use, and long-context workflows.",
762 REASONING_HIGH,
763 ),
764 gemini_model(
765 PROVIDER_GEMINI,
766 "gemini-3.1-pro-preview",
767 "Gemini 3.1 Pro Preview",
768 "Gemini model for complex coding, long context, and tool-heavy agent workflows.",
769 REASONING_HIGH,
770 ),
771 gemini_model(
772 PROVIDER_GEMINI,
773 "gemini-3.1-pro-preview-customtools",
774 "Gemini 3.1 Pro Preview Custom Tools",
775 "Gemini preview variant exposed for custom tool validation and tool-heavy coding workflows.",
776 REASONING_HIGH,
777 ),
778 gemini_model(
779 PROVIDER_GEMINI,
780 "gemini-3-flash-preview",
781 "Gemini 3 Flash Preview",
782 "Fast Gemini model for everyday coding, tool use, and multimodal prompts.",
783 REASONING_MEDIUM,
784 ),
785 gemini_model(
786 PROVIDER_GEMINI,
787 "gemini-3.1-flash-lite-preview",
788 "Gemini 3.1 Flash-Lite Preview",
789 "Lightweight Gemini model for low-latency coding and agent interactions.",
790 REASONING_LOW,
791 ),
792 gemini_model(
793 PROVIDER_VERTEX,
794 "gemini-3.8-flash",
795 "Gemini 3.8 Flash",
796 "Google's most intelligent Flash model on Vertex AI for long-horizon software engineering and autonomous agents.",
797 REASONING_MEDIUM,
798 ),
799 gemini_model(
800 PROVIDER_VERTEX,
801 "gemini-3.5-flash",
802 "Gemini 3.5 Flash",
803 "Stable Gemini Flash model on Vertex AI for agentic coding, tool use, and long-horizon workflows.",
804 REASONING_MEDIUM,
805 ),
806 gemini_model(
807 PROVIDER_VERTEX,
808 "gemini-3.7-flash",
809 "Gemini 3.7 Flash",
810 "Google's latest speed-tier Gemini model on Vertex AI for high-throughput agentic coding, tool use, and long-context workflows.",
811 REASONING_HIGH,
812 ),
813 gemini_model(
814 PROVIDER_VERTEX,
815 "gemini-3.1-pro-preview",
816 "Gemini 3.1 Pro Preview",
817 "Gemini model on Vertex AI for complex coding, long context, and tool-heavy agent workflows.",
818 REASONING_HIGH,
819 ),
820 gemini_model(
821 PROVIDER_VERTEX,
822 "gemini-3-flash-preview",
823 "Gemini 3 Flash Preview",
824 "Fast Gemini model on Vertex AI for everyday coding, tool use, and multimodal prompts.",
825 REASONING_MEDIUM,
826 ),
827 gemini_model(
828 PROVIDER_VERTEX,
829 "gemini-3.1-flash-lite-preview",
830 "Gemini 3.1 Flash-Lite Preview",
831 "Lightweight Gemini model on Vertex AI for low-latency coding and agent interactions.",
832 REASONING_LOW,
833 ),
834 xai_model(
835 PROVIDER_XAI,
836 "grok-4.7",
837 "Grok 4.7",
838 "xAI's most capable model for coding, chat, long-running agents, and configurable reasoning.",
839 500_000,
840 REASONING_HIGH,
841 XAI_REASONING,
842 true,
843 false,
844 ),
845 xai_model(
846 PROVIDER_XAI,
847 "grok-4.6",
848 "Grok 4.6",
849 "xAI's flagship model for coding, long-running agents, knowledge work, and configurable reasoning.",
850 500_000,
851 REASONING_HIGH,
852 XAI_REASONING,
853 true,
854 false,
855 ),
856 xai_model(
857 PROVIDER_XAI,
858 "grok-4.3",
859 "Grok 4.3",
860 "xAI flagship model for chat, coding, tool use, and configurable reasoning.",
861 1_000_000,
862 REASONING_LOW,
863 XAI_CONFIGURABLE_REASONING,
864 true,
865 false,
866 ),
867 xai_model(
868 PROVIDER_XAI,
869 "grok-4.20-multi-agent-0309",
870 "Grok 4.20 Multi-Agent",
871 "xAI long-context model with agentic tool-calling and reasoning.",
872 2_000_000,
873 REASONING_LOW,
874 XAI_REASONING,
875 true,
876 false,
877 ),
878 xai_model(
879 PROVIDER_XAI,
880 "grok-4.20-0309-reasoning",
881 "Grok 4.20 Reasoning",
882 "xAI long-context reasoning model for complex tool-heavy workflows.",
883 2_000_000,
884 REASONING_LOW,
885 XAI_REASONING,
886 true,
887 false,
888 ),
889 xai_model(
890 PROVIDER_XAI,
891 "grok-4.20-0309-non-reasoning",
892 "Grok 4.20 Non-Reasoning",
893 "xAI long-context model for lower-latency non-reasoning workflows.",
894 2_000_000,
895 REASONING_NONE,
896 XAI_NO_REASONING,
897 true,
898 false,
899 ),
900 xai_model(
901 PROVIDER_SUPERGROK,
902 "grok-4.7",
903 "Grok 4.7",
904 "SuperGrok OAuth access to xAI's most capable coding and long-running agent model.",
905 500_000,
906 REASONING_HIGH,
907 XAI_REASONING,
908 true,
909 false,
910 ),
911 xai_model(
912 PROVIDER_SUPERGROK,
913 "grok-4.6",
914 "Grok 4.6",
915 "SuperGrok OAuth access to xAI's flagship coding and long-running agent model.",
916 500_000,
917 REASONING_HIGH,
918 XAI_REASONING,
919 true,
920 false,
921 ),
922 xai_model(
923 PROVIDER_SUPERGROK,
924 "grok-composer-2.5-fast",
925 "Grok Composer 2.5 Fast",
926 "SuperGrok OAuth access to xAI Composer 2.5 Fast for lower-latency agentic coding.",
927 200_000,
928 REASONING_NONE,
929 &[],
930 false,
931 false,
932 ),
933 xai_model(
934 PROVIDER_SUPERGROK,
935 "grok-4.3",
936 "Grok 4.3",
937 "SuperGrok OAuth access to xAI Grok 4.3.",
938 1_000_000,
939 REASONING_LOW,
940 XAI_CONFIGURABLE_REASONING,
941 true,
942 true,
943 ),
944 xai_model(
945 PROVIDER_SUPERGROK,
946 "grok-4.20-multi-agent-0309",
947 "Grok 4.20 Multi-Agent",
948 "SuperGrok OAuth access to xAI's long-context multi-agent model.",
949 2_000_000,
950 REASONING_LOW,
951 XAI_REASONING,
952 true,
953 true,
954 ),
955 xai_model(
956 PROVIDER_SUPERGROK,
957 "grok-4.20-0309-reasoning",
958 "Grok 4.20 Reasoning",
959 "SuperGrok OAuth access to xAI's long-context reasoning model.",
960 2_000_000,
961 REASONING_LOW,
962 XAI_REASONING,
963 true,
964 true,
965 ),
966 xai_model(
967 PROVIDER_SUPERGROK,
968 "grok-4.20-0309-non-reasoning",
969 "Grok 4.20 Non-Reasoning",
970 "SuperGrok OAuth access to xAI's long-context non-reasoning model.",
971 2_000_000,
972 REASONING_NONE,
973 XAI_NO_REASONING,
974 true,
975 true,
976 ),
977 opencode_model(
978 PROVIDER_OPENCODE,
979 "gpt-6.1-sol",
980 "GPT 6.1 Sol",
981 "OpenCode Zen GPT 6.1 Sol gateway model.",
982 1_050_000,
983 REASONING_MEDIUM,
984 STANDARD_REASONING,
985 ),
986 retired(opencode_model(
987 PROVIDER_OPENCODE,
988 "gpt-6-sol",
989 "GPT 6 Sol",
990 "OpenCode Zen GPT 6 Sol gateway model.",
991 1_050_000,
992 REASONING_MEDIUM,
993 STANDARD_REASONING,
994 )),
995 retired(opencode_model(
996 PROVIDER_OPENCODE,
997 "gpt-5.5",
998 "GPT 5.5",
999 "OpenCode Zen GPT 5.5 gateway model.",
1000 1_050_000,
1001 REASONING_MEDIUM,
1002 STANDARD_REASONING,
1003 )),
1004 retired(opencode_model(
1005 PROVIDER_OPENCODE,
1006 "gpt-5.3-codex-spark",
1007 "GPT 5.3 Codex Spark",
1008 "OpenCode Zen low-latency Codex model.",
1009 128_000,
1010 REASONING_HIGH,
1011 STANDARD_REASONING,
1012 )),
1013 opencode_model(
1014 PROVIDER_OPENCODE,
1015 "big-pickle",
1016 "Big Pickle",
1017 "OpenCode Zen free coding model.",
1018 256_000,
1019 REASONING_NONE,
1020 &[],
1021 ),
1022 opencode_model(
1023 PROVIDER_OPENCODE,
1024 "mimo-v2.5-free",
1025 "MiMo V2.5 Free",
1026 "OpenCode Zen free Xiaomi MiMo coding model.",
1027 256_000,
1028 REASONING_NONE,
1029 &[],
1030 ),
1031 opencode_model(
1032 PROVIDER_OPENCODE,
1033 "nemotron-3-ultra-free",
1034 "Nemotron 3 Ultra Free",
1035 "OpenCode Zen free Nemotron coding model.",
1036 128_000,
1037 REASONING_NONE,
1038 &[],
1039 ),
1040 opencode_model(
1041 PROVIDER_OPENCODE,
1042 "north-mini-code-free",
1043 "North Mini Code Free",
1044 "OpenCode Zen free North Mini coding model.",
1045 128_000,
1046 REASONING_NONE,
1047 &[],
1048 ),
1049 opencode_model(
1050 PROVIDER_OPENCODE,
1051 "deepseek-v4-flash",
1052 "DeepSeek V4 Flash",
1053 "OpenCode Zen DeepSeek coding model.",
1054 128_000,
1055 REASONING_HIGH,
1056 deepseek::DEEPSEEK_REASONING,
1057 ),
1058 opencode_model(
1059 PROVIDER_OPENCODE,
1060 "deepseek-v4-pro",
1061 "DeepSeek V4 Pro",
1062 "OpenCode Zen DeepSeek Pro coding model.",
1063 128_000,
1064 REASONING_HIGH,
1065 deepseek::DEEPSEEK_REASONING,
1066 ),
1067 opencode_model(
1068 PROVIDER_OPENCODE_GO,
1069 "kimi-k2.6",
1070 "Kimi K2.6",
1071 "OpenCode Go Kimi coding model.",
1072 256_000,
1073 REASONING_NONE,
1074 &[],
1075 ),
1076 opencode_model(
1077 PROVIDER_OPENCODE_GO,
1078 "qwen3.6-plus",
1079 "Qwen3.6 Plus",
1080 "OpenCode Go Qwen coding model.",
1081 256_000,
1082 REASONING_NONE,
1083 &[],
1084 ),
1085 opencode_model(
1086 PROVIDER_OPENCODE_GO,
1087 "glm-5.1",
1088 "GLM-5.1",
1089 "OpenCode Go GLM coding model.",
1090 256_000,
1091 REASONING_NONE,
1092 &[],
1093 ),
1094 opencode_model(
1095 PROVIDER_OPENCODE_GO,
1096 "deepseek-v4-flash",
1097 "DeepSeek V4 Flash",
1098 "OpenCode Go DeepSeek coding model.",
1099 128_000,
1100 REASONING_HIGH,
1101 deepseek::DEEPSEEK_REASONING,
1102 ),
1103 opencode_model(
1104 PROVIDER_OPENCODE_GO,
1105 "deepseek-v4-pro",
1106 "DeepSeek V4 Pro",
1107 "OpenCode Go DeepSeek Pro coding model.",
1108 128_000,
1109 REASONING_HIGH,
1110 deepseek::DEEPSEEK_REASONING,
1111 ),
1112 opencode_model(
1113 PROVIDER_KIMI_CODE,
1114 "kimi-for-coding",
1115 "K2.7 Code",
1116 "Kimi Code subscription coding model (OAuth via api.kimi.com/coding/v1).",
1117 262_144,
1118 REASONING_NONE,
1119 &[],
1120 ),
1121 openrouter::X_AI_GROK_4_6,
1122 openrouter::X_AI_GROK_4_7,
1123 openrouter::MOONSHOTAI_KIMI_K3,
1124 openrouter::MOONSHOTAI_KIMI_K2_7_CODE,
1125 openrouter::MOONSHOTAI_KIMI_K2_6,
1126 openrouter::ANTHROPIC_CLAUDE_OPUS_5_5,
1127 openrouter::ANTHROPIC_CLAUDE_SONNET_5_5,
1128 openrouter::ANTHROPIC_CLAUDE_HAIKU_5_5,
1129 openrouter::ANTHROPIC_CLAUDE_FABLE_5_1,
1130 openrouter::ANTHROPIC_CLAUDE_OPUS_5,
1131 openrouter::OPENAI_GPT_6_1_TERRA,
1132 openrouter::OPENAI_GPT_6_1_SOL,
1133 retired(openrouter::OPENAI_GPT_6_SOL),
1134 openrouter::OPENAI_GPT_6_LUNA,
1135 openrouter::OPENAI_GPT_6_ASTRA,
1136 openrouter::GOOGLE_GEMINI_3_8_FLASH,
1137 openrouter::GOOGLE_GEMINI_3_7_FLASH,
1138 openrouter::DEEPSEEK_DEEPSEEK_V4_PRO_0813,
1139 openrouter::DEEPSEEK_DEEPSEEK_V4_1_FLASH,
1140 openrouter::QWEN_QWEN3_8_MAX_0902,
1141 openrouter::QWEN_QWEN3_8_FLASH,
1142 openrouter::Z_AI_GLM_5_3,
1143 openrouter::Z_AI_GLM_5_3_FLASH,
1144 openrouter::XIAOMI_MIMO_V2_6_PRO,
1145 openrouter::MISTRALAI_MISTRAL_LARGE_4_0,
1146 openrouter::META_MUSE_SPARK_1_3,
1147 ModelCatalogEntry {
1148 id: "accounts/fireworks/models/qwen3-235b-a22b",
1149 display_name: "Qwen3 235B A22B",
1150 description: "Fireworks Responses-capable serverless model with client-executed function tool support.",
1151 provider: PROVIDER_FIREWORKS,
1152 default_reasoning: REASONING_NONE,
1153 supported_reasoning: &[],
1154 context_window: 131_072,
1155 max_context_window: 131_072,
1156 auto_compact_token_limit: 0,
1157 supports_compaction: false,
1158 supports_images: false,
1159 supports_tools: true,
1160 supports_structured: true,
1161 edit_tool: Some(EDIT_TOOL_PATCH),
1162 hidden: false,
1163 },
1164 roder_cloud_model(
1165 "roder.cloud/free",
1166 "Roder Free",
1167 "Free hosted model on roder.cloud.",
1168 32_768,
1169 ),
1170 retired(roder_cloud_model(
1171 "roder.cloud/openai/gpt-5.5",
1172 "GPT-5.5 (Roder Cloud)",
1173 "roder.cloud hosted route for OpenAI GPT-5.5.",
1174 400_000,
1175 )),
1176 roder_cloud_model(
1177 "roder.cloud/anthropic/claude-opus-4-7",
1178 "Claude Opus 4.7 (Roder Cloud)",
1179 "roder.cloud hosted route for Anthropic Claude Opus 4.7.",
1180 200_000,
1181 ),
1182 roder_cloud_model(
1183 "roder.cloud/google/gemini-3.1-pro-preview",
1184 "Gemini 3.1 Pro (Roder Cloud)",
1185 "roder.cloud hosted route for Google Gemini 3.1 Pro Preview.",
1186 200_000,
1187 ),
1188 poolside_model(
1189 "poolside/laguna-m.1",
1190 "Laguna M.1",
1191 "Poolside flagship agentic coding model.",
1192 REASONING_MEDIUM,
1193 ),
1194 poolside_model(
1195 "poolside/laguna-xs.2",
1196 "Laguna XS.2",
1197 "Poolside lightweight agentic coding model.",
1198 REASONING_MEDIUM,
1199 ),
1200 xiaomi_mimo::PAYG_V25_PRO,
1201 xiaomi_mimo::PAYG_V2_PRO,
1202 xiaomi_mimo::PAYG_V25,
1203 xiaomi_mimo::PAYG_V2_OMNI,
1204 xiaomi_mimo::PAYG_V2_FLASH,
1205 xiaomi_mimo::TOKEN_PLAN_V25_PRO,
1206 xiaomi_mimo::TOKEN_PLAN_V2_PRO,
1207 xiaomi_mimo::TOKEN_PLAN_V25,
1208 xiaomi_mimo::TOKEN_PLAN_V2_OMNI,
1209 xiaomi_mimo::TOKEN_PLAN_V2_FLASH,
1210 synthetic::SYN_LARGE_TEXT,
1211 synthetic::SYN_SMALL_TEXT,
1212 synthetic::SYN_LARGE_VISION,
1213 synthetic::SYN_SMALL_VISION,
1214 synthetic::HF_MINIMAX_M3,
1215 synthetic::HF_QWEN3_6_27B,
1216 synthetic::HF_KIMI_K2_6,
1217 synthetic::HF_NEMOTRON_3_SUPER,
1218 synthetic::HF_GLM_4_7,
1219 synthetic::HF_GLM_4_7_FLASH,
1220 synthetic::HF_GLM_5_1,
1221 synthetic::HF_GLM_5_2,
1222 synthetic::HF_GPT_OSS_120B,
1223 synthetic::HF_QWEN3_5_397B_A17B,
1224 deepseek::DEEPSEEK_CHAT,
1225 deepseek::DEEPSEEK_REASONER,
1226 deepseek::DEEPSEEK_V4_FLASH,
1227 deepseek::DEEPSEEK_V4_PRO,
1228 ModelCatalogEntry {
1229 id: "composer-2.5",
1230 display_name: "Composer 2.5",
1231 description: "Cursor Composer model exposed through direct AgentService inference.",
1232 provider: PROVIDER_CURSOR,
1233 default_reasoning: REASONING_NONE,
1234 supported_reasoning: &[],
1235 context_window: 200_000,
1236 max_context_window: 200_000,
1237 auto_compact_token_limit: 180_000,
1238 supports_compaction: true,
1239 supports_images: false,
1240 supports_tools: false,
1241 supports_structured: false,
1242 edit_tool: None,
1243 hidden: false,
1244 },
1245 cursor_model(
1246 "composer-2.5-fast",
1247 "Composer 2.5 Fast",
1248 "Cursor Composer 2.5 fast variant for lower-latency agent turns.",
1249 200_000,
1250 180_000,
1251 REASONING_NONE,
1252 &[],
1253 ),
1254 cursor_model(
1255 "claude-fable-5",
1256 "Claude Fable 5",
1257 "Anthropic Claude Fable 5, Anthropic's most powerful frontier model, routed through Cursor's AgentService.",
1258 1_000_000,
1259 900_000,
1260 REASONING_HIGH,
1261 OPUS_REASONING,
1262 ),
1263 cursor_model(
1264 "claude-opus-4-8",
1265 "Claude Opus 4.8",
1266 "Anthropic Claude Opus 4.8 routed through Cursor's AgentService.",
1267 1_000_000,
1268 900_000,
1269 REASONING_HIGH,
1270 OPUS_REASONING,
1271 ),
1272 cursor_model(
1273 "claude-sonnet-4-6",
1274 "Claude Sonnet 4.6",
1275 "Anthropic Claude Sonnet 4.6 routed through Cursor's AgentService.",
1276 1_000_000,
1277 900_000,
1278 REASONING_MEDIUM,
1279 SONNET_REASONING,
1280 ),
1281 retired(cursor_model(
1282 "gpt-5.5",
1283 "GPT-5.5",
1284 "OpenAI GPT-5.5 routed through Cursor's AgentService.",
1285 1_050_000,
1286 945_000,
1287 REASONING_MEDIUM,
1288 STANDARD_REASONING,
1289 )),
1290 retired(cursor_model(
1291 "gpt-5.5-fast",
1292 "GPT-5.5 Fast",
1293 "OpenAI GPT-5.5 fast variant routed through Cursor's AgentService.",
1294 1_050_000,
1295 945_000,
1296 REASONING_MEDIUM,
1297 STANDARD_REASONING,
1298 )),
1299 cursor_model(
1300 "gemini-3.1-pro",
1301 "Gemini 3.1 Pro",
1302 "Google Gemini 3.1 Pro routed through Cursor's AgentService.",
1303 1_000_000,
1304 900_000,
1305 REASONING_MEDIUM,
1306 GEMINI_REASONING,
1307 ),
1308 cursor_model(
1309 "gemini-4-argon",
1310 "Gemini 4 Argon",
1311 "Google Gemini 4 Argon routed through Cursor's AgentService for complex multi-step coding.",
1312 1_000_000,
1313 900_000,
1314 REASONING_HIGH,
1315 &[
1316 ReasoningOption {
1317 effort: REASONING_LOW,
1318 description: "Low Gemini thinking",
1319 },
1320 ReasoningOption {
1321 effort: REASONING_MEDIUM,
1322 description: "Medium Gemini thinking",
1323 },
1324 ReasoningOption {
1325 effort: REASONING_HIGH,
1326 description: "High Gemini thinking",
1327 },
1328 ],
1329 ),
1330 cursor_model(
1331 "grok-4.6",
1332 "Grok 4.6",
1333 "xAI Grok 4.6 routed through Cursor's AgentService for long-running coding and knowledge-work agents.",
1334 256_000,
1335 230_400,
1336 REASONING_HIGH,
1337 STANDARD_REASONING,
1338 ),
1339 cursor_model(
1340 "gemini-3.7-flash",
1341 "Gemini 3.7 Flash",
1342 "Google Gemini 3.7 Flash routed through Cursor's AgentService for high-throughput agentic coding.",
1343 1_000_000,
1344 900_000,
1345 REASONING_HIGH,
1346 GEMINI_REASONING,
1347 ),
1348 ModelCatalogEntry {
1349 id: "text-embedding-3-large",
1350 display_name: "Text Embedding 3 Large",
1351 description: "OpenAI embedding model for local semantic memories.",
1352 provider: PROVIDER_OPENAI,
1353 default_reasoning: REASONING_NONE,
1354 supported_reasoning: &[],
1355 context_window: 0,
1356 max_context_window: 0,
1357 auto_compact_token_limit: 0,
1358 supports_compaction: false,
1359 supports_images: false,
1360 supports_tools: true,
1361 supports_structured: false,
1362 edit_tool: None,
1363 hidden: true,
1364 },
1365 ModelCatalogEntry {
1366 id: "gemini-embedding-2",
1367 display_name: "Gemini Embedding 2",
1368 description: "Google Gemini embedding model for local semantic memories.",
1369 provider: PROVIDER_GOOGLE,
1370 default_reasoning: REASONING_NONE,
1371 supported_reasoning: &[],
1372 context_window: 0,
1373 max_context_window: 0,
1374 auto_compact_token_limit: 0,
1375 supports_compaction: false,
1376 supports_images: false,
1377 supports_tools: false,
1378 supports_structured: false,
1379 edit_tool: None,
1380 hidden: true,
1381 },
1382 ModelCatalogEntry {
1383 id: "zembed-1",
1384 display_name: "ZeroEntropy zembed-1",
1385 description: "ZeroEntropy embedding model for local semantic memories.",
1386 provider: PROVIDER_ZEROENTROPY,
1387 default_reasoning: REASONING_NONE,
1388 supported_reasoning: &[],
1389 context_window: 0,
1390 max_context_window: 0,
1391 auto_compact_token_limit: 0,
1392 supports_compaction: false,
1393 supports_images: false,
1394 supports_tools: false,
1395 supports_structured: false,
1396 edit_tool: None,
1397 hidden: true,
1398 },
1399 ModelCatalogEntry {
1400 id: "mock",
1401 display_name: "Mock",
1402 description: "Local deterministic mock provider for tests and offline development.",
1403 provider: PROVIDER_MOCK,
1404 default_reasoning: REASONING_NONE,
1405 supported_reasoning: MOCK_REASONING,
1406 context_window: 128_000,
1407 max_context_window: 128_000,
1408 auto_compact_token_limit: 115_200,
1409 supports_compaction: false,
1410 supports_images: false,
1411 supports_tools: true,
1412 supports_structured: false,
1413 edit_tool: None,
1414 hidden: true,
1415 },
1416];
1417
1418const fn openai_model(
1419 id: &'static str,
1420 display_name: &'static str,
1421 description: &'static str,
1422 context_window: u32,
1423 auto_compact_token_limit: u32,
1424 supports_compaction: bool,
1425 supported_reasoning: &'static [ReasoningOption],
1426) -> ModelCatalogEntry {
1427 ModelCatalogEntry {
1428 id,
1429 display_name,
1430 description,
1431 provider: PROVIDER_OPENAI,
1432 default_reasoning: REASONING_MEDIUM,
1433 supported_reasoning,
1434 context_window,
1435 max_context_window: context_window,
1436 auto_compact_token_limit,
1437 supports_compaction,
1438 supports_images: false,
1439 supports_tools: true,
1440 supports_structured: false,
1441 edit_tool: Some("patch"),
1442 hidden: false,
1443 }
1444}
1445
1446#[allow(clippy::too_many_arguments)]
1447const fn anthropic_model(
1448 id: &'static str,
1449 display_name: &'static str,
1450 description: &'static str,
1451 context_window: u32,
1452 auto_compact_token_limit: u32,
1453 default_reasoning: &'static str,
1454 supported_reasoning: &'static [ReasoningOption],
1455 supports_compaction: bool,
1466) -> ModelCatalogEntry {
1467 ModelCatalogEntry {
1468 id,
1469 display_name,
1470 description,
1471 provider: PROVIDER_ANTHROPIC,
1472 default_reasoning,
1473 supported_reasoning,
1474 context_window,
1475 max_context_window: context_window,
1476 auto_compact_token_limit,
1477 supports_compaction,
1478 supports_images: false,
1479 supports_tools: true,
1480 supports_structured: false,
1481 edit_tool: Some("edit"),
1482 hidden: false,
1483 }
1484}
1485
1486const fn claude_code_model(
1487 id: &'static str,
1488 display_name: &'static str,
1489 description: &'static str,
1490 context_window: u32,
1491 auto_compact_token_limit: u32,
1492 default_reasoning: &'static str,
1493 supported_reasoning: &'static [ReasoningOption],
1494) -> ModelCatalogEntry {
1495 ModelCatalogEntry {
1496 id,
1497 display_name,
1498 description,
1499 provider: PROVIDER_CLAUDE_CODE,
1500 default_reasoning,
1501 supported_reasoning,
1502 context_window,
1503 max_context_window: context_window,
1504 auto_compact_token_limit,
1505 supports_compaction: false,
1511 supports_images: true,
1512 supports_tools: true,
1513 supports_structured: false,
1514 edit_tool: Some(EDIT_TOOL_EDIT),
1515 hidden: false,
1516 }
1517}
1518
1519const fn gemini_model(
1520 provider: &'static str,
1521 id: &'static str,
1522 display_name: &'static str,
1523 description: &'static str,
1524 default_reasoning: &'static str,
1525) -> ModelCatalogEntry {
1526 ModelCatalogEntry {
1527 id,
1528 display_name,
1529 description,
1530 provider,
1531 default_reasoning,
1532 supported_reasoning: GEMINI_REASONING,
1533 context_window: 1_048_576,
1534 max_context_window: 1_048_576,
1535 auto_compact_token_limit: 943_718,
1536 supports_compaction: false,
1537 supports_images: true,
1538 supports_tools: true,
1539 supports_structured: true,
1540 edit_tool: Some("edit"),
1541 hidden: false,
1542 }
1543}
1544
1545const fn xai_model(
1546 provider: &'static str,
1547 id: &'static str,
1548 display_name: &'static str,
1549 description: &'static str,
1550 context_window: u32,
1551 default_reasoning: &'static str,
1552 supported_reasoning: &'static [ReasoningOption],
1553 supports_images: bool,
1554 hidden: bool,
1555) -> ModelCatalogEntry {
1556 ModelCatalogEntry {
1557 id,
1558 display_name,
1559 description,
1560 provider,
1561 default_reasoning,
1562 supported_reasoning,
1563 context_window,
1564 max_context_window: context_window,
1565 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1566 supports_compaction: false,
1567 supports_images,
1568 supports_tools: true,
1569 supports_structured: true,
1570 edit_tool: Some("edit"),
1571 hidden,
1572 }
1573}
1574
1575const fn opencode_model(
1576 provider: &'static str,
1577 id: &'static str,
1578 display_name: &'static str,
1579 description: &'static str,
1580 context_window: u32,
1581 default_reasoning: &'static str,
1582 supported_reasoning: &'static [ReasoningOption],
1583) -> ModelCatalogEntry {
1584 ModelCatalogEntry {
1585 id,
1586 display_name,
1587 description,
1588 provider,
1589 default_reasoning,
1590 supported_reasoning,
1591 context_window,
1592 max_context_window: context_window,
1593 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1594 supports_compaction: false,
1595 supports_images: false,
1596 supports_tools: true,
1597 supports_structured: true,
1598 edit_tool: Some("edit"),
1599 hidden: false,
1600 }
1601}
1602
1603const fn roder_cloud_model(
1604 id: &'static str,
1605 display_name: &'static str,
1606 description: &'static str,
1607 context_window: u32,
1608) -> ModelCatalogEntry {
1609 ModelCatalogEntry {
1610 id,
1611 display_name,
1612 description,
1613 provider: PROVIDER_RODER_CLOUD,
1614 default_reasoning: REASONING_NONE,
1615 supported_reasoning: RODER_CLOUD_REASONING,
1616 context_window,
1617 max_context_window: context_window,
1618 auto_compact_token_limit: 0,
1619 supports_compaction: false,
1620 supports_images: false,
1621 supports_tools: false,
1622 supports_structured: false,
1623 edit_tool: None,
1624 hidden: false,
1625 }
1626}
1627
1628const fn poolside_model(
1629 id: &'static str,
1630 display_name: &'static str,
1631 description: &'static str,
1632 default_reasoning: &'static str,
1633) -> ModelCatalogEntry {
1634 ModelCatalogEntry {
1635 id,
1636 display_name,
1637 description,
1638 provider: PROVIDER_POOLSIDE,
1639 default_reasoning,
1640 supported_reasoning: POOLSIDE_REASONING,
1641 context_window: 131_072,
1642 max_context_window: 131_072,
1643 auto_compact_token_limit: 117_964,
1644 supports_compaction: false,
1645 supports_images: false,
1646 supports_tools: true,
1647 supports_structured: true,
1648 edit_tool: Some("edit"),
1649 hidden: false,
1650 }
1651}
1652
1653const fn cursor_model(
1654 id: &'static str,
1655 display_name: &'static str,
1656 description: &'static str,
1657 context_window: u32,
1658 auto_compact_token_limit: u32,
1659 default_reasoning: &'static str,
1660 supported_reasoning: &'static [ReasoningOption],
1661) -> ModelCatalogEntry {
1662 ModelCatalogEntry {
1663 id,
1664 display_name,
1665 description,
1666 provider: PROVIDER_CURSOR,
1667 default_reasoning,
1668 supported_reasoning,
1669 context_window,
1670 max_context_window: context_window,
1671 auto_compact_token_limit,
1672 supports_compaction: true,
1673 supports_images: true,
1677 supports_tools: false,
1678 supports_structured: false,
1679 edit_tool: None,
1680 hidden: false,
1681 }
1682}
1683
1684pub fn built_in_providers() -> &'static [ProviderCatalogEntry] {
1685 BUILT_IN_PROVIDERS
1686}
1687
1688pub fn built_in_models(include_hidden: bool) -> Vec<&'static ModelCatalogEntry> {
1689 BUILT_IN_MODELS
1690 .iter()
1691 .filter(|model| include_hidden || !model.hidden)
1692 .collect()
1693}
1694
1695pub fn models_for_provider(provider: &str, include_hidden: bool) -> Vec<ModelDescriptor> {
1696 built_in_models(include_hidden)
1697 .into_iter()
1698 .filter(|model| model.provider == provider)
1699 .map(ModelDescriptor::from)
1700 .collect()
1701}
1702
1703pub fn models_for_codex(include_hidden: bool) -> Vec<ModelDescriptor> {
1704 built_in_models(include_hidden)
1705 .into_iter()
1706 .filter(|model| model.provider == PROVIDER_OPENAI || model.provider == PROVIDER_CODEX)
1707 .map(ModelDescriptor::from)
1708 .collect()
1709}
1710
1711pub fn lookup_model(id: &str) -> Option<&'static ModelCatalogEntry> {
1712 BUILT_IN_MODELS.iter().find(|model| model.id == id)
1713}
1714
1715pub fn lookup_model_for_provider(provider: &str, id: &str) -> Option<&'static ModelCatalogEntry> {
1725 BUILT_IN_MODELS
1726 .iter()
1727 .find(|model| model.provider == provider && model.id == id)
1728 .or_else(|| lookup_model(id))
1729}
1730
1731pub fn built_in_model_profile(id: &str) -> Option<ModelHarnessProfile> {
1732 lookup_model(id).map(model_harness_profile_from_catalog)
1733}
1734
1735pub fn built_in_model_profile_for_provider(
1741 provider: &str,
1742 id: &str,
1743) -> Option<ModelHarnessProfile> {
1744 lookup_model_for_provider(provider, id).map(model_harness_profile_from_catalog)
1745}
1746
1747pub fn built_in_model_profiles() -> Vec<ModelHarnessProfile> {
1748 built_in_models(true)
1749 .into_iter()
1750 .map(model_harness_profile_from_catalog)
1751 .collect()
1752}
1753
1754fn model_harness_profile_from_catalog(model: &ModelCatalogEntry) -> ModelHarnessProfile {
1755 let provider_family = provider_family_for_provider(model.provider);
1756 ModelHarnessProfile {
1757 model: model.id.to_string(),
1758 provider: model.provider.to_string(),
1759 provider_family,
1760 edit_tool: model.edit_tool.map(str::to_string),
1761 schema_policy: schema_policy_for_family(provider_family),
1762 instruction_overlay: instruction_overlay_for_family(provider_family),
1763 reasoning: ModelProfileReasoning {
1764 orientation: Some(model.default_reasoning.to_string()),
1765 execution: Some(default_execution_reasoning(model)),
1766 verification: Some(model.default_reasoning.to_string()),
1767 recovery: Some(model.default_reasoning.to_string()),
1768 },
1769 parallel_tool_calls: Some(
1770 model.supports_tools
1771 && matches!(
1772 provider_family,
1773 ProviderFamily::OpenAi | ProviderFamily::Xai | ProviderFamily::Opencode
1774 ),
1775 ),
1776 auto_compact_token_limit: (model.auto_compact_token_limit > 0)
1777 .then_some(model.auto_compact_token_limit),
1778 }
1779}
1780
1781pub fn provider_family_for_provider(provider: &str) -> ProviderFamily {
1782 match provider {
1783 PROVIDER_OPENAI | PROVIDER_CODEX => ProviderFamily::OpenAi,
1784 PROVIDER_ANTHROPIC | PROVIDER_CLAUDE_CODE => ProviderFamily::Anthropic,
1785 PROVIDER_GEMINI | PROVIDER_VERTEX => ProviderFamily::Gemini,
1786 PROVIDER_XAI | PROVIDER_SUPERGROK => ProviderFamily::Xai,
1787 PROVIDER_OPENCODE | PROVIDER_OPENCODE_GO => ProviderFamily::Opencode,
1788 PROVIDER_OPENROUTER | PROVIDER_FIREWORKS | PROVIDER_RODER_CLOUD => ProviderFamily::OpenAi,
1789 PROVIDER_POOLSIDE => ProviderFamily::Poolside,
1790 PROVIDER_CURSOR => ProviderFamily::Cursor,
1791 PROVIDER_XIAOMI_MIMO | PROVIDER_XIAOMI_MIMO_TOKEN_PLAN => ProviderFamily::OpenAi,
1792 PROVIDER_KIMI_CODE => ProviderFamily::OpenAi,
1793 PROVIDER_SYNTHETIC => ProviderFamily::OpenAi,
1794 PROVIDER_DEEPSEEK => ProviderFamily::OpenAi,
1795 _ => ProviderFamily::Mock,
1796 }
1797}
1798
1799fn schema_policy_for_family(family: ProviderFamily) -> ModelSchemaPolicy {
1800 match family {
1801 ProviderFamily::OpenAi => ModelSchemaPolicy::RequiredFirstFlat,
1802 _ => ModelSchemaPolicy::StandardRequiredFirst,
1803 }
1804}
1805
1806fn instruction_overlay_for_family(family: ProviderFamily) -> ModelInstructionOverlay {
1807 match family {
1808 ProviderFamily::OpenAi => ModelInstructionOverlay::LiteralToolOutputs,
1809 ProviderFamily::Anthropic | ProviderFamily::Gemini => {
1810 ModelInstructionOverlay::IntuitiveContext
1811 }
1812 _ => ModelInstructionOverlay::Standard,
1813 }
1814}
1815
1816fn default_execution_reasoning(model: &ModelCatalogEntry) -> String {
1817 if model
1818 .supported_reasoning
1819 .iter()
1820 .any(|option| option.effort == REASONING_LOW)
1821 {
1822 REASONING_LOW.to_string()
1823 } else {
1824 model.default_reasoning.to_string()
1825 }
1826}
1827
1828pub fn model_supports_reasoning_effort(model: &str, effort: &str) -> bool {
1829 lookup_model(model)
1830 .map(|entry| {
1831 entry
1832 .supported_reasoning
1833 .iter()
1834 .any(|option| option.effort == effort)
1835 })
1836 .unwrap_or(false)
1837}
1838
1839pub fn normalize_provider_id(provider: &str) -> String {
1840 match provider.trim().to_ascii_lowercase().as_str() {
1841 "grok" | "x-ai" | "x.ai" => PROVIDER_XAI.to_string(),
1842 "grok-oauth" | "xai-oauth" | "x-ai-oauth" | "xai-grok-oauth" => {
1843 PROVIDER_SUPERGROK.to_string()
1844 }
1845 "opencode" => PROVIDER_OPENCODE.to_string(),
1846 "go" | "opencode_go" | "opencode-go" => PROVIDER_OPENCODE_GO.to_string(),
1847 "openrouter" => PROVIDER_OPENROUTER.to_string(),
1848 "fireworks" | "fireworks-ai" | "fireworks_ai" => PROVIDER_FIREWORKS.to_string(),
1849 "roder-cloud" | "roder_cloud" | "rodercloud" | "roder.cloud" => {
1850 PROVIDER_RODER_CLOUD.to_string()
1851 }
1852 "laguna" | "poolside" => PROVIDER_POOLSIDE.to_string(),
1853 "composer" | "cursor-composer" => PROVIDER_CURSOR.to_string(),
1854 "claude_code" | "claudecode" => PROVIDER_CLAUDE_CODE.to_string(),
1855 "kimi" | "kimi-code" | "kimi_code" | "moonshot" => PROVIDER_KIMI_CODE.to_string(),
1856 "synthetic" | "synthetic-ai" | "synthetic_ai" | "synthetic.new" => {
1857 PROVIDER_SYNTHETIC.to_string()
1858 }
1859 "deepseek" | "deepseek-platform" | "deepseek_platform" => PROVIDER_DEEPSEEK.to_string(),
1860 provider => provider.to_string(),
1861 }
1862}
1863
1864impl From<&ModelCatalogEntry> for ModelDescriptor {
1865 fn from(model: &ModelCatalogEntry) -> Self {
1866 let supported_reasoning = model
1867 .supported_reasoning
1868 .iter()
1869 .map(|option| ReasoningEffortDescriptor {
1870 effort: option.effort.to_string(),
1871 description: option.description.to_string(),
1872 })
1873 .collect::<Vec<_>>();
1874 Self {
1875 id: model.id.to_string(),
1876 name: model.display_name.to_string(),
1877 context_window: (model.context_window > 0).then_some(model.context_window),
1878 default_reasoning: (!supported_reasoning.is_empty())
1879 .then(|| model.default_reasoning.to_string()),
1880 supported_reasoning,
1881 }
1882 }
1883}
1884
1885#[cfg(test)]
1886mod tests {
1887 use super::*;
1888
1889 #[test]
1890 fn openrouter_catalog_entries_are_well_formed() {
1891 let entries = BUILT_IN_MODELS
1892 .iter()
1893 .filter(|model| model.provider == PROVIDER_OPENROUTER)
1894 .collect::<Vec<_>>();
1895 assert!(!entries.is_empty());
1896 for model in entries {
1897 assert!(
1898 model.id.contains('/'),
1899 "{} is not an OpenRouter slug",
1900 model.id
1901 );
1902 assert!(
1903 model.context_window > 0,
1904 "{} has no context window",
1905 model.id
1906 );
1907 assert_eq!(
1908 model.max_context_window, model.context_window,
1909 "{}",
1910 model.id
1911 );
1912 assert!(
1913 model.auto_compact_token_limit > 0
1914 && model.auto_compact_token_limit < model.context_window,
1915 "{} compaction threshold must sit inside its window",
1916 model.id
1917 );
1918 assert!(
1921 !model.supports_compaction,
1922 "{} must compact client-side",
1923 model.id
1924 );
1925 assert!(model.supports_tools, "{} must support tools", model.id);
1926 }
1927 }
1928
1929 #[test]
1930 fn openrouter_catalog_resolves_kimi_k3_with_live_limits() {
1931 let k3 = lookup_model_for_provider(PROVIDER_OPENROUTER, "moonshotai/kimi-k3")
1932 .expect("kimi k3 is catalogued");
1933 assert_eq!(k3.context_window, 1_048_576);
1934 assert!(k3.supports_images);
1935 assert!(!k3.supported_reasoning.is_empty());
1936 assert_eq!(k3.auto_compact_token_limit, 943_718);
1937 }
1938
1939 #[test]
1940 fn catalog_contains_gode_providers() {
1941 let ids = BUILT_IN_PROVIDERS
1942 .iter()
1943 .map(|provider| provider.id)
1944 .collect::<Vec<_>>();
1945 assert_eq!(
1946 ids,
1947 vec![
1948 "mock",
1949 "openai",
1950 "codex",
1951 "anthropic",
1952 "claude-code",
1953 "gemini",
1954 "vertex",
1955 "xai",
1956 "supergrok",
1957 "opencode",
1958 "opencode-go",
1959 "openrouter",
1960 "fireworks",
1961 "roder-cloud",
1962 "poolside",
1963 "cursor",
1964 "xiaomi-mimo",
1965 "xiaomi-mimo-token-plan",
1966 "synthetic",
1967 "deepseek",
1968 "kimi-code"
1969 ]
1970 );
1971 }
1972
1973 #[test]
1974 fn gemini_provider_defaults_to_stable_35_flash() {
1975 let provider = BUILT_IN_PROVIDERS
1976 .iter()
1977 .find(|provider| provider.id == PROVIDER_GEMINI)
1978 .unwrap();
1979
1980 assert_eq!(provider.default_model, "gemini-3.5-flash");
1981
1982 let model = lookup_model("gemini-3.5-flash").unwrap();
1983 assert_eq!(model.display_name, "Gemini 3.5 Flash");
1984 assert_eq!(model.provider, PROVIDER_GEMINI);
1985 assert_eq!(model.context_window, 1_048_576);
1986 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
1987 assert!(model.supports_tools);
1988 assert!(model.supports_structured);
1989 assert_eq!(
1990 model
1991 .supported_reasoning
1992 .iter()
1993 .map(|option| option.effort)
1994 .collect::<Vec<_>>(),
1995 vec![
1996 REASONING_MINIMAL,
1997 REASONING_LOW,
1998 REASONING_MEDIUM,
1999 REASONING_HIGH
2000 ]
2001 );
2002 }
2003
2004 #[test]
2005 fn vertex_provider_mirrors_gemini_models_under_vertex_id() {
2006 let provider = BUILT_IN_PROVIDERS
2007 .iter()
2008 .find(|provider| provider.id == PROVIDER_VERTEX)
2009 .unwrap();
2010
2011 assert_eq!(provider.default_model, "gemini-3.5-flash");
2012 assert_eq!(provider.env_key, Some("GOOGLE_APPLICATION_CREDENTIALS"));
2013 assert_eq!(provider.env_aliases, &["VERTEX_CREDENTIALS_JSON"]);
2014
2015 let model = lookup_model_for_provider(PROVIDER_VERTEX, "gemini-3.5-flash").unwrap();
2016 assert_eq!(model.provider, PROVIDER_VERTEX);
2017 assert_eq!(model.context_window, 1_048_576);
2018 assert!(model.supports_tools);
2019 assert_eq!(
2020 provider_family_for_provider(PROVIDER_VERTEX),
2021 ProviderFamily::Gemini
2022 );
2023 }
2024
2025 #[test]
2026 fn gemini_38_flash_is_offered_on_both_google_providers() {
2027 for provider in [PROVIDER_GEMINI, PROVIDER_VERTEX] {
2028 let model = lookup_model_for_provider(provider, "gemini-3.8-flash").unwrap();
2029 assert_eq!(model.provider, provider);
2030 assert_eq!(model.display_name, "Gemini 3.8 Flash");
2031 assert_eq!(model.context_window, 1_048_576);
2032 assert_eq!(model.auto_compact_token_limit, 943_718);
2033 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
2034 assert!(model.supports_tools);
2035 assert!(model.supports_structured);
2036 assert!(model.supports_images);
2037 assert!(!model.hidden);
2038 }
2039 }
2040
2041 #[test]
2042 fn catalog_contains_gode_visible_models() {
2043 let ids = built_in_models(false)
2044 .into_iter()
2045 .map(|model| model.id)
2046 .collect::<Vec<_>>();
2047 assert_eq!(
2048 ids,
2049 vec![
2050 "gpt-6.1-terra",
2051 "gpt-6-astra",
2052 "gpt-6.1-sol",
2053 "gpt-6-luna",
2054 "claude-opus-5-5",
2055 "claude-sonnet-5",
2056 "claude-sonnet-5-5",
2057 "claude-fable-5-1",
2058 "claude-fable-5",
2059 "claude-opus-4-8",
2060 "claude-opus-4-7",
2061 "claude-sonnet-4-6",
2062 "claude-haiku-4-5-20251001",
2063 "claude-haiku-5-5",
2064 "claude-opus-5-5",
2065 "claude-sonnet-5",
2066 "claude-sonnet-5-5",
2067 "fable",
2068 "sonnet",
2069 "opus",
2070 "haiku",
2071 "claude-haiku-5-5",
2072 "claude-sonnet-4-6",
2073 "claude-opus-4-8",
2074 "claude-fable-5",
2075 "claude-fable-5-1",
2076 "gemini-3.8-flash",
2077 "gemini-3.5-flash",
2078 "gemini-3.7-flash",
2079 "gemini-3.1-pro-preview",
2080 "gemini-3.1-pro-preview-customtools",
2081 "gemini-3-flash-preview",
2082 "gemini-3.1-flash-lite-preview",
2083 "gemini-3.8-flash",
2084 "gemini-3.5-flash",
2085 "gemini-3.7-flash",
2086 "gemini-3.1-pro-preview",
2087 "gemini-3-flash-preview",
2088 "gemini-3.1-flash-lite-preview",
2089 "grok-4.7",
2090 "grok-4.6",
2091 "grok-4.3",
2092 "grok-4.20-multi-agent-0309",
2093 "grok-4.20-0309-reasoning",
2094 "grok-4.20-0309-non-reasoning",
2095 "grok-4.7",
2096 "grok-4.6",
2097 "grok-composer-2.5-fast",
2098 "gpt-6.1-sol",
2099 "big-pickle",
2100 "mimo-v2.5-free",
2101 "nemotron-3-ultra-free",
2102 "north-mini-code-free",
2103 "deepseek-v4-flash",
2104 "deepseek-v4-pro",
2105 "kimi-k2.6",
2106 "qwen3.6-plus",
2107 "glm-5.1",
2108 "deepseek-v4-flash",
2109 "deepseek-v4-pro",
2110 "kimi-for-coding",
2111 "x-ai/grok-4.6",
2112 "x-ai/grok-4.7",
2113 "moonshotai/kimi-k3",
2114 "moonshotai/kimi-k2.7-code",
2115 "moonshotai/kimi-k2.6",
2116 "anthropic/claude-opus-5.5",
2117 "anthropic/claude-sonnet-5.5",
2118 "anthropic/claude-haiku-5.5",
2119 "anthropic/claude-fable-5.1",
2120 "anthropic/claude-opus-5",
2121 "openai/gpt-6.1-terra",
2122 "openai/gpt-6.1-sol",
2123 "openai/gpt-6-luna",
2124 "openai/gpt-6-astra",
2125 "google/gemini-3.8-flash",
2126 "google/gemini-3.7-flash",
2127 "deepseek/deepseek-v4-pro-0813",
2128 "deepseek/deepseek-v4.1-flash",
2129 "qwen/qwen3.8-max-0902",
2130 "qwen/qwen3.8-flash",
2131 "z-ai/glm-5.3",
2132 "z-ai/glm-5.3-flash",
2133 "xiaomi/mimo-v2.6-pro",
2134 "mistralai/mistral-large-4-0",
2135 "meta/muse-spark-1.3",
2136 "accounts/fireworks/models/qwen3-235b-a22b",
2137 "roder.cloud/free",
2138 "roder.cloud/anthropic/claude-opus-4-7",
2139 "roder.cloud/google/gemini-3.1-pro-preview",
2140 "poolside/laguna-m.1",
2141 "poolside/laguna-xs.2",
2142 "mimo-v2.5-pro",
2143 "mimo-v2-pro",
2144 "mimo-v2.5",
2145 "mimo-v2-omni",
2146 "mimo-v2-flash",
2147 "mimo-v2.5-pro",
2148 "mimo-v2-pro",
2149 "mimo-v2.5",
2150 "mimo-v2-omni",
2151 "mimo-v2-flash",
2152 "syn:large:text",
2153 "syn:small:text",
2154 "syn:large:vision",
2155 "syn:small:vision",
2156 "hf:MiniMaxAI/MiniMax-M3",
2157 "hf:Qwen/Qwen3.6-27B",
2158 "hf:moonshotai/Kimi-K2.6",
2159 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
2160 "hf:zai-org/GLM-4.7",
2161 "hf:zai-org/GLM-4.7-Flash",
2162 "hf:zai-org/GLM-5.1",
2163 "hf:zai-org/GLM-5.2",
2164 "hf:openai/gpt-oss-120b",
2165 "hf:Qwen/Qwen3.5-397B-A17B",
2166 "deepseek-chat",
2167 "deepseek-reasoner",
2168 "deepseek-v4-flash",
2169 "deepseek-v4-pro",
2170 "composer-2.5",
2171 "composer-2.5-fast",
2172 "claude-fable-5",
2173 "claude-opus-4-8",
2174 "claude-sonnet-4-6",
2175 "gemini-3.1-pro",
2176 "gemini-4-argon",
2177 "grok-4.6",
2178 "gemini-3.7-flash",
2179 ]
2180 );
2181 }
2182
2183 #[test]
2184 fn provider_model_lists_match_gode_catalog() {
2185 assert_eq!(models_for_provider(PROVIDER_OPENAI, false).len(), 4);
2186 assert_eq!(models_for_codex(false).len(), 4);
2187 assert_eq!(models_for_provider(PROVIDER_ANTHROPIC, false).len(), 10);
2188 assert_eq!(models_for_provider(PROVIDER_CLAUDE_CODE, false).len(), 12);
2189 assert_eq!(models_for_provider(PROVIDER_GEMINI, false).len(), 7);
2190 assert_eq!(models_for_provider(PROVIDER_VERTEX, false).len(), 6);
2191 assert_eq!(models_for_provider(PROVIDER_XAI, false).len(), 6);
2192 assert_eq!(models_for_provider(PROVIDER_SUPERGROK, false).len(), 3);
2193 assert_eq!(models_for_provider(PROVIDER_OPENCODE, false).len(), 7);
2194 assert_eq!(models_for_provider(PROVIDER_OPENCODE_GO, false).len(), 5);
2195 assert_eq!(models_for_provider(PROVIDER_OPENROUTER, false).len(), 25);
2196 assert_eq!(models_for_provider(PROVIDER_FIREWORKS, false).len(), 1);
2197 assert_eq!(models_for_provider(PROVIDER_RODER_CLOUD, false).len(), 3);
2198 assert_eq!(models_for_provider(PROVIDER_POOLSIDE, false).len(), 2);
2199 assert_eq!(models_for_provider(PROVIDER_CURSOR, false).len(), 9);
2200 assert_eq!(models_for_provider(PROVIDER_XIAOMI_MIMO, false).len(), 5);
2201 assert_eq!(
2202 models_for_provider(PROVIDER_XIAOMI_MIMO_TOKEN_PLAN, false).len(),
2203 5
2204 );
2205 assert_eq!(models_for_provider(PROVIDER_KIMI_CODE, false).len(), 1);
2206 assert_eq!(models_for_provider(PROVIDER_SYNTHETIC, false).len(), 14);
2207 assert_eq!(models_for_provider(PROVIDER_DEEPSEEK, false).len(), 4);
2208 assert_eq!(models_for_provider(PROVIDER_MOCK, true).len(), 1);
2209 }
2210
2211 #[test]
2212 fn codex_model_list_matches_current_subscription_roster() {
2213 let codex_provider = built_in_providers()
2214 .iter()
2215 .find(|provider| provider.id == PROVIDER_CODEX)
2216 .expect("codex provider");
2217 assert_eq!(codex_provider.default_model, "gpt-6.1-sol");
2218
2219 let ids = models_for_codex(false)
2220 .into_iter()
2221 .map(|model| model.id)
2222 .collect::<Vec<_>>();
2223
2224 assert_eq!(
2225 ids,
2226 vec!["gpt-6.1-terra", "gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna"]
2227 );
2228 }
2229
2230 #[test]
2231 fn new_codex_models_match_current_subscription_metadata() {
2232 let assert_model = |id: &str,
2233 name: &str,
2234 description: &str,
2235 default_reasoning: &str,
2236 efforts: &[&str],
2237 context_window: u32,
2238 max_context_window: u32| {
2239 let model = lookup_model_for_provider(PROVIDER_OPENAI, id).unwrap();
2240
2241 assert_eq!(model.display_name, name, "{id} display name");
2242 assert_eq!(model.description, description, "{id} description");
2243 assert_eq!(model.provider, PROVIDER_OPENAI, "{id} provider");
2244 assert_eq!(
2245 model.default_reasoning, default_reasoning,
2246 "{id} default reasoning"
2247 );
2248 assert_eq!(
2249 model
2250 .supported_reasoning
2251 .iter()
2252 .map(|option| option.effort)
2253 .collect::<Vec<_>>(),
2254 efforts,
2255 "{id} efforts"
2256 );
2257 assert_eq!(model.context_window, context_window, "{id} context window");
2258 assert_eq!(
2259 model.max_context_window, max_context_window,
2260 "{id} max context window"
2261 );
2262 assert_eq!(
2263 model.auto_compact_token_limit,
2264 context_window.saturating_mul(9) / 10,
2265 "{id} auto compact limit"
2266 );
2267 assert!(model.supports_compaction, "{id} compaction support");
2268 assert!(model.supports_images, "{id} image support");
2269 assert!(model.supports_tools, "{id} tool support");
2270 let offered = matches!(
2272 id,
2273 "gpt-6-astra" | "gpt-6-luna" | "gpt-6.1-sol" | "gpt-6.1-terra"
2274 );
2275 assert_eq!(model.hidden, !offered, "{id} visibility");
2276 };
2277
2278 assert_model(
2279 "gpt-6-astra",
2280 "GPT-6-Astra",
2281 "OpenAI's most capable model, built for the hardest end-to-end work.",
2282 REASONING_HIGH,
2283 &[
2284 REASONING_LOW,
2285 REASONING_MEDIUM,
2286 REASONING_HIGH,
2287 REASONING_XHIGH,
2288 REASONING_MAX,
2289 ],
2290 1_050_000,
2291 1_050_000,
2292 );
2293 assert_model(
2294 "gpt-6-sol",
2295 "GPT-6 Sol",
2296 "Agentic coding model balancing intelligence and cost.",
2297 REASONING_MEDIUM,
2298 &[
2299 REASONING_NONE,
2300 REASONING_LOW,
2301 REASONING_MEDIUM,
2302 REASONING_HIGH,
2303 REASONING_XHIGH,
2304 REASONING_MAX,
2305 ],
2306 1_050_000,
2307 1_050_000,
2308 );
2309 assert_model(
2310 "gpt-6.1-terra",
2311 "GPT-6.1 Terra",
2312 "Fast computer-use and coding model with a 1M context window. $1 / $5 per 1M input/output tokens.",
2313 REASONING_MEDIUM,
2314 &[
2315 REASONING_NONE,
2316 REASONING_LOW,
2317 REASONING_MEDIUM,
2318 REASONING_HIGH,
2319 REASONING_XHIGH,
2320 REASONING_MAX,
2321 ],
2322 1_000_000,
2323 1_000_000,
2324 );
2325 assert_model(
2326 "gpt-6.1-sol",
2327 "GPT-6.1-Sol",
2328 "Agentic coding model balancing intelligence and cost.",
2329 REASONING_MEDIUM,
2330 &[
2331 REASONING_NONE,
2332 REASONING_LOW,
2333 REASONING_MEDIUM,
2334 REASONING_HIGH,
2335 REASONING_XHIGH,
2336 REASONING_MAX,
2337 ],
2338 1_050_000,
2339 1_050_000,
2340 );
2341 assert_model(
2342 "gpt-6-luna",
2343 "GPT-6 Luna",
2344 "Efficient model for focused, high-volume tasks.",
2345 REASONING_MEDIUM,
2346 &[
2347 REASONING_NONE,
2348 REASONING_LOW,
2349 REASONING_MEDIUM,
2350 REASONING_HIGH,
2351 REASONING_XHIGH,
2352 REASONING_MAX,
2353 ],
2354 1_050_000,
2355 1_050_000,
2356 );
2357 assert_model(
2358 "gpt-5.6-sol",
2359 "GPT-5.6-Sol",
2360 "Latest frontier agentic coding model.",
2361 REASONING_LOW,
2362 &[
2363 REASONING_LOW,
2364 REASONING_MEDIUM,
2365 REASONING_HIGH,
2366 REASONING_XHIGH,
2367 REASONING_MAX,
2368 REASONING_ULTRA,
2369 ],
2370 372_000,
2371 372_000,
2372 );
2373 assert_model(
2374 "gpt-5.6-terra",
2375 "GPT-5.6-Terra",
2376 "Balanced agentic coding model for everyday work.",
2377 REASONING_MEDIUM,
2378 &[
2379 REASONING_LOW,
2380 REASONING_MEDIUM,
2381 REASONING_HIGH,
2382 REASONING_XHIGH,
2383 REASONING_MAX,
2384 REASONING_ULTRA,
2385 ],
2386 372_000,
2387 372_000,
2388 );
2389 assert_model(
2390 "gpt-5.6-luna",
2391 "GPT-5.6-Luna",
2392 "Fast and affordable agentic coding model.",
2393 REASONING_MEDIUM,
2394 &[
2395 REASONING_LOW,
2396 REASONING_MEDIUM,
2397 REASONING_HIGH,
2398 REASONING_XHIGH,
2399 REASONING_MAX,
2400 ],
2401 372_000,
2402 372_000,
2403 );
2404 assert_model(
2405 "gpt-5.4",
2406 "GPT-5.4",
2407 "Strong model for everyday coding.",
2408 REASONING_MEDIUM,
2409 &[
2410 REASONING_LOW,
2411 REASONING_MEDIUM,
2412 REASONING_HIGH,
2413 REASONING_XHIGH,
2414 ],
2415 272_000,
2416 1_000_000,
2417 );
2418 }
2419
2420 #[test]
2421 fn deepseek_catalog_defaults_to_chat_model() {
2422 let provider = BUILT_IN_PROVIDERS
2423 .iter()
2424 .find(|provider| provider.id == PROVIDER_DEEPSEEK)
2425 .expect("deepseek provider registered");
2426 assert_eq!(provider.name, "DeepSeek Platform");
2427 assert_eq!(provider.default_model, "deepseek-chat");
2428 assert_eq!(provider.base_url, Some("https://api.deepseek.com/v1"));
2429 assert_eq!(provider.env_key, Some("DEEPSEEK_API_KEY"));
2430 assert_eq!(
2431 normalize_provider_id("deepseek-platform"),
2432 PROVIDER_DEEPSEEK
2433 );
2434 assert_eq!(
2435 provider_family_for_provider(PROVIDER_DEEPSEEK),
2436 ProviderFamily::OpenAi
2437 );
2438
2439 let models = models_for_provider(PROVIDER_DEEPSEEK, false);
2440 assert_eq!(models.len(), 4);
2441 assert!(models.iter().any(|model| model.id == "deepseek-chat"));
2442 assert!(models.iter().any(|model| model.id == "deepseek-reasoner"));
2443 assert!(models.iter().any(|model| model.id == "deepseek-v4-flash"));
2444 assert!(models.iter().any(|model| model.id == "deepseek-v4-pro"));
2445
2446 let flash = models
2447 .iter()
2448 .find(|model| model.id == "deepseek-v4-flash")
2449 .expect("flash model");
2450 assert_eq!(flash.default_reasoning, Some(REASONING_HIGH.to_string()));
2451 assert_eq!(
2452 flash
2453 .supported_reasoning
2454 .iter()
2455 .map(|option| option.effort.as_str())
2456 .collect::<Vec<_>>(),
2457 vec![
2458 REASONING_NONE,
2459 REASONING_LOW,
2460 REASONING_HIGH,
2461 REASONING_XHIGH,
2462 REASONING_MAX,
2463 ]
2464 );
2465
2466 let chat = models
2467 .iter()
2468 .find(|model| model.id == "deepseek-chat")
2469 .expect("chat model");
2470 assert_eq!(chat.default_reasoning, Some(REASONING_NONE.to_string()));
2471 assert!(
2472 chat.supported_reasoning
2473 .iter()
2474 .any(|option| option.effort == REASONING_HIGH)
2475 );
2476 }
2477
2478 #[test]
2479 fn synthetic_catalog_defaults_to_large_text_alias() {
2480 let provider = built_in_providers()
2481 .iter()
2482 .find(|provider| provider.id == PROVIDER_SYNTHETIC)
2483 .expect("synthetic provider registered");
2484 assert_eq!(provider.name, "Synthetic");
2485 assert_eq!(provider.default_model, "syn:large:text");
2486 assert_eq!(
2487 provider.base_url,
2488 Some("https://api.synthetic.new/openai/v1")
2489 );
2490 assert_eq!(provider.env_key, Some("SYNTHETIC_API_KEY"));
2491 assert!(provider.env_aliases.contains(&"RODER_SYNTHETIC_API_KEY"));
2492 assert!(!provider.supports_websockets);
2493
2494 let models = models_for_provider(PROVIDER_SYNTHETIC, false);
2495 let default = models
2496 .iter()
2497 .find(|model| model.id == provider.default_model)
2498 .expect("default synthetic model present");
2499 assert_eq!(default.name, "Synthetic Large (Text)");
2500 assert!(models.iter().any(|model| model.id == "syn:small:text"));
2501 let vision = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:vision")
2502 .expect("vision alias present");
2503 assert!(vision.supports_images);
2504 assert_eq!(
2505 provider_family_for_provider(PROVIDER_SYNTHETIC),
2506 ProviderFamily::OpenAi
2507 );
2508 }
2509
2510 #[test]
2511 fn synthetic_model_ids_preserve_alias_and_hf_segments() {
2512 assert_eq!(normalize_provider_id("synthetic"), PROVIDER_SYNTHETIC);
2513 assert_eq!(normalize_provider_id("synthetic.new"), PROVIDER_SYNTHETIC);
2514 let alias = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:text")
2516 .expect("syn alias resolves");
2517 assert_eq!(alias.id, "syn:large:text");
2518 assert_eq!(alias.provider, PROVIDER_SYNTHETIC);
2519 let label = "synthetic/hf:zai-org/GLM-5.2";
2523 let (provider, model) = label.split_once('/').unwrap();
2524 assert_eq!(provider, PROVIDER_SYNTHETIC);
2525 assert_eq!(model, "hf:zai-org/GLM-5.2");
2526 }
2527
2528 #[test]
2529 fn synthetic_always_on_models_are_pinned_with_documented_context_windows() {
2530 let glm_5_2 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-5.2")
2531 .expect("GLM-5.2 pinned");
2532 assert_eq!(glm_5_2.provider, PROVIDER_SYNTHETIC);
2533 assert_eq!(glm_5_2.context_window, 524_288);
2534 assert!(!glm_5_2.supports_images);
2535
2536 let minimax = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:MiniMaxAI/MiniMax-M3")
2537 .expect("MiniMax-M3 pinned");
2538 assert_eq!(minimax.context_window, 524_288);
2539
2540 let glm_4_7 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-4.7")
2541 .expect("GLM-4.7 pinned");
2542 assert_eq!(glm_4_7.context_window, 202_752);
2543
2544 let gpt_oss = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:openai/gpt-oss-120b")
2545 .expect("gpt-oss-120b pinned");
2546 assert_eq!(gpt_oss.context_window, 131_072);
2547
2548 let qwen_3_5 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:Qwen/Qwen3.5-397B-A17B")
2549 .expect("Qwen3.5 397B pinned");
2550 assert_eq!(qwen_3_5.context_window, 262_144);
2551
2552 let always_on = [
2554 "hf:MiniMaxAI/MiniMax-M3",
2555 "hf:Qwen/Qwen3.6-27B",
2556 "hf:moonshotai/Kimi-K2.6",
2557 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
2558 "hf:zai-org/GLM-4.7",
2559 "hf:zai-org/GLM-4.7-Flash",
2560 "hf:zai-org/GLM-5.1",
2561 "hf:zai-org/GLM-5.2",
2562 "hf:openai/gpt-oss-120b",
2563 "hf:Qwen/Qwen3.5-397B-A17B",
2564 ];
2565 for id in always_on {
2566 let entry = lookup_model_for_provider(PROVIDER_SYNTHETIC, id)
2567 .unwrap_or_else(|| panic!("{id} should be pinned in the synthetic catalog"));
2568 assert_eq!(entry.provider, PROVIDER_SYNTHETIC);
2569 assert!(entry.supports_tools);
2570 assert!(entry.supports_structured);
2571 }
2572 }
2573
2574 #[test]
2575 fn claude_code_catalog_uses_long_context_windows() {
2576 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-sonnet-4-6").unwrap();
2577 let claude_code =
2578 lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-sonnet-4-6").unwrap();
2579
2580 assert_eq!(direct.context_window, 1_000_000);
2581 assert_eq!(claude_code.context_window, 1_000_000);
2582 assert_eq!(claude_code.auto_compact_token_limit, 900_000);
2583 assert!(!claude_code.supports_compaction);
2586 assert!(direct.supports_compaction);
2590 assert_eq!(direct.auto_compact_token_limit, 900_000);
2591 }
2592
2593 #[test]
2594 fn claude_haiku_does_not_advertise_server_side_compaction() {
2595 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
2596
2597 assert!(!haiku.supports_compaction);
2602 assert_eq!(haiku.auto_compact_token_limit, 180_000);
2603 }
2604
2605 #[test]
2606 fn claude_fable_5_1_is_offered_directly_and_through_the_claude_code_harness() {
2607 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-fable-5-1").unwrap();
2608 assert_eq!(direct.display_name, "Claude Fable 5.1");
2609 assert_eq!(direct.context_window, 1_000_000);
2610 assert_eq!(direct.auto_compact_token_limit, 900_000);
2611 assert_eq!(direct.default_reasoning, REASONING_HIGH);
2612 assert!(direct.supports_compaction);
2613 assert_eq!(
2614 direct
2615 .supported_reasoning
2616 .iter()
2617 .map(|option| option.effort)
2618 .collect::<Vec<_>>(),
2619 vec![
2620 REASONING_LOW,
2621 REASONING_MEDIUM,
2622 REASONING_HIGH,
2623 REASONING_XHIGH,
2624 REASONING_MAX
2625 ]
2626 );
2627
2628 let harness = lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-fable-5-1").unwrap();
2629 assert_eq!(harness.provider, PROVIDER_CLAUDE_CODE);
2630 assert_eq!(harness.context_window, 1_000_000);
2631 assert!(!harness.supports_compaction);
2634 }
2635
2636 #[test]
2637 fn google_embedding_model_is_hidden_from_chat_lists() {
2638 assert!(lookup_model("gemini-embedding-2").is_some());
2639 assert!(
2640 models_for_provider(PROVIDER_GOOGLE, false)
2641 .iter()
2642 .all(|model| model.id != "gemini-embedding-2")
2643 );
2644 let model = lookup_model("gemini-embedding-2").unwrap();
2645 assert!(model.hidden);
2646 assert!(!model.supports_tools);
2647 }
2648
2649 #[test]
2650 fn zeroentropy_embedding_model_is_hidden_from_chat_lists() {
2651 assert!(lookup_model("zembed-1").is_some());
2652 assert!(
2653 models_for_provider(PROVIDER_ZEROENTROPY, false)
2654 .iter()
2655 .all(|model| model.id != "zembed-1")
2656 );
2657 let model = lookup_model("zembed-1").unwrap();
2658 assert!(model.hidden);
2659 assert!(!model.supports_tools);
2660 }
2661
2662 #[test]
2663 fn catalog_model_profile_derives_openai_defaults() {
2664 let profile = built_in_model_profile("gpt-5.5").unwrap();
2665
2666 assert_eq!(profile.provider_family, ProviderFamily::OpenAi);
2667 assert_eq!(profile.edit_tool.as_deref(), Some(EDIT_TOOL_PATCH));
2668 assert_eq!(profile.schema_policy, ModelSchemaPolicy::RequiredFirstFlat);
2669 assert_eq!(
2670 profile.instruction_overlay,
2671 ModelInstructionOverlay::LiteralToolOutputs
2672 );
2673 assert_eq!(profile.reasoning.execution.as_deref(), Some(REASONING_LOW));
2674 assert_eq!(profile.parallel_tool_calls, Some(true));
2675 }
2676
2677 #[test]
2678 fn poolside_catalog_defaults_to_thinking_enabled() {
2679 let laguna = lookup_model("poolside/laguna-m.1").unwrap();
2680 assert_eq!(laguna.default_reasoning, REASONING_MEDIUM);
2681 assert_eq!(
2682 laguna
2683 .supported_reasoning
2684 .iter()
2685 .map(|option| option.effort)
2686 .collect::<Vec<_>>(),
2687 vec![REASONING_NONE, REASONING_MEDIUM]
2688 );
2689 }
2690
2691 #[test]
2692 fn xiaomi_mimo_catalog_uses_chat_completions_kind_and_exact_model_ids() {
2693 let provider = BUILT_IN_PROVIDERS
2694 .iter()
2695 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO)
2696 .unwrap();
2697 let token_plan = BUILT_IN_PROVIDERS
2698 .iter()
2699 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO_TOKEN_PLAN)
2700 .unwrap();
2701
2702 assert_eq!(provider.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2703 assert_eq!(token_plan.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2704 assert_eq!(provider.env_key, Some("MIMO_API_KEY"));
2705 assert_eq!(token_plan.env_key, Some("MIMO_TOKEN_PLAN_API_KEY"));
2706
2707 let ids = models_for_provider(PROVIDER_XIAOMI_MIMO, false)
2708 .into_iter()
2709 .map(|model| model.id)
2710 .collect::<Vec<_>>();
2711 assert_eq!(
2712 ids,
2713 vec![
2714 "mimo-v2.5-pro",
2715 "mimo-v2-pro",
2716 "mimo-v2.5",
2717 "mimo-v2-omni",
2718 "mimo-v2-flash"
2719 ]
2720 );
2721 assert!(lookup_model("out-of-v2-flash").is_none());
2722 }
2723
2724 #[test]
2725 fn supergrok_catalog_exposes_grok_47_46_and_composer_with_expected_context_windows() {
2726 let grok47 = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-4.7").unwrap();
2727 assert_eq!(grok47.display_name, "Grok 4.7");
2728 assert_eq!(grok47.context_window, 500_000);
2729 assert_eq!(grok47.auto_compact_token_limit, 450_000);
2730 assert_eq!(grok47.default_reasoning, REASONING_HIGH);
2731
2732 let grok46 = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-4.6").unwrap();
2733 assert_eq!(grok46.display_name, "Grok 4.6");
2734 assert_eq!(grok46.context_window, 500_000);
2735 assert_eq!(grok46.auto_compact_token_limit, 450_000);
2736 assert_eq!(grok46.default_reasoning, REASONING_HIGH);
2737
2738 let composer =
2739 lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-composer-2.5-fast").unwrap();
2740 assert_eq!(composer.display_name, "Grok Composer 2.5 Fast");
2741 assert_eq!(composer.context_window, 200_000);
2742 assert_eq!(composer.auto_compact_token_limit, 180_000);
2743 assert!(composer.supported_reasoning.is_empty());
2744 assert!(!composer.supports_images);
2745
2746 let visible = models_for_provider(PROVIDER_SUPERGROK, false)
2747 .into_iter()
2748 .map(|model| model.id)
2749 .collect::<Vec<_>>();
2750 assert_eq!(
2751 visible,
2752 vec![
2753 "grok-4.7".to_string(),
2754 "grok-4.6".to_string(),
2755 "grok-composer-2.5-fast".to_string(),
2756 ]
2757 );
2758 }
2759
2760 #[test]
2761 fn xai_catalog_entries_match_current_grok_contract() {
2762 let grok46 = models_for_provider(PROVIDER_XAI, false)
2763 .into_iter()
2764 .find(|model| model.id == "grok-4.6")
2765 .unwrap();
2766 assert_eq!(grok46.context_window, Some(500_000));
2767 assert_eq!(grok46.default_reasoning.as_deref(), Some(REASONING_HIGH));
2768 assert_eq!(
2769 grok46
2770 .supported_reasoning
2771 .iter()
2772 .map(|option| option.effort.as_str())
2773 .collect::<Vec<_>>(),
2774 vec![
2775 REASONING_LOW,
2776 REASONING_MEDIUM,
2777 REASONING_HIGH,
2778 REASONING_XHIGH
2779 ]
2780 );
2781
2782 let grok43 = models_for_provider(PROVIDER_XAI, false)
2783 .into_iter()
2784 .find(|model| model.id == "grok-4.3")
2785 .unwrap();
2786 assert_eq!(grok43.context_window, Some(1_000_000));
2787 assert_eq!(grok43.default_reasoning.as_deref(), Some(REASONING_LOW));
2788 assert_eq!(
2789 grok43
2790 .supported_reasoning
2791 .iter()
2792 .map(|option| option.effort.as_str())
2793 .collect::<Vec<_>>(),
2794 vec![
2795 REASONING_NONE,
2796 REASONING_LOW,
2797 REASONING_MEDIUM,
2798 REASONING_HIGH,
2799 REASONING_XHIGH
2800 ]
2801 );
2802
2803 let grok420 = lookup_model("grok-4.20-multi-agent-0309").unwrap();
2804 assert_eq!(grok420.context_window, 2_000_000);
2805 assert_eq!(grok420.auto_compact_token_limit, 1_800_000);
2806 assert_eq!(grok420.provider, PROVIDER_XAI);
2807 }
2808
2809 #[test]
2810 fn provider_aliases_normalize_xai_and_supergrok() {
2811 assert_eq!(normalize_provider_id("grok"), PROVIDER_XAI);
2812 assert_eq!(normalize_provider_id("x.ai"), PROVIDER_XAI);
2813 assert_eq!(normalize_provider_id("x-ai"), PROVIDER_XAI);
2814 assert_eq!(normalize_provider_id("xai-oauth"), PROVIDER_SUPERGROK);
2815 assert_eq!(normalize_provider_id("grok-oauth"), PROVIDER_SUPERGROK);
2816 assert_eq!(normalize_provider_id("supergrok"), PROVIDER_SUPERGROK);
2817 assert_eq!(normalize_provider_id("laguna"), PROVIDER_POOLSIDE);
2818 assert_eq!(normalize_provider_id("composer"), PROVIDER_CURSOR);
2819 }
2820
2821 #[test]
2822 fn fireworks_catalog_preserves_account_scoped_default_model() {
2823 let provider = BUILT_IN_PROVIDERS
2824 .iter()
2825 .find(|provider| provider.id == PROVIDER_FIREWORKS)
2826 .unwrap();
2827
2828 assert_eq!(
2829 provider.default_model,
2830 "accounts/fireworks/models/qwen3-235b-a22b"
2831 );
2832 assert_eq!(provider.env_key, Some("FIREWORKS_API_KEY"));
2833 assert_eq!(provider.env_aliases, &["RODER_FIREWORKS_API_KEY"]);
2834
2835 let model = lookup_model_for_provider(PROVIDER_FIREWORKS, provider.default_model).unwrap();
2836 assert_eq!(model.provider, PROVIDER_FIREWORKS);
2837 assert!(model.supports_tools);
2838 assert!(model.supports_structured);
2839 assert_eq!(
2840 provider_family_for_provider(PROVIDER_FIREWORKS),
2841 ProviderFamily::OpenAi
2842 );
2843 }
2844
2845 #[test]
2846 fn cursor_catalog_profile_is_text_only_agentservice() {
2847 let composer = lookup_model("composer-2.5").unwrap();
2848 assert_eq!(composer.provider, PROVIDER_CURSOR);
2849 assert!(!composer.supports_tools);
2850 assert!(!composer.supports_structured);
2851
2852 let profile = built_in_model_profile("composer-2.5").unwrap();
2853 assert_eq!(profile.provider_family, ProviderFamily::Cursor);
2854 assert_eq!(profile.parallel_tool_calls, Some(false));
2855 }
2856
2857 #[test]
2858 fn provider_aware_lookup_resolves_cursor_proxied_models_to_cursor_family() {
2859 let id_only = built_in_model_profile("claude-opus-4-8").unwrap();
2861 assert_eq!(id_only.provider_family, ProviderFamily::Anthropic);
2862
2863 let cursor =
2865 built_in_model_profile_for_provider(PROVIDER_CURSOR, "claude-opus-4-8").unwrap();
2866 assert_eq!(cursor.provider_family, ProviderFamily::Cursor);
2867 assert_eq!(cursor.provider, PROVIDER_CURSOR);
2868 assert_eq!(cursor.parallel_tool_calls, Some(false));
2869
2870 let anthropic =
2871 built_in_model_profile_for_provider(PROVIDER_ANTHROPIC, "claude-opus-4-8").unwrap();
2872 assert_eq!(anthropic.provider_family, ProviderFamily::Anthropic);
2873
2874 let fallback =
2876 built_in_model_profile_for_provider("does-not-exist", "claude-opus-4-8").unwrap();
2877 assert_eq!(fallback.provider_family, ProviderFamily::Anthropic);
2878 }
2879
2880 #[test]
2881 fn opencode_default_model_is_a_listed_opencode_model() {
2882 let provider = built_in_providers()
2883 .iter()
2884 .find(|provider| provider.id == PROVIDER_OPENCODE)
2885 .expect("opencode provider");
2886 assert!(
2887 models_for_provider(PROVIDER_OPENCODE, false)
2888 .iter()
2889 .any(|model| model.id == provider.default_model),
2890 "opencode default {} should be listed for opencode",
2891 provider.default_model
2892 );
2893 }
2894
2895 #[test]
2896 fn retired_gpt_5_models_are_hidden_but_still_resolvable() {
2897 let visible = built_in_models(false)
2898 .into_iter()
2899 .map(|model| model.id)
2900 .collect::<Vec<_>>();
2901 for id in [
2902 "gpt-5.6-sol",
2903 "gpt-5.6-terra",
2904 "gpt-5.6-luna",
2905 "gpt-5.5",
2906 "gpt-5.5-fast",
2907 "gpt-5.4",
2908 "gpt-5.4-mini",
2909 "gpt-5.3-codex-spark",
2910 "roder.cloud/openai/gpt-5.5",
2911 ] {
2912 assert!(
2913 !visible.contains(&id),
2914 "{id} should be hidden from model lists"
2915 );
2916 assert!(lookup_model(id).is_some(), "{id} should still resolve");
2918 }
2919 assert!(
2920 !visible.contains(&"gpt-6-sol")
2921 && visible.contains(&"gpt-6.1-sol")
2922 && visible.contains(&"gpt-6.1-terra")
2923 );
2924 assert!(lookup_model("gpt-6-sol").is_some());
2925 }
2926
2927 #[test]
2928 fn retired_cursor_gpt55_keeps_standard_reasoning_metadata() {
2929 let gpt55 = ModelDescriptor::from(
2930 lookup_model_for_provider(PROVIDER_CURSOR, "gpt-5.5")
2931 .expect("cursor catalog should still resolve retired gpt-5.5"),
2932 );
2933
2934 assert_eq!(gpt55.default_reasoning.as_deref(), Some(REASONING_MEDIUM));
2935 assert_eq!(
2936 gpt55
2937 .supported_reasoning
2938 .iter()
2939 .map(|option| option.effort.as_str())
2940 .collect::<Vec<_>>(),
2941 vec![
2942 REASONING_LOW,
2943 REASONING_MEDIUM,
2944 REASONING_HIGH,
2945 REASONING_XHIGH
2946 ]
2947 );
2948
2949 let gpt55_fast = ModelDescriptor::from(
2950 lookup_model_for_provider(PROVIDER_CURSOR, "gpt-5.5-fast")
2951 .expect("cursor catalog should still resolve retired gpt-5.5-fast"),
2952 );
2953 assert_eq!(
2954 gpt55_fast.default_reasoning.as_deref(),
2955 Some(REASONING_MEDIUM)
2956 );
2957 assert_eq!(gpt55_fast.supported_reasoning.len(), 4);
2958 }
2959
2960 #[test]
2961 fn cursor_opus_advertises_configurable_reasoning_effort() {
2962 let opus = models_for_provider(PROVIDER_CURSOR, false)
2963 .into_iter()
2964 .find(|model| model.id == "claude-opus-4-8")
2965 .expect("cursor catalog should expose claude-opus-4-8");
2966
2967 assert_eq!(opus.default_reasoning.as_deref(), Some(REASONING_HIGH));
2968 assert_eq!(
2969 opus.supported_reasoning
2970 .iter()
2971 .map(|option| option.effort.as_str())
2972 .collect::<Vec<_>>(),
2973 vec![
2974 REASONING_LOW,
2975 REASONING_MEDIUM,
2976 REASONING_HIGH,
2977 REASONING_XHIGH,
2978 REASONING_MAX
2979 ]
2980 );
2981
2982 let sonnet = models_for_provider(PROVIDER_CURSOR, false)
2985 .into_iter()
2986 .find(|model| model.id == "claude-sonnet-4-6")
2987 .expect("cursor catalog should expose claude-sonnet-4-6");
2988 assert_eq!(sonnet.default_reasoning.as_deref(), Some(REASONING_MEDIUM));
2989 assert_eq!(
2990 sonnet
2991 .supported_reasoning
2992 .iter()
2993 .map(|option| option.effort.as_str())
2994 .collect::<Vec<_>>(),
2995 vec![
2996 REASONING_LOW,
2997 REASONING_MEDIUM,
2998 REASONING_HIGH,
2999 REASONING_MAX
3000 ]
3001 );
3002 }
3003
3004 #[test]
3005 fn claude_opus_and_sonnet_advertise_max_effort() {
3006 let efforts = |id: &str| {
3007 lookup_model(id)
3008 .unwrap()
3009 .supported_reasoning
3010 .iter()
3011 .map(|option| option.effort)
3012 .collect::<Vec<_>>()
3013 };
3014
3015 for id in ["claude-opus-4-8", "claude-opus-4-7"] {
3017 assert_eq!(
3018 efforts(id),
3019 vec![
3020 REASONING_LOW,
3021 REASONING_MEDIUM,
3022 REASONING_HIGH,
3023 REASONING_XHIGH,
3024 REASONING_MAX
3025 ],
3026 "{id} effort levels"
3027 );
3028 }
3029
3030 assert_eq!(
3032 efforts("claude-sonnet-4-6"),
3033 vec![
3034 REASONING_LOW,
3035 REASONING_MEDIUM,
3036 REASONING_HIGH,
3037 REASONING_MAX
3038 ]
3039 );
3040
3041 assert!(!efforts("gpt-5.5").contains(&REASONING_MAX));
3043 }
3044
3045 #[test]
3046 fn claude_haiku_does_not_advertise_reasoning_effort() {
3047 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
3048
3049 assert_eq!(haiku.default_reasoning, REASONING_NONE);
3050 assert!(haiku.supported_reasoning.is_empty());
3051
3052 let descriptor = ModelDescriptor::from(haiku);
3053 assert_eq!(descriptor.default_reasoning, None);
3054 assert!(descriptor.supported_reasoning.is_empty());
3055 }
3056
3057 #[test]
3058 fn openai_context_windows_match_current_catalog_values() {
3059 let gpt55 = lookup_model("gpt-5.5").unwrap();
3060 assert_eq!(gpt55.context_window, 1_050_000);
3061 assert_eq!(gpt55.max_context_window, 1_050_000);
3062 assert_eq!(gpt55.auto_compact_token_limit, 945_000);
3063
3064 let mini = lookup_model("gpt-5.4-mini").unwrap();
3065 assert_eq!(mini.context_window, 400_000);
3066 assert_eq!(mini.max_context_window, 400_000);
3067 assert_eq!(mini.auto_compact_token_limit, 360_000);
3068
3069 let spark = lookup_model("gpt-5.3-codex-spark").unwrap();
3070 assert_eq!(spark.provider, PROVIDER_CODEX);
3071 assert_eq!(spark.context_window, 128_000);
3072 assert_eq!(spark.max_context_window, 128_000);
3073 assert_eq!(spark.auto_compact_token_limit, 115_200);
3074 }
3075
3076 #[test]
3077 fn auto_compact_defaults_to_ninety_percent_of_context_window() {
3078 for model in BUILT_IN_MODELS {
3079 if model.context_window == 0 || model.auto_compact_token_limit == 0 {
3080 continue;
3081 }
3082 assert_eq!(
3083 model.auto_compact_token_limit,
3084 model.context_window.saturating_mul(9) / 10,
3085 "{} should compact at 90% of its context window",
3086 model.id
3087 );
3088 }
3089 }
3090}