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