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