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_6_SOL,
534 openai_codex::GPT_6_LUNA,
535 openai_codex::GPT_56_SOL,
536 openai_codex::GPT_56_TERRA,
537 openai_codex::GPT_56_LUNA,
538 openai_model(
539 "gpt-5.5",
540 "GPT-5.5",
541 "Frontier model for complex coding, research, and real-world work.",
542 1_050_000,
543 945_000,
544 true,
545 STANDARD_REASONING,
546 ),
547 openai_codex::GPT_54,
548 openai_model(
549 "gpt-5.4-mini",
550 "GPT-5.4-Mini",
551 "Small, fast, and cost-efficient model for simpler coding tasks.",
552 400_000,
553 360_000,
554 true,
555 STANDARD_REASONING,
556 ),
557 ModelCatalogEntry {
558 id: "gpt-5.3-codex-spark",
559 display_name: "GPT-5.3-Codex-Spark",
560 description: "Ultra-fast coding model optimized for low-latency Codex workflows.",
561 provider: PROVIDER_CODEX,
562 default_reasoning: REASONING_HIGH,
563 supported_reasoning: STANDARD_REASONING,
564 context_window: 128_000,
565 max_context_window: 128_000,
566 auto_compact_token_limit: 115_200,
567 supports_compaction: true,
568 supports_images: false,
569 supports_tools: true,
570 supports_structured: false,
571 edit_tool: Some("patch"),
572 hidden: false,
573 },
574 ModelCatalogEntry {
575 id: "codex-auto-review",
576 display_name: "Codex Auto Review",
577 description: "Automatic approval review model for Codex.",
578 provider: PROVIDER_OPENAI,
579 default_reasoning: REASONING_MEDIUM,
580 supported_reasoning: STANDARD_REASONING,
581 context_window: 272_000,
582 max_context_window: 272_000,
583 auto_compact_token_limit: 244_800,
584 supports_compaction: false,
585 supports_images: false,
586 supports_tools: true,
587 supports_structured: false,
588 edit_tool: Some("patch"),
589 hidden: true,
590 },
591 anthropic_model(
592 "claude-fable-5-1",
593 "Claude Fable 5.1",
594 "Anthropic's most capable widely released model; successor to Fable 5 for frontier reasoning and long-horizon agentic work.",
595 1_000_000,
596 900_000,
597 REASONING_HIGH,
598 OPUS_REASONING,
599 true,
600 ),
601 anthropic_model(
602 "claude-fable-5",
603 "Claude Fable 5",
604 "Anthropic's most powerful, most intelligent model; a new tier above Opus for frontier reasoning and agentic work.",
605 1_000_000,
606 900_000,
607 REASONING_HIGH,
608 OPUS_REASONING,
609 true,
610 ),
611 anthropic_model(
612 "claude-opus-4-8",
613 "Claude Opus 4.8",
614 "Anthropic's most capable Opus-tier model for complex reasoning, long-horizon agentic coding, and high-autonomy work.",
615 1_000_000,
616 900_000,
617 REASONING_HIGH,
618 OPUS_REASONING,
619 true,
620 ),
621 anthropic_model(
622 "claude-opus-4-7",
623 "Claude Opus 4.7",
624 "Most capable Claude model for complex reasoning and agentic coding.",
625 1_000_000,
626 900_000,
627 REASONING_HIGH,
628 OPUS_REASONING,
629 true,
630 ),
631 anthropic_model(
632 "claude-sonnet-4-6",
633 "Claude Sonnet 4.6",
634 "Balanced Claude model for coding, tool use, and everyday agent workflows.",
635 1_000_000,
636 900_000,
637 REASONING_MEDIUM,
638 SONNET_REASONING,
639 true,
640 ),
641 anthropic_model(
642 "claude-haiku-4-5-20251001",
643 "Claude Haiku 4.5",
644 "Fast Claude model for lower-latency tool workflows.",
645 200_000,
646 180_000,
647 REASONING_NONE,
648 &[],
649 false,
651 ),
652 claude_code_model(
653 "fable",
654 "Claude Code Fable",
655 "Claude Code harness Fable alias for the most powerful frontier model.",
656 1_000_000,
657 900_000,
658 REASONING_HIGH,
659 OPUS_REASONING,
660 ),
661 claude_code_model(
662 "sonnet",
663 "Claude Code Sonnet",
664 "Claude Code harness Sonnet alias for coding and tool workflows.",
665 1_000_000,
666 900_000,
667 REASONING_MEDIUM,
668 SONNET_REASONING,
669 ),
670 claude_code_model(
671 "opus",
672 "Claude Code Opus",
673 "Claude Code harness Opus alias for complex long-horizon agentic work.",
674 1_000_000,
675 900_000,
676 REASONING_HIGH,
677 OPUS_REASONING,
678 ),
679 claude_code_model(
680 "haiku",
681 "Claude Code Haiku",
682 "Claude Code harness Haiku alias for fast lower-latency coding turns.",
683 200_000,
684 180_000,
685 REASONING_NONE,
686 &[],
687 ),
688 claude_code_model(
689 "claude-sonnet-4-6",
690 "Claude Code Sonnet 4.6",
691 "Claude Sonnet 4.6 through the local Claude Code harness.",
692 1_000_000,
693 900_000,
694 REASONING_MEDIUM,
695 SONNET_REASONING,
696 ),
697 claude_code_model(
698 "claude-opus-4-8",
699 "Claude Code Opus 4.8",
700 "Claude Opus 4.8 through the local Claude Code harness.",
701 1_000_000,
702 900_000,
703 REASONING_HIGH,
704 OPUS_REASONING,
705 ),
706 claude_code_model(
707 "claude-fable-5",
708 "Claude Code Fable 5",
709 "Claude Fable 5 through the local Claude Code harness.",
710 1_000_000,
711 900_000,
712 REASONING_HIGH,
713 OPUS_REASONING,
714 ),
715 claude_code_model(
716 "claude-fable-5-1",
717 "Claude Code Fable 5.1",
718 "Claude Fable 5.1 through the local Claude Code harness.",
719 1_000_000,
720 900_000,
721 REASONING_HIGH,
722 OPUS_REASONING,
723 ),
724 gemini_model(
725 PROVIDER_GEMINI,
726 "gemini-3.8-flash",
727 "Gemini 3.8 Flash",
728 "Google's most intelligent Flash model for long-horizon software engineering, autonomous agents, and complex workflows.",
729 REASONING_MEDIUM,
730 ),
731 gemini_model(
732 PROVIDER_GEMINI,
733 "gemini-3.5-flash",
734 "Gemini 3.5 Flash",
735 "Stable Gemini Flash model for agentic coding, tool use, and long-horizon workflows.",
736 REASONING_MEDIUM,
737 ),
738 gemini_model(
739 PROVIDER_GEMINI,
740 "gemini-3.7-flash",
741 "Gemini 3.7 Flash",
742 "Google's latest speed-tier Gemini model for high-throughput agentic coding, tool use, and long-context workflows.",
743 REASONING_HIGH,
744 ),
745 gemini_model(
746 PROVIDER_GEMINI,
747 "gemini-3.1-pro-preview",
748 "Gemini 3.1 Pro Preview",
749 "Gemini model for complex coding, long context, and tool-heavy agent workflows.",
750 REASONING_HIGH,
751 ),
752 gemini_model(
753 PROVIDER_GEMINI,
754 "gemini-3.1-pro-preview-customtools",
755 "Gemini 3.1 Pro Preview Custom Tools",
756 "Gemini preview variant exposed for custom tool validation and tool-heavy coding workflows.",
757 REASONING_HIGH,
758 ),
759 gemini_model(
760 PROVIDER_GEMINI,
761 "gemini-3-flash-preview",
762 "Gemini 3 Flash Preview",
763 "Fast Gemini model for everyday coding, tool use, and multimodal prompts.",
764 REASONING_MEDIUM,
765 ),
766 gemini_model(
767 PROVIDER_GEMINI,
768 "gemini-3.1-flash-lite-preview",
769 "Gemini 3.1 Flash-Lite Preview",
770 "Lightweight Gemini model for low-latency coding and agent interactions.",
771 REASONING_LOW,
772 ),
773 gemini_model(
774 PROVIDER_VERTEX,
775 "gemini-3.8-flash",
776 "Gemini 3.8 Flash",
777 "Google's most intelligent Flash model on Vertex AI for long-horizon software engineering and autonomous agents.",
778 REASONING_MEDIUM,
779 ),
780 gemini_model(
781 PROVIDER_VERTEX,
782 "gemini-3.5-flash",
783 "Gemini 3.5 Flash",
784 "Stable Gemini Flash model on Vertex AI for agentic coding, tool use, and long-horizon workflows.",
785 REASONING_MEDIUM,
786 ),
787 gemini_model(
788 PROVIDER_VERTEX,
789 "gemini-3.7-flash",
790 "Gemini 3.7 Flash",
791 "Google's latest speed-tier Gemini model on Vertex AI for high-throughput agentic coding, tool use, and long-context workflows.",
792 REASONING_HIGH,
793 ),
794 gemini_model(
795 PROVIDER_VERTEX,
796 "gemini-3.1-pro-preview",
797 "Gemini 3.1 Pro Preview",
798 "Gemini model on Vertex AI for complex coding, long context, and tool-heavy agent workflows.",
799 REASONING_HIGH,
800 ),
801 gemini_model(
802 PROVIDER_VERTEX,
803 "gemini-3-flash-preview",
804 "Gemini 3 Flash Preview",
805 "Fast Gemini model on Vertex AI for everyday coding, tool use, and multimodal prompts.",
806 REASONING_MEDIUM,
807 ),
808 gemini_model(
809 PROVIDER_VERTEX,
810 "gemini-3.1-flash-lite-preview",
811 "Gemini 3.1 Flash-Lite Preview",
812 "Lightweight Gemini model on Vertex AI for low-latency coding and agent interactions.",
813 REASONING_LOW,
814 ),
815 xai_model(
816 PROVIDER_XAI,
817 "grok-4.6",
818 "Grok 4.6",
819 "xAI's flagship model for coding, long-running agents, knowledge work, and configurable reasoning.",
820 500_000,
821 REASONING_HIGH,
822 XAI_REASONING,
823 true,
824 false,
825 ),
826 xai_model(
827 PROVIDER_XAI,
828 "grok-4.3",
829 "Grok 4.3",
830 "xAI flagship model for chat, coding, tool use, and configurable reasoning.",
831 1_000_000,
832 REASONING_LOW,
833 XAI_CONFIGURABLE_REASONING,
834 true,
835 false,
836 ),
837 xai_model(
838 PROVIDER_XAI,
839 "grok-4.20-multi-agent-0309",
840 "Grok 4.20 Multi-Agent",
841 "xAI long-context model with agentic tool-calling and reasoning.",
842 2_000_000,
843 REASONING_LOW,
844 XAI_REASONING,
845 true,
846 false,
847 ),
848 xai_model(
849 PROVIDER_XAI,
850 "grok-4.20-0309-reasoning",
851 "Grok 4.20 Reasoning",
852 "xAI long-context reasoning model for complex tool-heavy workflows.",
853 2_000_000,
854 REASONING_LOW,
855 XAI_REASONING,
856 true,
857 false,
858 ),
859 xai_model(
860 PROVIDER_XAI,
861 "grok-4.20-0309-non-reasoning",
862 "Grok 4.20 Non-Reasoning",
863 "xAI long-context model for lower-latency non-reasoning workflows.",
864 2_000_000,
865 REASONING_NONE,
866 XAI_NO_REASONING,
867 true,
868 false,
869 ),
870 xai_model(
871 PROVIDER_SUPERGROK,
872 "grok-4.6",
873 "Grok 4.6",
874 "SuperGrok OAuth access to xAI's flagship coding and long-running agent model.",
875 500_000,
876 REASONING_HIGH,
877 XAI_REASONING,
878 true,
879 false,
880 ),
881 xai_model(
882 PROVIDER_SUPERGROK,
883 "grok-composer-2.5-fast",
884 "Grok Composer 2.5 Fast",
885 "SuperGrok OAuth access to xAI Composer 2.5 Fast for lower-latency agentic coding.",
886 200_000,
887 REASONING_NONE,
888 &[],
889 false,
890 false,
891 ),
892 xai_model(
893 PROVIDER_SUPERGROK,
894 "grok-4.3",
895 "Grok 4.3",
896 "SuperGrok OAuth access to xAI Grok 4.3.",
897 1_000_000,
898 REASONING_LOW,
899 XAI_CONFIGURABLE_REASONING,
900 true,
901 true,
902 ),
903 xai_model(
904 PROVIDER_SUPERGROK,
905 "grok-4.20-multi-agent-0309",
906 "Grok 4.20 Multi-Agent",
907 "SuperGrok OAuth access to xAI's long-context multi-agent model.",
908 2_000_000,
909 REASONING_LOW,
910 XAI_REASONING,
911 true,
912 true,
913 ),
914 xai_model(
915 PROVIDER_SUPERGROK,
916 "grok-4.20-0309-reasoning",
917 "Grok 4.20 Reasoning",
918 "SuperGrok OAuth access to xAI's long-context reasoning model.",
919 2_000_000,
920 REASONING_LOW,
921 XAI_REASONING,
922 true,
923 true,
924 ),
925 xai_model(
926 PROVIDER_SUPERGROK,
927 "grok-4.20-0309-non-reasoning",
928 "Grok 4.20 Non-Reasoning",
929 "SuperGrok OAuth access to xAI's long-context non-reasoning model.",
930 2_000_000,
931 REASONING_NONE,
932 XAI_NO_REASONING,
933 true,
934 true,
935 ),
936 opencode_model(
937 PROVIDER_OPENCODE,
938 "gpt-5.5",
939 "GPT 5.5",
940 "OpenCode Zen GPT 5.5 gateway model.",
941 1_050_000,
942 REASONING_MEDIUM,
943 STANDARD_REASONING,
944 ),
945 opencode_model(
946 PROVIDER_OPENCODE,
947 "gpt-5.3-codex-spark",
948 "GPT 5.3 Codex Spark",
949 "OpenCode Zen low-latency Codex model.",
950 128_000,
951 REASONING_HIGH,
952 STANDARD_REASONING,
953 ),
954 opencode_model(
955 PROVIDER_OPENCODE,
956 "big-pickle",
957 "Big Pickle",
958 "OpenCode Zen free coding model.",
959 256_000,
960 REASONING_NONE,
961 &[],
962 ),
963 opencode_model(
964 PROVIDER_OPENCODE,
965 "mimo-v2.5-free",
966 "MiMo V2.5 Free",
967 "OpenCode Zen free Xiaomi MiMo coding model.",
968 256_000,
969 REASONING_NONE,
970 &[],
971 ),
972 opencode_model(
973 PROVIDER_OPENCODE,
974 "nemotron-3-ultra-free",
975 "Nemotron 3 Ultra Free",
976 "OpenCode Zen free Nemotron coding model.",
977 128_000,
978 REASONING_NONE,
979 &[],
980 ),
981 opencode_model(
982 PROVIDER_OPENCODE,
983 "north-mini-code-free",
984 "North Mini Code Free",
985 "OpenCode Zen free North Mini coding model.",
986 128_000,
987 REASONING_NONE,
988 &[],
989 ),
990 opencode_model(
991 PROVIDER_OPENCODE,
992 "deepseek-v4-flash",
993 "DeepSeek V4 Flash",
994 "OpenCode Zen DeepSeek coding model.",
995 128_000,
996 REASONING_HIGH,
997 deepseek::DEEPSEEK_REASONING,
998 ),
999 opencode_model(
1000 PROVIDER_OPENCODE,
1001 "deepseek-v4-pro",
1002 "DeepSeek V4 Pro",
1003 "OpenCode Zen DeepSeek Pro coding model.",
1004 128_000,
1005 REASONING_HIGH,
1006 deepseek::DEEPSEEK_REASONING,
1007 ),
1008 opencode_model(
1009 PROVIDER_OPENCODE_GO,
1010 "kimi-k2.6",
1011 "Kimi K2.6",
1012 "OpenCode Go Kimi coding model.",
1013 256_000,
1014 REASONING_NONE,
1015 &[],
1016 ),
1017 opencode_model(
1018 PROVIDER_OPENCODE_GO,
1019 "qwen3.6-plus",
1020 "Qwen3.6 Plus",
1021 "OpenCode Go Qwen coding model.",
1022 256_000,
1023 REASONING_NONE,
1024 &[],
1025 ),
1026 opencode_model(
1027 PROVIDER_OPENCODE_GO,
1028 "glm-5.1",
1029 "GLM-5.1",
1030 "OpenCode Go GLM coding model.",
1031 256_000,
1032 REASONING_NONE,
1033 &[],
1034 ),
1035 opencode_model(
1036 PROVIDER_OPENCODE_GO,
1037 "deepseek-v4-flash",
1038 "DeepSeek V4 Flash",
1039 "OpenCode Go DeepSeek coding model.",
1040 128_000,
1041 REASONING_HIGH,
1042 deepseek::DEEPSEEK_REASONING,
1043 ),
1044 opencode_model(
1045 PROVIDER_OPENCODE_GO,
1046 "deepseek-v4-pro",
1047 "DeepSeek V4 Pro",
1048 "OpenCode Go DeepSeek Pro coding model.",
1049 128_000,
1050 REASONING_HIGH,
1051 deepseek::DEEPSEEK_REASONING,
1052 ),
1053 opencode_model(
1054 PROVIDER_KIMI_CODE,
1055 "kimi-for-coding",
1056 "K2.7 Code",
1057 "Kimi Code subscription coding model (OAuth via api.kimi.com/coding/v1).",
1058 262_144,
1059 REASONING_NONE,
1060 &[],
1061 ),
1062 ModelCatalogEntry {
1063 id: "x-ai/grok-4.6",
1064 display_name: "Grok 4.6",
1065 description: "OpenRouter route for xAI's flagship model for coding and long-running agent workflows.",
1066 provider: PROVIDER_OPENROUTER,
1067 default_reasoning: REASONING_HIGH,
1068 supported_reasoning: OPENROUTER_REASONING,
1069 context_window: 500_000,
1070 max_context_window: 500_000,
1071 auto_compact_token_limit: 450_000,
1072 supports_compaction: true,
1073 supports_images: true,
1074 supports_tools: true,
1075 supports_structured: true,
1076 edit_tool: Some(EDIT_TOOL_PATCH),
1077 hidden: false,
1078 },
1079 ModelCatalogEntry {
1080 id: "accounts/fireworks/models/qwen3-235b-a22b",
1081 display_name: "Qwen3 235B A22B",
1082 description: "Fireworks Responses-capable serverless model with client-executed function tool support.",
1083 provider: PROVIDER_FIREWORKS,
1084 default_reasoning: REASONING_NONE,
1085 supported_reasoning: &[],
1086 context_window: 131_072,
1087 max_context_window: 131_072,
1088 auto_compact_token_limit: 0,
1089 supports_compaction: false,
1090 supports_images: false,
1091 supports_tools: true,
1092 supports_structured: true,
1093 edit_tool: Some(EDIT_TOOL_PATCH),
1094 hidden: false,
1095 },
1096 roder_cloud_model(
1097 "roder.cloud/free",
1098 "Roder Free",
1099 "Free hosted model on roder.cloud.",
1100 32_768,
1101 ),
1102 roder_cloud_model(
1103 "roder.cloud/openai/gpt-5.5",
1104 "GPT-5.5 (Roder Cloud)",
1105 "roder.cloud hosted route for OpenAI GPT-5.5.",
1106 400_000,
1107 ),
1108 roder_cloud_model(
1109 "roder.cloud/anthropic/claude-opus-4-7",
1110 "Claude Opus 4.7 (Roder Cloud)",
1111 "roder.cloud hosted route for Anthropic Claude Opus 4.7.",
1112 200_000,
1113 ),
1114 roder_cloud_model(
1115 "roder.cloud/google/gemini-3.1-pro-preview",
1116 "Gemini 3.1 Pro (Roder Cloud)",
1117 "roder.cloud hosted route for Google Gemini 3.1 Pro Preview.",
1118 200_000,
1119 ),
1120 poolside_model(
1121 "poolside/laguna-m.1",
1122 "Laguna M.1",
1123 "Poolside flagship agentic coding model.",
1124 REASONING_MEDIUM,
1125 ),
1126 poolside_model(
1127 "poolside/laguna-xs.2",
1128 "Laguna XS.2",
1129 "Poolside lightweight agentic coding model.",
1130 REASONING_MEDIUM,
1131 ),
1132 xiaomi_mimo::PAYG_V25_PRO,
1133 xiaomi_mimo::PAYG_V2_PRO,
1134 xiaomi_mimo::PAYG_V25,
1135 xiaomi_mimo::PAYG_V2_OMNI,
1136 xiaomi_mimo::PAYG_V2_FLASH,
1137 xiaomi_mimo::TOKEN_PLAN_V25_PRO,
1138 xiaomi_mimo::TOKEN_PLAN_V2_PRO,
1139 xiaomi_mimo::TOKEN_PLAN_V25,
1140 xiaomi_mimo::TOKEN_PLAN_V2_OMNI,
1141 xiaomi_mimo::TOKEN_PLAN_V2_FLASH,
1142 synthetic::SYN_LARGE_TEXT,
1143 synthetic::SYN_SMALL_TEXT,
1144 synthetic::SYN_LARGE_VISION,
1145 synthetic::SYN_SMALL_VISION,
1146 synthetic::HF_MINIMAX_M3,
1147 synthetic::HF_QWEN3_6_27B,
1148 synthetic::HF_KIMI_K2_6,
1149 synthetic::HF_NEMOTRON_3_SUPER,
1150 synthetic::HF_GLM_4_7,
1151 synthetic::HF_GLM_4_7_FLASH,
1152 synthetic::HF_GLM_5_1,
1153 synthetic::HF_GLM_5_2,
1154 synthetic::HF_GPT_OSS_120B,
1155 synthetic::HF_QWEN3_5_397B_A17B,
1156 deepseek::DEEPSEEK_CHAT,
1157 deepseek::DEEPSEEK_REASONER,
1158 deepseek::DEEPSEEK_V4_FLASH,
1159 deepseek::DEEPSEEK_V4_PRO,
1160 ModelCatalogEntry {
1161 id: "composer-2.5",
1162 display_name: "Composer 2.5",
1163 description: "Cursor Composer model exposed through direct AgentService inference.",
1164 provider: PROVIDER_CURSOR,
1165 default_reasoning: REASONING_NONE,
1166 supported_reasoning: &[],
1167 context_window: 200_000,
1168 max_context_window: 200_000,
1169 auto_compact_token_limit: 180_000,
1170 supports_compaction: true,
1171 supports_images: false,
1172 supports_tools: false,
1173 supports_structured: false,
1174 edit_tool: None,
1175 hidden: false,
1176 },
1177 cursor_model(
1178 "composer-2.5-fast",
1179 "Composer 2.5 Fast",
1180 "Cursor Composer 2.5 fast variant for lower-latency agent turns.",
1181 200_000,
1182 180_000,
1183 REASONING_NONE,
1184 &[],
1185 ),
1186 cursor_model(
1187 "claude-fable-5",
1188 "Claude Fable 5",
1189 "Anthropic Claude Fable 5, Anthropic's most powerful frontier model, routed through Cursor's AgentService.",
1190 1_000_000,
1191 900_000,
1192 REASONING_HIGH,
1193 OPUS_REASONING,
1194 ),
1195 cursor_model(
1196 "claude-opus-4-8",
1197 "Claude Opus 4.8",
1198 "Anthropic Claude Opus 4.8 routed through Cursor's AgentService.",
1199 1_000_000,
1200 900_000,
1201 REASONING_HIGH,
1202 OPUS_REASONING,
1203 ),
1204 cursor_model(
1205 "claude-sonnet-4-6",
1206 "Claude Sonnet 4.6",
1207 "Anthropic Claude Sonnet 4.6 routed through Cursor's AgentService.",
1208 1_000_000,
1209 900_000,
1210 REASONING_MEDIUM,
1211 SONNET_REASONING,
1212 ),
1213 cursor_model(
1214 "gpt-5.5",
1215 "GPT-5.5",
1216 "OpenAI GPT-5.5 routed through Cursor's AgentService.",
1217 1_050_000,
1218 945_000,
1219 REASONING_MEDIUM,
1220 STANDARD_REASONING,
1221 ),
1222 cursor_model(
1223 "gpt-5.5-fast",
1224 "GPT-5.5 Fast",
1225 "OpenAI GPT-5.5 fast variant routed through Cursor's AgentService.",
1226 1_050_000,
1227 945_000,
1228 REASONING_MEDIUM,
1229 STANDARD_REASONING,
1230 ),
1231 cursor_model(
1232 "gemini-3.1-pro-preview",
1233 "Gemini 3.1 Pro",
1234 "Google Gemini 3.1 Pro routed through Cursor's AgentService.",
1235 1_048_576,
1236 943_718,
1237 REASONING_MEDIUM,
1238 GEMINI_REASONING,
1239 ),
1240 cursor_model(
1241 "grok-4.6",
1242 "Grok 4.6",
1243 "xAI Grok 4.6 routed through Cursor's AgentService for long-running coding and knowledge-work agents.",
1244 256_000,
1245 230_400,
1246 REASONING_HIGH,
1247 STANDARD_REASONING,
1248 ),
1249 cursor_model(
1250 "gemini-3.7-flash",
1251 "Gemini 3.7 Flash",
1252 "Google Gemini 3.7 Flash routed through Cursor's AgentService for high-throughput agentic coding.",
1253 1_000_000,
1254 900_000,
1255 REASONING_HIGH,
1256 GEMINI_REASONING,
1257 ),
1258 cursor_model(
1259 "grok-4.3",
1260 "Grok 4.3",
1261 "xAI Grok 4.3 routed through Cursor's AgentService.",
1262 1_000_000,
1263 900_000,
1264 REASONING_MEDIUM,
1265 STANDARD_REASONING,
1266 ),
1267 ModelCatalogEntry {
1268 id: "text-embedding-3-large",
1269 display_name: "Text Embedding 3 Large",
1270 description: "OpenAI embedding model for local semantic memories.",
1271 provider: PROVIDER_OPENAI,
1272 default_reasoning: REASONING_NONE,
1273 supported_reasoning: &[],
1274 context_window: 0,
1275 max_context_window: 0,
1276 auto_compact_token_limit: 0,
1277 supports_compaction: false,
1278 supports_images: false,
1279 supports_tools: true,
1280 supports_structured: false,
1281 edit_tool: None,
1282 hidden: true,
1283 },
1284 ModelCatalogEntry {
1285 id: "gemini-embedding-2",
1286 display_name: "Gemini Embedding 2",
1287 description: "Google Gemini embedding model for local semantic memories.",
1288 provider: PROVIDER_GOOGLE,
1289 default_reasoning: REASONING_NONE,
1290 supported_reasoning: &[],
1291 context_window: 0,
1292 max_context_window: 0,
1293 auto_compact_token_limit: 0,
1294 supports_compaction: false,
1295 supports_images: false,
1296 supports_tools: false,
1297 supports_structured: false,
1298 edit_tool: None,
1299 hidden: true,
1300 },
1301 ModelCatalogEntry {
1302 id: "zembed-1",
1303 display_name: "ZeroEntropy zembed-1",
1304 description: "ZeroEntropy embedding model for local semantic memories.",
1305 provider: PROVIDER_ZEROENTROPY,
1306 default_reasoning: REASONING_NONE,
1307 supported_reasoning: &[],
1308 context_window: 0,
1309 max_context_window: 0,
1310 auto_compact_token_limit: 0,
1311 supports_compaction: false,
1312 supports_images: false,
1313 supports_tools: false,
1314 supports_structured: false,
1315 edit_tool: None,
1316 hidden: true,
1317 },
1318 ModelCatalogEntry {
1319 id: "mock",
1320 display_name: "Mock",
1321 description: "Local deterministic mock provider for tests and offline development.",
1322 provider: PROVIDER_MOCK,
1323 default_reasoning: REASONING_NONE,
1324 supported_reasoning: MOCK_REASONING,
1325 context_window: 128_000,
1326 max_context_window: 128_000,
1327 auto_compact_token_limit: 115_200,
1328 supports_compaction: false,
1329 supports_images: false,
1330 supports_tools: true,
1331 supports_structured: false,
1332 edit_tool: None,
1333 hidden: true,
1334 },
1335];
1336
1337const fn openai_model(
1338 id: &'static str,
1339 display_name: &'static str,
1340 description: &'static str,
1341 context_window: u32,
1342 auto_compact_token_limit: u32,
1343 supports_compaction: bool,
1344 supported_reasoning: &'static [ReasoningOption],
1345) -> ModelCatalogEntry {
1346 ModelCatalogEntry {
1347 id,
1348 display_name,
1349 description,
1350 provider: PROVIDER_OPENAI,
1351 default_reasoning: REASONING_MEDIUM,
1352 supported_reasoning,
1353 context_window,
1354 max_context_window: context_window,
1355 auto_compact_token_limit,
1356 supports_compaction,
1357 supports_images: false,
1358 supports_tools: true,
1359 supports_structured: false,
1360 edit_tool: Some("patch"),
1361 hidden: false,
1362 }
1363}
1364
1365#[allow(clippy::too_many_arguments)]
1366const fn anthropic_model(
1367 id: &'static str,
1368 display_name: &'static str,
1369 description: &'static str,
1370 context_window: u32,
1371 auto_compact_token_limit: u32,
1372 default_reasoning: &'static str,
1373 supported_reasoning: &'static [ReasoningOption],
1374 supports_compaction: bool,
1385) -> ModelCatalogEntry {
1386 ModelCatalogEntry {
1387 id,
1388 display_name,
1389 description,
1390 provider: PROVIDER_ANTHROPIC,
1391 default_reasoning,
1392 supported_reasoning,
1393 context_window,
1394 max_context_window: context_window,
1395 auto_compact_token_limit,
1396 supports_compaction,
1397 supports_images: false,
1398 supports_tools: true,
1399 supports_structured: false,
1400 edit_tool: Some("edit"),
1401 hidden: false,
1402 }
1403}
1404
1405const fn claude_code_model(
1406 id: &'static str,
1407 display_name: &'static str,
1408 description: &'static str,
1409 context_window: u32,
1410 auto_compact_token_limit: u32,
1411 default_reasoning: &'static str,
1412 supported_reasoning: &'static [ReasoningOption],
1413) -> ModelCatalogEntry {
1414 ModelCatalogEntry {
1415 id,
1416 display_name,
1417 description,
1418 provider: PROVIDER_CLAUDE_CODE,
1419 default_reasoning,
1420 supported_reasoning,
1421 context_window,
1422 max_context_window: context_window,
1423 auto_compact_token_limit,
1424 supports_compaction: false,
1430 supports_images: true,
1431 supports_tools: true,
1432 supports_structured: false,
1433 edit_tool: Some(EDIT_TOOL_EDIT),
1434 hidden: false,
1435 }
1436}
1437
1438const fn gemini_model(
1439 provider: &'static str,
1440 id: &'static str,
1441 display_name: &'static str,
1442 description: &'static str,
1443 default_reasoning: &'static str,
1444) -> ModelCatalogEntry {
1445 ModelCatalogEntry {
1446 id,
1447 display_name,
1448 description,
1449 provider,
1450 default_reasoning,
1451 supported_reasoning: GEMINI_REASONING,
1452 context_window: 1_048_576,
1453 max_context_window: 1_048_576,
1454 auto_compact_token_limit: 943_718,
1455 supports_compaction: false,
1456 supports_images: true,
1457 supports_tools: true,
1458 supports_structured: true,
1459 edit_tool: Some("edit"),
1460 hidden: false,
1461 }
1462}
1463
1464const fn xai_model(
1465 provider: &'static str,
1466 id: &'static str,
1467 display_name: &'static str,
1468 description: &'static str,
1469 context_window: u32,
1470 default_reasoning: &'static str,
1471 supported_reasoning: &'static [ReasoningOption],
1472 supports_images: bool,
1473 hidden: bool,
1474) -> ModelCatalogEntry {
1475 ModelCatalogEntry {
1476 id,
1477 display_name,
1478 description,
1479 provider,
1480 default_reasoning,
1481 supported_reasoning,
1482 context_window,
1483 max_context_window: context_window,
1484 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1485 supports_compaction: false,
1486 supports_images,
1487 supports_tools: true,
1488 supports_structured: true,
1489 edit_tool: Some("edit"),
1490 hidden,
1491 }
1492}
1493
1494const fn opencode_model(
1495 provider: &'static str,
1496 id: &'static str,
1497 display_name: &'static str,
1498 description: &'static str,
1499 context_window: u32,
1500 default_reasoning: &'static str,
1501 supported_reasoning: &'static [ReasoningOption],
1502) -> ModelCatalogEntry {
1503 ModelCatalogEntry {
1504 id,
1505 display_name,
1506 description,
1507 provider,
1508 default_reasoning,
1509 supported_reasoning,
1510 context_window,
1511 max_context_window: context_window,
1512 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1513 supports_compaction: false,
1514 supports_images: false,
1515 supports_tools: true,
1516 supports_structured: true,
1517 edit_tool: Some("edit"),
1518 hidden: false,
1519 }
1520}
1521
1522const fn roder_cloud_model(
1523 id: &'static str,
1524 display_name: &'static str,
1525 description: &'static str,
1526 context_window: u32,
1527) -> ModelCatalogEntry {
1528 ModelCatalogEntry {
1529 id,
1530 display_name,
1531 description,
1532 provider: PROVIDER_RODER_CLOUD,
1533 default_reasoning: REASONING_NONE,
1534 supported_reasoning: RODER_CLOUD_REASONING,
1535 context_window,
1536 max_context_window: context_window,
1537 auto_compact_token_limit: 0,
1538 supports_compaction: false,
1539 supports_images: false,
1540 supports_tools: false,
1541 supports_structured: false,
1542 edit_tool: None,
1543 hidden: false,
1544 }
1545}
1546
1547const fn poolside_model(
1548 id: &'static str,
1549 display_name: &'static str,
1550 description: &'static str,
1551 default_reasoning: &'static str,
1552) -> ModelCatalogEntry {
1553 ModelCatalogEntry {
1554 id,
1555 display_name,
1556 description,
1557 provider: PROVIDER_POOLSIDE,
1558 default_reasoning,
1559 supported_reasoning: POOLSIDE_REASONING,
1560 context_window: 131_072,
1561 max_context_window: 131_072,
1562 auto_compact_token_limit: 117_964,
1563 supports_compaction: false,
1564 supports_images: false,
1565 supports_tools: true,
1566 supports_structured: true,
1567 edit_tool: Some("edit"),
1568 hidden: false,
1569 }
1570}
1571
1572const fn cursor_model(
1573 id: &'static str,
1574 display_name: &'static str,
1575 description: &'static str,
1576 context_window: u32,
1577 auto_compact_token_limit: u32,
1578 default_reasoning: &'static str,
1579 supported_reasoning: &'static [ReasoningOption],
1580) -> ModelCatalogEntry {
1581 ModelCatalogEntry {
1582 id,
1583 display_name,
1584 description,
1585 provider: PROVIDER_CURSOR,
1586 default_reasoning,
1587 supported_reasoning,
1588 context_window,
1589 max_context_window: context_window,
1590 auto_compact_token_limit,
1591 supports_compaction: true,
1592 supports_images: true,
1596 supports_tools: false,
1597 supports_structured: false,
1598 edit_tool: None,
1599 hidden: false,
1600 }
1601}
1602
1603pub fn built_in_providers() -> &'static [ProviderCatalogEntry] {
1604 BUILT_IN_PROVIDERS
1605}
1606
1607pub fn built_in_models(include_hidden: bool) -> Vec<&'static ModelCatalogEntry> {
1608 BUILT_IN_MODELS
1609 .iter()
1610 .filter(|model| include_hidden || !model.hidden)
1611 .collect()
1612}
1613
1614pub fn models_for_provider(provider: &str, include_hidden: bool) -> Vec<ModelDescriptor> {
1615 built_in_models(include_hidden)
1616 .into_iter()
1617 .filter(|model| model.provider == provider)
1618 .map(ModelDescriptor::from)
1619 .collect()
1620}
1621
1622pub fn models_for_codex(include_hidden: bool) -> Vec<ModelDescriptor> {
1623 built_in_models(include_hidden)
1624 .into_iter()
1625 .filter(|model| model.provider == PROVIDER_OPENAI || model.provider == PROVIDER_CODEX)
1626 .map(ModelDescriptor::from)
1627 .collect()
1628}
1629
1630pub fn lookup_model(id: &str) -> Option<&'static ModelCatalogEntry> {
1631 BUILT_IN_MODELS.iter().find(|model| model.id == id)
1632}
1633
1634pub fn lookup_model_for_provider(provider: &str, id: &str) -> Option<&'static ModelCatalogEntry> {
1644 BUILT_IN_MODELS
1645 .iter()
1646 .find(|model| model.provider == provider && model.id == id)
1647 .or_else(|| lookup_model(id))
1648}
1649
1650pub fn built_in_model_profile(id: &str) -> Option<ModelHarnessProfile> {
1651 lookup_model(id).map(model_harness_profile_from_catalog)
1652}
1653
1654pub fn built_in_model_profile_for_provider(
1660 provider: &str,
1661 id: &str,
1662) -> Option<ModelHarnessProfile> {
1663 lookup_model_for_provider(provider, id).map(model_harness_profile_from_catalog)
1664}
1665
1666pub fn built_in_model_profiles() -> Vec<ModelHarnessProfile> {
1667 built_in_models(true)
1668 .into_iter()
1669 .map(model_harness_profile_from_catalog)
1670 .collect()
1671}
1672
1673fn model_harness_profile_from_catalog(model: &ModelCatalogEntry) -> ModelHarnessProfile {
1674 let provider_family = provider_family_for_provider(model.provider);
1675 ModelHarnessProfile {
1676 model: model.id.to_string(),
1677 provider: model.provider.to_string(),
1678 provider_family,
1679 edit_tool: model.edit_tool.map(str::to_string),
1680 schema_policy: schema_policy_for_family(provider_family),
1681 instruction_overlay: instruction_overlay_for_family(provider_family),
1682 reasoning: ModelProfileReasoning {
1683 orientation: Some(model.default_reasoning.to_string()),
1684 execution: Some(default_execution_reasoning(model)),
1685 verification: Some(model.default_reasoning.to_string()),
1686 recovery: Some(model.default_reasoning.to_string()),
1687 },
1688 parallel_tool_calls: Some(
1689 model.supports_tools
1690 && matches!(
1691 provider_family,
1692 ProviderFamily::OpenAi | ProviderFamily::Xai | ProviderFamily::Opencode
1693 ),
1694 ),
1695 auto_compact_token_limit: (model.auto_compact_token_limit > 0)
1696 .then_some(model.auto_compact_token_limit),
1697 }
1698}
1699
1700pub fn provider_family_for_provider(provider: &str) -> ProviderFamily {
1701 match provider {
1702 PROVIDER_OPENAI | PROVIDER_CODEX => ProviderFamily::OpenAi,
1703 PROVIDER_ANTHROPIC | PROVIDER_CLAUDE_CODE => ProviderFamily::Anthropic,
1704 PROVIDER_GEMINI | PROVIDER_VERTEX => ProviderFamily::Gemini,
1705 PROVIDER_XAI | PROVIDER_SUPERGROK => ProviderFamily::Xai,
1706 PROVIDER_OPENCODE | PROVIDER_OPENCODE_GO => ProviderFamily::Opencode,
1707 PROVIDER_OPENROUTER | PROVIDER_FIREWORKS | PROVIDER_RODER_CLOUD => ProviderFamily::OpenAi,
1708 PROVIDER_POOLSIDE => ProviderFamily::Poolside,
1709 PROVIDER_CURSOR => ProviderFamily::Cursor,
1710 PROVIDER_XIAOMI_MIMO | PROVIDER_XIAOMI_MIMO_TOKEN_PLAN => ProviderFamily::OpenAi,
1711 PROVIDER_KIMI_CODE => ProviderFamily::OpenAi,
1712 PROVIDER_SYNTHETIC => ProviderFamily::OpenAi,
1713 PROVIDER_DEEPSEEK => ProviderFamily::OpenAi,
1714 _ => ProviderFamily::Mock,
1715 }
1716}
1717
1718fn schema_policy_for_family(family: ProviderFamily) -> ModelSchemaPolicy {
1719 match family {
1720 ProviderFamily::OpenAi => ModelSchemaPolicy::RequiredFirstFlat,
1721 _ => ModelSchemaPolicy::StandardRequiredFirst,
1722 }
1723}
1724
1725fn instruction_overlay_for_family(family: ProviderFamily) -> ModelInstructionOverlay {
1726 match family {
1727 ProviderFamily::OpenAi => ModelInstructionOverlay::LiteralToolOutputs,
1728 ProviderFamily::Anthropic | ProviderFamily::Gemini => {
1729 ModelInstructionOverlay::IntuitiveContext
1730 }
1731 _ => ModelInstructionOverlay::Standard,
1732 }
1733}
1734
1735fn default_execution_reasoning(model: &ModelCatalogEntry) -> String {
1736 if model
1737 .supported_reasoning
1738 .iter()
1739 .any(|option| option.effort == REASONING_LOW)
1740 {
1741 REASONING_LOW.to_string()
1742 } else {
1743 model.default_reasoning.to_string()
1744 }
1745}
1746
1747pub fn model_supports_reasoning_effort(model: &str, effort: &str) -> bool {
1748 lookup_model(model)
1749 .map(|entry| {
1750 entry
1751 .supported_reasoning
1752 .iter()
1753 .any(|option| option.effort == effort)
1754 })
1755 .unwrap_or(false)
1756}
1757
1758pub fn normalize_provider_id(provider: &str) -> String {
1759 match provider.trim().to_ascii_lowercase().as_str() {
1760 "grok" | "x-ai" | "x.ai" => PROVIDER_XAI.to_string(),
1761 "grok-oauth" | "xai-oauth" | "x-ai-oauth" | "xai-grok-oauth" => {
1762 PROVIDER_SUPERGROK.to_string()
1763 }
1764 "opencode" => PROVIDER_OPENCODE.to_string(),
1765 "go" | "opencode_go" | "opencode-go" => PROVIDER_OPENCODE_GO.to_string(),
1766 "openrouter" => PROVIDER_OPENROUTER.to_string(),
1767 "fireworks" | "fireworks-ai" | "fireworks_ai" => PROVIDER_FIREWORKS.to_string(),
1768 "roder-cloud" | "roder_cloud" | "rodercloud" | "roder.cloud" => {
1769 PROVIDER_RODER_CLOUD.to_string()
1770 }
1771 "laguna" | "poolside" => PROVIDER_POOLSIDE.to_string(),
1772 "composer" | "cursor-composer" => PROVIDER_CURSOR.to_string(),
1773 "claude_code" | "claudecode" => PROVIDER_CLAUDE_CODE.to_string(),
1774 "kimi" | "kimi-code" | "kimi_code" | "moonshot" => PROVIDER_KIMI_CODE.to_string(),
1775 "synthetic" | "synthetic-ai" | "synthetic_ai" | "synthetic.new" => {
1776 PROVIDER_SYNTHETIC.to_string()
1777 }
1778 "deepseek" | "deepseek-platform" | "deepseek_platform" => PROVIDER_DEEPSEEK.to_string(),
1779 provider => provider.to_string(),
1780 }
1781}
1782
1783impl From<&ModelCatalogEntry> for ModelDescriptor {
1784 fn from(model: &ModelCatalogEntry) -> Self {
1785 let supported_reasoning = model
1786 .supported_reasoning
1787 .iter()
1788 .map(|option| ReasoningEffortDescriptor {
1789 effort: option.effort.to_string(),
1790 description: option.description.to_string(),
1791 })
1792 .collect::<Vec<_>>();
1793 Self {
1794 id: model.id.to_string(),
1795 name: model.display_name.to_string(),
1796 context_window: (model.context_window > 0).then_some(model.context_window),
1797 default_reasoning: (!supported_reasoning.is_empty())
1798 .then(|| model.default_reasoning.to_string()),
1799 supported_reasoning,
1800 }
1801 }
1802}
1803
1804#[cfg(test)]
1805mod tests {
1806 use super::*;
1807
1808 #[test]
1809 fn catalog_contains_gode_providers() {
1810 let ids = BUILT_IN_PROVIDERS
1811 .iter()
1812 .map(|provider| provider.id)
1813 .collect::<Vec<_>>();
1814 assert_eq!(
1815 ids,
1816 vec![
1817 "mock",
1818 "openai",
1819 "codex",
1820 "anthropic",
1821 "claude-code",
1822 "gemini",
1823 "vertex",
1824 "xai",
1825 "supergrok",
1826 "opencode",
1827 "opencode-go",
1828 "openrouter",
1829 "fireworks",
1830 "roder-cloud",
1831 "poolside",
1832 "cursor",
1833 "xiaomi-mimo",
1834 "xiaomi-mimo-token-plan",
1835 "synthetic",
1836 "deepseek",
1837 "kimi-code"
1838 ]
1839 );
1840 }
1841
1842 #[test]
1843 fn gemini_provider_defaults_to_stable_35_flash() {
1844 let provider = BUILT_IN_PROVIDERS
1845 .iter()
1846 .find(|provider| provider.id == PROVIDER_GEMINI)
1847 .unwrap();
1848
1849 assert_eq!(provider.default_model, "gemini-3.5-flash");
1850
1851 let model = lookup_model("gemini-3.5-flash").unwrap();
1852 assert_eq!(model.display_name, "Gemini 3.5 Flash");
1853 assert_eq!(model.provider, PROVIDER_GEMINI);
1854 assert_eq!(model.context_window, 1_048_576);
1855 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
1856 assert!(model.supports_tools);
1857 assert!(model.supports_structured);
1858 assert_eq!(
1859 model
1860 .supported_reasoning
1861 .iter()
1862 .map(|option| option.effort)
1863 .collect::<Vec<_>>(),
1864 vec![
1865 REASONING_MINIMAL,
1866 REASONING_LOW,
1867 REASONING_MEDIUM,
1868 REASONING_HIGH
1869 ]
1870 );
1871 }
1872
1873 #[test]
1874 fn vertex_provider_mirrors_gemini_models_under_vertex_id() {
1875 let provider = BUILT_IN_PROVIDERS
1876 .iter()
1877 .find(|provider| provider.id == PROVIDER_VERTEX)
1878 .unwrap();
1879
1880 assert_eq!(provider.default_model, "gemini-3.5-flash");
1881 assert_eq!(provider.env_key, Some("GOOGLE_APPLICATION_CREDENTIALS"));
1882 assert_eq!(provider.env_aliases, &["VERTEX_CREDENTIALS_JSON"]);
1883
1884 let model = lookup_model_for_provider(PROVIDER_VERTEX, "gemini-3.5-flash").unwrap();
1885 assert_eq!(model.provider, PROVIDER_VERTEX);
1886 assert_eq!(model.context_window, 1_048_576);
1887 assert!(model.supports_tools);
1888 assert_eq!(
1889 provider_family_for_provider(PROVIDER_VERTEX),
1890 ProviderFamily::Gemini
1891 );
1892 }
1893
1894 #[test]
1895 fn gemini_38_flash_is_offered_on_both_google_providers() {
1896 for provider in [PROVIDER_GEMINI, PROVIDER_VERTEX] {
1897 let model = lookup_model_for_provider(provider, "gemini-3.8-flash").unwrap();
1898 assert_eq!(model.provider, provider);
1899 assert_eq!(model.display_name, "Gemini 3.8 Flash");
1900 assert_eq!(model.context_window, 1_048_576);
1901 assert_eq!(model.auto_compact_token_limit, 943_718);
1902 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
1903 assert!(model.supports_tools);
1904 assert!(model.supports_structured);
1905 assert!(model.supports_images);
1906 assert!(!model.hidden);
1907 }
1908 }
1909
1910 #[test]
1911 fn catalog_contains_gode_visible_models() {
1912 let ids = built_in_models(false)
1913 .into_iter()
1914 .map(|model| model.id)
1915 .collect::<Vec<_>>();
1916 assert_eq!(
1917 ids,
1918 vec![
1919 "gpt-6-astra",
1920 "gpt-6-sol",
1921 "gpt-6-luna",
1922 "gpt-5.6-sol",
1923 "gpt-5.6-terra",
1924 "gpt-5.6-luna",
1925 "gpt-5.5",
1926 "gpt-5.4",
1927 "gpt-5.4-mini",
1928 "gpt-5.3-codex-spark",
1929 "claude-fable-5-1",
1930 "claude-fable-5",
1931 "claude-opus-4-8",
1932 "claude-opus-4-7",
1933 "claude-sonnet-4-6",
1934 "claude-haiku-4-5-20251001",
1935 "fable",
1936 "sonnet",
1937 "opus",
1938 "haiku",
1939 "claude-sonnet-4-6",
1940 "claude-opus-4-8",
1941 "claude-fable-5",
1942 "claude-fable-5-1",
1943 "gemini-3.8-flash",
1944 "gemini-3.5-flash",
1945 "gemini-3.7-flash",
1946 "gemini-3.1-pro-preview",
1947 "gemini-3.1-pro-preview-customtools",
1948 "gemini-3-flash-preview",
1949 "gemini-3.1-flash-lite-preview",
1950 "gemini-3.8-flash",
1951 "gemini-3.5-flash",
1952 "gemini-3.7-flash",
1953 "gemini-3.1-pro-preview",
1954 "gemini-3-flash-preview",
1955 "gemini-3.1-flash-lite-preview",
1956 "grok-4.6",
1957 "grok-4.3",
1958 "grok-4.20-multi-agent-0309",
1959 "grok-4.20-0309-reasoning",
1960 "grok-4.20-0309-non-reasoning",
1961 "grok-4.6",
1962 "grok-composer-2.5-fast",
1963 "gpt-5.5",
1964 "gpt-5.3-codex-spark",
1965 "big-pickle",
1966 "mimo-v2.5-free",
1967 "nemotron-3-ultra-free",
1968 "north-mini-code-free",
1969 "deepseek-v4-flash",
1970 "deepseek-v4-pro",
1971 "kimi-k2.6",
1972 "qwen3.6-plus",
1973 "glm-5.1",
1974 "deepseek-v4-flash",
1975 "deepseek-v4-pro",
1976 "kimi-for-coding",
1977 "x-ai/grok-4.6",
1978 "accounts/fireworks/models/qwen3-235b-a22b",
1979 "roder.cloud/free",
1980 "roder.cloud/openai/gpt-5.5",
1981 "roder.cloud/anthropic/claude-opus-4-7",
1982 "roder.cloud/google/gemini-3.1-pro-preview",
1983 "poolside/laguna-m.1",
1984 "poolside/laguna-xs.2",
1985 "mimo-v2.5-pro",
1986 "mimo-v2-pro",
1987 "mimo-v2.5",
1988 "mimo-v2-omni",
1989 "mimo-v2-flash",
1990 "mimo-v2.5-pro",
1991 "mimo-v2-pro",
1992 "mimo-v2.5",
1993 "mimo-v2-omni",
1994 "mimo-v2-flash",
1995 "syn:large:text",
1996 "syn:small:text",
1997 "syn:large:vision",
1998 "syn:small:vision",
1999 "hf:MiniMaxAI/MiniMax-M3",
2000 "hf:Qwen/Qwen3.6-27B",
2001 "hf:moonshotai/Kimi-K2.6",
2002 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
2003 "hf:zai-org/GLM-4.7",
2004 "hf:zai-org/GLM-4.7-Flash",
2005 "hf:zai-org/GLM-5.1",
2006 "hf:zai-org/GLM-5.2",
2007 "hf:openai/gpt-oss-120b",
2008 "hf:Qwen/Qwen3.5-397B-A17B",
2009 "deepseek-chat",
2010 "deepseek-reasoner",
2011 "deepseek-v4-flash",
2012 "deepseek-v4-pro",
2013 "composer-2.5",
2014 "composer-2.5-fast",
2015 "claude-fable-5",
2016 "claude-opus-4-8",
2017 "claude-sonnet-4-6",
2018 "gpt-5.5",
2019 "gpt-5.5-fast",
2020 "gemini-3.1-pro-preview",
2021 "grok-4.6",
2022 "gemini-3.7-flash",
2023 "grok-4.3",
2024 ]
2025 );
2026 }
2027
2028 #[test]
2029 fn provider_model_lists_match_gode_catalog() {
2030 assert_eq!(models_for_provider(PROVIDER_OPENAI, false).len(), 9);
2031 assert_eq!(models_for_codex(false).len(), 10);
2032 assert_eq!(models_for_provider(PROVIDER_ANTHROPIC, false).len(), 6);
2033 assert_eq!(models_for_provider(PROVIDER_CLAUDE_CODE, false).len(), 8);
2034 assert_eq!(models_for_provider(PROVIDER_GEMINI, false).len(), 7);
2035 assert_eq!(models_for_provider(PROVIDER_VERTEX, false).len(), 6);
2036 assert_eq!(models_for_provider(PROVIDER_XAI, false).len(), 5);
2037 assert_eq!(models_for_provider(PROVIDER_SUPERGROK, false).len(), 2);
2038 assert_eq!(models_for_provider(PROVIDER_OPENCODE, false).len(), 8);
2039 assert_eq!(models_for_provider(PROVIDER_OPENCODE_GO, false).len(), 5);
2040 assert_eq!(models_for_provider(PROVIDER_OPENROUTER, false).len(), 1);
2041 assert_eq!(models_for_provider(PROVIDER_FIREWORKS, false).len(), 1);
2042 assert_eq!(models_for_provider(PROVIDER_RODER_CLOUD, false).len(), 4);
2043 assert_eq!(models_for_provider(PROVIDER_POOLSIDE, false).len(), 2);
2044 assert_eq!(models_for_provider(PROVIDER_CURSOR, false).len(), 11);
2045 assert_eq!(models_for_provider(PROVIDER_XIAOMI_MIMO, false).len(), 5);
2046 assert_eq!(
2047 models_for_provider(PROVIDER_XIAOMI_MIMO_TOKEN_PLAN, false).len(),
2048 5
2049 );
2050 assert_eq!(models_for_provider(PROVIDER_KIMI_CODE, false).len(), 1);
2051 assert_eq!(models_for_provider(PROVIDER_SYNTHETIC, false).len(), 14);
2052 assert_eq!(models_for_provider(PROVIDER_DEEPSEEK, false).len(), 4);
2053 assert_eq!(models_for_provider(PROVIDER_MOCK, true).len(), 1);
2054 }
2055
2056 #[test]
2057 fn codex_model_list_matches_current_subscription_roster() {
2058 let codex_provider = built_in_providers()
2059 .iter()
2060 .find(|provider| provider.id == PROVIDER_CODEX)
2061 .expect("codex provider");
2062 assert_eq!(codex_provider.default_model, "gpt-5.6-sol");
2063
2064 let ids = models_for_codex(false)
2065 .into_iter()
2066 .map(|model| model.id)
2067 .collect::<Vec<_>>();
2068
2069 assert_eq!(
2070 ids,
2071 vec![
2072 "gpt-6-astra",
2073 "gpt-6-sol",
2074 "gpt-6-luna",
2075 "gpt-5.6-sol",
2076 "gpt-5.6-terra",
2077 "gpt-5.6-luna",
2078 "gpt-5.5",
2079 "gpt-5.4",
2080 "gpt-5.4-mini",
2081 "gpt-5.3-codex-spark",
2082 ]
2083 );
2084 }
2085
2086 #[test]
2087 fn new_codex_models_match_current_subscription_metadata() {
2088 let assert_model = |id: &str,
2089 name: &str,
2090 description: &str,
2091 default_reasoning: &str,
2092 efforts: &[&str],
2093 context_window: u32,
2094 max_context_window: u32| {
2095 let model = lookup_model_for_provider(PROVIDER_OPENAI, id).unwrap();
2096
2097 assert_eq!(model.display_name, name, "{id} display name");
2098 assert_eq!(model.description, description, "{id} description");
2099 assert_eq!(model.provider, PROVIDER_OPENAI, "{id} provider");
2100 assert_eq!(
2101 model.default_reasoning, default_reasoning,
2102 "{id} default reasoning"
2103 );
2104 assert_eq!(
2105 model
2106 .supported_reasoning
2107 .iter()
2108 .map(|option| option.effort)
2109 .collect::<Vec<_>>(),
2110 efforts,
2111 "{id} efforts"
2112 );
2113 assert_eq!(model.context_window, context_window, "{id} context window");
2114 assert_eq!(
2115 model.max_context_window, max_context_window,
2116 "{id} max context window"
2117 );
2118 assert_eq!(
2119 model.auto_compact_token_limit,
2120 context_window.saturating_mul(9) / 10,
2121 "{id} auto compact limit"
2122 );
2123 assert!(model.supports_compaction, "{id} compaction support");
2124 assert!(model.supports_images, "{id} image support");
2125 assert!(model.supports_tools, "{id} tool support");
2126 assert!(!model.hidden, "{id} visibility");
2127 };
2128
2129 assert_model(
2130 "gpt-6-astra",
2131 "GPT-6-Astra",
2132 "OpenAI's most capable model, built for the hardest end-to-end work.",
2133 REASONING_HIGH,
2134 &[
2135 REASONING_LOW,
2136 REASONING_MEDIUM,
2137 REASONING_HIGH,
2138 REASONING_XHIGH,
2139 REASONING_MAX,
2140 ],
2141 1_050_000,
2142 1_050_000,
2143 );
2144 assert_model(
2145 "gpt-6-sol",
2146 "GPT-6-Sol",
2147 "GPT-6 agentic coding model balancing capability and cost.",
2148 REASONING_MEDIUM,
2149 &[
2150 REASONING_LOW,
2151 REASONING_MEDIUM,
2152 REASONING_HIGH,
2153 REASONING_XHIGH,
2154 REASONING_MAX,
2155 ],
2156 372_000,
2157 372_000,
2158 );
2159 assert_model(
2160 "gpt-6-luna",
2161 "GPT-6-Luna",
2162 "Fast and affordable GPT-6 agentic coding model.",
2163 REASONING_MEDIUM,
2164 &[
2165 REASONING_LOW,
2166 REASONING_MEDIUM,
2167 REASONING_HIGH,
2168 REASONING_XHIGH,
2169 REASONING_MAX,
2170 ],
2171 372_000,
2172 372_000,
2173 );
2174 assert_model(
2175 "gpt-5.6-sol",
2176 "GPT-5.6-Sol",
2177 "Latest frontier agentic coding model.",
2178 REASONING_LOW,
2179 &[
2180 REASONING_LOW,
2181 REASONING_MEDIUM,
2182 REASONING_HIGH,
2183 REASONING_XHIGH,
2184 REASONING_MAX,
2185 REASONING_ULTRA,
2186 ],
2187 372_000,
2188 372_000,
2189 );
2190 assert_model(
2191 "gpt-5.6-terra",
2192 "GPT-5.6-Terra",
2193 "Balanced agentic coding model for everyday work.",
2194 REASONING_MEDIUM,
2195 &[
2196 REASONING_LOW,
2197 REASONING_MEDIUM,
2198 REASONING_HIGH,
2199 REASONING_XHIGH,
2200 REASONING_MAX,
2201 REASONING_ULTRA,
2202 ],
2203 372_000,
2204 372_000,
2205 );
2206 assert_model(
2207 "gpt-5.6-luna",
2208 "GPT-5.6-Luna",
2209 "Fast and affordable agentic coding model.",
2210 REASONING_MEDIUM,
2211 &[
2212 REASONING_LOW,
2213 REASONING_MEDIUM,
2214 REASONING_HIGH,
2215 REASONING_XHIGH,
2216 REASONING_MAX,
2217 ],
2218 372_000,
2219 372_000,
2220 );
2221 assert_model(
2222 "gpt-5.4",
2223 "GPT-5.4",
2224 "Strong model for everyday coding.",
2225 REASONING_MEDIUM,
2226 &[
2227 REASONING_LOW,
2228 REASONING_MEDIUM,
2229 REASONING_HIGH,
2230 REASONING_XHIGH,
2231 ],
2232 272_000,
2233 1_000_000,
2234 );
2235 }
2236
2237 #[test]
2238 fn deepseek_catalog_defaults_to_chat_model() {
2239 let provider = BUILT_IN_PROVIDERS
2240 .iter()
2241 .find(|provider| provider.id == PROVIDER_DEEPSEEK)
2242 .expect("deepseek provider registered");
2243 assert_eq!(provider.name, "DeepSeek Platform");
2244 assert_eq!(provider.default_model, "deepseek-chat");
2245 assert_eq!(provider.base_url, Some("https://api.deepseek.com/v1"));
2246 assert_eq!(provider.env_key, Some("DEEPSEEK_API_KEY"));
2247 assert_eq!(
2248 normalize_provider_id("deepseek-platform"),
2249 PROVIDER_DEEPSEEK
2250 );
2251 assert_eq!(
2252 provider_family_for_provider(PROVIDER_DEEPSEEK),
2253 ProviderFamily::OpenAi
2254 );
2255
2256 let models = models_for_provider(PROVIDER_DEEPSEEK, false);
2257 assert_eq!(models.len(), 4);
2258 assert!(models.iter().any(|model| model.id == "deepseek-chat"));
2259 assert!(models.iter().any(|model| model.id == "deepseek-reasoner"));
2260 assert!(models.iter().any(|model| model.id == "deepseek-v4-flash"));
2261 assert!(models.iter().any(|model| model.id == "deepseek-v4-pro"));
2262
2263 let flash = models
2264 .iter()
2265 .find(|model| model.id == "deepseek-v4-flash")
2266 .expect("flash model");
2267 assert_eq!(flash.default_reasoning, Some(REASONING_HIGH.to_string()));
2268 assert_eq!(
2269 flash
2270 .supported_reasoning
2271 .iter()
2272 .map(|option| option.effort.as_str())
2273 .collect::<Vec<_>>(),
2274 vec![
2275 REASONING_NONE,
2276 REASONING_LOW,
2277 REASONING_HIGH,
2278 REASONING_XHIGH,
2279 REASONING_MAX,
2280 ]
2281 );
2282
2283 let chat = models
2284 .iter()
2285 .find(|model| model.id == "deepseek-chat")
2286 .expect("chat model");
2287 assert_eq!(chat.default_reasoning, Some(REASONING_NONE.to_string()));
2288 assert!(
2289 chat.supported_reasoning
2290 .iter()
2291 .any(|option| option.effort == REASONING_HIGH)
2292 );
2293 }
2294
2295 #[test]
2296 fn synthetic_catalog_defaults_to_large_text_alias() {
2297 let provider = built_in_providers()
2298 .iter()
2299 .find(|provider| provider.id == PROVIDER_SYNTHETIC)
2300 .expect("synthetic provider registered");
2301 assert_eq!(provider.name, "Synthetic");
2302 assert_eq!(provider.default_model, "syn:large:text");
2303 assert_eq!(
2304 provider.base_url,
2305 Some("https://api.synthetic.new/openai/v1")
2306 );
2307 assert_eq!(provider.env_key, Some("SYNTHETIC_API_KEY"));
2308 assert!(provider.env_aliases.contains(&"RODER_SYNTHETIC_API_KEY"));
2309 assert!(!provider.supports_websockets);
2310
2311 let models = models_for_provider(PROVIDER_SYNTHETIC, false);
2312 let default = models
2313 .iter()
2314 .find(|model| model.id == provider.default_model)
2315 .expect("default synthetic model present");
2316 assert_eq!(default.name, "Synthetic Large (Text)");
2317 assert!(models.iter().any(|model| model.id == "syn:small:text"));
2318 let vision = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:vision")
2319 .expect("vision alias present");
2320 assert!(vision.supports_images);
2321 assert_eq!(
2322 provider_family_for_provider(PROVIDER_SYNTHETIC),
2323 ProviderFamily::OpenAi
2324 );
2325 }
2326
2327 #[test]
2328 fn synthetic_model_ids_preserve_alias_and_hf_segments() {
2329 assert_eq!(normalize_provider_id("synthetic"), PROVIDER_SYNTHETIC);
2330 assert_eq!(normalize_provider_id("synthetic.new"), PROVIDER_SYNTHETIC);
2331 let alias = lookup_model_for_provider(PROVIDER_SYNTHETIC, "syn:large:text")
2333 .expect("syn alias resolves");
2334 assert_eq!(alias.id, "syn:large:text");
2335 assert_eq!(alias.provider, PROVIDER_SYNTHETIC);
2336 let label = "synthetic/hf:zai-org/GLM-5.2";
2340 let (provider, model) = label.split_once('/').unwrap();
2341 assert_eq!(provider, PROVIDER_SYNTHETIC);
2342 assert_eq!(model, "hf:zai-org/GLM-5.2");
2343 }
2344
2345 #[test]
2346 fn synthetic_always_on_models_are_pinned_with_documented_context_windows() {
2347 let glm_5_2 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-5.2")
2348 .expect("GLM-5.2 pinned");
2349 assert_eq!(glm_5_2.provider, PROVIDER_SYNTHETIC);
2350 assert_eq!(glm_5_2.context_window, 524_288);
2351 assert!(!glm_5_2.supports_images);
2352
2353 let minimax = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:MiniMaxAI/MiniMax-M3")
2354 .expect("MiniMax-M3 pinned");
2355 assert_eq!(minimax.context_window, 524_288);
2356
2357 let glm_4_7 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:zai-org/GLM-4.7")
2358 .expect("GLM-4.7 pinned");
2359 assert_eq!(glm_4_7.context_window, 202_752);
2360
2361 let gpt_oss = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:openai/gpt-oss-120b")
2362 .expect("gpt-oss-120b pinned");
2363 assert_eq!(gpt_oss.context_window, 131_072);
2364
2365 let qwen_3_5 = lookup_model_for_provider(PROVIDER_SYNTHETIC, "hf:Qwen/Qwen3.5-397B-A17B")
2366 .expect("Qwen3.5 397B pinned");
2367 assert_eq!(qwen_3_5.context_window, 262_144);
2368
2369 let always_on = [
2371 "hf:MiniMaxAI/MiniMax-M3",
2372 "hf:Qwen/Qwen3.6-27B",
2373 "hf:moonshotai/Kimi-K2.6",
2374 "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
2375 "hf:zai-org/GLM-4.7",
2376 "hf:zai-org/GLM-4.7-Flash",
2377 "hf:zai-org/GLM-5.1",
2378 "hf:zai-org/GLM-5.2",
2379 "hf:openai/gpt-oss-120b",
2380 "hf:Qwen/Qwen3.5-397B-A17B",
2381 ];
2382 for id in always_on {
2383 let entry = lookup_model_for_provider(PROVIDER_SYNTHETIC, id)
2384 .unwrap_or_else(|| panic!("{id} should be pinned in the synthetic catalog"));
2385 assert_eq!(entry.provider, PROVIDER_SYNTHETIC);
2386 assert!(entry.supports_tools);
2387 assert!(entry.supports_structured);
2388 }
2389 }
2390
2391 #[test]
2392 fn claude_code_catalog_uses_long_context_windows() {
2393 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-sonnet-4-6").unwrap();
2394 let claude_code =
2395 lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-sonnet-4-6").unwrap();
2396
2397 assert_eq!(direct.context_window, 1_000_000);
2398 assert_eq!(claude_code.context_window, 1_000_000);
2399 assert_eq!(claude_code.auto_compact_token_limit, 900_000);
2400 assert!(!claude_code.supports_compaction);
2403 assert!(direct.supports_compaction);
2407 assert_eq!(direct.auto_compact_token_limit, 900_000);
2408 }
2409
2410 #[test]
2411 fn claude_haiku_does_not_advertise_server_side_compaction() {
2412 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
2413
2414 assert!(!haiku.supports_compaction);
2419 assert_eq!(haiku.auto_compact_token_limit, 180_000);
2420 }
2421
2422 #[test]
2423 fn claude_fable_5_1_is_offered_directly_and_through_the_claude_code_harness() {
2424 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-fable-5-1").unwrap();
2425 assert_eq!(direct.display_name, "Claude Fable 5.1");
2426 assert_eq!(direct.context_window, 1_000_000);
2427 assert_eq!(direct.auto_compact_token_limit, 900_000);
2428 assert_eq!(direct.default_reasoning, REASONING_HIGH);
2429 assert!(direct.supports_compaction);
2430 assert_eq!(
2431 direct
2432 .supported_reasoning
2433 .iter()
2434 .map(|option| option.effort)
2435 .collect::<Vec<_>>(),
2436 vec![
2437 REASONING_LOW,
2438 REASONING_MEDIUM,
2439 REASONING_HIGH,
2440 REASONING_XHIGH,
2441 REASONING_MAX
2442 ]
2443 );
2444
2445 let harness = lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-fable-5-1").unwrap();
2446 assert_eq!(harness.provider, PROVIDER_CLAUDE_CODE);
2447 assert_eq!(harness.context_window, 1_000_000);
2448 assert!(!harness.supports_compaction);
2451 }
2452
2453 #[test]
2454 fn google_embedding_model_is_hidden_from_chat_lists() {
2455 assert!(lookup_model("gemini-embedding-2").is_some());
2456 assert!(
2457 models_for_provider(PROVIDER_GOOGLE, false)
2458 .iter()
2459 .all(|model| model.id != "gemini-embedding-2")
2460 );
2461 let model = lookup_model("gemini-embedding-2").unwrap();
2462 assert!(model.hidden);
2463 assert!(!model.supports_tools);
2464 }
2465
2466 #[test]
2467 fn zeroentropy_embedding_model_is_hidden_from_chat_lists() {
2468 assert!(lookup_model("zembed-1").is_some());
2469 assert!(
2470 models_for_provider(PROVIDER_ZEROENTROPY, false)
2471 .iter()
2472 .all(|model| model.id != "zembed-1")
2473 );
2474 let model = lookup_model("zembed-1").unwrap();
2475 assert!(model.hidden);
2476 assert!(!model.supports_tools);
2477 }
2478
2479 #[test]
2480 fn catalog_model_profile_derives_openai_defaults() {
2481 let profile = built_in_model_profile("gpt-5.5").unwrap();
2482
2483 assert_eq!(profile.provider_family, ProviderFamily::OpenAi);
2484 assert_eq!(profile.edit_tool.as_deref(), Some(EDIT_TOOL_PATCH));
2485 assert_eq!(profile.schema_policy, ModelSchemaPolicy::RequiredFirstFlat);
2486 assert_eq!(
2487 profile.instruction_overlay,
2488 ModelInstructionOverlay::LiteralToolOutputs
2489 );
2490 assert_eq!(profile.reasoning.execution.as_deref(), Some(REASONING_LOW));
2491 assert_eq!(profile.parallel_tool_calls, Some(true));
2492 }
2493
2494 #[test]
2495 fn poolside_catalog_defaults_to_thinking_enabled() {
2496 let laguna = lookup_model("poolside/laguna-m.1").unwrap();
2497 assert_eq!(laguna.default_reasoning, REASONING_MEDIUM);
2498 assert_eq!(
2499 laguna
2500 .supported_reasoning
2501 .iter()
2502 .map(|option| option.effort)
2503 .collect::<Vec<_>>(),
2504 vec![REASONING_NONE, REASONING_MEDIUM]
2505 );
2506 }
2507
2508 #[test]
2509 fn xiaomi_mimo_catalog_uses_chat_completions_kind_and_exact_model_ids() {
2510 let provider = BUILT_IN_PROVIDERS
2511 .iter()
2512 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO)
2513 .unwrap();
2514 let token_plan = BUILT_IN_PROVIDERS
2515 .iter()
2516 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO_TOKEN_PLAN)
2517 .unwrap();
2518
2519 assert_eq!(provider.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2520 assert_eq!(token_plan.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
2521 assert_eq!(provider.env_key, Some("MIMO_API_KEY"));
2522 assert_eq!(token_plan.env_key, Some("MIMO_TOKEN_PLAN_API_KEY"));
2523
2524 let ids = models_for_provider(PROVIDER_XIAOMI_MIMO, false)
2525 .into_iter()
2526 .map(|model| model.id)
2527 .collect::<Vec<_>>();
2528 assert_eq!(
2529 ids,
2530 vec![
2531 "mimo-v2.5-pro",
2532 "mimo-v2-pro",
2533 "mimo-v2.5",
2534 "mimo-v2-omni",
2535 "mimo-v2-flash"
2536 ]
2537 );
2538 assert!(lookup_model("out-of-v2-flash").is_none());
2539 }
2540
2541 #[test]
2542 fn supergrok_catalog_exposes_grok_46_and_composer_with_expected_context_windows() {
2543 let grok46 = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-4.6").unwrap();
2544 assert_eq!(grok46.display_name, "Grok 4.6");
2545 assert_eq!(grok46.context_window, 500_000);
2546 assert_eq!(grok46.auto_compact_token_limit, 450_000);
2547 assert_eq!(grok46.default_reasoning, REASONING_HIGH);
2548
2549 let composer =
2550 lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-composer-2.5-fast").unwrap();
2551 assert_eq!(composer.display_name, "Grok Composer 2.5 Fast");
2552 assert_eq!(composer.context_window, 200_000);
2553 assert_eq!(composer.auto_compact_token_limit, 180_000);
2554 assert!(composer.supported_reasoning.is_empty());
2555 assert!(!composer.supports_images);
2556
2557 let visible = models_for_provider(PROVIDER_SUPERGROK, false)
2558 .into_iter()
2559 .map(|model| model.id)
2560 .collect::<Vec<_>>();
2561 assert_eq!(
2562 visible,
2563 vec!["grok-4.6".to_string(), "grok-composer-2.5-fast".to_string()]
2564 );
2565 }
2566
2567 #[test]
2568 fn xai_catalog_entries_match_current_grok_contract() {
2569 let grok46 = models_for_provider(PROVIDER_XAI, false)
2570 .into_iter()
2571 .find(|model| model.id == "grok-4.6")
2572 .unwrap();
2573 assert_eq!(grok46.context_window, Some(500_000));
2574 assert_eq!(grok46.default_reasoning.as_deref(), Some(REASONING_HIGH));
2575 assert_eq!(
2576 grok46
2577 .supported_reasoning
2578 .iter()
2579 .map(|option| option.effort.as_str())
2580 .collect::<Vec<_>>(),
2581 vec![
2582 REASONING_LOW,
2583 REASONING_MEDIUM,
2584 REASONING_HIGH,
2585 REASONING_XHIGH
2586 ]
2587 );
2588
2589 let grok43 = models_for_provider(PROVIDER_XAI, false)
2590 .into_iter()
2591 .find(|model| model.id == "grok-4.3")
2592 .unwrap();
2593 assert_eq!(grok43.context_window, Some(1_000_000));
2594 assert_eq!(grok43.default_reasoning.as_deref(), Some(REASONING_LOW));
2595 assert_eq!(
2596 grok43
2597 .supported_reasoning
2598 .iter()
2599 .map(|option| option.effort.as_str())
2600 .collect::<Vec<_>>(),
2601 vec![
2602 REASONING_NONE,
2603 REASONING_LOW,
2604 REASONING_MEDIUM,
2605 REASONING_HIGH,
2606 REASONING_XHIGH
2607 ]
2608 );
2609
2610 let grok420 = lookup_model("grok-4.20-multi-agent-0309").unwrap();
2611 assert_eq!(grok420.context_window, 2_000_000);
2612 assert_eq!(grok420.auto_compact_token_limit, 1_800_000);
2613 assert_eq!(grok420.provider, PROVIDER_XAI);
2614 }
2615
2616 #[test]
2617 fn provider_aliases_normalize_xai_and_supergrok() {
2618 assert_eq!(normalize_provider_id("grok"), PROVIDER_XAI);
2619 assert_eq!(normalize_provider_id("x.ai"), PROVIDER_XAI);
2620 assert_eq!(normalize_provider_id("x-ai"), PROVIDER_XAI);
2621 assert_eq!(normalize_provider_id("xai-oauth"), PROVIDER_SUPERGROK);
2622 assert_eq!(normalize_provider_id("grok-oauth"), PROVIDER_SUPERGROK);
2623 assert_eq!(normalize_provider_id("supergrok"), PROVIDER_SUPERGROK);
2624 assert_eq!(normalize_provider_id("laguna"), PROVIDER_POOLSIDE);
2625 assert_eq!(normalize_provider_id("composer"), PROVIDER_CURSOR);
2626 }
2627
2628 #[test]
2629 fn fireworks_catalog_preserves_account_scoped_default_model() {
2630 let provider = BUILT_IN_PROVIDERS
2631 .iter()
2632 .find(|provider| provider.id == PROVIDER_FIREWORKS)
2633 .unwrap();
2634
2635 assert_eq!(
2636 provider.default_model,
2637 "accounts/fireworks/models/qwen3-235b-a22b"
2638 );
2639 assert_eq!(provider.env_key, Some("FIREWORKS_API_KEY"));
2640 assert_eq!(provider.env_aliases, &["RODER_FIREWORKS_API_KEY"]);
2641
2642 let model = lookup_model_for_provider(PROVIDER_FIREWORKS, provider.default_model).unwrap();
2643 assert_eq!(model.provider, PROVIDER_FIREWORKS);
2644 assert!(model.supports_tools);
2645 assert!(model.supports_structured);
2646 assert_eq!(
2647 provider_family_for_provider(PROVIDER_FIREWORKS),
2648 ProviderFamily::OpenAi
2649 );
2650 }
2651
2652 #[test]
2653 fn cursor_catalog_profile_is_text_only_agentservice() {
2654 let composer = lookup_model("composer-2.5").unwrap();
2655 assert_eq!(composer.provider, PROVIDER_CURSOR);
2656 assert!(!composer.supports_tools);
2657 assert!(!composer.supports_structured);
2658
2659 let profile = built_in_model_profile("composer-2.5").unwrap();
2660 assert_eq!(profile.provider_family, ProviderFamily::Cursor);
2661 assert_eq!(profile.parallel_tool_calls, Some(false));
2662 }
2663
2664 #[test]
2665 fn provider_aware_lookup_resolves_cursor_proxied_models_to_cursor_family() {
2666 let id_only = built_in_model_profile("claude-opus-4-8").unwrap();
2668 assert_eq!(id_only.provider_family, ProviderFamily::Anthropic);
2669
2670 let cursor =
2672 built_in_model_profile_for_provider(PROVIDER_CURSOR, "claude-opus-4-8").unwrap();
2673 assert_eq!(cursor.provider_family, ProviderFamily::Cursor);
2674 assert_eq!(cursor.provider, PROVIDER_CURSOR);
2675 assert_eq!(cursor.parallel_tool_calls, Some(false));
2676
2677 let anthropic =
2678 built_in_model_profile_for_provider(PROVIDER_ANTHROPIC, "claude-opus-4-8").unwrap();
2679 assert_eq!(anthropic.provider_family, ProviderFamily::Anthropic);
2680
2681 let fallback =
2683 built_in_model_profile_for_provider("does-not-exist", "claude-opus-4-8").unwrap();
2684 assert_eq!(fallback.provider_family, ProviderFamily::Anthropic);
2685 }
2686
2687 #[test]
2688 fn cursor_gpt55_advertises_standard_reasoning_effort() {
2689 let gpt55 = models_for_provider(PROVIDER_CURSOR, false)
2690 .into_iter()
2691 .find(|model| model.id == "gpt-5.5")
2692 .expect("cursor catalog should expose gpt-5.5");
2693
2694 assert_eq!(gpt55.default_reasoning.as_deref(), Some(REASONING_MEDIUM));
2695 assert_eq!(
2696 gpt55
2697 .supported_reasoning
2698 .iter()
2699 .map(|option| option.effort.as_str())
2700 .collect::<Vec<_>>(),
2701 vec![
2702 REASONING_LOW,
2703 REASONING_MEDIUM,
2704 REASONING_HIGH,
2705 REASONING_XHIGH
2706 ]
2707 );
2708
2709 let gpt55_fast = models_for_provider(PROVIDER_CURSOR, false)
2710 .into_iter()
2711 .find(|model| model.id == "gpt-5.5-fast")
2712 .expect("cursor catalog should expose gpt-5.5-fast");
2713 assert_eq!(
2714 gpt55_fast.default_reasoning.as_deref(),
2715 Some(REASONING_MEDIUM)
2716 );
2717 assert_eq!(gpt55_fast.supported_reasoning.len(), 4);
2718 }
2719
2720 #[test]
2721 fn cursor_opus_advertises_configurable_reasoning_effort() {
2722 let opus = models_for_provider(PROVIDER_CURSOR, false)
2723 .into_iter()
2724 .find(|model| model.id == "claude-opus-4-8")
2725 .expect("cursor catalog should expose claude-opus-4-8");
2726
2727 assert_eq!(opus.default_reasoning.as_deref(), Some(REASONING_HIGH));
2728 assert_eq!(
2729 opus.supported_reasoning
2730 .iter()
2731 .map(|option| option.effort.as_str())
2732 .collect::<Vec<_>>(),
2733 vec![
2734 REASONING_LOW,
2735 REASONING_MEDIUM,
2736 REASONING_HIGH,
2737 REASONING_XHIGH,
2738 REASONING_MAX
2739 ]
2740 );
2741
2742 let sonnet = models_for_provider(PROVIDER_CURSOR, false)
2745 .into_iter()
2746 .find(|model| model.id == "claude-sonnet-4-6")
2747 .expect("cursor catalog should expose claude-sonnet-4-6");
2748 assert_eq!(sonnet.default_reasoning.as_deref(), Some(REASONING_MEDIUM));
2749 assert_eq!(
2750 sonnet
2751 .supported_reasoning
2752 .iter()
2753 .map(|option| option.effort.as_str())
2754 .collect::<Vec<_>>(),
2755 vec![
2756 REASONING_LOW,
2757 REASONING_MEDIUM,
2758 REASONING_HIGH,
2759 REASONING_MAX
2760 ]
2761 );
2762 }
2763
2764 #[test]
2765 fn claude_opus_and_sonnet_advertise_max_effort() {
2766 let efforts = |id: &str| {
2767 lookup_model(id)
2768 .unwrap()
2769 .supported_reasoning
2770 .iter()
2771 .map(|option| option.effort)
2772 .collect::<Vec<_>>()
2773 };
2774
2775 for id in ["claude-opus-4-8", "claude-opus-4-7"] {
2777 assert_eq!(
2778 efforts(id),
2779 vec![
2780 REASONING_LOW,
2781 REASONING_MEDIUM,
2782 REASONING_HIGH,
2783 REASONING_XHIGH,
2784 REASONING_MAX
2785 ],
2786 "{id} effort levels"
2787 );
2788 }
2789
2790 assert_eq!(
2792 efforts("claude-sonnet-4-6"),
2793 vec![
2794 REASONING_LOW,
2795 REASONING_MEDIUM,
2796 REASONING_HIGH,
2797 REASONING_MAX
2798 ]
2799 );
2800
2801 assert!(!efforts("gpt-5.5").contains(&REASONING_MAX));
2803 }
2804
2805 #[test]
2806 fn claude_haiku_does_not_advertise_reasoning_effort() {
2807 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
2808
2809 assert_eq!(haiku.default_reasoning, REASONING_NONE);
2810 assert!(haiku.supported_reasoning.is_empty());
2811
2812 let descriptor = ModelDescriptor::from(haiku);
2813 assert_eq!(descriptor.default_reasoning, None);
2814 assert!(descriptor.supported_reasoning.is_empty());
2815 }
2816
2817 #[test]
2818 fn openai_context_windows_match_current_catalog_values() {
2819 let gpt55 = lookup_model("gpt-5.5").unwrap();
2820 assert_eq!(gpt55.context_window, 1_050_000);
2821 assert_eq!(gpt55.max_context_window, 1_050_000);
2822 assert_eq!(gpt55.auto_compact_token_limit, 945_000);
2823
2824 let mini = lookup_model("gpt-5.4-mini").unwrap();
2825 assert_eq!(mini.context_window, 400_000);
2826 assert_eq!(mini.max_context_window, 400_000);
2827 assert_eq!(mini.auto_compact_token_limit, 360_000);
2828
2829 let spark = lookup_model("gpt-5.3-codex-spark").unwrap();
2830 assert_eq!(spark.provider, PROVIDER_CODEX);
2831 assert_eq!(spark.context_window, 128_000);
2832 assert_eq!(spark.max_context_window, 128_000);
2833 assert_eq!(spark.auto_compact_token_limit, 115_200);
2834 }
2835
2836 #[test]
2837 fn auto_compact_defaults_to_ninety_percent_of_context_window() {
2838 for model in BUILT_IN_MODELS {
2839 if model.context_window == 0 || model.auto_compact_token_limit == 0 {
2840 continue;
2841 }
2842 assert_eq!(
2843 model.auto_compact_token_limit,
2844 model.context_window.saturating_mul(9) / 10,
2845 "{} should compact at 90% of its context window",
2846 model.id
2847 );
2848 }
2849 }
2850}