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