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