1use serde::Serialize;
2
3use crate::inference::{
4 ModelDescriptor, ModelHarnessProfile, ModelInstructionOverlay, ModelProfileReasoning,
5 ModelSchemaPolicy, ProviderFamily, ReasoningEffortDescriptor,
6};
7
8pub mod image_models;
9mod xiaomi_mimo;
10
11pub use image_models::{
12 IMAGE_PROVIDER_GOOGLE, IMAGE_PROVIDER_OPENAI, ImageModelCatalogEntry,
13 ImageProviderCatalogEntry, built_in_image_providers, image_model_descriptors,
14 image_models_for_provider, lookup_image_model, lookup_image_provider,
15};
16pub use xiaomi_mimo::{XIAOMI_MIMO_ENV_ALIASES, XIAOMI_MIMO_TOKEN_PLAN_ENV_ALIASES};
17
18pub const PROVIDER_MOCK: &str = "mock";
19pub const PROVIDER_OPENAI: &str = "openai";
20pub const PROVIDER_CODEX: &str = "codex";
21pub const PROVIDER_ANTHROPIC: &str = "anthropic";
22pub const PROVIDER_CLAUDE_CODE: &str = "claude-code";
23pub const PROVIDER_GEMINI: &str = "gemini";
24pub const PROVIDER_VERTEX: &str = "vertex";
25pub const PROVIDER_GOOGLE: &str = "google";
26pub const PROVIDER_ZEROENTROPY: &str = "zeroentropy";
27pub const PROVIDER_XAI: &str = "xai";
28pub const PROVIDER_SUPERGROK: &str = "supergrok";
29pub const PROVIDER_OPENCODE: &str = "opencode";
30pub const PROVIDER_OPENCODE_GO: &str = "opencode-go";
31pub const PROVIDER_OPENROUTER: &str = "openrouter";
32pub const PROVIDER_RODER_CLOUD: &str = "roder-cloud";
33pub const PROVIDER_POOLSIDE: &str = "poolside";
34pub const PROVIDER_CURSOR: &str = "cursor";
35pub const PROVIDER_XIAOMI_MIMO: &str = "xiaomi-mimo";
36pub const PROVIDER_XIAOMI_MIMO_TOKEN_PLAN: &str = "xiaomi-mimo-token-plan";
37
38pub const PROVIDER_KIND_MOCK: &str = "mock";
39pub const PROVIDER_KIND_OPENAI: &str = "openai";
40pub const PROVIDER_KIND_CHAT_COMPLETIONS: &str = "chat_completions";
41pub const PROVIDER_KIND_ANTHROPIC: &str = "anthropic";
42pub const PROVIDER_KIND_CLAUDE_CODE: &str = "claude_code";
43pub const PROVIDER_KIND_GEMINI: &str = "gemini";
44pub const PROVIDER_KIND_VERTEX: &str = "vertex";
45pub const PROVIDER_KIND_XAI: &str = "xai";
46pub const PROVIDER_KIND_OPENCODE: &str = "opencode";
47pub const PROVIDER_KIND_OPENROUTER: &str = "openrouter";
48pub const PROVIDER_KIND_RODER_CLOUD: &str = "roder_cloud";
49pub const PROVIDER_KIND_POOLSIDE: &str = "poolside";
50pub const PROVIDER_KIND_CURSOR: &str = "cursor";
51pub const PROVIDER_KIND_XIAOMI_MIMO: &str = PROVIDER_KIND_CHAT_COMPLETIONS;
52
53pub const REASONING_NONE: &str = "none";
54pub const REASONING_MINIMAL: &str = "minimal";
55pub const REASONING_LOW: &str = "low";
56pub const REASONING_MEDIUM: &str = "medium";
57pub const REASONING_HIGH: &str = "high";
58pub const REASONING_XHIGH: &str = "xhigh";
59pub const REASONING_MAX: &str = "max";
60
61pub const DEFAULT_MODEL_ID: &str = "gpt-5.5";
62pub const EDIT_TOOL_PATCH: &str = "patch";
63pub const EDIT_TOOL_EDIT: &str = "edit";
64
65#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
66pub struct ProviderCatalogEntry {
67 pub id: &'static str,
68 pub name: &'static str,
69 pub kind: &'static str,
70 pub default_model: &'static str,
71 pub base_url: Option<&'static str>,
72 pub env_key: Option<&'static str>,
73 pub env_aliases: &'static [&'static str],
74 pub requires_auth: bool,
75 pub supports_websockets: bool,
76}
77
78#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
79pub struct ReasoningOption {
80 pub effort: &'static str,
81 pub description: &'static str,
82}
83
84#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
85pub struct ModelCatalogEntry {
86 pub id: &'static str,
87 pub display_name: &'static str,
88 pub description: &'static str,
89 pub provider: &'static str,
90 pub default_reasoning: &'static str,
91 pub supported_reasoning: &'static [ReasoningOption],
92 pub context_window: u32,
93 pub max_context_window: u32,
94 pub auto_compact_token_limit: u32,
95 pub supports_compaction: bool,
96 pub supports_images: bool,
97 pub supports_tools: bool,
98 pub supports_structured: bool,
99 pub edit_tool: Option<&'static str>,
100 pub hidden: bool,
101}
102
103pub const STANDARD_REASONING: &[ReasoningOption] = &[
104 ReasoningOption {
105 effort: REASONING_LOW,
106 description: "Fast responses with lighter reasoning",
107 },
108 ReasoningOption {
109 effort: REASONING_MEDIUM,
110 description: "Balances speed and reasoning depth for everyday tasks",
111 },
112 ReasoningOption {
113 effort: REASONING_HIGH,
114 description: "Greater reasoning depth for complex problems",
115 },
116 ReasoningOption {
117 effort: REASONING_XHIGH,
118 description: "Extra high reasoning depth for complex problems",
119 },
120];
121
122pub const OPUS_REASONING: &[ReasoningOption] = &[
126 ReasoningOption {
127 effort: REASONING_LOW,
128 description: "Most efficient; best for short, scoped tasks",
129 },
130 ReasoningOption {
131 effort: REASONING_MEDIUM,
132 description: "Balanced reasoning depth for cost-sensitive workflows",
133 },
134 ReasoningOption {
135 effort: REASONING_HIGH,
136 description: "High capability for complex reasoning and agentic tasks",
137 },
138 ReasoningOption {
139 effort: REASONING_XHIGH,
140 description: "Extended capability for long-horizon coding and agentic work",
141 },
142 ReasoningOption {
143 effort: REASONING_MAX,
144 description: "Absolute maximum capability with no constraints on token spending",
145 },
146];
147
148pub const SONNET_REASONING: &[ReasoningOption] = &[
150 ReasoningOption {
151 effort: REASONING_LOW,
152 description: "Most efficient; lowest latency and cost",
153 },
154 ReasoningOption {
155 effort: REASONING_MEDIUM,
156 description: "Balances speed, cost, and performance for most tasks",
157 },
158 ReasoningOption {
159 effort: REASONING_HIGH,
160 description: "Greater reasoning depth for complex problems",
161 },
162 ReasoningOption {
163 effort: REASONING_MAX,
164 description: "Absolute maximum capability with no constraints on token spending",
165 },
166];
167
168pub const GPT_52_REASONING: &[ReasoningOption] = &[
169 ReasoningOption {
170 effort: REASONING_LOW,
171 description: "Balances speed with some reasoning; useful for straightforward queries and short explanations",
172 },
173 ReasoningOption {
174 effort: REASONING_MEDIUM,
175 description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks",
176 },
177 ReasoningOption {
178 effort: REASONING_HIGH,
179 description: "Maximizes reasoning depth for complex or ambiguous problems",
180 },
181 ReasoningOption {
182 effort: REASONING_XHIGH,
183 description: "Extra high reasoning for complex problems",
184 },
185];
186
187pub const HAIKU_REASONING: &[ReasoningOption] = &[
188 ReasoningOption {
189 effort: REASONING_LOW,
190 description: "Fast responses with lighter reasoning",
191 },
192 ReasoningOption {
193 effort: REASONING_MEDIUM,
194 description: "Balances speed and reasoning depth for everyday tasks",
195 },
196];
197
198pub const GEMINI_REASONING: &[ReasoningOption] = &[
199 ReasoningOption {
200 effort: REASONING_MINIMAL,
201 description: "Minimal Gemini thinking",
202 },
203 ReasoningOption {
204 effort: REASONING_LOW,
205 description: "Low Gemini thinking",
206 },
207 ReasoningOption {
208 effort: REASONING_MEDIUM,
209 description: "Medium Gemini thinking",
210 },
211 ReasoningOption {
212 effort: REASONING_HIGH,
213 description: "High Gemini thinking",
214 },
215];
216
217pub const MOCK_REASONING: &[ReasoningOption] = &[ReasoningOption {
218 effort: REASONING_NONE,
219 description: "No model-side reasoning",
220}];
221
222pub const POOLSIDE_REASONING: &[ReasoningOption] = &[
223 ReasoningOption {
224 effort: REASONING_NONE,
225 description: "Disable Poolside thinking for lower latency",
226 },
227 ReasoningOption {
228 effort: REASONING_MEDIUM,
229 description: "Enable Poolside thinking",
230 },
231];
232
233pub const GEMINI_ENV_ALIASES: &[&str] = &[
234 "GEMINI_API_KEY",
235 "GOOGLE_API_KEY",
236 "GOOGLE_GENAI_API_KEY",
237 "GOOGLE_AI_API_KEY",
238];
239
240pub const VERTEX_ENV_ALIASES: &[&str] = &["VERTEX_CREDENTIALS_JSON"];
241
242pub const XAI_ENV_ALIASES: &[&str] = &["RODER_XAI_API_KEY"];
243
244pub const XAI_CONFIGURABLE_REASONING: &[ReasoningOption] = &[
245 ReasoningOption {
246 effort: REASONING_NONE,
247 description: "No xAI reasoning effort",
248 },
249 ReasoningOption {
250 effort: REASONING_LOW,
251 description: "Low xAI reasoning effort",
252 },
253 ReasoningOption {
254 effort: REASONING_MEDIUM,
255 description: "Medium xAI reasoning effort",
256 },
257 ReasoningOption {
258 effort: REASONING_HIGH,
259 description: "High xAI reasoning effort",
260 },
261];
262
263pub const XAI_REASONING: &[ReasoningOption] = &[
264 ReasoningOption {
265 effort: REASONING_LOW,
266 description: "Low xAI reasoning effort",
267 },
268 ReasoningOption {
269 effort: REASONING_MEDIUM,
270 description: "Medium xAI reasoning effort",
271 },
272 ReasoningOption {
273 effort: REASONING_HIGH,
274 description: "High xAI reasoning effort",
275 },
276];
277
278pub const XAI_NO_REASONING: &[ReasoningOption] = &[ReasoningOption {
279 effort: REASONING_NONE,
280 description: "No xAI reasoning effort",
281}];
282
283pub const OPENROUTER_REASONING: &[ReasoningOption] = &[
284 ReasoningOption {
285 effort: REASONING_NONE,
286 description: "Disable OpenRouter reasoning controls",
287 },
288 ReasoningOption {
289 effort: REASONING_LOW,
290 description: "Low OpenRouter reasoning effort",
291 },
292 ReasoningOption {
293 effort: REASONING_MEDIUM,
294 description: "Medium OpenRouter reasoning effort",
295 },
296 ReasoningOption {
297 effort: REASONING_HIGH,
298 description: "High OpenRouter reasoning effort",
299 },
300];
301
302pub const RODER_CLOUD_REASONING: &[ReasoningOption] = &[ReasoningOption {
309 effort: REASONING_NONE,
310 description: "roder.cloud forwards no reasoning controls",
311}];
312
313pub const BUILT_IN_PROVIDERS: &[ProviderCatalogEntry] = &[
314 ProviderCatalogEntry {
315 id: PROVIDER_MOCK,
316 name: "Mock",
317 kind: PROVIDER_KIND_MOCK,
318 default_model: "mock",
319 base_url: None,
320 env_key: None,
321 env_aliases: &[],
322 requires_auth: false,
323 supports_websockets: false,
324 },
325 ProviderCatalogEntry {
326 id: PROVIDER_OPENAI,
327 name: "OpenAI",
328 kind: PROVIDER_KIND_OPENAI,
329 default_model: DEFAULT_MODEL_ID,
330 base_url: Some("https://api.openai.com/v1"),
331 env_key: Some("OPENAI_API_KEY"),
332 env_aliases: &[],
333 requires_auth: true,
334 supports_websockets: true,
335 },
336 ProviderCatalogEntry {
337 id: PROVIDER_CODEX,
338 name: "Codex",
339 kind: PROVIDER_KIND_OPENAI,
340 default_model: DEFAULT_MODEL_ID,
341 base_url: Some("https://api.openai.com/v1"),
342 env_key: Some("OPENAI_API_KEY"),
343 env_aliases: &[],
344 requires_auth: true,
345 supports_websockets: true,
346 },
347 ProviderCatalogEntry {
348 id: PROVIDER_ANTHROPIC,
349 name: "Anthropic",
350 kind: PROVIDER_KIND_ANTHROPIC,
351 default_model: "claude-sonnet-4-6",
352 base_url: Some("https://api.anthropic.com"),
353 env_key: Some("ANTHROPIC_API_KEY"),
354 env_aliases: &[],
355 requires_auth: true,
356 supports_websockets: false,
357 },
358 ProviderCatalogEntry {
359 id: PROVIDER_CLAUDE_CODE,
360 name: "Claude Code",
361 kind: PROVIDER_KIND_CLAUDE_CODE,
362 default_model: "sonnet",
363 base_url: None,
364 env_key: None,
365 env_aliases: &["CLAUDE_CODE_CLI_PATH", "RODER_CLAUDE_CODE_CLI_PATH"],
366 requires_auth: false,
367 supports_websockets: false,
368 },
369 ProviderCatalogEntry {
370 id: PROVIDER_GEMINI,
371 name: "Gemini",
372 kind: PROVIDER_KIND_GEMINI,
373 default_model: "gemini-3.5-flash",
374 base_url: None,
375 env_key: Some("GEMINI_API_TOKEN"),
376 env_aliases: GEMINI_ENV_ALIASES,
377 requires_auth: true,
378 supports_websockets: false,
379 },
380 ProviderCatalogEntry {
381 id: PROVIDER_VERTEX,
382 name: "Vertex AI",
383 kind: PROVIDER_KIND_VERTEX,
384 default_model: "gemini-3.5-flash",
385 base_url: None,
386 env_key: Some("GOOGLE_APPLICATION_CREDENTIALS"),
387 env_aliases: VERTEX_ENV_ALIASES,
388 requires_auth: true,
389 supports_websockets: false,
390 },
391 ProviderCatalogEntry {
392 id: PROVIDER_XAI,
393 name: "xAI",
394 kind: PROVIDER_KIND_XAI,
395 default_model: "grok-4.3",
396 base_url: Some("https://api.x.ai/v1"),
397 env_key: Some("XAI_API_KEY"),
398 env_aliases: XAI_ENV_ALIASES,
399 requires_auth: true,
400 supports_websockets: false,
401 },
402 ProviderCatalogEntry {
403 id: PROVIDER_SUPERGROK,
404 name: "SuperGrok",
405 kind: PROVIDER_KIND_XAI,
406 default_model: "grok-4.3",
407 base_url: Some("https://api.x.ai/v1"),
408 env_key: None,
409 env_aliases: &[],
410 requires_auth: true,
411 supports_websockets: false,
412 },
413 ProviderCatalogEntry {
414 id: PROVIDER_OPENCODE,
415 name: "OpenCode Zen",
416 kind: PROVIDER_KIND_OPENCODE,
417 default_model: "gpt-5.5",
418 base_url: Some("https://opencode.ai/zen/v1"),
419 env_key: Some("OPENCODE_API_KEY"),
420 env_aliases: &["OPENCODE_ZEN_API_KEY", "RODER_OPENCODE_API_KEY"],
421 requires_auth: true,
422 supports_websockets: false,
423 },
424 ProviderCatalogEntry {
425 id: PROVIDER_OPENCODE_GO,
426 name: "OpenCode Go",
427 kind: PROVIDER_KIND_OPENCODE,
428 default_model: "kimi-k2.6",
429 base_url: Some("https://opencode.ai/zen/go/v1"),
430 env_key: Some("OPENCODE_GO_API_KEY"),
431 env_aliases: &["RODER_OPENCODE_GO_API_KEY", "OPENCODE_API_KEY"],
432 requires_auth: true,
433 supports_websockets: false,
434 },
435 ProviderCatalogEntry {
436 id: PROVIDER_OPENROUTER,
437 name: "OpenRouter",
438 kind: PROVIDER_KIND_OPENROUTER,
439 default_model: "x-ai/grok-build-0.1",
440 base_url: Some("https://openrouter.ai/api/v1"),
441 env_key: Some("OPENROUTER_API_KEY"),
442 env_aliases: &["RODER_OPENROUTER_API_KEY"],
443 requires_auth: true,
444 supports_websockets: false,
445 },
446 ProviderCatalogEntry {
447 id: PROVIDER_RODER_CLOUD,
448 name: "Roder Cloud",
449 kind: PROVIDER_KIND_RODER_CLOUD,
450 default_model: "roder.cloud/free",
451 base_url: None,
455 env_key: Some("RODER_CLOUD_API_KEY"),
456 env_aliases: &["RODER_CLOUD_TOKEN"],
457 requires_auth: true,
458 supports_websockets: false,
459 },
460 ProviderCatalogEntry {
461 id: PROVIDER_POOLSIDE,
462 name: "Poolside",
463 kind: PROVIDER_KIND_POOLSIDE,
464 default_model: "poolside/laguna-m.1",
465 base_url: Some("https://inference.poolside.ai/v1"),
466 env_key: Some("POOLSIDE_API_KEY"),
467 env_aliases: &["RODER_POOLSIDE_API_KEY"],
468 requires_auth: true,
469 supports_websockets: false,
470 },
471 ProviderCatalogEntry {
472 id: PROVIDER_CURSOR,
473 name: "Cursor",
474 kind: PROVIDER_KIND_CURSOR,
475 default_model: "composer-2.5",
476 base_url: Some("https://agentn.global.api5.cursor.sh"),
477 env_key: Some("CURSOR_API_KEY"),
478 env_aliases: &["RODER_CURSOR_API_KEY"],
479 requires_auth: true,
480 supports_websockets: false,
481 },
482 xiaomi_mimo::PAY_AS_YOU_GO_PROVIDER,
483 xiaomi_mimo::TOKEN_PLAN_PROVIDER,
484];
485
486pub const BUILT_IN_MODELS: &[ModelCatalogEntry] = &[
487 openai_model(
488 "gpt-5.5",
489 "GPT-5.5",
490 "Frontier model for complex coding, research, and real-world work.",
491 1_050_000,
492 945_000,
493 true,
494 STANDARD_REASONING,
495 ),
496 openai_model(
497 "gpt-5.4-mini",
498 "GPT-5.4-Mini",
499 "Small, fast, and cost-efficient model for simpler coding tasks.",
500 400_000,
501 360_000,
502 true,
503 STANDARD_REASONING,
504 ),
505 ModelCatalogEntry {
506 id: "gpt-5.3-codex-spark",
507 display_name: "GPT-5.3-Codex-Spark",
508 description: "Ultra-fast coding model optimized for low-latency Codex workflows.",
509 provider: PROVIDER_CODEX,
510 default_reasoning: REASONING_HIGH,
511 supported_reasoning: STANDARD_REASONING,
512 context_window: 128_000,
513 max_context_window: 128_000,
514 auto_compact_token_limit: 115_200,
515 supports_compaction: true,
516 supports_images: false,
517 supports_tools: true,
518 supports_structured: false,
519 edit_tool: Some("patch"),
520 hidden: false,
521 },
522 ModelCatalogEntry {
523 id: "codex-auto-review",
524 display_name: "Codex Auto Review",
525 description: "Automatic approval review model for Codex.",
526 provider: PROVIDER_OPENAI,
527 default_reasoning: REASONING_MEDIUM,
528 supported_reasoning: STANDARD_REASONING,
529 context_window: 272_000,
530 max_context_window: 272_000,
531 auto_compact_token_limit: 244_800,
532 supports_compaction: false,
533 supports_images: false,
534 supports_tools: true,
535 supports_structured: false,
536 edit_tool: Some("patch"),
537 hidden: true,
538 },
539 anthropic_model(
540 "claude-fable-5",
541 "Claude Fable 5",
542 "Anthropic's most powerful, most intelligent model; a new tier above Opus for frontier reasoning and agentic work.",
543 1_000_000,
544 900_000,
545 REASONING_HIGH,
546 OPUS_REASONING,
547 true,
548 ),
549 anthropic_model(
550 "claude-opus-4-8",
551 "Claude Opus 4.8",
552 "Anthropic's most capable Opus-tier model for complex reasoning, long-horizon agentic coding, and high-autonomy work.",
553 1_000_000,
554 900_000,
555 REASONING_HIGH,
556 OPUS_REASONING,
557 true,
558 ),
559 anthropic_model(
560 "claude-opus-4-7",
561 "Claude Opus 4.7",
562 "Most capable Claude model for complex reasoning and agentic coding.",
563 1_000_000,
564 900_000,
565 REASONING_HIGH,
566 OPUS_REASONING,
567 true,
568 ),
569 anthropic_model(
570 "claude-sonnet-4-6",
571 "Claude Sonnet 4.6",
572 "Balanced Claude model for coding, tool use, and everyday agent workflows.",
573 1_000_000,
574 900_000,
575 REASONING_MEDIUM,
576 SONNET_REASONING,
577 true,
578 ),
579 anthropic_model(
580 "claude-haiku-4-5-20251001",
581 "Claude Haiku 4.5",
582 "Fast Claude model for lower-latency tool workflows.",
583 200_000,
584 180_000,
585 REASONING_NONE,
586 &[],
587 false,
589 ),
590 claude_code_model(
591 "fable",
592 "Claude Code Fable",
593 "Claude Code harness Fable alias for the most powerful frontier model.",
594 1_000_000,
595 900_000,
596 REASONING_HIGH,
597 OPUS_REASONING,
598 ),
599 claude_code_model(
600 "sonnet",
601 "Claude Code Sonnet",
602 "Claude Code harness Sonnet alias for coding and tool workflows.",
603 1_000_000,
604 900_000,
605 REASONING_MEDIUM,
606 SONNET_REASONING,
607 ),
608 claude_code_model(
609 "opus",
610 "Claude Code Opus",
611 "Claude Code harness Opus alias for complex long-horizon agentic work.",
612 1_000_000,
613 900_000,
614 REASONING_HIGH,
615 OPUS_REASONING,
616 ),
617 claude_code_model(
618 "haiku",
619 "Claude Code Haiku",
620 "Claude Code harness Haiku alias for fast lower-latency coding turns.",
621 200_000,
622 180_000,
623 REASONING_NONE,
624 &[],
625 ),
626 claude_code_model(
627 "claude-sonnet-4-6",
628 "Claude Code Sonnet 4.6",
629 "Claude Sonnet 4.6 through the local Claude Code harness.",
630 1_000_000,
631 900_000,
632 REASONING_MEDIUM,
633 SONNET_REASONING,
634 ),
635 claude_code_model(
636 "claude-opus-4-8",
637 "Claude Code Opus 4.8",
638 "Claude Opus 4.8 through the local Claude Code harness.",
639 1_000_000,
640 900_000,
641 REASONING_HIGH,
642 OPUS_REASONING,
643 ),
644 claude_code_model(
645 "claude-fable-5",
646 "Claude Code Fable 5",
647 "Claude Fable 5 through the local Claude Code harness.",
648 1_000_000,
649 900_000,
650 REASONING_HIGH,
651 OPUS_REASONING,
652 ),
653 gemini_model(
654 PROVIDER_GEMINI,
655 "gemini-3.5-flash",
656 "Gemini 3.5 Flash",
657 "Stable Gemini Flash model for agentic coding, tool use, and long-horizon workflows.",
658 REASONING_MEDIUM,
659 ),
660 gemini_model(
661 PROVIDER_GEMINI,
662 "gemini-3.1-pro-preview",
663 "Gemini 3.1 Pro Preview",
664 "Gemini model for complex coding, long context, and tool-heavy agent workflows.",
665 REASONING_HIGH,
666 ),
667 gemini_model(
668 PROVIDER_GEMINI,
669 "gemini-3.1-pro-preview-customtools",
670 "Gemini 3.1 Pro Preview Custom Tools",
671 "Gemini preview variant exposed for custom tool validation and tool-heavy coding workflows.",
672 REASONING_HIGH,
673 ),
674 gemini_model(
675 PROVIDER_GEMINI,
676 "gemini-3-flash-preview",
677 "Gemini 3 Flash Preview",
678 "Fast Gemini model for everyday coding, tool use, and multimodal prompts.",
679 REASONING_MEDIUM,
680 ),
681 gemini_model(
682 PROVIDER_GEMINI,
683 "gemini-3.1-flash-lite-preview",
684 "Gemini 3.1 Flash-Lite Preview",
685 "Lightweight Gemini model for low-latency coding and agent interactions.",
686 REASONING_LOW,
687 ),
688 gemini_model(
689 PROVIDER_VERTEX,
690 "gemini-3.5-flash",
691 "Gemini 3.5 Flash",
692 "Stable Gemini Flash model on Vertex AI for agentic coding, tool use, and long-horizon workflows.",
693 REASONING_MEDIUM,
694 ),
695 gemini_model(
696 PROVIDER_VERTEX,
697 "gemini-3.1-pro-preview",
698 "Gemini 3.1 Pro Preview",
699 "Gemini model on Vertex AI for complex coding, long context, and tool-heavy agent workflows.",
700 REASONING_HIGH,
701 ),
702 gemini_model(
703 PROVIDER_VERTEX,
704 "gemini-3-flash-preview",
705 "Gemini 3 Flash Preview",
706 "Fast Gemini model on Vertex AI for everyday coding, tool use, and multimodal prompts.",
707 REASONING_MEDIUM,
708 ),
709 gemini_model(
710 PROVIDER_VERTEX,
711 "gemini-3.1-flash-lite-preview",
712 "Gemini 3.1 Flash-Lite Preview",
713 "Lightweight Gemini model on Vertex AI for low-latency coding and agent interactions.",
714 REASONING_LOW,
715 ),
716 xai_model(
717 PROVIDER_XAI,
718 "grok-4.3",
719 "Grok 4.3",
720 "xAI flagship model for chat, coding, tool use, and configurable reasoning.",
721 1_000_000,
722 REASONING_LOW,
723 XAI_CONFIGURABLE_REASONING,
724 ),
725 xai_model(
726 PROVIDER_XAI,
727 "grok-4.20-multi-agent-0309",
728 "Grok 4.20 Multi-Agent",
729 "xAI long-context model with agentic tool-calling and reasoning.",
730 2_000_000,
731 REASONING_LOW,
732 XAI_REASONING,
733 ),
734 xai_model(
735 PROVIDER_XAI,
736 "grok-4.20-0309-reasoning",
737 "Grok 4.20 Reasoning",
738 "xAI long-context reasoning model for complex tool-heavy workflows.",
739 2_000_000,
740 REASONING_LOW,
741 XAI_REASONING,
742 ),
743 xai_model(
744 PROVIDER_XAI,
745 "grok-4.20-0309-non-reasoning",
746 "Grok 4.20 Non-Reasoning",
747 "xAI long-context model for lower-latency non-reasoning workflows.",
748 2_000_000,
749 REASONING_NONE,
750 XAI_NO_REASONING,
751 ),
752 xai_model(
753 PROVIDER_SUPERGROK,
754 "grok-4.3",
755 "Grok 4.3",
756 "SuperGrok OAuth access to xAI Grok 4.3.",
757 1_000_000,
758 REASONING_LOW,
759 XAI_CONFIGURABLE_REASONING,
760 ),
761 xai_model(
762 PROVIDER_SUPERGROK,
763 "grok-4.20-multi-agent-0309",
764 "Grok 4.20 Multi-Agent",
765 "SuperGrok OAuth access to xAI's long-context multi-agent model.",
766 2_000_000,
767 REASONING_LOW,
768 XAI_REASONING,
769 ),
770 xai_model(
771 PROVIDER_SUPERGROK,
772 "grok-4.20-0309-reasoning",
773 "Grok 4.20 Reasoning",
774 "SuperGrok OAuth access to xAI's long-context reasoning model.",
775 2_000_000,
776 REASONING_LOW,
777 XAI_REASONING,
778 ),
779 xai_model(
780 PROVIDER_SUPERGROK,
781 "grok-4.20-0309-non-reasoning",
782 "Grok 4.20 Non-Reasoning",
783 "SuperGrok OAuth access to xAI's long-context non-reasoning model.",
784 2_000_000,
785 REASONING_NONE,
786 XAI_NO_REASONING,
787 ),
788 opencode_model(
789 PROVIDER_OPENCODE,
790 "gpt-5.5",
791 "GPT 5.5",
792 "OpenCode Zen GPT 5.5 gateway model.",
793 1_050_000,
794 REASONING_MEDIUM,
795 STANDARD_REASONING,
796 ),
797 opencode_model(
798 PROVIDER_OPENCODE,
799 "gpt-5.3-codex-spark",
800 "GPT 5.3 Codex Spark",
801 "OpenCode Zen low-latency Codex model.",
802 128_000,
803 REASONING_HIGH,
804 STANDARD_REASONING,
805 ),
806 opencode_model(
807 PROVIDER_OPENCODE,
808 "big-pickle",
809 "Big Pickle",
810 "OpenCode Zen free coding model.",
811 256_000,
812 REASONING_NONE,
813 &[],
814 ),
815 opencode_model(
816 PROVIDER_OPENCODE,
817 "deepseek-v4-flash-free",
818 "DeepSeek V4 Flash Free",
819 "OpenCode Zen free DeepSeek coding model.",
820 128_000,
821 REASONING_NONE,
822 &[],
823 ),
824 opencode_model(
825 PROVIDER_OPENCODE,
826 "minimax-m2.5-free",
827 "MiniMax M2.5 Free",
828 "OpenCode Zen free MiniMax coding model.",
829 256_000,
830 REASONING_NONE,
831 &[],
832 ),
833 opencode_model(
834 PROVIDER_OPENCODE,
835 "nemotron-3-super-free",
836 "Nemotron 3 Super Free",
837 "OpenCode Zen free Nemotron coding model.",
838 128_000,
839 REASONING_NONE,
840 &[],
841 ),
842 opencode_model(
843 PROVIDER_OPENCODE_GO,
844 "kimi-k2.6",
845 "Kimi K2.6",
846 "OpenCode Go Kimi coding model.",
847 256_000,
848 REASONING_NONE,
849 &[],
850 ),
851 opencode_model(
852 PROVIDER_OPENCODE_GO,
853 "qwen3.6-plus",
854 "Qwen3.6 Plus",
855 "OpenCode Go Qwen coding model.",
856 256_000,
857 REASONING_NONE,
858 &[],
859 ),
860 opencode_model(
861 PROVIDER_OPENCODE_GO,
862 "glm-5.1",
863 "GLM-5.1",
864 "OpenCode Go GLM coding model.",
865 256_000,
866 REASONING_NONE,
867 &[],
868 ),
869 opencode_model(
870 PROVIDER_OPENCODE_GO,
871 "deepseek-v4-flash",
872 "DeepSeek V4 Flash",
873 "OpenCode Go DeepSeek coding model.",
874 128_000,
875 REASONING_NONE,
876 &[],
877 ),
878 ModelCatalogEntry {
879 id: "x-ai/grok-build-0.1",
880 display_name: "Grok Build 0.1",
881 description: "OpenRouter route for xAI's fast coding model for agentic software engineering workflows.",
882 provider: PROVIDER_OPENROUTER,
883 default_reasoning: REASONING_LOW,
884 supported_reasoning: OPENROUTER_REASONING,
885 context_window: 256_000,
886 max_context_window: 256_000,
887 auto_compact_token_limit: 230_400,
888 supports_compaction: true,
889 supports_images: true,
890 supports_tools: true,
891 supports_structured: true,
892 edit_tool: Some(EDIT_TOOL_PATCH),
893 hidden: false,
894 },
895 roder_cloud_model(
896 "roder.cloud/free",
897 "Roder Free",
898 "Free hosted model on roder.cloud.",
899 32_768,
900 ),
901 roder_cloud_model(
902 "roder.cloud/openai/gpt-5.5",
903 "GPT-5.5 (Roder Cloud)",
904 "roder.cloud hosted route for OpenAI GPT-5.5.",
905 400_000,
906 ),
907 roder_cloud_model(
908 "roder.cloud/anthropic/claude-opus-4-7",
909 "Claude Opus 4.7 (Roder Cloud)",
910 "roder.cloud hosted route for Anthropic Claude Opus 4.7.",
911 200_000,
912 ),
913 roder_cloud_model(
914 "roder.cloud/google/gemini-3.1-pro-preview",
915 "Gemini 3.1 Pro (Roder Cloud)",
916 "roder.cloud hosted route for Google Gemini 3.1 Pro Preview.",
917 200_000,
918 ),
919 poolside_model(
920 "poolside/laguna-m.1",
921 "Laguna M.1",
922 "Poolside flagship agentic coding model.",
923 REASONING_MEDIUM,
924 ),
925 poolside_model(
926 "poolside/laguna-xs.2",
927 "Laguna XS.2",
928 "Poolside lightweight agentic coding model.",
929 REASONING_MEDIUM,
930 ),
931 xiaomi_mimo::PAYG_V25_PRO,
932 xiaomi_mimo::PAYG_V2_PRO,
933 xiaomi_mimo::PAYG_V25,
934 xiaomi_mimo::PAYG_V2_OMNI,
935 xiaomi_mimo::PAYG_V2_FLASH,
936 xiaomi_mimo::TOKEN_PLAN_V25_PRO,
937 xiaomi_mimo::TOKEN_PLAN_V2_PRO,
938 xiaomi_mimo::TOKEN_PLAN_V25,
939 xiaomi_mimo::TOKEN_PLAN_V2_OMNI,
940 xiaomi_mimo::TOKEN_PLAN_V2_FLASH,
941 ModelCatalogEntry {
942 id: "composer-2.5",
943 display_name: "Composer 2.5",
944 description: "Cursor Composer model exposed through direct AgentService inference.",
945 provider: PROVIDER_CURSOR,
946 default_reasoning: REASONING_NONE,
947 supported_reasoning: &[],
948 context_window: 200_000,
949 max_context_window: 200_000,
950 auto_compact_token_limit: 180_000,
951 supports_compaction: true,
952 supports_images: false,
953 supports_tools: false,
954 supports_structured: false,
955 edit_tool: None,
956 hidden: false,
957 },
958 cursor_model(
959 "claude-opus-4-8",
960 "Claude Opus 4.8",
961 "Anthropic Claude Opus 4.8 routed through Cursor's AgentService.",
962 1_000_000,
963 900_000,
964 REASONING_HIGH,
965 OPUS_REASONING,
966 ),
967 cursor_model(
968 "claude-sonnet-4-6",
969 "Claude Sonnet 4.6",
970 "Anthropic Claude Sonnet 4.6 routed through Cursor's AgentService.",
971 1_000_000,
972 900_000,
973 REASONING_NONE,
974 &[],
975 ),
976 cursor_model(
977 "gpt-5.5",
978 "GPT-5.5",
979 "OpenAI GPT-5.5 routed through Cursor's AgentService.",
980 1_050_000,
981 945_000,
982 REASONING_NONE,
983 &[],
984 ),
985 cursor_model(
986 "gemini-3.1-pro-preview",
987 "Gemini 3.1 Pro",
988 "Google Gemini 3.1 Pro routed through Cursor's AgentService.",
989 1_048_576,
990 943_718,
991 REASONING_NONE,
992 &[],
993 ),
994 cursor_model(
995 "grok-4.3",
996 "Grok 4.3",
997 "xAI Grok 4.3 routed through Cursor's AgentService.",
998 1_000_000,
999 900_000,
1000 REASONING_NONE,
1001 &[],
1002 ),
1003 ModelCatalogEntry {
1004 id: "text-embedding-3-large",
1005 display_name: "Text Embedding 3 Large",
1006 description: "OpenAI embedding model for local semantic memories.",
1007 provider: PROVIDER_OPENAI,
1008 default_reasoning: REASONING_NONE,
1009 supported_reasoning: &[],
1010 context_window: 0,
1011 max_context_window: 0,
1012 auto_compact_token_limit: 0,
1013 supports_compaction: false,
1014 supports_images: false,
1015 supports_tools: true,
1016 supports_structured: false,
1017 edit_tool: None,
1018 hidden: true,
1019 },
1020 ModelCatalogEntry {
1021 id: "gemini-embedding-2",
1022 display_name: "Gemini Embedding 2",
1023 description: "Google Gemini embedding model for local semantic memories.",
1024 provider: PROVIDER_GOOGLE,
1025 default_reasoning: REASONING_NONE,
1026 supported_reasoning: &[],
1027 context_window: 0,
1028 max_context_window: 0,
1029 auto_compact_token_limit: 0,
1030 supports_compaction: false,
1031 supports_images: false,
1032 supports_tools: false,
1033 supports_structured: false,
1034 edit_tool: None,
1035 hidden: true,
1036 },
1037 ModelCatalogEntry {
1038 id: "zembed-1",
1039 display_name: "ZeroEntropy zembed-1",
1040 description: "ZeroEntropy embedding model for local semantic memories.",
1041 provider: PROVIDER_ZEROENTROPY,
1042 default_reasoning: REASONING_NONE,
1043 supported_reasoning: &[],
1044 context_window: 0,
1045 max_context_window: 0,
1046 auto_compact_token_limit: 0,
1047 supports_compaction: false,
1048 supports_images: false,
1049 supports_tools: false,
1050 supports_structured: false,
1051 edit_tool: None,
1052 hidden: true,
1053 },
1054 ModelCatalogEntry {
1055 id: "mock",
1056 display_name: "Mock",
1057 description: "Local deterministic mock provider for tests and offline development.",
1058 provider: PROVIDER_MOCK,
1059 default_reasoning: REASONING_NONE,
1060 supported_reasoning: MOCK_REASONING,
1061 context_window: 128_000,
1062 max_context_window: 128_000,
1063 auto_compact_token_limit: 115_200,
1064 supports_compaction: false,
1065 supports_images: false,
1066 supports_tools: true,
1067 supports_structured: false,
1068 edit_tool: None,
1069 hidden: true,
1070 },
1071];
1072
1073const fn openai_model(
1074 id: &'static str,
1075 display_name: &'static str,
1076 description: &'static str,
1077 context_window: u32,
1078 auto_compact_token_limit: u32,
1079 supports_compaction: bool,
1080 supported_reasoning: &'static [ReasoningOption],
1081) -> ModelCatalogEntry {
1082 ModelCatalogEntry {
1083 id,
1084 display_name,
1085 description,
1086 provider: PROVIDER_OPENAI,
1087 default_reasoning: REASONING_MEDIUM,
1088 supported_reasoning,
1089 context_window,
1090 max_context_window: context_window,
1091 auto_compact_token_limit,
1092 supports_compaction,
1093 supports_images: false,
1094 supports_tools: true,
1095 supports_structured: false,
1096 edit_tool: Some("patch"),
1097 hidden: false,
1098 }
1099}
1100
1101#[allow(clippy::too_many_arguments)]
1102const fn anthropic_model(
1103 id: &'static str,
1104 display_name: &'static str,
1105 description: &'static str,
1106 context_window: u32,
1107 auto_compact_token_limit: u32,
1108 default_reasoning: &'static str,
1109 supported_reasoning: &'static [ReasoningOption],
1110 supports_compaction: bool,
1121) -> ModelCatalogEntry {
1122 ModelCatalogEntry {
1123 id,
1124 display_name,
1125 description,
1126 provider: PROVIDER_ANTHROPIC,
1127 default_reasoning,
1128 supported_reasoning,
1129 context_window,
1130 max_context_window: context_window,
1131 auto_compact_token_limit,
1132 supports_compaction,
1133 supports_images: false,
1134 supports_tools: true,
1135 supports_structured: false,
1136 edit_tool: Some("edit"),
1137 hidden: false,
1138 }
1139}
1140
1141const fn claude_code_model(
1142 id: &'static str,
1143 display_name: &'static str,
1144 description: &'static str,
1145 context_window: u32,
1146 auto_compact_token_limit: u32,
1147 default_reasoning: &'static str,
1148 supported_reasoning: &'static [ReasoningOption],
1149) -> ModelCatalogEntry {
1150 ModelCatalogEntry {
1151 id,
1152 display_name,
1153 description,
1154 provider: PROVIDER_CLAUDE_CODE,
1155 default_reasoning,
1156 supported_reasoning,
1157 context_window,
1158 max_context_window: context_window,
1159 auto_compact_token_limit,
1160 supports_compaction: false,
1166 supports_images: false,
1167 supports_tools: true,
1168 supports_structured: false,
1169 edit_tool: Some(EDIT_TOOL_EDIT),
1170 hidden: false,
1171 }
1172}
1173
1174const fn gemini_model(
1175 provider: &'static str,
1176 id: &'static str,
1177 display_name: &'static str,
1178 description: &'static str,
1179 default_reasoning: &'static str,
1180) -> ModelCatalogEntry {
1181 ModelCatalogEntry {
1182 id,
1183 display_name,
1184 description,
1185 provider,
1186 default_reasoning,
1187 supported_reasoning: GEMINI_REASONING,
1188 context_window: 1_048_576,
1189 max_context_window: 1_048_576,
1190 auto_compact_token_limit: 943_718,
1191 supports_compaction: false,
1192 supports_images: true,
1193 supports_tools: true,
1194 supports_structured: true,
1195 edit_tool: Some("edit"),
1196 hidden: false,
1197 }
1198}
1199
1200const fn xai_model(
1201 provider: &'static str,
1202 id: &'static str,
1203 display_name: &'static str,
1204 description: &'static str,
1205 context_window: u32,
1206 default_reasoning: &'static str,
1207 supported_reasoning: &'static [ReasoningOption],
1208) -> ModelCatalogEntry {
1209 ModelCatalogEntry {
1210 id,
1211 display_name,
1212 description,
1213 provider,
1214 default_reasoning,
1215 supported_reasoning,
1216 context_window,
1217 max_context_window: context_window,
1218 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1219 supports_compaction: false,
1220 supports_images: true,
1221 supports_tools: true,
1222 supports_structured: true,
1223 edit_tool: Some("edit"),
1224 hidden: false,
1225 }
1226}
1227
1228const fn opencode_model(
1229 provider: &'static str,
1230 id: &'static str,
1231 display_name: &'static str,
1232 description: &'static str,
1233 context_window: u32,
1234 default_reasoning: &'static str,
1235 supported_reasoning: &'static [ReasoningOption],
1236) -> ModelCatalogEntry {
1237 ModelCatalogEntry {
1238 id,
1239 display_name,
1240 description,
1241 provider,
1242 default_reasoning,
1243 supported_reasoning,
1244 context_window,
1245 max_context_window: context_window,
1246 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1247 supports_compaction: false,
1248 supports_images: false,
1249 supports_tools: true,
1250 supports_structured: true,
1251 edit_tool: Some("edit"),
1252 hidden: false,
1253 }
1254}
1255
1256const fn roder_cloud_model(
1257 id: &'static str,
1258 display_name: &'static str,
1259 description: &'static str,
1260 context_window: u32,
1261) -> ModelCatalogEntry {
1262 ModelCatalogEntry {
1263 id,
1264 display_name,
1265 description,
1266 provider: PROVIDER_RODER_CLOUD,
1267 default_reasoning: REASONING_NONE,
1268 supported_reasoning: RODER_CLOUD_REASONING,
1269 context_window,
1270 max_context_window: context_window,
1271 auto_compact_token_limit: 0,
1272 supports_compaction: false,
1273 supports_images: false,
1274 supports_tools: false,
1275 supports_structured: false,
1276 edit_tool: None,
1277 hidden: false,
1278 }
1279}
1280
1281const fn poolside_model(
1282 id: &'static str,
1283 display_name: &'static str,
1284 description: &'static str,
1285 default_reasoning: &'static str,
1286) -> ModelCatalogEntry {
1287 ModelCatalogEntry {
1288 id,
1289 display_name,
1290 description,
1291 provider: PROVIDER_POOLSIDE,
1292 default_reasoning,
1293 supported_reasoning: POOLSIDE_REASONING,
1294 context_window: 131_072,
1295 max_context_window: 131_072,
1296 auto_compact_token_limit: 117_964,
1297 supports_compaction: false,
1298 supports_images: false,
1299 supports_tools: true,
1300 supports_structured: true,
1301 edit_tool: Some("edit"),
1302 hidden: false,
1303 }
1304}
1305
1306const fn cursor_model(
1307 id: &'static str,
1308 display_name: &'static str,
1309 description: &'static str,
1310 context_window: u32,
1311 auto_compact_token_limit: u32,
1312 default_reasoning: &'static str,
1313 supported_reasoning: &'static [ReasoningOption],
1314) -> ModelCatalogEntry {
1315 ModelCatalogEntry {
1316 id,
1317 display_name,
1318 description,
1319 provider: PROVIDER_CURSOR,
1320 default_reasoning,
1321 supported_reasoning,
1322 context_window,
1323 max_context_window: context_window,
1324 auto_compact_token_limit,
1325 supports_compaction: true,
1326 supports_images: true,
1330 supports_tools: false,
1331 supports_structured: false,
1332 edit_tool: None,
1333 hidden: false,
1334 }
1335}
1336
1337pub fn built_in_providers() -> &'static [ProviderCatalogEntry] {
1338 BUILT_IN_PROVIDERS
1339}
1340
1341pub fn built_in_models(include_hidden: bool) -> Vec<&'static ModelCatalogEntry> {
1342 BUILT_IN_MODELS
1343 .iter()
1344 .filter(|model| include_hidden || !model.hidden)
1345 .collect()
1346}
1347
1348pub fn models_for_provider(provider: &str, include_hidden: bool) -> Vec<ModelDescriptor> {
1349 built_in_models(include_hidden)
1350 .into_iter()
1351 .filter(|model| model.provider == provider)
1352 .map(ModelDescriptor::from)
1353 .collect()
1354}
1355
1356pub fn models_for_codex(include_hidden: bool) -> Vec<ModelDescriptor> {
1357 built_in_models(include_hidden)
1358 .into_iter()
1359 .filter(|model| model.provider == PROVIDER_OPENAI || model.provider == PROVIDER_CODEX)
1360 .map(ModelDescriptor::from)
1361 .collect()
1362}
1363
1364pub fn lookup_model(id: &str) -> Option<&'static ModelCatalogEntry> {
1365 BUILT_IN_MODELS.iter().find(|model| model.id == id)
1366}
1367
1368pub fn lookup_model_for_provider(provider: &str, id: &str) -> Option<&'static ModelCatalogEntry> {
1378 BUILT_IN_MODELS
1379 .iter()
1380 .find(|model| model.provider == provider && model.id == id)
1381 .or_else(|| lookup_model(id))
1382}
1383
1384pub fn built_in_model_profile(id: &str) -> Option<ModelHarnessProfile> {
1385 lookup_model(id).map(model_harness_profile_from_catalog)
1386}
1387
1388pub fn built_in_model_profile_for_provider(
1394 provider: &str,
1395 id: &str,
1396) -> Option<ModelHarnessProfile> {
1397 lookup_model_for_provider(provider, id).map(model_harness_profile_from_catalog)
1398}
1399
1400pub fn built_in_model_profiles() -> Vec<ModelHarnessProfile> {
1401 built_in_models(true)
1402 .into_iter()
1403 .map(model_harness_profile_from_catalog)
1404 .collect()
1405}
1406
1407fn model_harness_profile_from_catalog(model: &ModelCatalogEntry) -> ModelHarnessProfile {
1408 let provider_family = provider_family_for_provider(model.provider);
1409 ModelHarnessProfile {
1410 model: model.id.to_string(),
1411 provider: model.provider.to_string(),
1412 provider_family,
1413 edit_tool: model.edit_tool.map(str::to_string),
1414 schema_policy: schema_policy_for_family(provider_family),
1415 instruction_overlay: instruction_overlay_for_family(provider_family),
1416 reasoning: ModelProfileReasoning {
1417 orientation: Some(model.default_reasoning.to_string()),
1418 execution: Some(default_execution_reasoning(model)),
1419 verification: Some(model.default_reasoning.to_string()),
1420 recovery: Some(model.default_reasoning.to_string()),
1421 },
1422 parallel_tool_calls: Some(
1423 model.supports_tools
1424 && matches!(
1425 provider_family,
1426 ProviderFamily::OpenAi | ProviderFamily::Xai | ProviderFamily::Opencode
1427 ),
1428 ),
1429 auto_compact_token_limit: (model.auto_compact_token_limit > 0)
1430 .then_some(model.auto_compact_token_limit),
1431 }
1432}
1433
1434pub fn provider_family_for_provider(provider: &str) -> ProviderFamily {
1435 match provider {
1436 PROVIDER_OPENAI | PROVIDER_CODEX => ProviderFamily::OpenAi,
1437 PROVIDER_ANTHROPIC | PROVIDER_CLAUDE_CODE => ProviderFamily::Anthropic,
1438 PROVIDER_GEMINI | PROVIDER_VERTEX => ProviderFamily::Gemini,
1439 PROVIDER_XAI | PROVIDER_SUPERGROK => ProviderFamily::Xai,
1440 PROVIDER_OPENCODE | PROVIDER_OPENCODE_GO => ProviderFamily::Opencode,
1441 PROVIDER_OPENROUTER | PROVIDER_RODER_CLOUD => ProviderFamily::OpenAi,
1442 PROVIDER_POOLSIDE => ProviderFamily::Poolside,
1443 PROVIDER_CURSOR => ProviderFamily::Cursor,
1444 PROVIDER_XIAOMI_MIMO | PROVIDER_XIAOMI_MIMO_TOKEN_PLAN => ProviderFamily::OpenAi,
1445 _ => ProviderFamily::Mock,
1446 }
1447}
1448
1449fn schema_policy_for_family(family: ProviderFamily) -> ModelSchemaPolicy {
1450 match family {
1451 ProviderFamily::OpenAi => ModelSchemaPolicy::RequiredFirstFlat,
1452 _ => ModelSchemaPolicy::StandardRequiredFirst,
1453 }
1454}
1455
1456fn instruction_overlay_for_family(family: ProviderFamily) -> ModelInstructionOverlay {
1457 match family {
1458 ProviderFamily::OpenAi => ModelInstructionOverlay::LiteralToolOutputs,
1459 ProviderFamily::Anthropic | ProviderFamily::Gemini => {
1460 ModelInstructionOverlay::IntuitiveContext
1461 }
1462 _ => ModelInstructionOverlay::Standard,
1463 }
1464}
1465
1466fn default_execution_reasoning(model: &ModelCatalogEntry) -> String {
1467 if model
1468 .supported_reasoning
1469 .iter()
1470 .any(|option| option.effort == REASONING_LOW)
1471 {
1472 REASONING_LOW.to_string()
1473 } else {
1474 model.default_reasoning.to_string()
1475 }
1476}
1477
1478pub fn model_supports_reasoning_effort(model: &str, effort: &str) -> bool {
1479 lookup_model(model)
1480 .map(|entry| {
1481 entry
1482 .supported_reasoning
1483 .iter()
1484 .any(|option| option.effort == effort)
1485 })
1486 .unwrap_or(false)
1487}
1488
1489pub fn normalize_provider_id(provider: &str) -> String {
1490 match provider.trim().to_ascii_lowercase().as_str() {
1491 "grok" | "x-ai" | "x.ai" => PROVIDER_XAI.to_string(),
1492 "grok-oauth" | "xai-oauth" | "x-ai-oauth" | "xai-grok-oauth" => {
1493 PROVIDER_SUPERGROK.to_string()
1494 }
1495 "opencode" => PROVIDER_OPENCODE.to_string(),
1496 "go" | "opencode_go" | "opencode-go" => PROVIDER_OPENCODE_GO.to_string(),
1497 "openrouter" => PROVIDER_OPENROUTER.to_string(),
1498 "roder-cloud" | "roder_cloud" | "rodercloud" | "roder.cloud" => {
1499 PROVIDER_RODER_CLOUD.to_string()
1500 }
1501 "laguna" | "poolside" => PROVIDER_POOLSIDE.to_string(),
1502 "composer" | "cursor-composer" => PROVIDER_CURSOR.to_string(),
1503 "claude_code" | "claudecode" => PROVIDER_CLAUDE_CODE.to_string(),
1504 provider => provider.to_string(),
1505 }
1506}
1507
1508impl From<&ModelCatalogEntry> for ModelDescriptor {
1509 fn from(model: &ModelCatalogEntry) -> Self {
1510 let supported_reasoning = model
1511 .supported_reasoning
1512 .iter()
1513 .map(|option| ReasoningEffortDescriptor {
1514 effort: option.effort.to_string(),
1515 description: option.description.to_string(),
1516 })
1517 .collect::<Vec<_>>();
1518 Self {
1519 id: model.id.to_string(),
1520 name: model.display_name.to_string(),
1521 context_window: (model.context_window > 0).then_some(model.context_window),
1522 default_reasoning: (!supported_reasoning.is_empty())
1523 .then(|| model.default_reasoning.to_string()),
1524 supported_reasoning,
1525 }
1526 }
1527}
1528
1529#[cfg(test)]
1530mod tests {
1531 use super::*;
1532
1533 #[test]
1534 fn catalog_contains_gode_providers() {
1535 let ids = BUILT_IN_PROVIDERS
1536 .iter()
1537 .map(|provider| provider.id)
1538 .collect::<Vec<_>>();
1539 assert_eq!(
1540 ids,
1541 vec![
1542 "mock",
1543 "openai",
1544 "codex",
1545 "anthropic",
1546 "claude-code",
1547 "gemini",
1548 "vertex",
1549 "xai",
1550 "supergrok",
1551 "opencode",
1552 "opencode-go",
1553 "openrouter",
1554 "roder-cloud",
1555 "poolside",
1556 "cursor",
1557 "xiaomi-mimo",
1558 "xiaomi-mimo-token-plan"
1559 ]
1560 );
1561 }
1562
1563 #[test]
1564 fn gemini_provider_defaults_to_stable_35_flash() {
1565 let provider = BUILT_IN_PROVIDERS
1566 .iter()
1567 .find(|provider| provider.id == PROVIDER_GEMINI)
1568 .unwrap();
1569
1570 assert_eq!(provider.default_model, "gemini-3.5-flash");
1571
1572 let model = lookup_model("gemini-3.5-flash").unwrap();
1573 assert_eq!(model.display_name, "Gemini 3.5 Flash");
1574 assert_eq!(model.provider, PROVIDER_GEMINI);
1575 assert_eq!(model.context_window, 1_048_576);
1576 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
1577 assert!(model.supports_tools);
1578 assert!(model.supports_structured);
1579 assert_eq!(
1580 model
1581 .supported_reasoning
1582 .iter()
1583 .map(|option| option.effort)
1584 .collect::<Vec<_>>(),
1585 vec![
1586 REASONING_MINIMAL,
1587 REASONING_LOW,
1588 REASONING_MEDIUM,
1589 REASONING_HIGH
1590 ]
1591 );
1592 }
1593
1594 #[test]
1595 fn vertex_provider_mirrors_gemini_models_under_vertex_id() {
1596 let provider = BUILT_IN_PROVIDERS
1597 .iter()
1598 .find(|provider| provider.id == PROVIDER_VERTEX)
1599 .unwrap();
1600
1601 assert_eq!(provider.default_model, "gemini-3.5-flash");
1602 assert_eq!(provider.env_key, Some("GOOGLE_APPLICATION_CREDENTIALS"));
1603 assert_eq!(provider.env_aliases, &["VERTEX_CREDENTIALS_JSON"]);
1604
1605 let model = lookup_model_for_provider(PROVIDER_VERTEX, "gemini-3.5-flash").unwrap();
1606 assert_eq!(model.provider, PROVIDER_VERTEX);
1607 assert_eq!(model.context_window, 1_048_576);
1608 assert!(model.supports_tools);
1609 assert_eq!(
1610 provider_family_for_provider(PROVIDER_VERTEX),
1611 ProviderFamily::Gemini
1612 );
1613 }
1614
1615 #[test]
1616 fn catalog_contains_gode_visible_models() {
1617 let ids = built_in_models(false)
1618 .into_iter()
1619 .map(|model| model.id)
1620 .collect::<Vec<_>>();
1621 assert_eq!(
1622 ids,
1623 vec![
1624 "gpt-5.5",
1625 "gpt-5.4-mini",
1626 "gpt-5.3-codex-spark",
1627 "claude-fable-5",
1628 "claude-opus-4-8",
1629 "claude-opus-4-7",
1630 "claude-sonnet-4-6",
1631 "claude-haiku-4-5-20251001",
1632 "fable",
1633 "sonnet",
1634 "opus",
1635 "haiku",
1636 "claude-sonnet-4-6",
1637 "claude-opus-4-8",
1638 "claude-fable-5",
1639 "gemini-3.5-flash",
1640 "gemini-3.1-pro-preview",
1641 "gemini-3.1-pro-preview-customtools",
1642 "gemini-3-flash-preview",
1643 "gemini-3.1-flash-lite-preview",
1644 "gemini-3.5-flash",
1645 "gemini-3.1-pro-preview",
1646 "gemini-3-flash-preview",
1647 "gemini-3.1-flash-lite-preview",
1648 "grok-4.3",
1649 "grok-4.20-multi-agent-0309",
1650 "grok-4.20-0309-reasoning",
1651 "grok-4.20-0309-non-reasoning",
1652 "grok-4.3",
1653 "grok-4.20-multi-agent-0309",
1654 "grok-4.20-0309-reasoning",
1655 "grok-4.20-0309-non-reasoning",
1656 "gpt-5.5",
1657 "gpt-5.3-codex-spark",
1658 "big-pickle",
1659 "deepseek-v4-flash-free",
1660 "minimax-m2.5-free",
1661 "nemotron-3-super-free",
1662 "kimi-k2.6",
1663 "qwen3.6-plus",
1664 "glm-5.1",
1665 "deepseek-v4-flash",
1666 "x-ai/grok-build-0.1",
1667 "roder.cloud/free",
1668 "roder.cloud/openai/gpt-5.5",
1669 "roder.cloud/anthropic/claude-opus-4-7",
1670 "roder.cloud/google/gemini-3.1-pro-preview",
1671 "poolside/laguna-m.1",
1672 "poolside/laguna-xs.2",
1673 "mimo-v2.5-pro",
1674 "mimo-v2-pro",
1675 "mimo-v2.5",
1676 "mimo-v2-omni",
1677 "mimo-v2-flash",
1678 "mimo-v2.5-pro",
1679 "mimo-v2-pro",
1680 "mimo-v2.5",
1681 "mimo-v2-omni",
1682 "mimo-v2-flash",
1683 "composer-2.5",
1684 "claude-opus-4-8",
1685 "claude-sonnet-4-6",
1686 "gpt-5.5",
1687 "gemini-3.1-pro-preview",
1688 "grok-4.3",
1689 ]
1690 );
1691 }
1692
1693 #[test]
1694 fn provider_model_lists_match_gode_catalog() {
1695 assert_eq!(models_for_provider(PROVIDER_OPENAI, false).len(), 2);
1696 assert_eq!(models_for_codex(false).len(), 3);
1697 assert_eq!(models_for_provider(PROVIDER_ANTHROPIC, false).len(), 5);
1698 assert_eq!(models_for_provider(PROVIDER_CLAUDE_CODE, false).len(), 7);
1699 assert_eq!(models_for_provider(PROVIDER_GEMINI, false).len(), 5);
1700 assert_eq!(models_for_provider(PROVIDER_VERTEX, false).len(), 4);
1701 assert_eq!(models_for_provider(PROVIDER_XAI, false).len(), 4);
1702 assert_eq!(models_for_provider(PROVIDER_SUPERGROK, false).len(), 4);
1703 assert_eq!(models_for_provider(PROVIDER_OPENCODE, false).len(), 6);
1704 assert_eq!(models_for_provider(PROVIDER_OPENCODE_GO, false).len(), 4);
1705 assert_eq!(models_for_provider(PROVIDER_OPENROUTER, false).len(), 1);
1706 assert_eq!(models_for_provider(PROVIDER_RODER_CLOUD, false).len(), 4);
1707 assert_eq!(models_for_provider(PROVIDER_POOLSIDE, false).len(), 2);
1708 assert_eq!(models_for_provider(PROVIDER_CURSOR, false).len(), 6);
1709 assert_eq!(models_for_provider(PROVIDER_XIAOMI_MIMO, false).len(), 5);
1710 assert_eq!(
1711 models_for_provider(PROVIDER_XIAOMI_MIMO_TOKEN_PLAN, false).len(),
1712 5
1713 );
1714 assert_eq!(models_for_provider(PROVIDER_MOCK, true).len(), 1);
1715 }
1716
1717 #[test]
1718 fn claude_code_catalog_uses_long_context_windows() {
1719 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-sonnet-4-6").unwrap();
1720 let claude_code =
1721 lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-sonnet-4-6").unwrap();
1722
1723 assert_eq!(direct.context_window, 1_000_000);
1724 assert_eq!(claude_code.context_window, 1_000_000);
1725 assert_eq!(claude_code.auto_compact_token_limit, 900_000);
1726 assert!(!claude_code.supports_compaction);
1729 assert!(direct.supports_compaction);
1733 assert_eq!(direct.auto_compact_token_limit, 900_000);
1734 }
1735
1736 #[test]
1737 fn claude_haiku_does_not_advertise_server_side_compaction() {
1738 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
1739
1740 assert!(!haiku.supports_compaction);
1745 assert_eq!(haiku.auto_compact_token_limit, 180_000);
1746 }
1747
1748 #[test]
1749 fn google_embedding_model_is_hidden_from_chat_lists() {
1750 assert!(lookup_model("gemini-embedding-2").is_some());
1751 assert!(
1752 models_for_provider(PROVIDER_GOOGLE, false)
1753 .iter()
1754 .all(|model| model.id != "gemini-embedding-2")
1755 );
1756 let model = lookup_model("gemini-embedding-2").unwrap();
1757 assert!(model.hidden);
1758 assert!(!model.supports_tools);
1759 }
1760
1761 #[test]
1762 fn zeroentropy_embedding_model_is_hidden_from_chat_lists() {
1763 assert!(lookup_model("zembed-1").is_some());
1764 assert!(
1765 models_for_provider(PROVIDER_ZEROENTROPY, false)
1766 .iter()
1767 .all(|model| model.id != "zembed-1")
1768 );
1769 let model = lookup_model("zembed-1").unwrap();
1770 assert!(model.hidden);
1771 assert!(!model.supports_tools);
1772 }
1773
1774 #[test]
1775 fn catalog_model_profile_derives_openai_defaults() {
1776 let profile = built_in_model_profile("gpt-5.5").unwrap();
1777
1778 assert_eq!(profile.provider_family, ProviderFamily::OpenAi);
1779 assert_eq!(profile.edit_tool.as_deref(), Some(EDIT_TOOL_PATCH));
1780 assert_eq!(profile.schema_policy, ModelSchemaPolicy::RequiredFirstFlat);
1781 assert_eq!(
1782 profile.instruction_overlay,
1783 ModelInstructionOverlay::LiteralToolOutputs
1784 );
1785 assert_eq!(profile.reasoning.execution.as_deref(), Some(REASONING_LOW));
1786 assert_eq!(profile.parallel_tool_calls, Some(true));
1787 }
1788
1789 #[test]
1790 fn poolside_catalog_defaults_to_thinking_enabled() {
1791 let laguna = lookup_model("poolside/laguna-m.1").unwrap();
1792 assert_eq!(laguna.default_reasoning, REASONING_MEDIUM);
1793 assert_eq!(
1794 laguna
1795 .supported_reasoning
1796 .iter()
1797 .map(|option| option.effort)
1798 .collect::<Vec<_>>(),
1799 vec![REASONING_NONE, REASONING_MEDIUM]
1800 );
1801 }
1802
1803 #[test]
1804 fn xiaomi_mimo_catalog_uses_chat_completions_kind_and_exact_model_ids() {
1805 let provider = BUILT_IN_PROVIDERS
1806 .iter()
1807 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO)
1808 .unwrap();
1809 let token_plan = BUILT_IN_PROVIDERS
1810 .iter()
1811 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO_TOKEN_PLAN)
1812 .unwrap();
1813
1814 assert_eq!(provider.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
1815 assert_eq!(token_plan.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
1816 assert_eq!(provider.env_key, Some("MIMO_API_KEY"));
1817 assert_eq!(token_plan.env_key, Some("MIMO_TOKEN_PLAN_API_KEY"));
1818
1819 let ids = models_for_provider(PROVIDER_XIAOMI_MIMO, false)
1820 .into_iter()
1821 .map(|model| model.id)
1822 .collect::<Vec<_>>();
1823 assert_eq!(
1824 ids,
1825 vec![
1826 "mimo-v2.5-pro",
1827 "mimo-v2-pro",
1828 "mimo-v2.5",
1829 "mimo-v2-omni",
1830 "mimo-v2-flash"
1831 ]
1832 );
1833 assert!(lookup_model("out-of-v2-flash").is_none());
1834 }
1835
1836 #[test]
1837 fn xai_catalog_entries_match_current_grok_contract() {
1838 let grok43 = models_for_provider(PROVIDER_XAI, false)
1839 .into_iter()
1840 .find(|model| model.id == "grok-4.3")
1841 .unwrap();
1842 assert_eq!(grok43.context_window, Some(1_000_000));
1843 assert_eq!(grok43.default_reasoning.as_deref(), Some(REASONING_LOW));
1844 assert_eq!(
1845 grok43
1846 .supported_reasoning
1847 .iter()
1848 .map(|option| option.effort.as_str())
1849 .collect::<Vec<_>>(),
1850 vec![
1851 REASONING_NONE,
1852 REASONING_LOW,
1853 REASONING_MEDIUM,
1854 REASONING_HIGH
1855 ]
1856 );
1857
1858 let grok420 = lookup_model("grok-4.20-multi-agent-0309").unwrap();
1859 assert_eq!(grok420.context_window, 2_000_000);
1860 assert_eq!(grok420.auto_compact_token_limit, 1_800_000);
1861 assert_eq!(grok420.provider, PROVIDER_XAI);
1862 }
1863
1864 #[test]
1865 fn provider_aliases_normalize_xai_and_supergrok() {
1866 assert_eq!(normalize_provider_id("grok"), PROVIDER_XAI);
1867 assert_eq!(normalize_provider_id("x.ai"), PROVIDER_XAI);
1868 assert_eq!(normalize_provider_id("x-ai"), PROVIDER_XAI);
1869 assert_eq!(normalize_provider_id("xai-oauth"), PROVIDER_SUPERGROK);
1870 assert_eq!(normalize_provider_id("grok-oauth"), PROVIDER_SUPERGROK);
1871 assert_eq!(normalize_provider_id("supergrok"), PROVIDER_SUPERGROK);
1872 assert_eq!(normalize_provider_id("laguna"), PROVIDER_POOLSIDE);
1873 assert_eq!(normalize_provider_id("composer"), PROVIDER_CURSOR);
1874 }
1875
1876 #[test]
1877 fn cursor_catalog_profile_is_text_only_agentservice() {
1878 let composer = lookup_model("composer-2.5").unwrap();
1879 assert_eq!(composer.provider, PROVIDER_CURSOR);
1880 assert!(!composer.supports_tools);
1881 assert!(!composer.supports_structured);
1882
1883 let profile = built_in_model_profile("composer-2.5").unwrap();
1884 assert_eq!(profile.provider_family, ProviderFamily::Cursor);
1885 assert_eq!(profile.parallel_tool_calls, Some(false));
1886 }
1887
1888 #[test]
1889 fn provider_aware_lookup_resolves_cursor_proxied_models_to_cursor_family() {
1890 let id_only = built_in_model_profile("claude-opus-4-8").unwrap();
1892 assert_eq!(id_only.provider_family, ProviderFamily::Anthropic);
1893
1894 let cursor =
1896 built_in_model_profile_for_provider(PROVIDER_CURSOR, "claude-opus-4-8").unwrap();
1897 assert_eq!(cursor.provider_family, ProviderFamily::Cursor);
1898 assert_eq!(cursor.provider, PROVIDER_CURSOR);
1899 assert_eq!(cursor.parallel_tool_calls, Some(false));
1900
1901 let anthropic =
1902 built_in_model_profile_for_provider(PROVIDER_ANTHROPIC, "claude-opus-4-8").unwrap();
1903 assert_eq!(anthropic.provider_family, ProviderFamily::Anthropic);
1904
1905 let fallback =
1907 built_in_model_profile_for_provider("does-not-exist", "claude-opus-4-8").unwrap();
1908 assert_eq!(fallback.provider_family, ProviderFamily::Anthropic);
1909 }
1910
1911 #[test]
1912 fn cursor_opus_advertises_configurable_reasoning_effort() {
1913 let opus = models_for_provider(PROVIDER_CURSOR, false)
1914 .into_iter()
1915 .find(|model| model.id == "claude-opus-4-8")
1916 .expect("cursor catalog should expose claude-opus-4-8");
1917
1918 assert_eq!(opus.default_reasoning.as_deref(), Some(REASONING_HIGH));
1919 assert_eq!(
1920 opus.supported_reasoning
1921 .iter()
1922 .map(|option| option.effort.as_str())
1923 .collect::<Vec<_>>(),
1924 vec![
1925 REASONING_LOW,
1926 REASONING_MEDIUM,
1927 REASONING_HIGH,
1928 REASONING_XHIGH,
1929 REASONING_MAX
1930 ]
1931 );
1932
1933 let sonnet = models_for_provider(PROVIDER_CURSOR, false)
1935 .into_iter()
1936 .find(|model| model.id == "claude-sonnet-4-6")
1937 .expect("cursor catalog should expose claude-sonnet-4-6");
1938 assert_eq!(sonnet.default_reasoning, None);
1939 assert!(sonnet.supported_reasoning.is_empty());
1940 }
1941
1942 #[test]
1943 fn claude_opus_and_sonnet_advertise_max_effort() {
1944 let efforts = |id: &str| {
1945 lookup_model(id)
1946 .unwrap()
1947 .supported_reasoning
1948 .iter()
1949 .map(|option| option.effort)
1950 .collect::<Vec<_>>()
1951 };
1952
1953 for id in ["claude-opus-4-8", "claude-opus-4-7"] {
1955 assert_eq!(
1956 efforts(id),
1957 vec![
1958 REASONING_LOW,
1959 REASONING_MEDIUM,
1960 REASONING_HIGH,
1961 REASONING_XHIGH,
1962 REASONING_MAX
1963 ],
1964 "{id} effort levels"
1965 );
1966 }
1967
1968 assert_eq!(
1970 efforts("claude-sonnet-4-6"),
1971 vec![
1972 REASONING_LOW,
1973 REASONING_MEDIUM,
1974 REASONING_HIGH,
1975 REASONING_MAX
1976 ]
1977 );
1978
1979 assert!(!efforts("gpt-5.5").contains(&REASONING_MAX));
1981 }
1982
1983 #[test]
1984 fn claude_haiku_does_not_advertise_reasoning_effort() {
1985 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
1986
1987 assert_eq!(haiku.default_reasoning, REASONING_NONE);
1988 assert!(haiku.supported_reasoning.is_empty());
1989
1990 let descriptor = ModelDescriptor::from(haiku);
1991 assert_eq!(descriptor.default_reasoning, None);
1992 assert!(descriptor.supported_reasoning.is_empty());
1993 }
1994
1995 #[test]
1996 fn openai_context_windows_match_current_catalog_values() {
1997 let gpt55 = lookup_model("gpt-5.5").unwrap();
1998 assert_eq!(gpt55.context_window, 1_050_000);
1999 assert_eq!(gpt55.max_context_window, 1_050_000);
2000 assert_eq!(gpt55.auto_compact_token_limit, 945_000);
2001
2002 let mini = lookup_model("gpt-5.4-mini").unwrap();
2003 assert_eq!(mini.context_window, 400_000);
2004 assert_eq!(mini.max_context_window, 400_000);
2005 assert_eq!(mini.auto_compact_token_limit, 360_000);
2006
2007 let spark = lookup_model("gpt-5.3-codex-spark").unwrap();
2008 assert_eq!(spark.provider, PROVIDER_CODEX);
2009 assert_eq!(spark.context_window, 128_000);
2010 assert_eq!(spark.max_context_window, 128_000);
2011 assert_eq!(spark.auto_compact_token_limit, 115_200);
2012 }
2013
2014 #[test]
2015 fn auto_compact_defaults_to_ninety_percent_of_context_window() {
2016 for model in BUILT_IN_MODELS {
2017 if model.context_window == 0 || model.auto_compact_token_limit == 0 {
2018 continue;
2019 }
2020 assert_eq!(
2021 model.auto_compact_token_limit,
2022 model.context_window.saturating_mul(9) / 10,
2023 "{} should compact at 90% of its context window",
2024 model.id
2025 );
2026 }
2027 }
2028}