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::DEEPSEEK_DEEPSEEK_V4_1_FLASH_1017,
1141 openrouter::QWEN_QWEN3_8_MAX_0902,
1142 openrouter::QWEN_QWEN3_8_FLASH,
1143 openrouter::Z_AI_GLM_5_3,
1144 openrouter::Z_AI_GLM_5_3_FLASH,
1145 openrouter::XIAOMI_MIMO_V2_6_PRO,
1146 openrouter::MISTRALAI_MISTRAL_LARGE_4_0,
1147 openrouter::META_MUSE_SPARK_1_3,
1148 ModelCatalogEntry {
1149 id: "accounts/fireworks/models/qwen3-235b-a22b",
1150 display_name: "Qwen3 235B A22B",
1151 description: "Fireworks Responses-capable serverless model with client-executed function tool support.",
1152 provider: PROVIDER_FIREWORKS,
1153 default_reasoning: REASONING_NONE,
1154 supported_reasoning: &[],
1155 context_window: 131_072,
1156 max_context_window: 131_072,
1157 auto_compact_token_limit: 0,
1158 supports_compaction: false,
1159 supports_images: false,
1160 supports_tools: true,
1161 supports_structured: true,
1162 edit_tool: Some(EDIT_TOOL_PATCH),
1163 hidden: false,
1164 },
1165 roder_cloud_model(
1166 "roder.cloud/free",
1167 "Roder Free",
1168 "Free hosted model on roder.cloud.",
1169 32_768,
1170 ),
1171 retired(roder_cloud_model(
1172 "roder.cloud/openai/gpt-5.5",
1173 "GPT-5.5 (Roder Cloud)",
1174 "roder.cloud hosted route for OpenAI GPT-5.5.",
1175 400_000,
1176 )),
1177 roder_cloud_model(
1178 "roder.cloud/anthropic/claude-opus-4-7",
1179 "Claude Opus 4.7 (Roder Cloud)",
1180 "roder.cloud hosted route for Anthropic Claude Opus 4.7.",
1181 200_000,
1182 ),
1183 roder_cloud_model(
1184 "roder.cloud/google/gemini-3.1-pro-preview",
1185 "Gemini 3.1 Pro (Roder Cloud)",
1186 "roder.cloud hosted route for Google Gemini 3.1 Pro Preview.",
1187 200_000,
1188 ),
1189 poolside_model(
1190 "poolside/laguna-m.1",
1191 "Laguna M.1",
1192 "Poolside flagship agentic coding model.",
1193 REASONING_MEDIUM,
1194 ),
1195 poolside_model(
1196 "poolside/laguna-xs.2",
1197 "Laguna XS.2",
1198 "Poolside lightweight agentic coding model.",
1199 REASONING_MEDIUM,
1200 ),
1201 xiaomi_mimo::PAYG_V25_PRO,
1202 xiaomi_mimo::PAYG_V2_PRO,
1203 xiaomi_mimo::PAYG_V25,
1204 xiaomi_mimo::PAYG_V2_OMNI,
1205 xiaomi_mimo::PAYG_V2_FLASH,
1206 xiaomi_mimo::TOKEN_PLAN_V25_PRO,
1207 xiaomi_mimo::TOKEN_PLAN_V2_PRO,
1208 xiaomi_mimo::TOKEN_PLAN_V25,
1209 xiaomi_mimo::TOKEN_PLAN_V2_OMNI,
1210 xiaomi_mimo::TOKEN_PLAN_V2_FLASH,
1211 synthetic::SYN_LARGE_TEXT,
1212 synthetic::SYN_SMALL_TEXT,
1213 synthetic::SYN_LARGE_VISION,
1214 synthetic::SYN_SMALL_VISION,
1215 synthetic::HF_MINIMAX_M3,
1216 synthetic::HF_QWEN3_6_27B,
1217 synthetic::HF_KIMI_K2_6,
1218 synthetic::HF_NEMOTRON_3_SUPER,
1219 synthetic::HF_GLM_4_7,
1220 synthetic::HF_GLM_4_7_FLASH,
1221 synthetic::HF_GLM_5_1,
1222 synthetic::HF_GLM_5_2,
1223 synthetic::HF_GPT_OSS_120B,
1224 synthetic::HF_QWEN3_5_397B_A17B,
1225 deepseek::DEEPSEEK_CHAT,
1226 deepseek::DEEPSEEK_REASONER,
1227 deepseek::DEEPSEEK_V4_FLASH,
1228 deepseek::DEEPSEEK_V4_PRO,
1229 ModelCatalogEntry {
1230 id: "composer-2.5",
1231 display_name: "Composer 2.5",
1232 description: "Cursor Composer model exposed through direct AgentService inference.",
1233 provider: PROVIDER_CURSOR,
1234 default_reasoning: REASONING_NONE,
1235 supported_reasoning: &[],
1236 context_window: 200_000,
1237 max_context_window: 200_000,
1238 auto_compact_token_limit: 180_000,
1239 supports_compaction: true,
1240 supports_images: false,
1241 supports_tools: false,
1242 supports_structured: false,
1243 edit_tool: None,
1244 hidden: false,
1245 },
1246 cursor_model(
1247 "composer-2.5-fast",
1248 "Composer 2.5 Fast",
1249 "Cursor Composer 2.5 fast variant for lower-latency agent turns.",
1250 200_000,
1251 180_000,
1252 REASONING_NONE,
1253 &[],
1254 ),
1255 cursor_model(
1256 "claude-fable-5",
1257 "Claude Fable 5",
1258 "Anthropic Claude Fable 5, Anthropic's most powerful frontier model, routed through Cursor's AgentService.",
1259 1_000_000,
1260 900_000,
1261 REASONING_HIGH,
1262 OPUS_REASONING,
1263 ),
1264 cursor_model(
1265 "claude-opus-4-8",
1266 "Claude Opus 4.8",
1267 "Anthropic Claude Opus 4.8 routed through Cursor's AgentService.",
1268 1_000_000,
1269 900_000,
1270 REASONING_HIGH,
1271 OPUS_REASONING,
1272 ),
1273 cursor_model(
1274 "claude-sonnet-4-6",
1275 "Claude Sonnet 4.6",
1276 "Anthropic Claude Sonnet 4.6 routed through Cursor's AgentService.",
1277 1_000_000,
1278 900_000,
1279 REASONING_MEDIUM,
1280 SONNET_REASONING,
1281 ),
1282 retired(cursor_model(
1283 "gpt-5.5",
1284 "GPT-5.5",
1285 "OpenAI GPT-5.5 routed through Cursor's AgentService.",
1286 1_050_000,
1287 945_000,
1288 REASONING_MEDIUM,
1289 STANDARD_REASONING,
1290 )),
1291 retired(cursor_model(
1292 "gpt-5.5-fast",
1293 "GPT-5.5 Fast",
1294 "OpenAI GPT-5.5 fast variant routed through Cursor's AgentService.",
1295 1_050_000,
1296 945_000,
1297 REASONING_MEDIUM,
1298 STANDARD_REASONING,
1299 )),
1300 cursor_model(
1301 "gemini-3.1-pro",
1302 "Gemini 3.1 Pro",
1303 "Google Gemini 3.1 Pro routed through Cursor's AgentService.",
1304 1_000_000,
1305 900_000,
1306 REASONING_MEDIUM,
1307 GEMINI_REASONING,
1308 ),
1309 cursor_model(
1310 "gemini-4-argon",
1311 "Gemini 4 Argon",
1312 "Google Gemini 4 Argon routed through Cursor's AgentService for complex multi-step coding.",
1313 1_000_000,
1314 900_000,
1315 REASONING_HIGH,
1316 &[
1317 ReasoningOption {
1318 effort: REASONING_LOW,
1319 description: "Low Gemini thinking",
1320 },
1321 ReasoningOption {
1322 effort: REASONING_MEDIUM,
1323 description: "Medium Gemini thinking",
1324 },
1325 ReasoningOption {
1326 effort: REASONING_HIGH,
1327 description: "High Gemini thinking",
1328 },
1329 ],
1330 ),
1331 cursor_model(
1332 "grok-4.6",
1333 "Grok 4.6",
1334 "xAI Grok 4.6 routed through Cursor's AgentService for long-running coding and knowledge-work agents.",
1335 256_000,
1336 230_400,
1337 REASONING_HIGH,
1338 STANDARD_REASONING,
1339 ),
1340 cursor_model(
1341 "gemini-3.7-flash",
1342 "Gemini 3.7 Flash",
1343 "Google Gemini 3.7 Flash routed through Cursor's AgentService for high-throughput agentic coding.",
1344 1_000_000,
1345 900_000,
1346 REASONING_HIGH,
1347 GEMINI_REASONING,
1348 ),
1349 ModelCatalogEntry {
1350 id: "text-embedding-3-large",
1351 display_name: "Text Embedding 3 Large",
1352 description: "OpenAI embedding model for local semantic memories.",
1353 provider: PROVIDER_OPENAI,
1354 default_reasoning: REASONING_NONE,
1355 supported_reasoning: &[],
1356 context_window: 0,
1357 max_context_window: 0,
1358 auto_compact_token_limit: 0,
1359 supports_compaction: false,
1360 supports_images: false,
1361 supports_tools: true,
1362 supports_structured: false,
1363 edit_tool: None,
1364 hidden: true,
1365 },
1366 ModelCatalogEntry {
1367 id: "gemini-embedding-2",
1368 display_name: "Gemini Embedding 2",
1369 description: "Google Gemini embedding model for local semantic memories.",
1370 provider: PROVIDER_GOOGLE,
1371 default_reasoning: REASONING_NONE,
1372 supported_reasoning: &[],
1373 context_window: 0,
1374 max_context_window: 0,
1375 auto_compact_token_limit: 0,
1376 supports_compaction: false,
1377 supports_images: false,
1378 supports_tools: false,
1379 supports_structured: false,
1380 edit_tool: None,
1381 hidden: true,
1382 },
1383 ModelCatalogEntry {
1384 id: "zembed-1",
1385 display_name: "ZeroEntropy zembed-1",
1386 description: "ZeroEntropy embedding model for local semantic memories.",
1387 provider: PROVIDER_ZEROENTROPY,
1388 default_reasoning: REASONING_NONE,
1389 supported_reasoning: &[],
1390 context_window: 0,
1391 max_context_window: 0,
1392 auto_compact_token_limit: 0,
1393 supports_compaction: false,
1394 supports_images: false,
1395 supports_tools: false,
1396 supports_structured: false,
1397 edit_tool: None,
1398 hidden: true,
1399 },
1400 ModelCatalogEntry {
1401 id: "mock",
1402 display_name: "Mock",
1403 description: "Local deterministic mock provider for tests and offline development.",
1404 provider: PROVIDER_MOCK,
1405 default_reasoning: REASONING_NONE,
1406 supported_reasoning: MOCK_REASONING,
1407 context_window: 128_000,
1408 max_context_window: 128_000,
1409 auto_compact_token_limit: 115_200,
1410 supports_compaction: false,
1411 supports_images: false,
1412 supports_tools: true,
1413 supports_structured: false,
1414 edit_tool: None,
1415 hidden: true,
1416 },
1417];
1418
1419const fn openai_model(
1420 id: &'static str,
1421 display_name: &'static str,
1422 description: &'static str,
1423 context_window: u32,
1424 auto_compact_token_limit: u32,
1425 supports_compaction: bool,
1426 supported_reasoning: &'static [ReasoningOption],
1427) -> ModelCatalogEntry {
1428 ModelCatalogEntry {
1429 id,
1430 display_name,
1431 description,
1432 provider: PROVIDER_OPENAI,
1433 default_reasoning: REASONING_MEDIUM,
1434 supported_reasoning,
1435 context_window,
1436 max_context_window: context_window,
1437 auto_compact_token_limit,
1438 supports_compaction,
1439 supports_images: false,
1440 supports_tools: true,
1441 supports_structured: false,
1442 edit_tool: Some("patch"),
1443 hidden: false,
1444 }
1445}
1446
1447#[allow(clippy::too_many_arguments)]
1448const fn anthropic_model(
1449 id: &'static str,
1450 display_name: &'static str,
1451 description: &'static str,
1452 context_window: u32,
1453 auto_compact_token_limit: u32,
1454 default_reasoning: &'static str,
1455 supported_reasoning: &'static [ReasoningOption],
1456 supports_compaction: bool,
1467) -> ModelCatalogEntry {
1468 ModelCatalogEntry {
1469 id,
1470 display_name,
1471 description,
1472 provider: PROVIDER_ANTHROPIC,
1473 default_reasoning,
1474 supported_reasoning,
1475 context_window,
1476 max_context_window: context_window,
1477 auto_compact_token_limit,
1478 supports_compaction,
1479 supports_images: false,
1480 supports_tools: true,
1481 supports_structured: false,
1482 edit_tool: Some("edit"),
1483 hidden: false,
1484 }
1485}
1486
1487const fn claude_code_model(
1488 id: &'static str,
1489 display_name: &'static str,
1490 description: &'static str,
1491 context_window: u32,
1492 auto_compact_token_limit: u32,
1493 default_reasoning: &'static str,
1494 supported_reasoning: &'static [ReasoningOption],
1495) -> ModelCatalogEntry {
1496 ModelCatalogEntry {
1497 id,
1498 display_name,
1499 description,
1500 provider: PROVIDER_CLAUDE_CODE,
1501 default_reasoning,
1502 supported_reasoning,
1503 context_window,
1504 max_context_window: context_window,
1505 auto_compact_token_limit,
1506 supports_compaction: false,
1512 supports_images: true,
1513 supports_tools: true,
1514 supports_structured: false,
1515 edit_tool: Some(EDIT_TOOL_EDIT),
1516 hidden: false,
1517 }
1518}
1519
1520const fn gemini_model(
1521 provider: &'static str,
1522 id: &'static str,
1523 display_name: &'static str,
1524 description: &'static str,
1525 default_reasoning: &'static str,
1526) -> ModelCatalogEntry {
1527 ModelCatalogEntry {
1528 id,
1529 display_name,
1530 description,
1531 provider,
1532 default_reasoning,
1533 supported_reasoning: GEMINI_REASONING,
1534 context_window: 1_048_576,
1535 max_context_window: 1_048_576,
1536 auto_compact_token_limit: 943_718,
1537 supports_compaction: false,
1538 supports_images: true,
1539 supports_tools: true,
1540 supports_structured: true,
1541 edit_tool: Some("edit"),
1542 hidden: false,
1543 }
1544}
1545
1546const fn xai_model(
1547 provider: &'static str,
1548 id: &'static str,
1549 display_name: &'static str,
1550 description: &'static str,
1551 context_window: u32,
1552 default_reasoning: &'static str,
1553 supported_reasoning: &'static [ReasoningOption],
1554 supports_images: bool,
1555 hidden: bool,
1556) -> ModelCatalogEntry {
1557 ModelCatalogEntry {
1558 id,
1559 display_name,
1560 description,
1561 provider,
1562 default_reasoning,
1563 supported_reasoning,
1564 context_window,
1565 max_context_window: context_window,
1566 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1567 supports_compaction: false,
1568 supports_images,
1569 supports_tools: true,
1570 supports_structured: true,
1571 edit_tool: Some("edit"),
1572 hidden,
1573 }
1574}
1575
1576const fn opencode_model(
1577 provider: &'static str,
1578 id: &'static str,
1579 display_name: &'static str,
1580 description: &'static str,
1581 context_window: u32,
1582 default_reasoning: &'static str,
1583 supported_reasoning: &'static [ReasoningOption],
1584) -> ModelCatalogEntry {
1585 ModelCatalogEntry {
1586 id,
1587 display_name,
1588 description,
1589 provider,
1590 default_reasoning,
1591 supported_reasoning,
1592 context_window,
1593 max_context_window: context_window,
1594 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1595 supports_compaction: false,
1596 supports_images: false,
1597 supports_tools: true,
1598 supports_structured: true,
1599 edit_tool: Some("edit"),
1600 hidden: false,
1601 }
1602}
1603
1604const fn roder_cloud_model(
1605 id: &'static str,
1606 display_name: &'static str,
1607 description: &'static str,
1608 context_window: u32,
1609) -> ModelCatalogEntry {
1610 ModelCatalogEntry {
1611 id,
1612 display_name,
1613 description,
1614 provider: PROVIDER_RODER_CLOUD,
1615 default_reasoning: REASONING_NONE,
1616 supported_reasoning: RODER_CLOUD_REASONING,
1617 context_window,
1618 max_context_window: context_window,
1619 auto_compact_token_limit: 0,
1620 supports_compaction: false,
1621 supports_images: false,
1622 supports_tools: false,
1623 supports_structured: false,
1624 edit_tool: None,
1625 hidden: false,
1626 }
1627}
1628
1629const fn poolside_model(
1630 id: &'static str,
1631 display_name: &'static str,
1632 description: &'static str,
1633 default_reasoning: &'static str,
1634) -> ModelCatalogEntry {
1635 ModelCatalogEntry {
1636 id,
1637 display_name,
1638 description,
1639 provider: PROVIDER_POOLSIDE,
1640 default_reasoning,
1641 supported_reasoning: POOLSIDE_REASONING,
1642 context_window: 131_072,
1643 max_context_window: 131_072,
1644 auto_compact_token_limit: 117_964,
1645 supports_compaction: false,
1646 supports_images: false,
1647 supports_tools: true,
1648 supports_structured: true,
1649 edit_tool: Some("edit"),
1650 hidden: false,
1651 }
1652}
1653
1654const fn cursor_model(
1655 id: &'static str,
1656 display_name: &'static str,
1657 description: &'static str,
1658 context_window: u32,
1659 auto_compact_token_limit: u32,
1660 default_reasoning: &'static str,
1661 supported_reasoning: &'static [ReasoningOption],
1662) -> ModelCatalogEntry {
1663 ModelCatalogEntry {
1664 id,
1665 display_name,
1666 description,
1667 provider: PROVIDER_CURSOR,
1668 default_reasoning,
1669 supported_reasoning,
1670 context_window,
1671 max_context_window: context_window,
1672 auto_compact_token_limit,
1673 supports_compaction: true,
1674 supports_images: true,
1678 supports_tools: false,
1679 supports_structured: false,
1680 edit_tool: None,
1681 hidden: false,
1682 }
1683}
1684
1685pub fn built_in_providers() -> &'static [ProviderCatalogEntry] {
1686 BUILT_IN_PROVIDERS
1687}
1688
1689pub fn built_in_models(include_hidden: bool) -> Vec<&'static ModelCatalogEntry> {
1690 BUILT_IN_MODELS
1691 .iter()
1692 .filter(|model| include_hidden || !model.hidden)
1693 .collect()
1694}
1695
1696pub fn models_for_provider(provider: &str, include_hidden: bool) -> Vec<ModelDescriptor> {
1697 built_in_models(include_hidden)
1698 .into_iter()
1699 .filter(|model| model.provider == provider)
1700 .map(ModelDescriptor::from)
1701 .collect()
1702}
1703
1704pub fn models_for_codex(include_hidden: bool) -> Vec<ModelDescriptor> {
1705 built_in_models(include_hidden)
1706 .into_iter()
1707 .filter(|model| model.provider == PROVIDER_OPENAI || model.provider == PROVIDER_CODEX)
1708 .map(ModelDescriptor::from)
1709 .collect()
1710}
1711
1712pub fn lookup_model(id: &str) -> Option<&'static ModelCatalogEntry> {
1713 BUILT_IN_MODELS.iter().find(|model| model.id == id)
1714}
1715
1716pub fn lookup_model_for_provider(provider: &str, id: &str) -> Option<&'static ModelCatalogEntry> {
1726 BUILT_IN_MODELS
1727 .iter()
1728 .find(|model| model.provider == provider && model.id == id)
1729 .or_else(|| lookup_model(id))
1730}
1731
1732pub fn built_in_model_profile(id: &str) -> Option<ModelHarnessProfile> {
1733 lookup_model(id).map(model_harness_profile_from_catalog)
1734}
1735
1736pub fn built_in_model_profile_for_provider(
1742 provider: &str,
1743 id: &str,
1744) -> Option<ModelHarnessProfile> {
1745 lookup_model_for_provider(provider, id).map(model_harness_profile_from_catalog)
1746}
1747
1748pub fn built_in_model_profiles() -> Vec<ModelHarnessProfile> {
1749 built_in_models(true)
1750 .into_iter()
1751 .map(model_harness_profile_from_catalog)
1752 .collect()
1753}
1754
1755fn model_harness_profile_from_catalog(model: &ModelCatalogEntry) -> ModelHarnessProfile {
1756 let provider_family = provider_family_for_provider(model.provider);
1757 ModelHarnessProfile {
1758 model: model.id.to_string(),
1759 provider: model.provider.to_string(),
1760 provider_family,
1761 edit_tool: model.edit_tool.map(str::to_string),
1762 schema_policy: schema_policy_for_family(provider_family),
1763 instruction_overlay: instruction_overlay_for_family(provider_family),
1764 reasoning: ModelProfileReasoning {
1765 orientation: Some(model.default_reasoning.to_string()),
1766 execution: Some(default_execution_reasoning(model)),
1767 verification: Some(model.default_reasoning.to_string()),
1768 recovery: Some(model.default_reasoning.to_string()),
1769 },
1770 parallel_tool_calls: Some(
1771 model.supports_tools
1772 && matches!(
1773 provider_family,
1774 ProviderFamily::OpenAi | ProviderFamily::Xai | ProviderFamily::Opencode
1775 ),
1776 ),
1777 auto_compact_token_limit: (model.auto_compact_token_limit > 0)
1778 .then_some(model.auto_compact_token_limit),
1779 }
1780}
1781
1782pub fn provider_family_for_provider(provider: &str) -> ProviderFamily {
1783 match provider {
1784 PROVIDER_OPENAI | PROVIDER_CODEX => ProviderFamily::OpenAi,
1785 PROVIDER_ANTHROPIC | PROVIDER_CLAUDE_CODE => ProviderFamily::Anthropic,
1786 PROVIDER_GEMINI | PROVIDER_VERTEX => ProviderFamily::Gemini,
1787 PROVIDER_XAI | PROVIDER_SUPERGROK => ProviderFamily::Xai,
1788 PROVIDER_OPENCODE | PROVIDER_OPENCODE_GO => ProviderFamily::Opencode,
1789 PROVIDER_OPENROUTER | PROVIDER_FIREWORKS | PROVIDER_RODER_CLOUD => ProviderFamily::OpenAi,
1790 PROVIDER_POOLSIDE => ProviderFamily::Poolside,
1791 PROVIDER_CURSOR => ProviderFamily::Cursor,
1792 PROVIDER_XIAOMI_MIMO | PROVIDER_XIAOMI_MIMO_TOKEN_PLAN => ProviderFamily::OpenAi,
1793 PROVIDER_KIMI_CODE => ProviderFamily::OpenAi,
1794 PROVIDER_SYNTHETIC => ProviderFamily::OpenAi,
1795 PROVIDER_DEEPSEEK => ProviderFamily::OpenAi,
1796 _ => ProviderFamily::Mock,
1797 }
1798}
1799
1800fn schema_policy_for_family(family: ProviderFamily) -> ModelSchemaPolicy {
1801 match family {
1802 ProviderFamily::OpenAi => ModelSchemaPolicy::RequiredFirstFlat,
1803 _ => ModelSchemaPolicy::StandardRequiredFirst,
1804 }
1805}
1806
1807fn instruction_overlay_for_family(family: ProviderFamily) -> ModelInstructionOverlay {
1808 match family {
1809 ProviderFamily::OpenAi => ModelInstructionOverlay::LiteralToolOutputs,
1810 ProviderFamily::Anthropic | ProviderFamily::Gemini => {
1811 ModelInstructionOverlay::IntuitiveContext
1812 }
1813 _ => ModelInstructionOverlay::Standard,
1814 }
1815}
1816
1817fn default_execution_reasoning(model: &ModelCatalogEntry) -> String {
1818 if model
1819 .supported_reasoning
1820 .iter()
1821 .any(|option| option.effort == REASONING_LOW)
1822 {
1823 REASONING_LOW.to_string()
1824 } else {
1825 model.default_reasoning.to_string()
1826 }
1827}
1828
1829pub fn model_supports_reasoning_effort(model: &str, effort: &str) -> bool {
1830 lookup_model(model)
1831 .map(|entry| {
1832 entry
1833 .supported_reasoning
1834 .iter()
1835 .any(|option| option.effort == effort)
1836 })
1837 .unwrap_or(false)
1838}
1839
1840pub fn normalize_provider_id(provider: &str) -> String {
1841 match provider.trim().to_ascii_lowercase().as_str() {
1842 "grok" | "x-ai" | "x.ai" => PROVIDER_XAI.to_string(),
1843 "grok-oauth" | "xai-oauth" | "x-ai-oauth" | "xai-grok-oauth" => {
1844 PROVIDER_SUPERGROK.to_string()
1845 }
1846 "opencode" => PROVIDER_OPENCODE.to_string(),
1847 "go" | "opencode_go" | "opencode-go" => PROVIDER_OPENCODE_GO.to_string(),
1848 "openrouter" => PROVIDER_OPENROUTER.to_string(),
1849 "fireworks" | "fireworks-ai" | "fireworks_ai" => PROVIDER_FIREWORKS.to_string(),
1850 "roder-cloud" | "roder_cloud" | "rodercloud" | "roder.cloud" => {
1851 PROVIDER_RODER_CLOUD.to_string()
1852 }
1853 "laguna" | "poolside" => PROVIDER_POOLSIDE.to_string(),
1854 "composer" | "cursor-composer" => PROVIDER_CURSOR.to_string(),
1855 "claude_code" | "claudecode" => PROVIDER_CLAUDE_CODE.to_string(),
1856 "kimi" | "kimi-code" | "kimi_code" | "moonshot" => PROVIDER_KIMI_CODE.to_string(),
1857 "synthetic" | "synthetic-ai" | "synthetic_ai" | "synthetic.new" => {
1858 PROVIDER_SYNTHETIC.to_string()
1859 }
1860 "deepseek" | "deepseek-platform" | "deepseek_platform" => PROVIDER_DEEPSEEK.to_string(),
1861 provider => provider.to_string(),
1862 }
1863}
1864
1865impl From<&ModelCatalogEntry> for ModelDescriptor {
1866 fn from(model: &ModelCatalogEntry) -> Self {
1867 let supported_reasoning = model
1868 .supported_reasoning
1869 .iter()
1870 .map(|option| ReasoningEffortDescriptor {
1871 effort: option.effort.to_string(),
1872 description: option.description.to_string(),
1873 })
1874 .collect::<Vec<_>>();
1875 Self {
1876 id: model.id.to_string(),
1877 name: model.display_name.to_string(),
1878 context_window: (model.context_window > 0).then_some(model.context_window),
1879 default_reasoning: (!supported_reasoning.is_empty())
1880 .then(|| model.default_reasoning.to_string()),
1881 supported_reasoning,
1882 }
1883 }
1884}
1885
1886#[cfg(test)]
1887mod tests {
1888 use super::*;
1889
1890 #[test]
1891 fn openrouter_catalog_entries_are_well_formed() {
1892 let entries = BUILT_IN_MODELS
1893 .iter()
1894 .filter(|model| model.provider == PROVIDER_OPENROUTER)
1895 .collect::<Vec<_>>();
1896 assert!(!entries.is_empty());
1897 for model in entries {
1898 assert!(
1899 model.id.contains('/'),
1900 "{} is not an OpenRouter slug",
1901 model.id
1902 );
1903 assert!(
1904 model.context_window > 0,
1905 "{} has no context window",
1906 model.id
1907 );
1908 assert_eq!(
1909 model.max_context_window, model.context_window,
1910 "{}",
1911 model.id
1912 );
1913 assert!(
1914 model.auto_compact_token_limit > 0
1915 && model.auto_compact_token_limit < model.context_window,
1916 "{} compaction threshold must sit inside its window",
1917 model.id
1918 );
1919 assert!(
1922 !model.supports_compaction,
1923 "{} must compact client-side",
1924 model.id
1925 );
1926 assert!(model.supports_tools, "{} must support tools", model.id);
1927 }
1928 }
1929
1930 #[test]
1931 fn openrouter_catalog_resolves_kimi_k3_with_live_limits() {
1932 let k3 = lookup_model_for_provider(PROVIDER_OPENROUTER, "moonshotai/kimi-k3")
1933 .expect("kimi k3 is catalogued");
1934 assert_eq!(k3.context_window, 1_048_576);
1935 assert!(k3.supports_images);
1936 assert!(!k3.supported_reasoning.is_empty());
1937 assert_eq!(k3.auto_compact_token_limit, 943_718);
1938 }
1939
1940 #[test]
1941 fn catalog_contains_gode_providers() {
1942 let ids = BUILT_IN_PROVIDERS
1943 .iter()
1944 .map(|provider| provider.id)
1945 .collect::<Vec<_>>();
1946 assert_eq!(
1947 ids,
1948 vec![
1949 "mock",
1950 "openai",
1951 "codex",
1952 "anthropic",
1953 "claude-code",
1954 "gemini",
1955 "vertex",
1956 "xai",
1957 "supergrok",
1958 "opencode",
1959 "opencode-go",
1960 "openrouter",
1961 "fireworks",
1962 "roder-cloud",
1963 "poolside",
1964 "cursor",
1965 "xiaomi-mimo",
1966 "xiaomi-mimo-token-plan",
1967 "synthetic",
1968 "deepseek",
1969 "kimi-code"
1970 ]
1971 );
1972 }
1973
1974 #[test]
1975 fn gemini_provider_defaults_to_stable_35_flash() {
1976 let provider = BUILT_IN_PROVIDERS
1977 .iter()
1978 .find(|provider| provider.id == PROVIDER_GEMINI)
1979 .unwrap();
1980
1981 assert_eq!(provider.default_model, "gemini-3.5-flash");
1982
1983 let model = lookup_model("gemini-3.5-flash").unwrap();
1984 assert_eq!(model.display_name, "Gemini 3.5 Flash");
1985 assert_eq!(model.provider, PROVIDER_GEMINI);
1986 assert_eq!(model.context_window, 1_048_576);
1987 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
1988 assert!(model.supports_tools);
1989 assert!(model.supports_structured);
1990 assert_eq!(
1991 model
1992 .supported_reasoning
1993 .iter()
1994 .map(|option| option.effort)
1995 .collect::<Vec<_>>(),
1996 vec![
1997 REASONING_MINIMAL,
1998 REASONING_LOW,
1999 REASONING_MEDIUM,
2000 REASONING_HIGH
2001 ]
2002 );
2003 }
2004
2005 #[test]
2006 fn vertex_provider_mirrors_gemini_models_under_vertex_id() {
2007 let provider = BUILT_IN_PROVIDERS
2008 .iter()
2009 .find(|provider| provider.id == PROVIDER_VERTEX)
2010 .unwrap();
2011
2012 assert_eq!(provider.default_model, "gemini-3.5-flash");
2013 assert_eq!(provider.env_key, Some("GOOGLE_APPLICATION_CREDENTIALS"));
2014 assert_eq!(provider.env_aliases, &["VERTEX_CREDENTIALS_JSON"]);
2015
2016 let model = lookup_model_for_provider(PROVIDER_VERTEX, "gemini-3.5-flash").unwrap();
2017 assert_eq!(model.provider, PROVIDER_VERTEX);
2018 assert_eq!(model.context_window, 1_048_576);
2019 assert!(model.supports_tools);
2020 assert_eq!(
2021 provider_family_for_provider(PROVIDER_VERTEX),
2022 ProviderFamily::Gemini
2023 );
2024 }
2025
2026 #[test]
2027 fn gemini_38_flash_is_offered_on_both_google_providers() {
2028 for provider in [PROVIDER_GEMINI, PROVIDER_VERTEX] {
2029 let model = lookup_model_for_provider(provider, "gemini-3.8-flash").unwrap();
2030 assert_eq!(model.provider, provider);
2031 assert_eq!(model.display_name, "Gemini 3.8 Flash");
2032 assert_eq!(model.context_window, 1_048_576);
2033 assert_eq!(model.auto_compact_token_limit, 943_718);
2034 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
2035 assert!(model.supports_tools);
2036 assert!(model.supports_structured);
2037 assert!(model.supports_images);
2038 assert!(!model.hidden);
2039 }
2040 }
2041
2042 #[test]
2043 fn catalog_contains_gode_visible_models() {
2044 let ids = built_in_models(false)
2045 .into_iter()
2046 .map(|model| model.id)
2047 .collect::<Vec<_>>();
2048 assert_eq!(
2049 ids,
2050 vec![
2051 "gpt-6.1-terra",
2052 "gpt-6-astra",
2053 "gpt-6.1-sol",
2054 "gpt-6-luna",
2055 "claude-opus-5-5",
2056 "claude-sonnet-5",
2057 "claude-sonnet-5-5",
2058 "claude-fable-5-1",
2059 "claude-fable-5",
2060 "claude-opus-4-8",
2061 "claude-opus-4-7",
2062 "claude-sonnet-4-6",
2063 "claude-haiku-4-5-20251001",
2064 "claude-haiku-5-5",
2065 "claude-opus-5-5",
2066 "claude-sonnet-5",
2067 "claude-sonnet-5-5",
2068 "fable",
2069 "sonnet",
2070 "opus",
2071 "haiku",
2072 "claude-haiku-5-5",
2073 "claude-sonnet-4-6",
2074 "claude-opus-4-8",
2075 "claude-fable-5",
2076 "claude-fable-5-1",
2077 "gemini-3.8-flash",
2078 "gemini-3.5-flash",
2079 "gemini-3.7-flash",
2080 "gemini-3.1-pro-preview",
2081 "gemini-3.1-pro-preview-customtools",
2082 "gemini-3-flash-preview",
2083 "gemini-3.1-flash-lite-preview",
2084 "gemini-3.8-flash",
2085 "gemini-3.5-flash",
2086 "gemini-3.7-flash",
2087 "gemini-3.1-pro-preview",
2088 "gemini-3-flash-preview",
2089 "gemini-3.1-flash-lite-preview",
2090 "grok-4.7",
2091 "grok-4.6",
2092 "grok-4.3",
2093 "grok-4.20-multi-agent-0309",
2094 "grok-4.20-0309-reasoning",
2095 "grok-4.20-0309-non-reasoning",
2096 "grok-4.7",
2097 "grok-4.6",
2098 "grok-composer-2.5-fast",
2099 "gpt-6.1-sol",
2100 "big-pickle",
2101 "mimo-v2.5-free",
2102 "nemotron-3-ultra-free",
2103 "north-mini-code-free",
2104 "deepseek-v4-flash",
2105 "deepseek-v4-pro",
2106 "kimi-k2.6",
2107 "qwen3.6-plus",
2108 "glm-5.1",
2109 "deepseek-v4-flash",
2110 "deepseek-v4-pro",
2111 "kimi-for-coding",
2112 "x-ai/grok-4.6",
2113 "x-ai/grok-4.7",
2114 "moonshotai/kimi-k3",
2115 "moonshotai/kimi-k2.7-code",
2116 "moonshotai/kimi-k2.6",
2117 "anthropic/claude-opus-5.5",
2118 "anthropic/claude-sonnet-5.5",
2119 "anthropic/claude-haiku-5.5",
2120 "anthropic/claude-fable-5.1",
2121 "anthropic/claude-opus-5",
2122 "openai/gpt-6.1-terra",
2123 "openai/gpt-6.1-sol",
2124 "openai/gpt-6-luna",
2125 "openai/gpt-6-astra",
2126 "google/gemini-3.8-flash",
2127 "google/gemini-3.7-flash",
2128 "deepseek/deepseek-v4-pro-0813",
2129 "deepseek/deepseek-v4.1-flash",
2130 "deepseek/deepseek-v4.1-flash-1017",
2131 "qwen/qwen3.8-max-0902",
2132 "qwen/qwen3.8-flash",
2133 "z-ai/glm-5.3",
2134 "z-ai/glm-5.3-flash",
2135 "xiaomi/mimo-v2.6-pro",
2136 "mistralai/mistral-large-4-0",
2137 "meta/muse-spark-1.3",
2138 "accounts/fireworks/models/qwen3-235b-a22b",
2139 "roder.cloud/free",
2140 "roder.cloud/anthropic/claude-opus-4-7",
2141 "roder.cloud/google/gemini-3.1-pro-preview",
2142 "poolside/laguna-m.1",
2143 "poolside/laguna-xs.2",
2144 "mimo-v2.5-pro",
2145 "mimo-v2-pro",
2146 "mimo-v2.5",
2147 "mimo-v2-omni",
2148 "mimo-v2-flash",
2149 "mimo-v2.5-pro",
2150 "mimo-v2-pro",
2151 "mimo-v2.5",
2152 "mimo-v2-omni",
2153 "mimo-v2-flash",
2154 "syn:large:text",
2155 "syn:small:text",
2156 "syn:large:vision",
2157 "syn:small:vision",
2158 "hf:MiniMaxAI/MiniMax-M3",
2159 "hf:Qwen/Qwen3.6-27B",
2160 "hf:moonshotai/Kimi-K2.6",
2161 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
2162 "hf:zai-org/GLM-4.7",
2163 "hf:zai-org/GLM-4.7-Flash",
2164 "hf:zai-org/GLM-5.1",
2165 "hf:zai-org/GLM-5.2",
2166 "hf:openai/gpt-oss-120b",
2167 "hf:Qwen/Qwen3.5-397B-A17B",
2168 "deepseek-chat",
2169 "deepseek-reasoner",
2170 "deepseek-v4-flash",
2171 "deepseek-v4-pro",
2172 "composer-2.5",
2173 "composer-2.5-fast",
2174 "claude-fable-5",
2175 "claude-opus-4-8",
2176 "claude-sonnet-4-6",
2177 "gemini-3.1-pro",
2178 "gemini-4-argon",
2179 "grok-4.6",
2180 "gemini-3.7-flash",
2181 ]
2182 );
2183 }
2184
2185 #[test]
2186 fn provider_model_lists_match_gode_catalog() {
2187 assert_eq!(models_for_provider(PROVIDER_OPENAI, false).len(), 4);
2188 assert_eq!(models_for_codex(false).len(), 4);
2189 assert_eq!(models_for_provider(PROVIDER_ANTHROPIC, false).len(), 10);
2190 assert_eq!(models_for_provider(PROVIDER_CLAUDE_CODE, false).len(), 12);
2191 assert_eq!(models_for_provider(PROVIDER_GEMINI, false).len(), 7);
2192 assert_eq!(models_for_provider(PROVIDER_VERTEX, false).len(), 6);
2193 assert_eq!(models_for_provider(PROVIDER_XAI, false).len(), 6);
2194 assert_eq!(models_for_provider(PROVIDER_SUPERGROK, false).len(), 3);
2195 assert_eq!(models_for_provider(PROVIDER_OPENCODE, false).len(), 7);
2196 assert_eq!(models_for_provider(PROVIDER_OPENCODE_GO, false).len(), 5);
2197 assert_eq!(models_for_provider(PROVIDER_OPENROUTER, false).len(), 26);
2198 assert_eq!(models_for_provider(PROVIDER_FIREWORKS, false).len(), 1);
2199 assert_eq!(models_for_provider(PROVIDER_RODER_CLOUD, false).len(), 3);
2200 assert_eq!(models_for_provider(PROVIDER_POOLSIDE, false).len(), 2);
2201 assert_eq!(models_for_provider(PROVIDER_CURSOR, false).len(), 9);
2202 assert_eq!(models_for_provider(PROVIDER_XIAOMI_MIMO, false).len(), 5);
2203 assert_eq!(
2204 models_for_provider(PROVIDER_XIAOMI_MIMO_TOKEN_PLAN, false).len(),
2205 5
2206 );
2207 assert_eq!(models_for_provider(PROVIDER_KIMI_CODE, false).len(), 1);
2208 assert_eq!(models_for_provider(PROVIDER_SYNTHETIC, false).len(), 14);
2209 assert_eq!(models_for_provider(PROVIDER_DEEPSEEK, false).len(), 4);
2210 assert_eq!(models_for_provider(PROVIDER_MOCK, true).len(), 1);
2211 }
2212
2213 #[test]
2214 fn codex_model_list_matches_current_subscription_roster() {
2215 let codex_provider = built_in_providers()
2216 .iter()
2217 .find(|provider| provider.id == PROVIDER_CODEX)
2218 .expect("codex provider");
2219 assert_eq!(codex_provider.default_model, "gpt-6.1-sol");
2220
2221 let ids = models_for_codex(false)
2222 .into_iter()
2223 .map(|model| model.id)
2224 .collect::<Vec<_>>();
2225
2226 assert_eq!(
2227 ids,
2228 vec!["gpt-6.1-terra", "gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna"]
2229 );
2230 }
2231
2232 #[test]
2233 fn new_codex_models_match_current_subscription_metadata() {
2234 let assert_model = |id: &str,
2235 name: &str,
2236 description: &str,
2237 default_reasoning: &str,
2238 efforts: &[&str],
2239 context_window: u32,
2240 max_context_window: u32| {
2241 let model = lookup_model_for_provider(PROVIDER_OPENAI, id).unwrap();
2242
2243 assert_eq!(model.display_name, name, "{id} display name");
2244 assert_eq!(model.description, description, "{id} description");
2245 assert_eq!(model.provider, PROVIDER_OPENAI, "{id} provider");
2246 assert_eq!(
2247 model.default_reasoning, default_reasoning,
2248 "{id} default reasoning"
2249 );
2250 assert_eq!(
2251 model
2252 .supported_reasoning
2253 .iter()
2254 .map(|option| option.effort)
2255 .collect::<Vec<_>>(),
2256 efforts,
2257 "{id} efforts"
2258 );
2259 assert_eq!(model.context_window, context_window, "{id} context window");
2260 assert_eq!(
2261 model.max_context_window, max_context_window,
2262 "{id} max context window"
2263 );
2264 assert_eq!(
2265 model.auto_compact_token_limit,
2266 context_window.saturating_mul(9) / 10,
2267 "{id} auto compact limit"
2268 );
2269 assert!(model.supports_compaction, "{id} compaction support");
2270 assert!(model.supports_images, "{id} image support");
2271 assert!(model.supports_tools, "{id} tool support");
2272 let offered = matches!(
2274 id,
2275 "gpt-6-astra" | "gpt-6-luna" | "gpt-6.1-sol" | "gpt-6.1-terra"
2276 );
2277 assert_eq!(model.hidden, !offered, "{id} visibility");
2278 };
2279
2280 assert_model(
2281 "gpt-6-astra",
2282 "GPT-6-Astra",
2283 "OpenAI's most capable model, built for the hardest end-to-end work.",
2284 REASONING_HIGH,
2285 &[
2286 REASONING_LOW,
2287 REASONING_MEDIUM,
2288 REASONING_HIGH,
2289 REASONING_XHIGH,
2290 REASONING_MAX,
2291 ],
2292 1_050_000,
2293 1_050_000,
2294 );
2295 assert_model(
2296 "gpt-6-sol",
2297 "GPT-6 Sol",
2298 "Agentic coding model balancing intelligence and cost.",
2299 REASONING_MEDIUM,
2300 &[
2301 REASONING_NONE,
2302 REASONING_LOW,
2303 REASONING_MEDIUM,
2304 REASONING_HIGH,
2305 REASONING_XHIGH,
2306 REASONING_MAX,
2307 ],
2308 1_050_000,
2309 1_050_000,
2310 );
2311 assert_model(
2312 "gpt-6.1-terra",
2313 "GPT-6.1 Terra",
2314 "Fast computer-use and coding model with a 1M context window. $1 / $5 per 1M input/output tokens.",
2315 REASONING_MEDIUM,
2316 &[
2317 REASONING_NONE,
2318 REASONING_LOW,
2319 REASONING_MEDIUM,
2320 REASONING_HIGH,
2321 REASONING_XHIGH,
2322 REASONING_MAX,
2323 ],
2324 1_000_000,
2325 1_000_000,
2326 );
2327 assert_model(
2328 "gpt-6.1-sol",
2329 "GPT-6.1-Sol",
2330 "Agentic coding model balancing intelligence and cost.",
2331 REASONING_MEDIUM,
2332 &[
2333 REASONING_NONE,
2334 REASONING_LOW,
2335 REASONING_MEDIUM,
2336 REASONING_HIGH,
2337 REASONING_XHIGH,
2338 REASONING_MAX,
2339 ],
2340 1_050_000,
2341 1_050_000,
2342 );
2343 assert_model(
2344 "gpt-6-luna",
2345 "GPT-6 Luna",
2346 "Efficient model for focused, high-volume tasks.",
2347 REASONING_MEDIUM,
2348 &[
2349 REASONING_NONE,
2350 REASONING_LOW,
2351 REASONING_MEDIUM,
2352 REASONING_HIGH,
2353 REASONING_XHIGH,
2354 REASONING_MAX,
2355 ],
2356 1_050_000,
2357 1_050_000,
2358 );
2359 assert_model(
2360 "gpt-5.6-sol",
2361 "GPT-5.6-Sol",
2362 "Latest frontier agentic coding model.",
2363 REASONING_LOW,
2364 &[
2365 REASONING_LOW,
2366 REASONING_MEDIUM,
2367 REASONING_HIGH,
2368 REASONING_XHIGH,
2369 REASONING_MAX,
2370 REASONING_ULTRA,
2371 ],
2372 372_000,
2373 372_000,
2374 );
2375 assert_model(
2376 "gpt-5.6-terra",
2377 "GPT-5.6-Terra",
2378 "Balanced agentic coding model for everyday work.",
2379 REASONING_MEDIUM,
2380 &[
2381 REASONING_LOW,
2382 REASONING_MEDIUM,
2383 REASONING_HIGH,
2384 REASONING_XHIGH,
2385 REASONING_MAX,
2386 REASONING_ULTRA,
2387 ],
2388 372_000,
2389 372_000,
2390 );
2391 assert_model(
2392 "gpt-5.6-luna",
2393 "GPT-5.6-Luna",
2394 "Fast and affordable agentic coding model.",
2395 REASONING_MEDIUM,
2396 &[
2397 REASONING_LOW,
2398 REASONING_MEDIUM,
2399 REASONING_HIGH,
2400 REASONING_XHIGH,
2401 REASONING_MAX,
2402 ],
2403 372_000,
2404 372_000,
2405 );
2406 assert_model(
2407 "gpt-5.4",
2408 "GPT-5.4",
2409 "Strong model for everyday coding.",
2410 REASONING_MEDIUM,
2411 &[
2412 REASONING_LOW,
2413 REASONING_MEDIUM,
2414 REASONING_HIGH,
2415 REASONING_XHIGH,
2416 ],
2417 272_000,
2418 1_000_000,
2419 );
2420 }
2421
2422 #[test]
2423 fn deepseek_catalog_defaults_to_chat_model() {
2424 let provider = BUILT_IN_PROVIDERS
2425 .iter()
2426 .find(|provider| provider.id == PROVIDER_DEEPSEEK)
2427 .expect("deepseek provider registered");
2428 assert_eq!(provider.name, "DeepSeek Platform");
2429 assert_eq!(provider.default_model, "deepseek-chat");
2430 assert_eq!(provider.base_url, Some("https://api.deepseek.com/v1"));
2431 assert_eq!(provider.env_key, Some("DEEPSEEK_API_KEY"));
2432 assert_eq!(
2433 normalize_provider_id("deepseek-platform"),
2434 PROVIDER_DEEPSEEK
2435 );
2436 assert_eq!(
2437 provider_family_for_provider(PROVIDER_DEEPSEEK),
2438 ProviderFamily::OpenAi
2439 );
2440
2441 let models = models_for_provider(PROVIDER_DEEPSEEK, false);
2442 assert_eq!(models.len(), 4);
2443 assert!(models.iter().any(|model| model.id == "deepseek-chat"));
2444 assert!(models.iter().any(|model| model.id == "deepseek-reasoner"));
2445 assert!(models.iter().any(|model| model.id == "deepseek-v4-flash"));
2446 assert!(models.iter().any(|model| model.id == "deepseek-v4-pro"));
2447
2448 let flash = models
2449 .iter()
2450 .find(|model| model.id == "deepseek-v4-flash")
2451 .expect("flash model");
2452 assert_eq!(flash.default_reasoning, Some(REASONING_HIGH.to_string()));
2453 assert_eq!(
2454 flash
2455 .supported_reasoning
2456 .iter()
2457 .map(|option| option.effort.as_str())
2458 .collect::<Vec<_>>(),
2459 vec![
2460 REASONING_NONE,
2461 REASONING_LOW,
2462 REASONING_HIGH,
2463 REASONING_XHIGH,
2464 REASONING_MAX,
2465 ]
2466 );
2467
2468 let chat = models
2469 .iter()
2470 .find(|model| model.id == "deepseek-chat")
2471 .expect("chat model");
2472 assert_eq!(chat.default_reasoning, Some(REASONING_NONE.to_string()));
2473 assert!(
2474 chat.supported_reasoning
2475 .iter()
2476 .any(|option| option.effort == REASONING_HIGH)
2477 );
2478 }
2479
2480 #[test]
2481 fn synthetic_catalog_defaults_to_large_text_alias() {
2482 let provider = built_in_providers()
2483 .iter()
2484 .find(|provider| provider.id == PROVIDER_SYNTHETIC)
2485 .expect("synthetic provider registered");
2486 assert_eq!(provider.name, "Synthetic");
2487 assert_eq!(provider.default_model, "syn:large:text");
2488 assert_eq!(
2489 provider.base_url,
2490 Some("https://api.synthetic.new/openai/v1")
2491 );
2492 assert_eq!(provider.env_key, Some("SYNTHETIC_API_KEY"));
2493 assert!(provider.env_aliases.contains(&"RODER_SYNTHETIC_API_KEY"));
2494 assert!(!provider.supports_websockets);
2495
2496 let models = models_for_provider(PROVIDER_SYNTHETIC, false);
2497 let default = models
2498 .iter()
2499 .find(|model| model.id == provider.default_model)
2500 .expect("default synthetic model present");
2501 assert_eq!(default.name, "Synthetic Large (Text)");
2502 assert!(models.iter().any(|model| model.id == "syn:small:text"));
2503 let vision = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:vision")
2504 .expect("vision alias present");
2505 assert!(vision.supports_images);
2506 assert_eq!(
2507 provider_family_for_provider(PROVIDER_SYNTHETIC),
2508 ProviderFamily::OpenAi
2509 );
2510 }
2511
2512 #[test]
2513 fn synthetic_model_ids_preserve_alias_and_hf_segments() {
2514 assert_eq!(normalize_provider_id("synthetic"), PROVIDER_SYNTHETIC);
2515 assert_eq!(normalize_provider_id("synthetic.new"), PROVIDER_SYNTHETIC);
2516 let alias = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:text")
2518 .expect("syn alias resolves");
2519 assert_eq!(alias.id, "syn:large:text");
2520 assert_eq!(alias.provider, PROVIDER_SYNTHETIC);
2521 let label = "synthetic/hf:zai-org/GLM-5.2";
2525 let (provider, model) = label.split_once('/').unwrap();
2526 assert_eq!(provider, PROVIDER_SYNTHETIC);
2527 assert_eq!(model, "hf:zai-org/GLM-5.2");
2528 }
2529
2530 #[test]
2531 fn synthetic_always_on_models_are_pinned_with_documented_context_windows() {
2532 let glm_5_2 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-5.2")
2533 .expect("GLM-5.2 pinned");
2534 assert_eq!(glm_5_2.provider, PROVIDER_SYNTHETIC);
2535 assert_eq!(glm_5_2.context_window, 524_288);
2536 assert!(!glm_5_2.supports_images);
2537
2538 let minimax = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:MiniMaxAI/MiniMax-M3")
2539 .expect("MiniMax-M3 pinned");
2540 assert_eq!(minimax.context_window, 524_288);
2541
2542 let glm_4_7 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-4.7")
2543 .expect("GLM-4.7 pinned");
2544 assert_eq!(glm_4_7.context_window, 202_752);
2545
2546 let gpt_oss = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:openai/gpt-oss-120b")
2547 .expect("gpt-oss-120b pinned");
2548 assert_eq!(gpt_oss.context_window, 131_072);
2549
2550 let qwen_3_5 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:Qwen/Qwen3.5-397B-A17B")
2551 .expect("Qwen3.5 397B pinned");
2552 assert_eq!(qwen_3_5.context_window, 262_144);
2553
2554 let always_on = [
2556 "hf:MiniMaxAI/MiniMax-M3",
2557 "hf:Qwen/Qwen3.6-27B",
2558 "hf:moonshotai/Kimi-K2.6",
2559 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
2560 "hf:zai-org/GLM-4.7",
2561 "hf:zai-org/GLM-4.7-Flash",
2562 "hf:zai-org/GLM-5.1",
2563 "hf:zai-org/GLM-5.2",
2564 "hf:openai/gpt-oss-120b",
2565 "hf:Qwen/Qwen3.5-397B-A17B",
2566 ];
2567 for id in always_on {
2568 let entry = lookup_model_for_provider(PROVIDER_SYNTHETIC, id)
2569 .unwrap_or_else(|| panic!("{id} should be pinned in the synthetic catalog"));
2570 assert_eq!(entry.provider, PROVIDER_SYNTHETIC);
2571 assert!(entry.supports_tools);
2572 assert!(entry.supports_structured);
2573 }
2574 }
2575
2576 #[test]
2577 fn claude_code_catalog_uses_long_context_windows() {
2578 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-sonnet-4-6").unwrap();
2579 let claude_code =
2580 lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-sonnet-4-6").unwrap();
2581
2582 assert_eq!(direct.context_window, 1_000_000);
2583 assert_eq!(claude_code.context_window, 1_000_000);
2584 assert_eq!(claude_code.auto_compact_token_limit, 900_000);
2585 assert!(!claude_code.supports_compaction);
2588 assert!(direct.supports_compaction);
2592 assert_eq!(direct.auto_compact_token_limit, 900_000);
2593 }
2594
2595 #[test]
2596 fn claude_haiku_does_not_advertise_server_side_compaction() {
2597 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
2598
2599 assert!(!haiku.supports_compaction);
2604 assert_eq!(haiku.auto_compact_token_limit, 180_000);
2605 }
2606
2607 #[test]
2608 fn claude_fable_5_1_is_offered_directly_and_through_the_claude_code_harness() {
2609 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-fable-5-1").unwrap();
2610 assert_eq!(direct.display_name, "Claude Fable 5.1");
2611 assert_eq!(direct.context_window, 1_000_000);
2612 assert_eq!(direct.auto_compact_token_limit, 900_000);
2613 assert_eq!(direct.default_reasoning, REASONING_HIGH);
2614 assert!(direct.supports_compaction);
2615 assert_eq!(
2616 direct
2617 .supported_reasoning
2618 .iter()
2619 .map(|option| option.effort)
2620 .collect::<Vec<_>>(),
2621 vec![
2622 REASONING_LOW,
2623 REASONING_MEDIUM,
2624 REASONING_HIGH,
2625 REASONING_XHIGH,
2626 REASONING_MAX
2627 ]
2628 );
2629
2630 let harness = lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-fable-5-1").unwrap();
2631 assert_eq!(harness.provider, PROVIDER_CLAUDE_CODE);
2632 assert_eq!(harness.context_window, 1_000_000);
2633 assert!(!harness.supports_compaction);
2636 }
2637
2638 #[test]
2639 fn google_embedding_model_is_hidden_from_chat_lists() {
2640 assert!(lookup_model("gemini-embedding-2").is_some());
2641 assert!(
2642 models_for_provider(PROVIDER_GOOGLE, false)
2643 .iter()
2644 .all(|model| model.id != "gemini-embedding-2")
2645 );
2646 let model = lookup_model("gemini-embedding-2").unwrap();
2647 assert!(model.hidden);
2648 assert!(!model.supports_tools);
2649 }
2650
2651 #[test]
2652 fn zeroentropy_embedding_model_is_hidden_from_chat_lists() {
2653 assert!(lookup_model("zembed-1").is_some());
2654 assert!(
2655 models_for_provider(PROVIDER_ZEROENTROPY, false)
2656 .iter()
2657 .all(|model| model.id != "zembed-1")
2658 );
2659 let model = lookup_model("zembed-1").unwrap();
2660 assert!(model.hidden);
2661 assert!(!model.supports_tools);
2662 }
2663
2664 #[test]
2665 fn catalog_model_profile_derives_openai_defaults() {
2666 let profile = built_in_model_profile("gpt-5.5").unwrap();
2667
2668 assert_eq!(profile.provider_family, ProviderFamily::OpenAi);
2669 assert_eq!(profile.edit_tool.as_deref(), Some(EDIT_TOOL_PATCH));
2670 assert_eq!(profile.schema_policy, ModelSchemaPolicy::RequiredFirstFlat);
2671 assert_eq!(
2672 profile.instruction_overlay,
2673 ModelInstructionOverlay::LiteralToolOutputs
2674 );
2675 assert_eq!(profile.reasoning.execution.as_deref(), Some(REASONING_LOW));
2676 assert_eq!(profile.parallel_tool_calls, Some(true));
2677 }
2678
2679 #[test]
2680 fn poolside_catalog_defaults_to_thinking_enabled() {
2681 let laguna = lookup_model("poolside/laguna-m.1").unwrap();
2682 assert_eq!(laguna.default_reasoning, REASONING_MEDIUM);
2683 assert_eq!(
2684 laguna
2685 .supported_reasoning
2686 .iter()
2687 .map(|option| option.effort)
2688 .collect::<Vec<_>>(),
2689 vec![REASONING_NONE, REASONING_MEDIUM]
2690 );
2691 }
2692
2693 #[test]
2694 fn xiaomi_mimo_catalog_uses_chat_completions_kind_and_exact_model_ids() {
2695 let provider = BUILT_IN_PROVIDERS
2696 .iter()
2697 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO)
2698 .unwrap();
2699 let token_plan = BUILT_IN_PROVIDERS
2700 .iter()
2701 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO_TOKEN_PLAN)
2702 .unwrap();
2703
2704 assert_eq!(provider.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2705 assert_eq!(token_plan.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2706 assert_eq!(provider.env_key, Some("MIMO_API_KEY"));
2707 assert_eq!(token_plan.env_key, Some("MIMO_TOKEN_PLAN_API_KEY"));
2708
2709 let ids = models_for_provider(PROVIDER_XIAOMI_MIMO, false)
2710 .into_iter()
2711 .map(|model| model.id)
2712 .collect::<Vec<_>>();
2713 assert_eq!(
2714 ids,
2715 vec![
2716 "mimo-v2.5-pro",
2717 "mimo-v2-pro",
2718 "mimo-v2.5",
2719 "mimo-v2-omni",
2720 "mimo-v2-flash"
2721 ]
2722 );
2723 assert!(lookup_model("out-of-v2-flash").is_none());
2724 }
2725
2726 #[test]
2727 fn supergrok_catalog_exposes_grok_47_46_and_composer_with_expected_context_windows() {
2728 let grok47 = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-4.7").unwrap();
2729 assert_eq!(grok47.display_name, "Grok 4.7");
2730 assert_eq!(grok47.context_window, 500_000);
2731 assert_eq!(grok47.auto_compact_token_limit, 450_000);
2732 assert_eq!(grok47.default_reasoning, REASONING_HIGH);
2733
2734 let grok46 = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-4.6").unwrap();
2735 assert_eq!(grok46.display_name, "Grok 4.6");
2736 assert_eq!(grok46.context_window, 500_000);
2737 assert_eq!(grok46.auto_compact_token_limit, 450_000);
2738 assert_eq!(grok46.default_reasoning, REASONING_HIGH);
2739
2740 let composer =
2741 lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-composer-2.5-fast").unwrap();
2742 assert_eq!(composer.display_name, "Grok Composer 2.5 Fast");
2743 assert_eq!(composer.context_window, 200_000);
2744 assert_eq!(composer.auto_compact_token_limit, 180_000);
2745 assert!(composer.supported_reasoning.is_empty());
2746 assert!(!composer.supports_images);
2747
2748 let visible = models_for_provider(PROVIDER_SUPERGROK, false)
2749 .into_iter()
2750 .map(|model| model.id)
2751 .collect::<Vec<_>>();
2752 assert_eq!(
2753 visible,
2754 vec![
2755 "grok-4.7".to_string(),
2756 "grok-4.6".to_string(),
2757 "grok-composer-2.5-fast".to_string(),
2758 ]
2759 );
2760 }
2761
2762 #[test]
2763 fn xai_catalog_entries_match_current_grok_contract() {
2764 let grok46 = models_for_provider(PROVIDER_XAI, false)
2765 .into_iter()
2766 .find(|model| model.id == "grok-4.6")
2767 .unwrap();
2768 assert_eq!(grok46.context_window, Some(500_000));
2769 assert_eq!(grok46.default_reasoning.as_deref(), Some(REASONING_HIGH));
2770 assert_eq!(
2771 grok46
2772 .supported_reasoning
2773 .iter()
2774 .map(|option| option.effort.as_str())
2775 .collect::<Vec<_>>(),
2776 vec![
2777 REASONING_LOW,
2778 REASONING_MEDIUM,
2779 REASONING_HIGH,
2780 REASONING_XHIGH
2781 ]
2782 );
2783
2784 let grok43 = models_for_provider(PROVIDER_XAI, false)
2785 .into_iter()
2786 .find(|model| model.id == "grok-4.3")
2787 .unwrap();
2788 assert_eq!(grok43.context_window, Some(1_000_000));
2789 assert_eq!(grok43.default_reasoning.as_deref(), Some(REASONING_LOW));
2790 assert_eq!(
2791 grok43
2792 .supported_reasoning
2793 .iter()
2794 .map(|option| option.effort.as_str())
2795 .collect::<Vec<_>>(),
2796 vec![
2797 REASONING_NONE,
2798 REASONING_LOW,
2799 REASONING_MEDIUM,
2800 REASONING_HIGH,
2801 REASONING_XHIGH
2802 ]
2803 );
2804
2805 let grok420 = lookup_model("grok-4.20-multi-agent-0309").unwrap();
2806 assert_eq!(grok420.context_window, 2_000_000);
2807 assert_eq!(grok420.auto_compact_token_limit, 1_800_000);
2808 assert_eq!(grok420.provider, PROVIDER_XAI);
2809 }
2810
2811 #[test]
2812 fn provider_aliases_normalize_xai_and_supergrok() {
2813 assert_eq!(normalize_provider_id("grok"), PROVIDER_XAI);
2814 assert_eq!(normalize_provider_id("x.ai"), PROVIDER_XAI);
2815 assert_eq!(normalize_provider_id("x-ai"), PROVIDER_XAI);
2816 assert_eq!(normalize_provider_id("xai-oauth"), PROVIDER_SUPERGROK);
2817 assert_eq!(normalize_provider_id("grok-oauth"), PROVIDER_SUPERGROK);
2818 assert_eq!(normalize_provider_id("supergrok"), PROVIDER_SUPERGROK);
2819 assert_eq!(normalize_provider_id("laguna"), PROVIDER_POOLSIDE);
2820 assert_eq!(normalize_provider_id("composer"), PROVIDER_CURSOR);
2821 }
2822
2823 #[test]
2824 fn fireworks_catalog_preserves_account_scoped_default_model() {
2825 let provider = BUILT_IN_PROVIDERS
2826 .iter()
2827 .find(|provider| provider.id == PROVIDER_FIREWORKS)
2828 .unwrap();
2829
2830 assert_eq!(
2831 provider.default_model,
2832 "accounts/fireworks/models/qwen3-235b-a22b"
2833 );
2834 assert_eq!(provider.env_key, Some("FIREWORKS_API_KEY"));
2835 assert_eq!(provider.env_aliases, &["RODER_FIREWORKS_API_KEY"]);
2836
2837 let model = lookup_model_for_provider(PROVIDER_FIREWORKS, provider.default_model).unwrap();
2838 assert_eq!(model.provider, PROVIDER_FIREWORKS);
2839 assert!(model.supports_tools);
2840 assert!(model.supports_structured);
2841 assert_eq!(
2842 provider_family_for_provider(PROVIDER_FIREWORKS),
2843 ProviderFamily::OpenAi
2844 );
2845 }
2846
2847 #[test]
2848 fn cursor_catalog_profile_is_text_only_agentservice() {
2849 let composer = lookup_model("composer-2.5").unwrap();
2850 assert_eq!(composer.provider, PROVIDER_CURSOR);
2851 assert!(!composer.supports_tools);
2852 assert!(!composer.supports_structured);
2853
2854 let profile = built_in_model_profile("composer-2.5").unwrap();
2855 assert_eq!(profile.provider_family, ProviderFamily::Cursor);
2856 assert_eq!(profile.parallel_tool_calls, Some(false));
2857 }
2858
2859 #[test]
2860 fn provider_aware_lookup_resolves_cursor_proxied_models_to_cursor_family() {
2861 let id_only = built_in_model_profile("claude-opus-4-8").unwrap();
2863 assert_eq!(id_only.provider_family, ProviderFamily::Anthropic);
2864
2865 let cursor =
2867 built_in_model_profile_for_provider(PROVIDER_CURSOR, "claude-opus-4-8").unwrap();
2868 assert_eq!(cursor.provider_family, ProviderFamily::Cursor);
2869 assert_eq!(cursor.provider, PROVIDER_CURSOR);
2870 assert_eq!(cursor.parallel_tool_calls, Some(false));
2871
2872 let anthropic =
2873 built_in_model_profile_for_provider(PROVIDER_ANTHROPIC, "claude-opus-4-8").unwrap();
2874 assert_eq!(anthropic.provider_family, ProviderFamily::Anthropic);
2875
2876 let fallback =
2878 built_in_model_profile_for_provider("does-not-exist", "claude-opus-4-8").unwrap();
2879 assert_eq!(fallback.provider_family, ProviderFamily::Anthropic);
2880 }
2881
2882 #[test]
2883 fn opencode_default_model_is_a_listed_opencode_model() {
2884 let provider = built_in_providers()
2885 .iter()
2886 .find(|provider| provider.id == PROVIDER_OPENCODE)
2887 .expect("opencode provider");
2888 assert!(
2889 models_for_provider(PROVIDER_OPENCODE, false)
2890 .iter()
2891 .any(|model| model.id == provider.default_model),
2892 "opencode default {} should be listed for opencode",
2893 provider.default_model
2894 );
2895 }
2896
2897 #[test]
2898 fn retired_gpt_5_models_are_hidden_but_still_resolvable() {
2899 let visible = built_in_models(false)
2900 .into_iter()
2901 .map(|model| model.id)
2902 .collect::<Vec<_>>();
2903 for id in [
2904 "gpt-5.6-sol",
2905 "gpt-5.6-terra",
2906 "gpt-5.6-luna",
2907 "gpt-5.5",
2908 "gpt-5.5-fast",
2909 "gpt-5.4",
2910 "gpt-5.4-mini",
2911 "gpt-5.3-codex-spark",
2912 "roder.cloud/openai/gpt-5.5",
2913 ] {
2914 assert!(
2915 !visible.contains(&id),
2916 "{id} should be hidden from model lists"
2917 );
2918 assert!(lookup_model(id).is_some(), "{id} should still resolve");
2920 }
2921 assert!(
2922 !visible.contains(&"gpt-6-sol")
2923 && visible.contains(&"gpt-6.1-sol")
2924 && visible.contains(&"gpt-6.1-terra")
2925 );
2926 assert!(lookup_model("gpt-6-sol").is_some());
2927 }
2928
2929 #[test]
2930 fn retired_cursor_gpt55_keeps_standard_reasoning_metadata() {
2931 let gpt55 = ModelDescriptor::from(
2932 lookup_model_for_provider(PROVIDER_CURSOR, "gpt-5.5")
2933 .expect("cursor catalog should still resolve retired gpt-5.5"),
2934 );
2935
2936 assert_eq!(gpt55.default_reasoning.as_deref(), Some(REASONING_MEDIUM));
2937 assert_eq!(
2938 gpt55
2939 .supported_reasoning
2940 .iter()
2941 .map(|option| option.effort.as_str())
2942 .collect::<Vec<_>>(),
2943 vec![
2944 REASONING_LOW,
2945 REASONING_MEDIUM,
2946 REASONING_HIGH,
2947 REASONING_XHIGH
2948 ]
2949 );
2950
2951 let gpt55_fast = ModelDescriptor::from(
2952 lookup_model_for_provider(PROVIDER_CURSOR, "gpt-5.5-fast")
2953 .expect("cursor catalog should still resolve retired gpt-5.5-fast"),
2954 );
2955 assert_eq!(
2956 gpt55_fast.default_reasoning.as_deref(),
2957 Some(REASONING_MEDIUM)
2958 );
2959 assert_eq!(gpt55_fast.supported_reasoning.len(), 4);
2960 }
2961
2962 #[test]
2963 fn cursor_opus_advertises_configurable_reasoning_effort() {
2964 let opus = models_for_provider(PROVIDER_CURSOR, false)
2965 .into_iter()
2966 .find(|model| model.id == "claude-opus-4-8")
2967 .expect("cursor catalog should expose claude-opus-4-8");
2968
2969 assert_eq!(opus.default_reasoning.as_deref(), Some(REASONING_HIGH));
2970 assert_eq!(
2971 opus.supported_reasoning
2972 .iter()
2973 .map(|option| option.effort.as_str())
2974 .collect::<Vec<_>>(),
2975 vec![
2976 REASONING_LOW,
2977 REASONING_MEDIUM,
2978 REASONING_HIGH,
2979 REASONING_XHIGH,
2980 REASONING_MAX
2981 ]
2982 );
2983
2984 let sonnet = models_for_provider(PROVIDER_CURSOR, false)
2987 .into_iter()
2988 .find(|model| model.id == "claude-sonnet-4-6")
2989 .expect("cursor catalog should expose claude-sonnet-4-6");
2990 assert_eq!(sonnet.default_reasoning.as_deref(), Some(REASONING_MEDIUM));
2991 assert_eq!(
2992 sonnet
2993 .supported_reasoning
2994 .iter()
2995 .map(|option| option.effort.as_str())
2996 .collect::<Vec<_>>(),
2997 vec![
2998 REASONING_LOW,
2999 REASONING_MEDIUM,
3000 REASONING_HIGH,
3001 REASONING_MAX
3002 ]
3003 );
3004 }
3005
3006 #[test]
3007 fn claude_opus_and_sonnet_advertise_max_effort() {
3008 let efforts = |id: &str| {
3009 lookup_model(id)
3010 .unwrap()
3011 .supported_reasoning
3012 .iter()
3013 .map(|option| option.effort)
3014 .collect::<Vec<_>>()
3015 };
3016
3017 for id in ["claude-opus-4-8", "claude-opus-4-7"] {
3019 assert_eq!(
3020 efforts(id),
3021 vec![
3022 REASONING_LOW,
3023 REASONING_MEDIUM,
3024 REASONING_HIGH,
3025 REASONING_XHIGH,
3026 REASONING_MAX
3027 ],
3028 "{id} effort levels"
3029 );
3030 }
3031
3032 assert_eq!(
3034 efforts("claude-sonnet-4-6"),
3035 vec![
3036 REASONING_LOW,
3037 REASONING_MEDIUM,
3038 REASONING_HIGH,
3039 REASONING_MAX
3040 ]
3041 );
3042
3043 assert!(!efforts("gpt-5.5").contains(&REASONING_MAX));
3045 }
3046
3047 #[test]
3048 fn claude_haiku_does_not_advertise_reasoning_effort() {
3049 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
3050
3051 assert_eq!(haiku.default_reasoning, REASONING_NONE);
3052 assert!(haiku.supported_reasoning.is_empty());
3053
3054 let descriptor = ModelDescriptor::from(haiku);
3055 assert_eq!(descriptor.default_reasoning, None);
3056 assert!(descriptor.supported_reasoning.is_empty());
3057 }
3058
3059 #[test]
3060 fn openai_context_windows_match_current_catalog_values() {
3061 let gpt55 = lookup_model("gpt-5.5").unwrap();
3062 assert_eq!(gpt55.context_window, 1_050_000);
3063 assert_eq!(gpt55.max_context_window, 1_050_000);
3064 assert_eq!(gpt55.auto_compact_token_limit, 945_000);
3065
3066 let mini = lookup_model("gpt-5.4-mini").unwrap();
3067 assert_eq!(mini.context_window, 400_000);
3068 assert_eq!(mini.max_context_window, 400_000);
3069 assert_eq!(mini.auto_compact_token_limit, 360_000);
3070
3071 let spark = lookup_model("gpt-5.3-codex-spark").unwrap();
3072 assert_eq!(spark.provider, PROVIDER_CODEX);
3073 assert_eq!(spark.context_window, 128_000);
3074 assert_eq!(spark.max_context_window, 128_000);
3075 assert_eq!(spark.auto_compact_token_limit, 115_200);
3076 }
3077
3078 #[test]
3079 fn auto_compact_defaults_to_ninety_percent_of_context_window() {
3080 for model in BUILT_IN_MODELS {
3081 if model.context_window == 0 || model.auto_compact_token_limit == 0 {
3082 continue;
3083 }
3084 assert_eq!(
3085 model.auto_compact_token_limit,
3086 model.context_window.saturating_mul(9) / 10,
3087 "{} should compact at 90% of its context window",
3088 model.id
3089 );
3090 }
3091 }
3092}