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