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