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 "claude-opus-4-8",
1068 "Claude Opus 4.8",
1069 "Anthropic Claude Opus 4.8 routed through Cursor's AgentService.",
1070 1_000_000,
1071 900_000,
1072 REASONING_HIGH,
1073 OPUS_REASONING,
1074 ),
1075 cursor_model(
1076 "claude-sonnet-4-6",
1077 "Claude Sonnet 4.6",
1078 "Anthropic Claude Sonnet 4.6 routed through Cursor's AgentService.",
1079 1_000_000,
1080 900_000,
1081 REASONING_NONE,
1082 &[],
1083 ),
1084 cursor_model(
1085 "gpt-5.5",
1086 "GPT-5.5",
1087 "OpenAI GPT-5.5 routed through Cursor's AgentService.",
1088 1_050_000,
1089 945_000,
1090 REASONING_NONE,
1091 &[],
1092 ),
1093 cursor_model(
1094 "gemini-3.1-pro-preview",
1095 "Gemini 3.1 Pro",
1096 "Google Gemini 3.1 Pro routed through Cursor's AgentService.",
1097 1_048_576,
1098 943_718,
1099 REASONING_NONE,
1100 &[],
1101 ),
1102 cursor_model(
1103 "grok-4.3",
1104 "Grok 4.3",
1105 "xAI Grok 4.3 routed through Cursor's AgentService.",
1106 1_000_000,
1107 900_000,
1108 REASONING_NONE,
1109 &[],
1110 ),
1111 ModelCatalogEntry {
1112 id: "text-embedding-3-large",
1113 display_name: "Text Embedding 3 Large",
1114 description: "OpenAI embedding model for local semantic memories.",
1115 provider: PROVIDER_OPENAI,
1116 default_reasoning: REASONING_NONE,
1117 supported_reasoning: &[],
1118 context_window: 0,
1119 max_context_window: 0,
1120 auto_compact_token_limit: 0,
1121 supports_compaction: false,
1122 supports_images: false,
1123 supports_tools: true,
1124 supports_structured: false,
1125 edit_tool: None,
1126 hidden: true,
1127 },
1128 ModelCatalogEntry {
1129 id: "gemini-embedding-2",
1130 display_name: "Gemini Embedding 2",
1131 description: "Google Gemini embedding model for local semantic memories.",
1132 provider: PROVIDER_GOOGLE,
1133 default_reasoning: REASONING_NONE,
1134 supported_reasoning: &[],
1135 context_window: 0,
1136 max_context_window: 0,
1137 auto_compact_token_limit: 0,
1138 supports_compaction: false,
1139 supports_images: false,
1140 supports_tools: false,
1141 supports_structured: false,
1142 edit_tool: None,
1143 hidden: true,
1144 },
1145 ModelCatalogEntry {
1146 id: "zembed-1",
1147 display_name: "ZeroEntropy zembed-1",
1148 description: "ZeroEntropy embedding model for local semantic memories.",
1149 provider: PROVIDER_ZEROENTROPY,
1150 default_reasoning: REASONING_NONE,
1151 supported_reasoning: &[],
1152 context_window: 0,
1153 max_context_window: 0,
1154 auto_compact_token_limit: 0,
1155 supports_compaction: false,
1156 supports_images: false,
1157 supports_tools: false,
1158 supports_structured: false,
1159 edit_tool: None,
1160 hidden: true,
1161 },
1162 ModelCatalogEntry {
1163 id: "mock",
1164 display_name: "Mock",
1165 description: "Local deterministic mock provider for tests and offline development.",
1166 provider: PROVIDER_MOCK,
1167 default_reasoning: REASONING_NONE,
1168 supported_reasoning: MOCK_REASONING,
1169 context_window: 128_000,
1170 max_context_window: 128_000,
1171 auto_compact_token_limit: 115_200,
1172 supports_compaction: false,
1173 supports_images: false,
1174 supports_tools: true,
1175 supports_structured: false,
1176 edit_tool: None,
1177 hidden: true,
1178 },
1179];
1180
1181const fn openai_model(
1182 id: &'static str,
1183 display_name: &'static str,
1184 description: &'static str,
1185 context_window: u32,
1186 auto_compact_token_limit: u32,
1187 supports_compaction: bool,
1188 supported_reasoning: &'static [ReasoningOption],
1189) -> ModelCatalogEntry {
1190 ModelCatalogEntry {
1191 id,
1192 display_name,
1193 description,
1194 provider: PROVIDER_OPENAI,
1195 default_reasoning: REASONING_MEDIUM,
1196 supported_reasoning,
1197 context_window,
1198 max_context_window: context_window,
1199 auto_compact_token_limit,
1200 supports_compaction,
1201 supports_images: false,
1202 supports_tools: true,
1203 supports_structured: false,
1204 edit_tool: Some("patch"),
1205 hidden: false,
1206 }
1207}
1208
1209#[allow(clippy::too_many_arguments)]
1210const fn anthropic_model(
1211 id: &'static str,
1212 display_name: &'static str,
1213 description: &'static str,
1214 context_window: u32,
1215 auto_compact_token_limit: u32,
1216 default_reasoning: &'static str,
1217 supported_reasoning: &'static [ReasoningOption],
1218 supports_compaction: bool,
1229) -> ModelCatalogEntry {
1230 ModelCatalogEntry {
1231 id,
1232 display_name,
1233 description,
1234 provider: PROVIDER_ANTHROPIC,
1235 default_reasoning,
1236 supported_reasoning,
1237 context_window,
1238 max_context_window: context_window,
1239 auto_compact_token_limit,
1240 supports_compaction,
1241 supports_images: false,
1242 supports_tools: true,
1243 supports_structured: false,
1244 edit_tool: Some("edit"),
1245 hidden: false,
1246 }
1247}
1248
1249const fn claude_code_model(
1250 id: &'static str,
1251 display_name: &'static str,
1252 description: &'static str,
1253 context_window: u32,
1254 auto_compact_token_limit: u32,
1255 default_reasoning: &'static str,
1256 supported_reasoning: &'static [ReasoningOption],
1257) -> ModelCatalogEntry {
1258 ModelCatalogEntry {
1259 id,
1260 display_name,
1261 description,
1262 provider: PROVIDER_CLAUDE_CODE,
1263 default_reasoning,
1264 supported_reasoning,
1265 context_window,
1266 max_context_window: context_window,
1267 auto_compact_token_limit,
1268 supports_compaction: false,
1274 supports_images: true,
1275 supports_tools: true,
1276 supports_structured: false,
1277 edit_tool: Some(EDIT_TOOL_EDIT),
1278 hidden: false,
1279 }
1280}
1281
1282const fn gemini_model(
1283 provider: &'static str,
1284 id: &'static str,
1285 display_name: &'static str,
1286 description: &'static str,
1287 default_reasoning: &'static str,
1288) -> ModelCatalogEntry {
1289 ModelCatalogEntry {
1290 id,
1291 display_name,
1292 description,
1293 provider,
1294 default_reasoning,
1295 supported_reasoning: GEMINI_REASONING,
1296 context_window: 1_048_576,
1297 max_context_window: 1_048_576,
1298 auto_compact_token_limit: 943_718,
1299 supports_compaction: false,
1300 supports_images: true,
1301 supports_tools: true,
1302 supports_structured: true,
1303 edit_tool: Some("edit"),
1304 hidden: false,
1305 }
1306}
1307
1308const fn xai_model(
1309 provider: &'static str,
1310 id: &'static str,
1311 display_name: &'static str,
1312 description: &'static str,
1313 context_window: u32,
1314 default_reasoning: &'static str,
1315 supported_reasoning: &'static [ReasoningOption],
1316 supports_images: bool,
1317 hidden: bool,
1318) -> ModelCatalogEntry {
1319 ModelCatalogEntry {
1320 id,
1321 display_name,
1322 description,
1323 provider,
1324 default_reasoning,
1325 supported_reasoning,
1326 context_window,
1327 max_context_window: context_window,
1328 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1329 supports_compaction: false,
1330 supports_images,
1331 supports_tools: true,
1332 supports_structured: true,
1333 edit_tool: Some("edit"),
1334 hidden,
1335 }
1336}
1337
1338const fn opencode_model(
1339 provider: &'static str,
1340 id: &'static str,
1341 display_name: &'static str,
1342 description: &'static str,
1343 context_window: u32,
1344 default_reasoning: &'static str,
1345 supported_reasoning: &'static [ReasoningOption],
1346) -> ModelCatalogEntry {
1347 ModelCatalogEntry {
1348 id,
1349 display_name,
1350 description,
1351 provider,
1352 default_reasoning,
1353 supported_reasoning,
1354 context_window,
1355 max_context_window: context_window,
1356 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1357 supports_compaction: false,
1358 supports_images: false,
1359 supports_tools: true,
1360 supports_structured: true,
1361 edit_tool: Some("edit"),
1362 hidden: false,
1363 }
1364}
1365
1366const fn roder_cloud_model(
1367 id: &'static str,
1368 display_name: &'static str,
1369 description: &'static str,
1370 context_window: u32,
1371) -> ModelCatalogEntry {
1372 ModelCatalogEntry {
1373 id,
1374 display_name,
1375 description,
1376 provider: PROVIDER_RODER_CLOUD,
1377 default_reasoning: REASONING_NONE,
1378 supported_reasoning: RODER_CLOUD_REASONING,
1379 context_window,
1380 max_context_window: context_window,
1381 auto_compact_token_limit: 0,
1382 supports_compaction: false,
1383 supports_images: false,
1384 supports_tools: false,
1385 supports_structured: false,
1386 edit_tool: None,
1387 hidden: false,
1388 }
1389}
1390
1391const fn poolside_model(
1392 id: &'static str,
1393 display_name: &'static str,
1394 description: &'static str,
1395 default_reasoning: &'static str,
1396) -> ModelCatalogEntry {
1397 ModelCatalogEntry {
1398 id,
1399 display_name,
1400 description,
1401 provider: PROVIDER_POOLSIDE,
1402 default_reasoning,
1403 supported_reasoning: POOLSIDE_REASONING,
1404 context_window: 131_072,
1405 max_context_window: 131_072,
1406 auto_compact_token_limit: 117_964,
1407 supports_compaction: false,
1408 supports_images: false,
1409 supports_tools: true,
1410 supports_structured: true,
1411 edit_tool: Some("edit"),
1412 hidden: false,
1413 }
1414}
1415
1416const fn cursor_model(
1417 id: &'static str,
1418 display_name: &'static str,
1419 description: &'static str,
1420 context_window: u32,
1421 auto_compact_token_limit: u32,
1422 default_reasoning: &'static str,
1423 supported_reasoning: &'static [ReasoningOption],
1424) -> ModelCatalogEntry {
1425 ModelCatalogEntry {
1426 id,
1427 display_name,
1428 description,
1429 provider: PROVIDER_CURSOR,
1430 default_reasoning,
1431 supported_reasoning,
1432 context_window,
1433 max_context_window: context_window,
1434 auto_compact_token_limit,
1435 supports_compaction: true,
1436 supports_images: true,
1440 supports_tools: false,
1441 supports_structured: false,
1442 edit_tool: None,
1443 hidden: false,
1444 }
1445}
1446
1447pub fn built_in_providers() -> &'static [ProviderCatalogEntry] {
1448 BUILT_IN_PROVIDERS
1449}
1450
1451pub fn built_in_models(include_hidden: bool) -> Vec<&'static ModelCatalogEntry> {
1452 BUILT_IN_MODELS
1453 .iter()
1454 .filter(|model| include_hidden || !model.hidden)
1455 .collect()
1456}
1457
1458pub fn models_for_provider(provider: &str, include_hidden: bool) -> Vec<ModelDescriptor> {
1459 built_in_models(include_hidden)
1460 .into_iter()
1461 .filter(|model| model.provider == provider)
1462 .map(ModelDescriptor::from)
1463 .collect()
1464}
1465
1466pub fn models_for_codex(include_hidden: bool) -> Vec<ModelDescriptor> {
1467 built_in_models(include_hidden)
1468 .into_iter()
1469 .filter(|model| model.provider == PROVIDER_OPENAI || model.provider == PROVIDER_CODEX)
1470 .map(ModelDescriptor::from)
1471 .collect()
1472}
1473
1474pub fn lookup_model(id: &str) -> Option<&'static ModelCatalogEntry> {
1475 BUILT_IN_MODELS.iter().find(|model| model.id == id)
1476}
1477
1478pub fn lookup_model_for_provider(provider: &str, id: &str) -> Option<&'static ModelCatalogEntry> {
1488 BUILT_IN_MODELS
1489 .iter()
1490 .find(|model| model.provider == provider && model.id == id)
1491 .or_else(|| lookup_model(id))
1492}
1493
1494pub fn built_in_model_profile(id: &str) -> Option<ModelHarnessProfile> {
1495 lookup_model(id).map(model_harness_profile_from_catalog)
1496}
1497
1498pub fn built_in_model_profile_for_provider(
1504 provider: &str,
1505 id: &str,
1506) -> Option<ModelHarnessProfile> {
1507 lookup_model_for_provider(provider, id).map(model_harness_profile_from_catalog)
1508}
1509
1510pub fn built_in_model_profiles() -> Vec<ModelHarnessProfile> {
1511 built_in_models(true)
1512 .into_iter()
1513 .map(model_harness_profile_from_catalog)
1514 .collect()
1515}
1516
1517fn model_harness_profile_from_catalog(model: &ModelCatalogEntry) -> ModelHarnessProfile {
1518 let provider_family = provider_family_for_provider(model.provider);
1519 ModelHarnessProfile {
1520 model: model.id.to_string(),
1521 provider: model.provider.to_string(),
1522 provider_family,
1523 edit_tool: model.edit_tool.map(str::to_string),
1524 schema_policy: schema_policy_for_family(provider_family),
1525 instruction_overlay: instruction_overlay_for_family(provider_family),
1526 reasoning: ModelProfileReasoning {
1527 orientation: Some(model.default_reasoning.to_string()),
1528 execution: Some(default_execution_reasoning(model)),
1529 verification: Some(model.default_reasoning.to_string()),
1530 recovery: Some(model.default_reasoning.to_string()),
1531 },
1532 parallel_tool_calls: Some(
1533 model.supports_tools
1534 && matches!(
1535 provider_family,
1536 ProviderFamily::OpenAi | ProviderFamily::Xai | ProviderFamily::Opencode
1537 ),
1538 ),
1539 auto_compact_token_limit: (model.auto_compact_token_limit > 0)
1540 .then_some(model.auto_compact_token_limit),
1541 }
1542}
1543
1544pub fn provider_family_for_provider(provider: &str) -> ProviderFamily {
1545 match provider {
1546 PROVIDER_OPENAI | PROVIDER_CODEX => ProviderFamily::OpenAi,
1547 PROVIDER_ANTHROPIC | PROVIDER_CLAUDE_CODE => ProviderFamily::Anthropic,
1548 PROVIDER_GEMINI | PROVIDER_VERTEX => ProviderFamily::Gemini,
1549 PROVIDER_XAI | PROVIDER_SUPERGROK => ProviderFamily::Xai,
1550 PROVIDER_OPENCODE | PROVIDER_OPENCODE_GO => ProviderFamily::Opencode,
1551 PROVIDER_OPENROUTER | PROVIDER_FIREWORKS | PROVIDER_RODER_CLOUD => ProviderFamily::OpenAi,
1552 PROVIDER_POOLSIDE => ProviderFamily::Poolside,
1553 PROVIDER_CURSOR => ProviderFamily::Cursor,
1554 PROVIDER_XIAOMI_MIMO | PROVIDER_XIAOMI_MIMO_TOKEN_PLAN => ProviderFamily::OpenAi,
1555 PROVIDER_KIMI_CODE => ProviderFamily::OpenAi,
1556 PROVIDER_SYNTHETIC => ProviderFamily::OpenAi,
1557 _ => ProviderFamily::Mock,
1558 }
1559}
1560
1561fn schema_policy_for_family(family: ProviderFamily) -> ModelSchemaPolicy {
1562 match family {
1563 ProviderFamily::OpenAi => ModelSchemaPolicy::RequiredFirstFlat,
1564 _ => ModelSchemaPolicy::StandardRequiredFirst,
1565 }
1566}
1567
1568fn instruction_overlay_for_family(family: ProviderFamily) -> ModelInstructionOverlay {
1569 match family {
1570 ProviderFamily::OpenAi => ModelInstructionOverlay::LiteralToolOutputs,
1571 ProviderFamily::Anthropic | ProviderFamily::Gemini => {
1572 ModelInstructionOverlay::IntuitiveContext
1573 }
1574 _ => ModelInstructionOverlay::Standard,
1575 }
1576}
1577
1578fn default_execution_reasoning(model: &ModelCatalogEntry) -> String {
1579 if model
1580 .supported_reasoning
1581 .iter()
1582 .any(|option| option.effort == REASONING_LOW)
1583 {
1584 REASONING_LOW.to_string()
1585 } else {
1586 model.default_reasoning.to_string()
1587 }
1588}
1589
1590pub fn model_supports_reasoning_effort(model: &str, effort: &str) -> bool {
1591 lookup_model(model)
1592 .map(|entry| {
1593 entry
1594 .supported_reasoning
1595 .iter()
1596 .any(|option| option.effort == effort)
1597 })
1598 .unwrap_or(false)
1599}
1600
1601pub fn normalize_provider_id(provider: &str) -> String {
1602 match provider.trim().to_ascii_lowercase().as_str() {
1603 "grok" | "x-ai" | "x.ai" => PROVIDER_XAI.to_string(),
1604 "grok-oauth" | "xai-oauth" | "x-ai-oauth" | "xai-grok-oauth" => {
1605 PROVIDER_SUPERGROK.to_string()
1606 }
1607 "opencode" => PROVIDER_OPENCODE.to_string(),
1608 "go" | "opencode_go" | "opencode-go" => PROVIDER_OPENCODE_GO.to_string(),
1609 "openrouter" => PROVIDER_OPENROUTER.to_string(),
1610 "fireworks" | "fireworks-ai" | "fireworks_ai" => PROVIDER_FIREWORKS.to_string(),
1611 "roder-cloud" | "roder_cloud" | "rodercloud" | "roder.cloud" => {
1612 PROVIDER_RODER_CLOUD.to_string()
1613 }
1614 "laguna" | "poolside" => PROVIDER_POOLSIDE.to_string(),
1615 "composer" | "cursor-composer" => PROVIDER_CURSOR.to_string(),
1616 "claude_code" | "claudecode" => PROVIDER_CLAUDE_CODE.to_string(),
1617 "kimi" | "kimi-code" | "kimi_code" | "moonshot" => PROVIDER_KIMI_CODE.to_string(),
1618 "synthetic" | "synthetic-ai" | "synthetic_ai" | "synthetic.new" => {
1619 PROVIDER_SYNTHETIC.to_string()
1620 }
1621 provider => provider.to_string(),
1622 }
1623}
1624
1625impl From<&ModelCatalogEntry> for ModelDescriptor {
1626 fn from(model: &ModelCatalogEntry) -> Self {
1627 let supported_reasoning = model
1628 .supported_reasoning
1629 .iter()
1630 .map(|option| ReasoningEffortDescriptor {
1631 effort: option.effort.to_string(),
1632 description: option.description.to_string(),
1633 })
1634 .collect::<Vec<_>>();
1635 Self {
1636 id: model.id.to_string(),
1637 name: model.display_name.to_string(),
1638 context_window: (model.context_window > 0).then_some(model.context_window),
1639 default_reasoning: (!supported_reasoning.is_empty())
1640 .then(|| model.default_reasoning.to_string()),
1641 supported_reasoning,
1642 }
1643 }
1644}
1645
1646#[cfg(test)]
1647mod tests {
1648 use super::*;
1649
1650 #[test]
1651 fn catalog_contains_gode_providers() {
1652 let ids = BUILT_IN_PROVIDERS
1653 .iter()
1654 .map(|provider| provider.id)
1655 .collect::<Vec<_>>();
1656 assert_eq!(
1657 ids,
1658 vec![
1659 "mock",
1660 "openai",
1661 "codex",
1662 "anthropic",
1663 "claude-code",
1664 "gemini",
1665 "vertex",
1666 "xai",
1667 "supergrok",
1668 "opencode",
1669 "opencode-go",
1670 "openrouter",
1671 "fireworks",
1672 "roder-cloud",
1673 "poolside",
1674 "cursor",
1675 "xiaomi-mimo",
1676 "xiaomi-mimo-token-plan",
1677 "synthetic",
1678 "kimi-code"
1679 ]
1680 );
1681 }
1682
1683 #[test]
1684 fn gemini_provider_defaults_to_stable_35_flash() {
1685 let provider = BUILT_IN_PROVIDERS
1686 .iter()
1687 .find(|provider| provider.id == PROVIDER_GEMINI)
1688 .unwrap();
1689
1690 assert_eq!(provider.default_model, "gemini-3.5-flash");
1691
1692 let model = lookup_model("gemini-3.5-flash").unwrap();
1693 assert_eq!(model.display_name, "Gemini 3.5 Flash");
1694 assert_eq!(model.provider, PROVIDER_GEMINI);
1695 assert_eq!(model.context_window, 1_048_576);
1696 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
1697 assert!(model.supports_tools);
1698 assert!(model.supports_structured);
1699 assert_eq!(
1700 model
1701 .supported_reasoning
1702 .iter()
1703 .map(|option| option.effort)
1704 .collect::<Vec<_>>(),
1705 vec![
1706 REASONING_MINIMAL,
1707 REASONING_LOW,
1708 REASONING_MEDIUM,
1709 REASONING_HIGH
1710 ]
1711 );
1712 }
1713
1714 #[test]
1715 fn vertex_provider_mirrors_gemini_models_under_vertex_id() {
1716 let provider = BUILT_IN_PROVIDERS
1717 .iter()
1718 .find(|provider| provider.id == PROVIDER_VERTEX)
1719 .unwrap();
1720
1721 assert_eq!(provider.default_model, "gemini-3.5-flash");
1722 assert_eq!(provider.env_key, Some("GOOGLE_APPLICATION_CREDENTIALS"));
1723 assert_eq!(provider.env_aliases, &["VERTEX_CREDENTIALS_JSON"]);
1724
1725 let model = lookup_model_for_provider(PROVIDER_VERTEX, "gemini-3.5-flash").unwrap();
1726 assert_eq!(model.provider, PROVIDER_VERTEX);
1727 assert_eq!(model.context_window, 1_048_576);
1728 assert!(model.supports_tools);
1729 assert_eq!(
1730 provider_family_for_provider(PROVIDER_VERTEX),
1731 ProviderFamily::Gemini
1732 );
1733 }
1734
1735 #[test]
1736 fn catalog_contains_gode_visible_models() {
1737 let ids = built_in_models(false)
1738 .into_iter()
1739 .map(|model| model.id)
1740 .collect::<Vec<_>>();
1741 assert_eq!(
1742 ids,
1743 vec![
1744 "gpt-5.5",
1745 "gpt-5.4-mini",
1746 "gpt-5.3-codex-spark",
1747 "claude-fable-5",
1748 "claude-opus-4-8",
1749 "claude-opus-4-7",
1750 "claude-sonnet-4-6",
1751 "claude-haiku-4-5-20251001",
1752 "fable",
1753 "sonnet",
1754 "opus",
1755 "haiku",
1756 "claude-sonnet-4-6",
1757 "claude-opus-4-8",
1758 "claude-fable-5",
1759 "gemini-3.5-flash",
1760 "gemini-3.1-pro-preview",
1761 "gemini-3.1-pro-preview-customtools",
1762 "gemini-3-flash-preview",
1763 "gemini-3.1-flash-lite-preview",
1764 "gemini-3.5-flash",
1765 "gemini-3.1-pro-preview",
1766 "gemini-3-flash-preview",
1767 "gemini-3.1-flash-lite-preview",
1768 "grok-4.3",
1769 "grok-4.20-multi-agent-0309",
1770 "grok-4.20-0309-reasoning",
1771 "grok-4.20-0309-non-reasoning",
1772 "grok-build-0.1",
1773 "grok-composer-2.5-fast",
1774 "gpt-5.5",
1775 "gpt-5.3-codex-spark",
1776 "big-pickle",
1777 "deepseek-v4-flash-free",
1778 "minimax-m2.5-free",
1779 "nemotron-3-super-free",
1780 "kimi-k2.6",
1781 "qwen3.6-plus",
1782 "glm-5.1",
1783 "deepseek-v4-flash",
1784 "kimi-for-coding",
1785 "x-ai/grok-build-0.1",
1786 "accounts/fireworks/models/qwen3-235b-a22b",
1787 "roder.cloud/free",
1788 "roder.cloud/openai/gpt-5.5",
1789 "roder.cloud/anthropic/claude-opus-4-7",
1790 "roder.cloud/google/gemini-3.1-pro-preview",
1791 "poolside/laguna-m.1",
1792 "poolside/laguna-xs.2",
1793 "mimo-v2.5-pro",
1794 "mimo-v2-pro",
1795 "mimo-v2.5",
1796 "mimo-v2-omni",
1797 "mimo-v2-flash",
1798 "mimo-v2.5-pro",
1799 "mimo-v2-pro",
1800 "mimo-v2.5",
1801 "mimo-v2-omni",
1802 "mimo-v2-flash",
1803 "syn:large:text",
1804 "syn:small:text",
1805 "syn:large:vision",
1806 "syn:small:vision",
1807 "hf:MiniMaxAI/MiniMax-M3",
1808 "hf:Qwen/Qwen3.6-27B",
1809 "hf:moonshotai/Kimi-K2.6",
1810 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
1811 "hf:zai-org/GLM-4.7",
1812 "hf:zai-org/GLM-4.7-Flash",
1813 "hf:zai-org/GLM-5.1",
1814 "hf:zai-org/GLM-5.2",
1815 "hf:openai/gpt-oss-120b",
1816 "hf:Qwen/Qwen3.5-397B-A17B",
1817 "composer-2.5",
1818 "claude-opus-4-8",
1819 "claude-sonnet-4-6",
1820 "gpt-5.5",
1821 "gemini-3.1-pro-preview",
1822 "grok-4.3",
1823 ]
1824 );
1825 }
1826
1827 #[test]
1828 fn provider_model_lists_match_gode_catalog() {
1829 assert_eq!(models_for_provider(PROVIDER_OPENAI, false).len(), 2);
1830 assert_eq!(models_for_codex(false).len(), 3);
1831 assert_eq!(models_for_provider(PROVIDER_ANTHROPIC, false).len(), 5);
1832 assert_eq!(models_for_provider(PROVIDER_CLAUDE_CODE, false).len(), 7);
1833 assert_eq!(models_for_provider(PROVIDER_GEMINI, false).len(), 5);
1834 assert_eq!(models_for_provider(PROVIDER_VERTEX, false).len(), 4);
1835 assert_eq!(models_for_provider(PROVIDER_XAI, false).len(), 4);
1836 assert_eq!(models_for_provider(PROVIDER_SUPERGROK, false).len(), 2);
1837 assert_eq!(models_for_provider(PROVIDER_OPENCODE, false).len(), 6);
1838 assert_eq!(models_for_provider(PROVIDER_OPENCODE_GO, false).len(), 4);
1839 assert_eq!(models_for_provider(PROVIDER_OPENROUTER, false).len(), 1);
1840 assert_eq!(models_for_provider(PROVIDER_FIREWORKS, false).len(), 1);
1841 assert_eq!(models_for_provider(PROVIDER_RODER_CLOUD, false).len(), 4);
1842 assert_eq!(models_for_provider(PROVIDER_POOLSIDE, false).len(), 2);
1843 assert_eq!(models_for_provider(PROVIDER_CURSOR, false).len(), 6);
1844 assert_eq!(models_for_provider(PROVIDER_XIAOMI_MIMO, false).len(), 5);
1845 assert_eq!(
1846 models_for_provider(PROVIDER_XIAOMI_MIMO_TOKEN_PLAN, false).len(),
1847 5
1848 );
1849 assert_eq!(models_for_provider(PROVIDER_KIMI_CODE, false).len(), 1);
1850 assert_eq!(models_for_provider(PROVIDER_SYNTHETIC, false).len(), 14);
1851 assert_eq!(models_for_provider(PROVIDER_MOCK, true).len(), 1);
1852 }
1853
1854 #[test]
1855 fn synthetic_catalog_defaults_to_large_text_alias() {
1856 let provider = built_in_providers()
1857 .iter()
1858 .find(|provider| provider.id == PROVIDER_SYNTHETIC)
1859 .expect("synthetic provider registered");
1860 assert_eq!(provider.name, "Synthetic");
1861 assert_eq!(provider.default_model, "syn:large:text");
1862 assert_eq!(
1863 provider.base_url,
1864 Some("https://api.synthetic.new/openai/v1")
1865 );
1866 assert_eq!(provider.env_key, Some("SYNTHETIC_API_KEY"));
1867 assert!(provider.env_aliases.contains(&"RODER_SYNTHETIC_API_KEY"));
1868 assert!(!provider.supports_websockets);
1869
1870 let models = models_for_provider(PROVIDER_SYNTHETIC, false);
1871 let default = models
1872 .iter()
1873 .find(|model| model.id == provider.default_model)
1874 .expect("default synthetic model present");
1875 assert_eq!(default.name, "Synthetic Large (Text)");
1876 assert!(models.iter().any(|model| model.id == "syn:small:text"));
1877 let vision = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:vision")
1878 .expect("vision alias present");
1879 assert!(vision.supports_images);
1880 assert_eq!(
1881 provider_family_for_provider(PROVIDER_SYNTHETIC),
1882 ProviderFamily::OpenAi
1883 );
1884 }
1885
1886 #[test]
1887 fn synthetic_model_ids_preserve_alias_and_hf_segments() {
1888 assert_eq!(normalize_provider_id("synthetic"), PROVIDER_SYNTHETIC);
1889 assert_eq!(normalize_provider_id("synthetic.new"), PROVIDER_SYNTHETIC);
1890 let alias = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:text")
1892 .expect("syn alias resolves");
1893 assert_eq!(alias.id, "syn:large:text");
1894 assert_eq!(alias.provider, PROVIDER_SYNTHETIC);
1895 let label = "synthetic/hf:zai-org/GLM-5.2";
1899 let (provider, model) = label.split_once('/').unwrap();
1900 assert_eq!(provider, PROVIDER_SYNTHETIC);
1901 assert_eq!(model, "hf:zai-org/GLM-5.2");
1902 }
1903
1904 #[test]
1905 fn synthetic_always_on_models_are_pinned_with_documented_context_windows() {
1906 let glm_5_2 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-5.2")
1907 .expect("GLM-5.2 pinned");
1908 assert_eq!(glm_5_2.provider, PROVIDER_SYNTHETIC);
1909 assert_eq!(glm_5_2.context_window, 524_288);
1910 assert!(!glm_5_2.supports_images);
1911
1912 let minimax = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:MiniMaxAI/MiniMax-M3")
1913 .expect("MiniMax-M3 pinned");
1914 assert_eq!(minimax.context_window, 524_288);
1915
1916 let glm_4_7 =
1917 lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-4.7").expect("GLM-4.7 pinned");
1918 assert_eq!(glm_4_7.context_window, 202_752);
1919
1920 let gpt_oss = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:openai/gpt-oss-120b")
1921 .expect("gpt-oss-120b pinned");
1922 assert_eq!(gpt_oss.context_window, 131_072);
1923
1924 let qwen_3_5 = lookup_model_for_provider(
1925 PROVIDER_SYNTHETIC,
1926 "hf:Qwen/Qwen3.5-397B-A17B",
1927 )
1928 .expect("Qwen3.5 397B pinned");
1929 assert_eq!(qwen_3_5.context_window, 262_144);
1930
1931 let always_on = [
1933 "hf:MiniMaxAI/MiniMax-M3",
1934 "hf:Qwen/Qwen3.6-27B",
1935 "hf:moonshotai/Kimi-K2.6",
1936 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
1937 "hf:zai-org/GLM-4.7",
1938 "hf:zai-org/GLM-4.7-Flash",
1939 "hf:zai-org/GLM-5.1",
1940 "hf:zai-org/GLM-5.2",
1941 "hf:openai/gpt-oss-120b",
1942 "hf:Qwen/Qwen3.5-397B-A17B",
1943 ];
1944 for id in always_on {
1945 let entry = lookup_model_for_provider(PROVIDER_SYNTHETIC, id)
1946 .unwrap_or_else(|| panic!("{id} should be pinned in the synthetic catalog"));
1947 assert_eq!(entry.provider, PROVIDER_SYNTHETIC);
1948 assert!(entry.supports_tools);
1949 assert!(entry.supports_structured);
1950 }
1951 }
1952
1953 #[test]
1954 fn claude_code_catalog_uses_long_context_windows() {
1955 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-sonnet-4-6").unwrap();
1956 let claude_code =
1957 lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-sonnet-4-6").unwrap();
1958
1959 assert_eq!(direct.context_window, 1_000_000);
1960 assert_eq!(claude_code.context_window, 1_000_000);
1961 assert_eq!(claude_code.auto_compact_token_limit, 900_000);
1962 assert!(!claude_code.supports_compaction);
1965 assert!(direct.supports_compaction);
1969 assert_eq!(direct.auto_compact_token_limit, 900_000);
1970 }
1971
1972 #[test]
1973 fn claude_haiku_does_not_advertise_server_side_compaction() {
1974 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
1975
1976 assert!(!haiku.supports_compaction);
1981 assert_eq!(haiku.auto_compact_token_limit, 180_000);
1982 }
1983
1984 #[test]
1985 fn google_embedding_model_is_hidden_from_chat_lists() {
1986 assert!(lookup_model("gemini-embedding-2").is_some());
1987 assert!(
1988 models_for_provider(PROVIDER_GOOGLE, false)
1989 .iter()
1990 .all(|model| model.id != "gemini-embedding-2")
1991 );
1992 let model = lookup_model("gemini-embedding-2").unwrap();
1993 assert!(model.hidden);
1994 assert!(!model.supports_tools);
1995 }
1996
1997 #[test]
1998 fn zeroentropy_embedding_model_is_hidden_from_chat_lists() {
1999 assert!(lookup_model("zembed-1").is_some());
2000 assert!(
2001 models_for_provider(PROVIDER_ZEROENTROPY, false)
2002 .iter()
2003 .all(|model| model.id != "zembed-1")
2004 );
2005 let model = lookup_model("zembed-1").unwrap();
2006 assert!(model.hidden);
2007 assert!(!model.supports_tools);
2008 }
2009
2010 #[test]
2011 fn catalog_model_profile_derives_openai_defaults() {
2012 let profile = built_in_model_profile("gpt-5.5").unwrap();
2013
2014 assert_eq!(profile.provider_family, ProviderFamily::OpenAi);
2015 assert_eq!(profile.edit_tool.as_deref(), Some(EDIT_TOOL_PATCH));
2016 assert_eq!(profile.schema_policy, ModelSchemaPolicy::RequiredFirstFlat);
2017 assert_eq!(
2018 profile.instruction_overlay,
2019 ModelInstructionOverlay::LiteralToolOutputs
2020 );
2021 assert_eq!(profile.reasoning.execution.as_deref(), Some(REASONING_LOW));
2022 assert_eq!(profile.parallel_tool_calls, Some(true));
2023 }
2024
2025 #[test]
2026 fn poolside_catalog_defaults_to_thinking_enabled() {
2027 let laguna = lookup_model("poolside/laguna-m.1").unwrap();
2028 assert_eq!(laguna.default_reasoning, REASONING_MEDIUM);
2029 assert_eq!(
2030 laguna
2031 .supported_reasoning
2032 .iter()
2033 .map(|option| option.effort)
2034 .collect::<Vec<_>>(),
2035 vec![REASONING_NONE, REASONING_MEDIUM]
2036 );
2037 }
2038
2039 #[test]
2040 fn xiaomi_mimo_catalog_uses_chat_completions_kind_and_exact_model_ids() {
2041 let provider = BUILT_IN_PROVIDERS
2042 .iter()
2043 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO)
2044 .unwrap();
2045 let token_plan = BUILT_IN_PROVIDERS
2046 .iter()
2047 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO_TOKEN_PLAN)
2048 .unwrap();
2049
2050 assert_eq!(provider.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2051 assert_eq!(token_plan.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2052 assert_eq!(provider.env_key, Some("MIMO_API_KEY"));
2053 assert_eq!(token_plan.env_key, Some("MIMO_TOKEN_PLAN_API_KEY"));
2054
2055 let ids = models_for_provider(PROVIDER_XIAOMI_MIMO, false)
2056 .into_iter()
2057 .map(|model| model.id)
2058 .collect::<Vec<_>>();
2059 assert_eq!(
2060 ids,
2061 vec![
2062 "mimo-v2.5-pro",
2063 "mimo-v2-pro",
2064 "mimo-v2.5",
2065 "mimo-v2-omni",
2066 "mimo-v2-flash"
2067 ]
2068 );
2069 assert!(lookup_model("out-of-v2-flash").is_none());
2070 }
2071
2072 #[test]
2073 fn supergrok_catalog_exposes_build_and_composer_with_expected_context_windows() {
2074 let build = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-build-0.1").unwrap();
2075 assert_eq!(build.display_name, "Grok Build 0.1");
2076 assert_eq!(build.context_window, 500_000);
2077 assert_eq!(build.auto_compact_token_limit, 450_000);
2078
2079 let composer =
2080 lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-composer-2.5-fast").unwrap();
2081 assert_eq!(composer.display_name, "Grok Composer 2.5 Fast");
2082 assert_eq!(composer.context_window, 200_000);
2083 assert_eq!(composer.auto_compact_token_limit, 180_000);
2084 assert!(composer.supported_reasoning.is_empty());
2085 assert!(!composer.supports_images);
2086
2087 let visible = models_for_provider(PROVIDER_SUPERGROK, false)
2088 .into_iter()
2089 .map(|model| model.id)
2090 .collect::<Vec<_>>();
2091 assert_eq!(
2092 visible,
2093 vec![
2094 "grok-build-0.1".to_string(),
2095 "grok-composer-2.5-fast".to_string()
2096 ]
2097 );
2098 }
2099
2100 #[test]
2101 fn xai_catalog_entries_match_current_grok_contract() {
2102 let grok43 = models_for_provider(PROVIDER_XAI, false)
2103 .into_iter()
2104 .find(|model| model.id == "grok-4.3")
2105 .unwrap();
2106 assert_eq!(grok43.context_window, Some(1_000_000));
2107 assert_eq!(grok43.default_reasoning.as_deref(), Some(REASONING_LOW));
2108 assert_eq!(
2109 grok43
2110 .supported_reasoning
2111 .iter()
2112 .map(|option| option.effort.as_str())
2113 .collect::<Vec<_>>(),
2114 vec![
2115 REASONING_NONE,
2116 REASONING_LOW,
2117 REASONING_MEDIUM,
2118 REASONING_HIGH
2119 ]
2120 );
2121
2122 let grok420 = lookup_model("grok-4.20-multi-agent-0309").unwrap();
2123 assert_eq!(grok420.context_window, 2_000_000);
2124 assert_eq!(grok420.auto_compact_token_limit, 1_800_000);
2125 assert_eq!(grok420.provider, PROVIDER_XAI);
2126 }
2127
2128 #[test]
2129 fn provider_aliases_normalize_xai_and_supergrok() {
2130 assert_eq!(normalize_provider_id("grok"), PROVIDER_XAI);
2131 assert_eq!(normalize_provider_id("x.ai"), PROVIDER_XAI);
2132 assert_eq!(normalize_provider_id("x-ai"), PROVIDER_XAI);
2133 assert_eq!(normalize_provider_id("xai-oauth"), PROVIDER_SUPERGROK);
2134 assert_eq!(normalize_provider_id("grok-oauth"), PROVIDER_SUPERGROK);
2135 assert_eq!(normalize_provider_id("supergrok"), PROVIDER_SUPERGROK);
2136 assert_eq!(normalize_provider_id("laguna"), PROVIDER_POOLSIDE);
2137 assert_eq!(normalize_provider_id("composer"), PROVIDER_CURSOR);
2138 }
2139
2140 #[test]
2141 fn fireworks_catalog_preserves_account_scoped_default_model() {
2142 let provider = BUILT_IN_PROVIDERS
2143 .iter()
2144 .find(|provider| provider.id == PROVIDER_FIREWORKS)
2145 .unwrap();
2146
2147 assert_eq!(
2148 provider.default_model,
2149 "accounts/fireworks/models/qwen3-235b-a22b"
2150 );
2151 assert_eq!(provider.env_key, Some("FIREWORKS_API_KEY"));
2152 assert_eq!(provider.env_aliases, &["RODER_FIREWORKS_API_KEY"]);
2153
2154 let model = lookup_model_for_provider(PROVIDER_FIREWORKS, provider.default_model).unwrap();
2155 assert_eq!(model.provider, PROVIDER_FIREWORKS);
2156 assert!(model.supports_tools);
2157 assert!(model.supports_structured);
2158 assert_eq!(
2159 provider_family_for_provider(PROVIDER_FIREWORKS),
2160 ProviderFamily::OpenAi
2161 );
2162 }
2163
2164 #[test]
2165 fn cursor_catalog_profile_is_text_only_agentservice() {
2166 let composer = lookup_model("composer-2.5").unwrap();
2167 assert_eq!(composer.provider, PROVIDER_CURSOR);
2168 assert!(!composer.supports_tools);
2169 assert!(!composer.supports_structured);
2170
2171 let profile = built_in_model_profile("composer-2.5").unwrap();
2172 assert_eq!(profile.provider_family, ProviderFamily::Cursor);
2173 assert_eq!(profile.parallel_tool_calls, Some(false));
2174 }
2175
2176 #[test]
2177 fn provider_aware_lookup_resolves_cursor_proxied_models_to_cursor_family() {
2178 let id_only = built_in_model_profile("claude-opus-4-8").unwrap();
2180 assert_eq!(id_only.provider_family, ProviderFamily::Anthropic);
2181
2182 let cursor =
2184 built_in_model_profile_for_provider(PROVIDER_CURSOR, "claude-opus-4-8").unwrap();
2185 assert_eq!(cursor.provider_family, ProviderFamily::Cursor);
2186 assert_eq!(cursor.provider, PROVIDER_CURSOR);
2187 assert_eq!(cursor.parallel_tool_calls, Some(false));
2188
2189 let anthropic =
2190 built_in_model_profile_for_provider(PROVIDER_ANTHROPIC, "claude-opus-4-8").unwrap();
2191 assert_eq!(anthropic.provider_family, ProviderFamily::Anthropic);
2192
2193 let fallback =
2195 built_in_model_profile_for_provider("does-not-exist", "claude-opus-4-8").unwrap();
2196 assert_eq!(fallback.provider_family, ProviderFamily::Anthropic);
2197 }
2198
2199 #[test]
2200 fn cursor_opus_advertises_configurable_reasoning_effort() {
2201 let opus = models_for_provider(PROVIDER_CURSOR, false)
2202 .into_iter()
2203 .find(|model| model.id == "claude-opus-4-8")
2204 .expect("cursor catalog should expose claude-opus-4-8");
2205
2206 assert_eq!(opus.default_reasoning.as_deref(), Some(REASONING_HIGH));
2207 assert_eq!(
2208 opus.supported_reasoning
2209 .iter()
2210 .map(|option| option.effort.as_str())
2211 .collect::<Vec<_>>(),
2212 vec![
2213 REASONING_LOW,
2214 REASONING_MEDIUM,
2215 REASONING_HIGH,
2216 REASONING_XHIGH,
2217 REASONING_MAX
2218 ]
2219 );
2220
2221 let sonnet = models_for_provider(PROVIDER_CURSOR, false)
2223 .into_iter()
2224 .find(|model| model.id == "claude-sonnet-4-6")
2225 .expect("cursor catalog should expose claude-sonnet-4-6");
2226 assert_eq!(sonnet.default_reasoning, None);
2227 assert!(sonnet.supported_reasoning.is_empty());
2228 }
2229
2230 #[test]
2231 fn claude_opus_and_sonnet_advertise_max_effort() {
2232 let efforts = |id: &str| {
2233 lookup_model(id)
2234 .unwrap()
2235 .supported_reasoning
2236 .iter()
2237 .map(|option| option.effort)
2238 .collect::<Vec<_>>()
2239 };
2240
2241 for id in ["claude-opus-4-8", "claude-opus-4-7"] {
2243 assert_eq!(
2244 efforts(id),
2245 vec![
2246 REASONING_LOW,
2247 REASONING_MEDIUM,
2248 REASONING_HIGH,
2249 REASONING_XHIGH,
2250 REASONING_MAX
2251 ],
2252 "{id} effort levels"
2253 );
2254 }
2255
2256 assert_eq!(
2258 efforts("claude-sonnet-4-6"),
2259 vec![
2260 REASONING_LOW,
2261 REASONING_MEDIUM,
2262 REASONING_HIGH,
2263 REASONING_MAX
2264 ]
2265 );
2266
2267 assert!(!efforts("gpt-5.5").contains(&REASONING_MAX));
2269 }
2270
2271 #[test]
2272 fn claude_haiku_does_not_advertise_reasoning_effort() {
2273 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
2274
2275 assert_eq!(haiku.default_reasoning, REASONING_NONE);
2276 assert!(haiku.supported_reasoning.is_empty());
2277
2278 let descriptor = ModelDescriptor::from(haiku);
2279 assert_eq!(descriptor.default_reasoning, None);
2280 assert!(descriptor.supported_reasoning.is_empty());
2281 }
2282
2283 #[test]
2284 fn openai_context_windows_match_current_catalog_values() {
2285 let gpt55 = lookup_model("gpt-5.5").unwrap();
2286 assert_eq!(gpt55.context_window, 1_050_000);
2287 assert_eq!(gpt55.max_context_window, 1_050_000);
2288 assert_eq!(gpt55.auto_compact_token_limit, 945_000);
2289
2290 let mini = lookup_model("gpt-5.4-mini").unwrap();
2291 assert_eq!(mini.context_window, 400_000);
2292 assert_eq!(mini.max_context_window, 400_000);
2293 assert_eq!(mini.auto_compact_token_limit, 360_000);
2294
2295 let spark = lookup_model("gpt-5.3-codex-spark").unwrap();
2296 assert_eq!(spark.provider, PROVIDER_CODEX);
2297 assert_eq!(spark.context_window, 128_000);
2298 assert_eq!(spark.max_context_window, 128_000);
2299 assert_eq!(spark.auto_compact_token_limit, 115_200);
2300 }
2301
2302 #[test]
2303 fn auto_compact_defaults_to_ninety_percent_of_context_window() {
2304 for model in BUILT_IN_MODELS {
2305 if model.context_window == 0 || model.auto_compact_token_limit == 0 {
2306 continue;
2307 }
2308 assert_eq!(
2309 model.auto_compact_token_limit,
2310 model.context_window.saturating_mul(9) / 10,
2311 "{} should compact at 90% of its context window",
2312 model.id
2313 );
2314 }
2315 }
2316}