1use serde::Serialize;
2
3use crate::inference::{
4 ModelDescriptor, ModelHarnessProfile, ModelInstructionOverlay, ModelProfileReasoning,
5 ModelSchemaPolicy, ProviderFamily, ReasoningEffortDescriptor,
6};
7
8pub mod image_models;
9mod xiaomi_mimo;
10
11pub use image_models::{
12 IMAGE_PROVIDER_GOOGLE, IMAGE_PROVIDER_OPENAI, ImageModelCatalogEntry,
13 ImageProviderCatalogEntry, built_in_image_providers, image_model_descriptors,
14 image_models_for_provider, lookup_image_model, lookup_image_provider,
15};
16pub use xiaomi_mimo::{XIAOMI_MIMO_ENV_ALIASES, XIAOMI_MIMO_TOKEN_PLAN_ENV_ALIASES};
17
18pub const PROVIDER_MOCK: &str = "mock";
19pub const PROVIDER_OPENAI: &str = "openai";
20pub const PROVIDER_CODEX: &str = "codex";
21pub const PROVIDER_ANTHROPIC: &str = "anthropic";
22pub const PROVIDER_CLAUDE_CODE: &str = "claude-code";
23pub const PROVIDER_GEMINI: &str = "gemini";
24pub const PROVIDER_VERTEX: &str = "vertex";
25pub const PROVIDER_GOOGLE: &str = "google";
26pub const PROVIDER_ZEROENTROPY: &str = "zeroentropy";
27pub const PROVIDER_XAI: &str = "xai";
28pub const PROVIDER_SUPERGROK: &str = "supergrok";
29pub const PROVIDER_OPENCODE: &str = "opencode";
30pub const PROVIDER_OPENCODE_GO: &str = "opencode-go";
31pub const PROVIDER_OPENROUTER: &str = "openrouter";
32pub const PROVIDER_FIREWORKS: &str = "fireworks";
33pub const PROVIDER_RODER_CLOUD: &str = "roder-cloud";
34pub const PROVIDER_POOLSIDE: &str = "poolside";
35pub const PROVIDER_CURSOR: &str = "cursor";
36pub const PROVIDER_XIAOMI_MIMO: &str = "xiaomi-mimo";
37pub const PROVIDER_XIAOMI_MIMO_TOKEN_PLAN: &str = "xiaomi-mimo-token-plan";
38pub const PROVIDER_KIMI_CODE: &str = "kimi-code";
39
40pub const PROVIDER_KIND_MOCK: &str = "mock";
41pub const PROVIDER_KIND_OPENAI: &str = "openai";
42pub const PROVIDER_KIND_CHAT_COMPLETIONS: &str = "chat_completions";
43pub const PROVIDER_KIND_ANTHROPIC: &str = "anthropic";
44pub const PROVIDER_KIND_CLAUDE_CODE: &str = "claude_code";
45pub const PROVIDER_KIND_GEMINI: &str = "gemini";
46pub const PROVIDER_KIND_VERTEX: &str = "vertex";
47pub const PROVIDER_KIND_XAI: &str = "xai";
48pub const PROVIDER_KIND_OPENCODE: &str = "opencode";
49pub const PROVIDER_KIND_OPENROUTER: &str = "openrouter";
50pub const PROVIDER_KIND_FIREWORKS: &str = "fireworks";
51pub const PROVIDER_KIND_RODER_CLOUD: &str = "roder_cloud";
52pub const PROVIDER_KIND_POOLSIDE: &str = "poolside";
53pub const PROVIDER_KIND_CURSOR: &str = "cursor";
54pub const PROVIDER_KIND_XIAOMI_MIMO: &str = PROVIDER_KIND_CHAT_COMPLETIONS;
55
56pub const REASONING_NONE: &str = "none";
57pub const REASONING_MINIMAL: &str = "minimal";
58pub const REASONING_LOW: &str = "low";
59pub const REASONING_MEDIUM: &str = "medium";
60pub const REASONING_HIGH: &str = "high";
61pub const REASONING_XHIGH: &str = "xhigh";
62pub const REASONING_MAX: &str = "max";
63
64pub const DEFAULT_MODEL_ID: &str = "gpt-5.5";
65pub const EDIT_TOOL_PATCH: &str = "patch";
66pub const EDIT_TOOL_EDIT: &str = "edit";
67
68#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
69pub struct ProviderCatalogEntry {
70 pub id: &'static str,
71 pub name: &'static str,
72 pub kind: &'static str,
73 pub default_model: &'static str,
74 pub base_url: Option<&'static str>,
75 pub env_key: Option<&'static str>,
76 pub env_aliases: &'static [&'static str],
77 pub requires_auth: bool,
78 pub supports_websockets: bool,
79}
80
81#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
82pub struct ReasoningOption {
83 pub effort: &'static str,
84 pub description: &'static str,
85}
86
87#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
88pub struct ModelCatalogEntry {
89 pub id: &'static str,
90 pub display_name: &'static str,
91 pub description: &'static str,
92 pub provider: &'static str,
93 pub default_reasoning: &'static str,
94 pub supported_reasoning: &'static [ReasoningOption],
95 pub context_window: u32,
96 pub max_context_window: u32,
97 pub auto_compact_token_limit: u32,
98 pub supports_compaction: bool,
99 pub supports_images: bool,
100 pub supports_tools: bool,
101 pub supports_structured: bool,
102 pub edit_tool: Option<&'static str>,
103 pub hidden: bool,
104}
105
106pub const STANDARD_REASONING: &[ReasoningOption] = &[
107 ReasoningOption {
108 effort: REASONING_LOW,
109 description: "Fast responses with lighter reasoning",
110 },
111 ReasoningOption {
112 effort: REASONING_MEDIUM,
113 description: "Balances speed and reasoning depth for everyday tasks",
114 },
115 ReasoningOption {
116 effort: REASONING_HIGH,
117 description: "Greater reasoning depth for complex problems",
118 },
119 ReasoningOption {
120 effort: REASONING_XHIGH,
121 description: "Extra high reasoning depth for complex problems",
122 },
123];
124
125pub const OPUS_REASONING: &[ReasoningOption] = &[
129 ReasoningOption {
130 effort: REASONING_LOW,
131 description: "Most efficient; best for short, scoped tasks",
132 },
133 ReasoningOption {
134 effort: REASONING_MEDIUM,
135 description: "Balanced reasoning depth for cost-sensitive workflows",
136 },
137 ReasoningOption {
138 effort: REASONING_HIGH,
139 description: "High capability for complex reasoning and agentic tasks",
140 },
141 ReasoningOption {
142 effort: REASONING_XHIGH,
143 description: "Extended capability for long-horizon coding and agentic work",
144 },
145 ReasoningOption {
146 effort: REASONING_MAX,
147 description: "Absolute maximum capability with no constraints on token spending",
148 },
149];
150
151pub const SONNET_REASONING: &[ReasoningOption] = &[
153 ReasoningOption {
154 effort: REASONING_LOW,
155 description: "Most efficient; lowest latency and cost",
156 },
157 ReasoningOption {
158 effort: REASONING_MEDIUM,
159 description: "Balances speed, cost, and performance for most tasks",
160 },
161 ReasoningOption {
162 effort: REASONING_HIGH,
163 description: "Greater reasoning depth for complex problems",
164 },
165 ReasoningOption {
166 effort: REASONING_MAX,
167 description: "Absolute maximum capability with no constraints on token spending",
168 },
169];
170
171pub const GPT_52_REASONING: &[ReasoningOption] = &[
172 ReasoningOption {
173 effort: REASONING_LOW,
174 description: "Balances speed with some reasoning; useful for straightforward queries and short explanations",
175 },
176 ReasoningOption {
177 effort: REASONING_MEDIUM,
178 description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks",
179 },
180 ReasoningOption {
181 effort: REASONING_HIGH,
182 description: "Maximizes reasoning depth for complex or ambiguous problems",
183 },
184 ReasoningOption {
185 effort: REASONING_XHIGH,
186 description: "Extra high reasoning for complex problems",
187 },
188];
189
190pub const HAIKU_REASONING: &[ReasoningOption] = &[
191 ReasoningOption {
192 effort: REASONING_LOW,
193 description: "Fast responses with lighter reasoning",
194 },
195 ReasoningOption {
196 effort: REASONING_MEDIUM,
197 description: "Balances speed and reasoning depth for everyday tasks",
198 },
199];
200
201pub const GEMINI_REASONING: &[ReasoningOption] = &[
202 ReasoningOption {
203 effort: REASONING_MINIMAL,
204 description: "Minimal Gemini thinking",
205 },
206 ReasoningOption {
207 effort: REASONING_LOW,
208 description: "Low Gemini thinking",
209 },
210 ReasoningOption {
211 effort: REASONING_MEDIUM,
212 description: "Medium Gemini thinking",
213 },
214 ReasoningOption {
215 effort: REASONING_HIGH,
216 description: "High Gemini thinking",
217 },
218];
219
220pub const MOCK_REASONING: &[ReasoningOption] = &[ReasoningOption {
221 effort: REASONING_NONE,
222 description: "No model-side reasoning",
223}];
224
225pub const POOLSIDE_REASONING: &[ReasoningOption] = &[
226 ReasoningOption {
227 effort: REASONING_NONE,
228 description: "Disable Poolside thinking for lower latency",
229 },
230 ReasoningOption {
231 effort: REASONING_MEDIUM,
232 description: "Enable Poolside thinking",
233 },
234];
235
236pub const GEMINI_ENV_ALIASES: &[&str] = &[
237 "GEMINI_API_KEY",
238 "GOOGLE_API_KEY",
239 "GOOGLE_GENAI_API_KEY",
240 "GOOGLE_AI_API_KEY",
241];
242
243pub const VERTEX_ENV_ALIASES: &[&str] = &["VERTEX_CREDENTIALS_JSON"];
244
245pub const XAI_ENV_ALIASES: &[&str] = &["RODER_XAI_API_KEY"];
246
247pub const XAI_CONFIGURABLE_REASONING: &[ReasoningOption] = &[
248 ReasoningOption {
249 effort: REASONING_NONE,
250 description: "No xAI reasoning effort",
251 },
252 ReasoningOption {
253 effort: REASONING_LOW,
254 description: "Low xAI reasoning effort",
255 },
256 ReasoningOption {
257 effort: REASONING_MEDIUM,
258 description: "Medium xAI reasoning effort",
259 },
260 ReasoningOption {
261 effort: REASONING_HIGH,
262 description: "High xAI reasoning effort",
263 },
264];
265
266pub const XAI_REASONING: &[ReasoningOption] = &[
267 ReasoningOption {
268 effort: REASONING_LOW,
269 description: "Low xAI reasoning effort",
270 },
271 ReasoningOption {
272 effort: REASONING_MEDIUM,
273 description: "Medium xAI reasoning effort",
274 },
275 ReasoningOption {
276 effort: REASONING_HIGH,
277 description: "High xAI reasoning effort",
278 },
279];
280
281pub const XAI_NO_REASONING: &[ReasoningOption] = &[ReasoningOption {
282 effort: REASONING_NONE,
283 description: "No xAI reasoning effort",
284}];
285
286pub const OPENROUTER_REASONING: &[ReasoningOption] = &[
287 ReasoningOption {
288 effort: REASONING_NONE,
289 description: "Disable OpenRouter reasoning controls",
290 },
291 ReasoningOption {
292 effort: REASONING_LOW,
293 description: "Low OpenRouter reasoning effort",
294 },
295 ReasoningOption {
296 effort: REASONING_MEDIUM,
297 description: "Medium OpenRouter reasoning effort",
298 },
299 ReasoningOption {
300 effort: REASONING_HIGH,
301 description: "High OpenRouter reasoning effort",
302 },
303];
304
305pub const RODER_CLOUD_REASONING: &[ReasoningOption] = &[ReasoningOption {
312 effort: REASONING_NONE,
313 description: "roder.cloud forwards no reasoning controls",
314}];
315
316pub const BUILT_IN_PROVIDERS: &[ProviderCatalogEntry] = &[
317 ProviderCatalogEntry {
318 id: PROVIDER_MOCK,
319 name: "Mock",
320 kind: PROVIDER_KIND_MOCK,
321 default_model: "mock",
322 base_url: None,
323 env_key: None,
324 env_aliases: &[],
325 requires_auth: false,
326 supports_websockets: false,
327 },
328 ProviderCatalogEntry {
329 id: PROVIDER_OPENAI,
330 name: "OpenAI",
331 kind: PROVIDER_KIND_OPENAI,
332 default_model: DEFAULT_MODEL_ID,
333 base_url: Some("https://api.openai.com/v1"),
334 env_key: Some("OPENAI_API_KEY"),
335 env_aliases: &[],
336 requires_auth: true,
337 supports_websockets: true,
338 },
339 ProviderCatalogEntry {
340 id: PROVIDER_CODEX,
341 name: "Codex",
342 kind: PROVIDER_KIND_OPENAI,
343 default_model: DEFAULT_MODEL_ID,
344 base_url: Some("https://api.openai.com/v1"),
345 env_key: Some("OPENAI_API_KEY"),
346 env_aliases: &[],
347 requires_auth: true,
348 supports_websockets: true,
349 },
350 ProviderCatalogEntry {
351 id: PROVIDER_ANTHROPIC,
352 name: "Anthropic",
353 kind: PROVIDER_KIND_ANTHROPIC,
354 default_model: "claude-sonnet-4-6",
355 base_url: Some("https://api.anthropic.com"),
356 env_key: Some("ANTHROPIC_API_KEY"),
357 env_aliases: &[],
358 requires_auth: true,
359 supports_websockets: false,
360 },
361 ProviderCatalogEntry {
362 id: PROVIDER_CLAUDE_CODE,
363 name: "Claude Code",
364 kind: PROVIDER_KIND_CLAUDE_CODE,
365 default_model: "sonnet",
366 base_url: None,
367 env_key: None,
368 env_aliases: &["CLAUDE_CODE_CLI_PATH", "RODER_CLAUDE_CODE_CLI_PATH"],
369 requires_auth: false,
370 supports_websockets: false,
371 },
372 ProviderCatalogEntry {
373 id: PROVIDER_GEMINI,
374 name: "Gemini",
375 kind: PROVIDER_KIND_GEMINI,
376 default_model: "gemini-3.5-flash",
377 base_url: None,
378 env_key: Some("GEMINI_API_TOKEN"),
379 env_aliases: GEMINI_ENV_ALIASES,
380 requires_auth: true,
381 supports_websockets: false,
382 },
383 ProviderCatalogEntry {
384 id: PROVIDER_VERTEX,
385 name: "Vertex AI",
386 kind: PROVIDER_KIND_VERTEX,
387 default_model: "gemini-3.5-flash",
388 base_url: None,
389 env_key: Some("GOOGLE_APPLICATION_CREDENTIALS"),
390 env_aliases: VERTEX_ENV_ALIASES,
391 requires_auth: true,
392 supports_websockets: false,
393 },
394 ProviderCatalogEntry {
395 id: PROVIDER_XAI,
396 name: "xAI",
397 kind: PROVIDER_KIND_XAI,
398 default_model: "grok-4.3",
399 base_url: Some("https://api.x.ai/v1"),
400 env_key: Some("XAI_API_KEY"),
401 env_aliases: XAI_ENV_ALIASES,
402 requires_auth: true,
403 supports_websockets: false,
404 },
405 ProviderCatalogEntry {
406 id: PROVIDER_SUPERGROK,
407 name: "SuperGrok",
408 kind: PROVIDER_KIND_XAI,
409 default_model: "grok-build-0.1",
410 base_url: Some("https://api.x.ai/v1"),
411 env_key: None,
412 env_aliases: &[],
413 requires_auth: true,
414 supports_websockets: false,
415 },
416 ProviderCatalogEntry {
417 id: PROVIDER_OPENCODE,
418 name: "OpenCode Zen",
419 kind: PROVIDER_KIND_OPENCODE,
420 default_model: "gpt-5.5",
421 base_url: Some("https://opencode.ai/zen/v1"),
422 env_key: Some("OPENCODE_API_KEY"),
423 env_aliases: &["OPENCODE_ZEN_API_KEY", "RODER_OPENCODE_API_KEY"],
424 requires_auth: true,
425 supports_websockets: false,
426 },
427 ProviderCatalogEntry {
428 id: PROVIDER_OPENCODE_GO,
429 name: "OpenCode Go",
430 kind: PROVIDER_KIND_OPENCODE,
431 default_model: "kimi-k2.6",
432 base_url: Some("https://opencode.ai/zen/go/v1"),
433 env_key: Some("OPENCODE_GO_API_KEY"),
434 env_aliases: &["RODER_OPENCODE_GO_API_KEY", "OPENCODE_API_KEY"],
435 requires_auth: true,
436 supports_websockets: false,
437 },
438 ProviderCatalogEntry {
439 id: PROVIDER_OPENROUTER,
440 name: "OpenRouter",
441 kind: PROVIDER_KIND_OPENROUTER,
442 default_model: "x-ai/grok-build-0.1",
443 base_url: Some("https://openrouter.ai/api/v1"),
444 env_key: Some("OPENROUTER_API_KEY"),
445 env_aliases: &["RODER_OPENROUTER_API_KEY"],
446 requires_auth: true,
447 supports_websockets: false,
448 },
449 ProviderCatalogEntry {
450 id: PROVIDER_FIREWORKS,
451 name: "Fireworks AI",
452 kind: PROVIDER_KIND_FIREWORKS,
453 default_model: "accounts/fireworks/models/qwen3-235b-a22b",
454 base_url: Some("https://api.fireworks.ai/inference/v1"),
455 env_key: Some("FIREWORKS_API_KEY"),
456 env_aliases: &["RODER_FIREWORKS_API_KEY"],
457 requires_auth: true,
458 supports_websockets: false,
459 },
460 ProviderCatalogEntry {
461 id: PROVIDER_RODER_CLOUD,
462 name: "Roder Cloud",
463 kind: PROVIDER_KIND_RODER_CLOUD,
464 default_model: "roder.cloud/free",
465 base_url: None,
469 env_key: Some("RODER_CLOUD_API_KEY"),
470 env_aliases: &["RODER_CLOUD_TOKEN"],
471 requires_auth: true,
472 supports_websockets: false,
473 },
474 ProviderCatalogEntry {
475 id: PROVIDER_POOLSIDE,
476 name: "Poolside",
477 kind: PROVIDER_KIND_POOLSIDE,
478 default_model: "poolside/laguna-m.1",
479 base_url: Some("https://inference.poolside.ai/v1"),
480 env_key: Some("POOLSIDE_API_KEY"),
481 env_aliases: &["RODER_POOLSIDE_API_KEY"],
482 requires_auth: true,
483 supports_websockets: false,
484 },
485 ProviderCatalogEntry {
486 id: PROVIDER_CURSOR,
487 name: "Cursor",
488 kind: PROVIDER_KIND_CURSOR,
489 default_model: "composer-2.5",
490 base_url: Some("https://agentn.global.api5.cursor.sh"),
491 env_key: Some("CURSOR_API_KEY"),
492 env_aliases: &["RODER_CURSOR_API_KEY"],
493 requires_auth: true,
494 supports_websockets: false,
495 },
496 xiaomi_mimo::PAY_AS_YOU_GO_PROVIDER,
497 xiaomi_mimo::TOKEN_PLAN_PROVIDER,
498 ProviderCatalogEntry {
499 id: PROVIDER_KIMI_CODE,
500 name: "Kimi Code",
501 kind: PROVIDER_KIND_CHAT_COMPLETIONS,
502 default_model: "kimi-for-coding",
503 base_url: Some("https://api.kimi.com/coding/v1"),
504 env_key: Some("KIMI_CODE_API_KEY"),
505 env_aliases: &["RODER_KIMI_CODE_API_KEY"],
506 requires_auth: true,
507 supports_websockets: false,
508 },
509];
510
511pub const BUILT_IN_MODELS: &[ModelCatalogEntry] = &[
512 openai_model(
513 "gpt-5.5",
514 "GPT-5.5",
515 "Frontier model for complex coding, research, and real-world work.",
516 1_050_000,
517 945_000,
518 true,
519 STANDARD_REASONING,
520 ),
521 openai_model(
522 "gpt-5.4-mini",
523 "GPT-5.4-Mini",
524 "Small, fast, and cost-efficient model for simpler coding tasks.",
525 400_000,
526 360_000,
527 true,
528 STANDARD_REASONING,
529 ),
530 ModelCatalogEntry {
531 id: "gpt-5.3-codex-spark",
532 display_name: "GPT-5.3-Codex-Spark",
533 description: "Ultra-fast coding model optimized for low-latency Codex workflows.",
534 provider: PROVIDER_CODEX,
535 default_reasoning: REASONING_HIGH,
536 supported_reasoning: STANDARD_REASONING,
537 context_window: 128_000,
538 max_context_window: 128_000,
539 auto_compact_token_limit: 115_200,
540 supports_compaction: true,
541 supports_images: false,
542 supports_tools: true,
543 supports_structured: false,
544 edit_tool: Some("patch"),
545 hidden: false,
546 },
547 ModelCatalogEntry {
548 id: "codex-auto-review",
549 display_name: "Codex Auto Review",
550 description: "Automatic approval review model for Codex.",
551 provider: PROVIDER_OPENAI,
552 default_reasoning: REASONING_MEDIUM,
553 supported_reasoning: STANDARD_REASONING,
554 context_window: 272_000,
555 max_context_window: 272_000,
556 auto_compact_token_limit: 244_800,
557 supports_compaction: false,
558 supports_images: false,
559 supports_tools: true,
560 supports_structured: false,
561 edit_tool: Some("patch"),
562 hidden: true,
563 },
564 anthropic_model(
565 "claude-fable-5",
566 "Claude Fable 5",
567 "Anthropic's most powerful, most intelligent model; a new tier above Opus for frontier reasoning and agentic work.",
568 1_000_000,
569 900_000,
570 REASONING_HIGH,
571 OPUS_REASONING,
572 true,
573 ),
574 anthropic_model(
575 "claude-opus-4-8",
576 "Claude Opus 4.8",
577 "Anthropic's most capable Opus-tier model for complex reasoning, long-horizon agentic coding, and high-autonomy work.",
578 1_000_000,
579 900_000,
580 REASONING_HIGH,
581 OPUS_REASONING,
582 true,
583 ),
584 anthropic_model(
585 "claude-opus-4-7",
586 "Claude Opus 4.7",
587 "Most capable Claude model for complex reasoning and agentic coding.",
588 1_000_000,
589 900_000,
590 REASONING_HIGH,
591 OPUS_REASONING,
592 true,
593 ),
594 anthropic_model(
595 "claude-sonnet-4-6",
596 "Claude Sonnet 4.6",
597 "Balanced Claude model for coding, tool use, and everyday agent workflows.",
598 1_000_000,
599 900_000,
600 REASONING_MEDIUM,
601 SONNET_REASONING,
602 true,
603 ),
604 anthropic_model(
605 "claude-haiku-4-5-20251001",
606 "Claude Haiku 4.5",
607 "Fast Claude model for lower-latency tool workflows.",
608 200_000,
609 180_000,
610 REASONING_NONE,
611 &[],
612 false,
614 ),
615 claude_code_model(
616 "fable",
617 "Claude Code Fable",
618 "Claude Code harness Fable alias for the most powerful frontier model.",
619 1_000_000,
620 900_000,
621 REASONING_HIGH,
622 OPUS_REASONING,
623 ),
624 claude_code_model(
625 "sonnet",
626 "Claude Code Sonnet",
627 "Claude Code harness Sonnet alias for coding and tool workflows.",
628 1_000_000,
629 900_000,
630 REASONING_MEDIUM,
631 SONNET_REASONING,
632 ),
633 claude_code_model(
634 "opus",
635 "Claude Code Opus",
636 "Claude Code harness Opus alias for complex long-horizon agentic work.",
637 1_000_000,
638 900_000,
639 REASONING_HIGH,
640 OPUS_REASONING,
641 ),
642 claude_code_model(
643 "haiku",
644 "Claude Code Haiku",
645 "Claude Code harness Haiku alias for fast lower-latency coding turns.",
646 200_000,
647 180_000,
648 REASONING_NONE,
649 &[],
650 ),
651 claude_code_model(
652 "claude-sonnet-4-6",
653 "Claude Code Sonnet 4.6",
654 "Claude Sonnet 4.6 through the local Claude Code harness.",
655 1_000_000,
656 900_000,
657 REASONING_MEDIUM,
658 SONNET_REASONING,
659 ),
660 claude_code_model(
661 "claude-opus-4-8",
662 "Claude Code Opus 4.8",
663 "Claude Opus 4.8 through the local Claude Code harness.",
664 1_000_000,
665 900_000,
666 REASONING_HIGH,
667 OPUS_REASONING,
668 ),
669 claude_code_model(
670 "claude-fable-5",
671 "Claude Code Fable 5",
672 "Claude Fable 5 through the local Claude Code harness.",
673 1_000_000,
674 900_000,
675 REASONING_HIGH,
676 OPUS_REASONING,
677 ),
678 gemini_model(
679 PROVIDER_GEMINI,
680 "gemini-3.5-flash",
681 "Gemini 3.5 Flash",
682 "Stable Gemini Flash model for agentic coding, tool use, and long-horizon workflows.",
683 REASONING_MEDIUM,
684 ),
685 gemini_model(
686 PROVIDER_GEMINI,
687 "gemini-3.1-pro-preview",
688 "Gemini 3.1 Pro Preview",
689 "Gemini model for complex coding, long context, and tool-heavy agent workflows.",
690 REASONING_HIGH,
691 ),
692 gemini_model(
693 PROVIDER_GEMINI,
694 "gemini-3.1-pro-preview-customtools",
695 "Gemini 3.1 Pro Preview Custom Tools",
696 "Gemini preview variant exposed for custom tool validation and tool-heavy coding workflows.",
697 REASONING_HIGH,
698 ),
699 gemini_model(
700 PROVIDER_GEMINI,
701 "gemini-3-flash-preview",
702 "Gemini 3 Flash Preview",
703 "Fast Gemini model for everyday coding, tool use, and multimodal prompts.",
704 REASONING_MEDIUM,
705 ),
706 gemini_model(
707 PROVIDER_GEMINI,
708 "gemini-3.1-flash-lite-preview",
709 "Gemini 3.1 Flash-Lite Preview",
710 "Lightweight Gemini model for low-latency coding and agent interactions.",
711 REASONING_LOW,
712 ),
713 gemini_model(
714 PROVIDER_VERTEX,
715 "gemini-3.5-flash",
716 "Gemini 3.5 Flash",
717 "Stable Gemini Flash model on Vertex AI for agentic coding, tool use, and long-horizon workflows.",
718 REASONING_MEDIUM,
719 ),
720 gemini_model(
721 PROVIDER_VERTEX,
722 "gemini-3.1-pro-preview",
723 "Gemini 3.1 Pro Preview",
724 "Gemini model on Vertex AI for complex coding, long context, and tool-heavy agent workflows.",
725 REASONING_HIGH,
726 ),
727 gemini_model(
728 PROVIDER_VERTEX,
729 "gemini-3-flash-preview",
730 "Gemini 3 Flash Preview",
731 "Fast Gemini model on Vertex AI for everyday coding, tool use, and multimodal prompts.",
732 REASONING_MEDIUM,
733 ),
734 gemini_model(
735 PROVIDER_VERTEX,
736 "gemini-3.1-flash-lite-preview",
737 "Gemini 3.1 Flash-Lite Preview",
738 "Lightweight Gemini model on Vertex AI for low-latency coding and agent interactions.",
739 REASONING_LOW,
740 ),
741 xai_model(
742 PROVIDER_XAI,
743 "grok-4.3",
744 "Grok 4.3",
745 "xAI flagship model for chat, coding, tool use, and configurable reasoning.",
746 1_000_000,
747 REASONING_LOW,
748 XAI_CONFIGURABLE_REASONING,
749 false,
750 ),
751 xai_model(
752 PROVIDER_XAI,
753 "grok-4.20-multi-agent-0309",
754 "Grok 4.20 Multi-Agent",
755 "xAI long-context model with agentic tool-calling and reasoning.",
756 2_000_000,
757 REASONING_LOW,
758 XAI_REASONING,
759 false,
760 ),
761 xai_model(
762 PROVIDER_XAI,
763 "grok-4.20-0309-reasoning",
764 "Grok 4.20 Reasoning",
765 "xAI long-context reasoning model for complex tool-heavy workflows.",
766 2_000_000,
767 REASONING_LOW,
768 XAI_REASONING,
769 false,
770 ),
771 xai_model(
772 PROVIDER_XAI,
773 "grok-4.20-0309-non-reasoning",
774 "Grok 4.20 Non-Reasoning",
775 "xAI long-context model for lower-latency non-reasoning workflows.",
776 2_000_000,
777 REASONING_NONE,
778 XAI_NO_REASONING,
779 false,
780 ),
781 xai_model(
782 PROVIDER_SUPERGROK,
783 "grok-build-0.1",
784 "Grok Build 0.1",
785 "SuperGrok OAuth access to xAI Grok Build, optimized for agentic coding and software engineering workflows.",
786 500_000,
787 REASONING_LOW,
788 XAI_CONFIGURABLE_REASONING,
789 false,
790 ),
791 xai_model(
792 PROVIDER_SUPERGROK,
793 "grok-composer-2.5-fast",
794 "Grok Composer 2.5 Fast",
795 "SuperGrok OAuth access to xAI Composer 2.5 Fast for lower-latency agentic coding.",
796 200_000,
797 REASONING_NONE,
798 &[],
799 false,
800 ),
801 xai_model(
802 PROVIDER_SUPERGROK,
803 "grok-4.3",
804 "Grok 4.3",
805 "SuperGrok OAuth access to xAI Grok 4.3.",
806 1_000_000,
807 REASONING_LOW,
808 XAI_CONFIGURABLE_REASONING,
809 true,
810 ),
811 xai_model(
812 PROVIDER_SUPERGROK,
813 "grok-4.20-multi-agent-0309",
814 "Grok 4.20 Multi-Agent",
815 "SuperGrok OAuth access to xAI's long-context multi-agent model.",
816 2_000_000,
817 REASONING_LOW,
818 XAI_REASONING,
819 true,
820 ),
821 xai_model(
822 PROVIDER_SUPERGROK,
823 "grok-4.20-0309-reasoning",
824 "Grok 4.20 Reasoning",
825 "SuperGrok OAuth access to xAI's long-context reasoning model.",
826 2_000_000,
827 REASONING_LOW,
828 XAI_REASONING,
829 true,
830 ),
831 xai_model(
832 PROVIDER_SUPERGROK,
833 "grok-4.20-0309-non-reasoning",
834 "Grok 4.20 Non-Reasoning",
835 "SuperGrok OAuth access to xAI's long-context non-reasoning model.",
836 2_000_000,
837 REASONING_NONE,
838 XAI_NO_REASONING,
839 true,
840 ),
841 opencode_model(
842 PROVIDER_OPENCODE,
843 "gpt-5.5",
844 "GPT 5.5",
845 "OpenCode Zen GPT 5.5 gateway model.",
846 1_050_000,
847 REASONING_MEDIUM,
848 STANDARD_REASONING,
849 ),
850 opencode_model(
851 PROVIDER_OPENCODE,
852 "gpt-5.3-codex-spark",
853 "GPT 5.3 Codex Spark",
854 "OpenCode Zen low-latency Codex model.",
855 128_000,
856 REASONING_HIGH,
857 STANDARD_REASONING,
858 ),
859 opencode_model(
860 PROVIDER_OPENCODE,
861 "big-pickle",
862 "Big Pickle",
863 "OpenCode Zen free coding model.",
864 256_000,
865 REASONING_NONE,
866 &[],
867 ),
868 opencode_model(
869 PROVIDER_OPENCODE,
870 "deepseek-v4-flash-free",
871 "DeepSeek V4 Flash Free",
872 "OpenCode Zen free DeepSeek coding model.",
873 128_000,
874 REASONING_NONE,
875 &[],
876 ),
877 opencode_model(
878 PROVIDER_OPENCODE,
879 "minimax-m2.5-free",
880 "MiniMax M2.5 Free",
881 "OpenCode Zen free MiniMax coding model.",
882 256_000,
883 REASONING_NONE,
884 &[],
885 ),
886 opencode_model(
887 PROVIDER_OPENCODE,
888 "nemotron-3-super-free",
889 "Nemotron 3 Super Free",
890 "OpenCode Zen free Nemotron coding model.",
891 128_000,
892 REASONING_NONE,
893 &[],
894 ),
895 opencode_model(
896 PROVIDER_OPENCODE_GO,
897 "kimi-k2.6",
898 "Kimi K2.6",
899 "OpenCode Go Kimi coding model.",
900 256_000,
901 REASONING_NONE,
902 &[],
903 ),
904 opencode_model(
905 PROVIDER_OPENCODE_GO,
906 "qwen3.6-plus",
907 "Qwen3.6 Plus",
908 "OpenCode Go Qwen coding model.",
909 256_000,
910 REASONING_NONE,
911 &[],
912 ),
913 opencode_model(
914 PROVIDER_OPENCODE_GO,
915 "glm-5.1",
916 "GLM-5.1",
917 "OpenCode Go GLM coding model.",
918 256_000,
919 REASONING_NONE,
920 &[],
921 ),
922 opencode_model(
923 PROVIDER_OPENCODE_GO,
924 "deepseek-v4-flash",
925 "DeepSeek V4 Flash",
926 "OpenCode Go DeepSeek coding model.",
927 128_000,
928 REASONING_NONE,
929 &[],
930 ),
931 opencode_model(
932 PROVIDER_KIMI_CODE,
933 "kimi-for-coding",
934 "K2.7 Code",
935 "Kimi Code subscription coding model (OAuth via api.kimi.com/coding/v1).",
936 262_144,
937 REASONING_NONE,
938 &[],
939 ),
940 ModelCatalogEntry {
941 id: "x-ai/grok-build-0.1",
942 display_name: "Grok Build 0.1",
943 description: "OpenRouter route for xAI's fast coding model for agentic software engineering workflows.",
944 provider: PROVIDER_OPENROUTER,
945 default_reasoning: REASONING_LOW,
946 supported_reasoning: OPENROUTER_REASONING,
947 context_window: 256_000,
948 max_context_window: 256_000,
949 auto_compact_token_limit: 230_400,
950 supports_compaction: true,
951 supports_images: true,
952 supports_tools: true,
953 supports_structured: true,
954 edit_tool: Some(EDIT_TOOL_PATCH),
955 hidden: false,
956 },
957 ModelCatalogEntry {
958 id: "accounts/fireworks/models/qwen3-235b-a22b",
959 display_name: "Qwen3 235B A22B",
960 description: "Fireworks Responses-capable serverless model with client-executed function tool support.",
961 provider: PROVIDER_FIREWORKS,
962 default_reasoning: REASONING_NONE,
963 supported_reasoning: &[],
964 context_window: 131_072,
965 max_context_window: 131_072,
966 auto_compact_token_limit: 0,
967 supports_compaction: false,
968 supports_images: false,
969 supports_tools: true,
970 supports_structured: true,
971 edit_tool: Some(EDIT_TOOL_PATCH),
972 hidden: false,
973 },
974 roder_cloud_model(
975 "roder.cloud/free",
976 "Roder Free",
977 "Free hosted model on roder.cloud.",
978 32_768,
979 ),
980 roder_cloud_model(
981 "roder.cloud/openai/gpt-5.5",
982 "GPT-5.5 (Roder Cloud)",
983 "roder.cloud hosted route for OpenAI GPT-5.5.",
984 400_000,
985 ),
986 roder_cloud_model(
987 "roder.cloud/anthropic/claude-opus-4-7",
988 "Claude Opus 4.7 (Roder Cloud)",
989 "roder.cloud hosted route for Anthropic Claude Opus 4.7.",
990 200_000,
991 ),
992 roder_cloud_model(
993 "roder.cloud/google/gemini-3.1-pro-preview",
994 "Gemini 3.1 Pro (Roder Cloud)",
995 "roder.cloud hosted route for Google Gemini 3.1 Pro Preview.",
996 200_000,
997 ),
998 poolside_model(
999 "poolside/laguna-m.1",
1000 "Laguna M.1",
1001 "Poolside flagship agentic coding model.",
1002 REASONING_MEDIUM,
1003 ),
1004 poolside_model(
1005 "poolside/laguna-xs.2",
1006 "Laguna XS.2",
1007 "Poolside lightweight agentic coding model.",
1008 REASONING_MEDIUM,
1009 ),
1010 xiaomi_mimo::PAYG_V25_PRO,
1011 xiaomi_mimo::PAYG_V2_PRO,
1012 xiaomi_mimo::PAYG_V25,
1013 xiaomi_mimo::PAYG_V2_OMNI,
1014 xiaomi_mimo::PAYG_V2_FLASH,
1015 xiaomi_mimo::TOKEN_PLAN_V25_PRO,
1016 xiaomi_mimo::TOKEN_PLAN_V2_PRO,
1017 xiaomi_mimo::TOKEN_PLAN_V25,
1018 xiaomi_mimo::TOKEN_PLAN_V2_OMNI,
1019 xiaomi_mimo::TOKEN_PLAN_V2_FLASH,
1020 ModelCatalogEntry {
1021 id: "composer-2.5",
1022 display_name: "Composer 2.5",
1023 description: "Cursor Composer model exposed through direct AgentService inference.",
1024 provider: PROVIDER_CURSOR,
1025 default_reasoning: REASONING_NONE,
1026 supported_reasoning: &[],
1027 context_window: 200_000,
1028 max_context_window: 200_000,
1029 auto_compact_token_limit: 180_000,
1030 supports_compaction: true,
1031 supports_images: false,
1032 supports_tools: false,
1033 supports_structured: false,
1034 edit_tool: None,
1035 hidden: false,
1036 },
1037 cursor_model(
1038 "claude-opus-4-8",
1039 "Claude Opus 4.8",
1040 "Anthropic Claude Opus 4.8 routed through Cursor's AgentService.",
1041 1_000_000,
1042 900_000,
1043 REASONING_HIGH,
1044 OPUS_REASONING,
1045 ),
1046 cursor_model(
1047 "claude-sonnet-4-6",
1048 "Claude Sonnet 4.6",
1049 "Anthropic Claude Sonnet 4.6 routed through Cursor's AgentService.",
1050 1_000_000,
1051 900_000,
1052 REASONING_NONE,
1053 &[],
1054 ),
1055 cursor_model(
1056 "gpt-5.5",
1057 "GPT-5.5",
1058 "OpenAI GPT-5.5 routed through Cursor's AgentService.",
1059 1_050_000,
1060 945_000,
1061 REASONING_NONE,
1062 &[],
1063 ),
1064 cursor_model(
1065 "gemini-3.1-pro-preview",
1066 "Gemini 3.1 Pro",
1067 "Google Gemini 3.1 Pro routed through Cursor's AgentService.",
1068 1_048_576,
1069 943_718,
1070 REASONING_NONE,
1071 &[],
1072 ),
1073 cursor_model(
1074 "grok-4.3",
1075 "Grok 4.3",
1076 "xAI Grok 4.3 routed through Cursor's AgentService.",
1077 1_000_000,
1078 900_000,
1079 REASONING_NONE,
1080 &[],
1081 ),
1082 ModelCatalogEntry {
1083 id: "text-embedding-3-large",
1084 display_name: "Text Embedding 3 Large",
1085 description: "OpenAI embedding model for local semantic memories.",
1086 provider: PROVIDER_OPENAI,
1087 default_reasoning: REASONING_NONE,
1088 supported_reasoning: &[],
1089 context_window: 0,
1090 max_context_window: 0,
1091 auto_compact_token_limit: 0,
1092 supports_compaction: false,
1093 supports_images: false,
1094 supports_tools: true,
1095 supports_structured: false,
1096 edit_tool: None,
1097 hidden: true,
1098 },
1099 ModelCatalogEntry {
1100 id: "gemini-embedding-2",
1101 display_name: "Gemini Embedding 2",
1102 description: "Google Gemini embedding model for local semantic memories.",
1103 provider: PROVIDER_GOOGLE,
1104 default_reasoning: REASONING_NONE,
1105 supported_reasoning: &[],
1106 context_window: 0,
1107 max_context_window: 0,
1108 auto_compact_token_limit: 0,
1109 supports_compaction: false,
1110 supports_images: false,
1111 supports_tools: false,
1112 supports_structured: false,
1113 edit_tool: None,
1114 hidden: true,
1115 },
1116 ModelCatalogEntry {
1117 id: "zembed-1",
1118 display_name: "ZeroEntropy zembed-1",
1119 description: "ZeroEntropy embedding model for local semantic memories.",
1120 provider: PROVIDER_ZEROENTROPY,
1121 default_reasoning: REASONING_NONE,
1122 supported_reasoning: &[],
1123 context_window: 0,
1124 max_context_window: 0,
1125 auto_compact_token_limit: 0,
1126 supports_compaction: false,
1127 supports_images: false,
1128 supports_tools: false,
1129 supports_structured: false,
1130 edit_tool: None,
1131 hidden: true,
1132 },
1133 ModelCatalogEntry {
1134 id: "mock",
1135 display_name: "Mock",
1136 description: "Local deterministic mock provider for tests and offline development.",
1137 provider: PROVIDER_MOCK,
1138 default_reasoning: REASONING_NONE,
1139 supported_reasoning: MOCK_REASONING,
1140 context_window: 128_000,
1141 max_context_window: 128_000,
1142 auto_compact_token_limit: 115_200,
1143 supports_compaction: false,
1144 supports_images: false,
1145 supports_tools: true,
1146 supports_structured: false,
1147 edit_tool: None,
1148 hidden: true,
1149 },
1150];
1151
1152const fn openai_model(
1153 id: &'static str,
1154 display_name: &'static str,
1155 description: &'static str,
1156 context_window: u32,
1157 auto_compact_token_limit: u32,
1158 supports_compaction: bool,
1159 supported_reasoning: &'static [ReasoningOption],
1160) -> ModelCatalogEntry {
1161 ModelCatalogEntry {
1162 id,
1163 display_name,
1164 description,
1165 provider: PROVIDER_OPENAI,
1166 default_reasoning: REASONING_MEDIUM,
1167 supported_reasoning,
1168 context_window,
1169 max_context_window: context_window,
1170 auto_compact_token_limit,
1171 supports_compaction,
1172 supports_images: false,
1173 supports_tools: true,
1174 supports_structured: false,
1175 edit_tool: Some("patch"),
1176 hidden: false,
1177 }
1178}
1179
1180#[allow(clippy::too_many_arguments)]
1181const fn anthropic_model(
1182 id: &'static str,
1183 display_name: &'static str,
1184 description: &'static str,
1185 context_window: u32,
1186 auto_compact_token_limit: u32,
1187 default_reasoning: &'static str,
1188 supported_reasoning: &'static [ReasoningOption],
1189 supports_compaction: bool,
1200) -> ModelCatalogEntry {
1201 ModelCatalogEntry {
1202 id,
1203 display_name,
1204 description,
1205 provider: PROVIDER_ANTHROPIC,
1206 default_reasoning,
1207 supported_reasoning,
1208 context_window,
1209 max_context_window: context_window,
1210 auto_compact_token_limit,
1211 supports_compaction,
1212 supports_images: false,
1213 supports_tools: true,
1214 supports_structured: false,
1215 edit_tool: Some("edit"),
1216 hidden: false,
1217 }
1218}
1219
1220const fn claude_code_model(
1221 id: &'static str,
1222 display_name: &'static str,
1223 description: &'static str,
1224 context_window: u32,
1225 auto_compact_token_limit: u32,
1226 default_reasoning: &'static str,
1227 supported_reasoning: &'static [ReasoningOption],
1228) -> ModelCatalogEntry {
1229 ModelCatalogEntry {
1230 id,
1231 display_name,
1232 description,
1233 provider: PROVIDER_CLAUDE_CODE,
1234 default_reasoning,
1235 supported_reasoning,
1236 context_window,
1237 max_context_window: context_window,
1238 auto_compact_token_limit,
1239 supports_compaction: false,
1245 supports_images: false,
1246 supports_tools: true,
1247 supports_structured: false,
1248 edit_tool: Some(EDIT_TOOL_EDIT),
1249 hidden: false,
1250 }
1251}
1252
1253const fn gemini_model(
1254 provider: &'static str,
1255 id: &'static str,
1256 display_name: &'static str,
1257 description: &'static str,
1258 default_reasoning: &'static str,
1259) -> ModelCatalogEntry {
1260 ModelCatalogEntry {
1261 id,
1262 display_name,
1263 description,
1264 provider,
1265 default_reasoning,
1266 supported_reasoning: GEMINI_REASONING,
1267 context_window: 1_048_576,
1268 max_context_window: 1_048_576,
1269 auto_compact_token_limit: 943_718,
1270 supports_compaction: false,
1271 supports_images: true,
1272 supports_tools: true,
1273 supports_structured: true,
1274 edit_tool: Some("edit"),
1275 hidden: false,
1276 }
1277}
1278
1279const fn xai_model(
1280 provider: &'static str,
1281 id: &'static str,
1282 display_name: &'static str,
1283 description: &'static str,
1284 context_window: u32,
1285 default_reasoning: &'static str,
1286 supported_reasoning: &'static [ReasoningOption],
1287 hidden: bool,
1288) -> ModelCatalogEntry {
1289 ModelCatalogEntry {
1290 id,
1291 display_name,
1292 description,
1293 provider,
1294 default_reasoning,
1295 supported_reasoning,
1296 context_window,
1297 max_context_window: context_window,
1298 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1299 supports_compaction: false,
1300 supports_images: true,
1301 supports_tools: true,
1302 supports_structured: true,
1303 edit_tool: Some("edit"),
1304 hidden,
1305 }
1306}
1307
1308const fn opencode_model(
1309 provider: &'static str,
1310 id: &'static str,
1311 display_name: &'static str,
1312 description: &'static str,
1313 context_window: u32,
1314 default_reasoning: &'static str,
1315 supported_reasoning: &'static [ReasoningOption],
1316) -> ModelCatalogEntry {
1317 ModelCatalogEntry {
1318 id,
1319 display_name,
1320 description,
1321 provider,
1322 default_reasoning,
1323 supported_reasoning,
1324 context_window,
1325 max_context_window: context_window,
1326 auto_compact_token_limit: context_window.saturating_mul(9) / 10,
1327 supports_compaction: false,
1328 supports_images: false,
1329 supports_tools: true,
1330 supports_structured: true,
1331 edit_tool: Some("edit"),
1332 hidden: false,
1333 }
1334}
1335
1336const fn roder_cloud_model(
1337 id: &'static str,
1338 display_name: &'static str,
1339 description: &'static str,
1340 context_window: u32,
1341) -> ModelCatalogEntry {
1342 ModelCatalogEntry {
1343 id,
1344 display_name,
1345 description,
1346 provider: PROVIDER_RODER_CLOUD,
1347 default_reasoning: REASONING_NONE,
1348 supported_reasoning: RODER_CLOUD_REASONING,
1349 context_window,
1350 max_context_window: context_window,
1351 auto_compact_token_limit: 0,
1352 supports_compaction: false,
1353 supports_images: false,
1354 supports_tools: false,
1355 supports_structured: false,
1356 edit_tool: None,
1357 hidden: false,
1358 }
1359}
1360
1361const fn poolside_model(
1362 id: &'static str,
1363 display_name: &'static str,
1364 description: &'static str,
1365 default_reasoning: &'static str,
1366) -> ModelCatalogEntry {
1367 ModelCatalogEntry {
1368 id,
1369 display_name,
1370 description,
1371 provider: PROVIDER_POOLSIDE,
1372 default_reasoning,
1373 supported_reasoning: POOLSIDE_REASONING,
1374 context_window: 131_072,
1375 max_context_window: 131_072,
1376 auto_compact_token_limit: 117_964,
1377 supports_compaction: false,
1378 supports_images: false,
1379 supports_tools: true,
1380 supports_structured: true,
1381 edit_tool: Some("edit"),
1382 hidden: false,
1383 }
1384}
1385
1386const fn cursor_model(
1387 id: &'static str,
1388 display_name: &'static str,
1389 description: &'static str,
1390 context_window: u32,
1391 auto_compact_token_limit: u32,
1392 default_reasoning: &'static str,
1393 supported_reasoning: &'static [ReasoningOption],
1394) -> ModelCatalogEntry {
1395 ModelCatalogEntry {
1396 id,
1397 display_name,
1398 description,
1399 provider: PROVIDER_CURSOR,
1400 default_reasoning,
1401 supported_reasoning,
1402 context_window,
1403 max_context_window: context_window,
1404 auto_compact_token_limit,
1405 supports_compaction: true,
1406 supports_images: true,
1410 supports_tools: false,
1411 supports_structured: false,
1412 edit_tool: None,
1413 hidden: false,
1414 }
1415}
1416
1417pub fn built_in_providers() -> &'static [ProviderCatalogEntry] {
1418 BUILT_IN_PROVIDERS
1419}
1420
1421pub fn built_in_models(include_hidden: bool) -> Vec<&'static ModelCatalogEntry> {
1422 BUILT_IN_MODELS
1423 .iter()
1424 .filter(|model| include_hidden || !model.hidden)
1425 .collect()
1426}
1427
1428pub fn models_for_provider(provider: &str, include_hidden: bool) -> Vec<ModelDescriptor> {
1429 built_in_models(include_hidden)
1430 .into_iter()
1431 .filter(|model| model.provider == provider)
1432 .map(ModelDescriptor::from)
1433 .collect()
1434}
1435
1436pub fn models_for_codex(include_hidden: bool) -> Vec<ModelDescriptor> {
1437 built_in_models(include_hidden)
1438 .into_iter()
1439 .filter(|model| model.provider == PROVIDER_OPENAI || model.provider == PROVIDER_CODEX)
1440 .map(ModelDescriptor::from)
1441 .collect()
1442}
1443
1444pub fn lookup_model(id: &str) -> Option<&'static ModelCatalogEntry> {
1445 BUILT_IN_MODELS.iter().find(|model| model.id == id)
1446}
1447
1448pub fn lookup_model_for_provider(provider: &str, id: &str) -> Option<&'static ModelCatalogEntry> {
1458 BUILT_IN_MODELS
1459 .iter()
1460 .find(|model| model.provider == provider && model.id == id)
1461 .or_else(|| lookup_model(id))
1462}
1463
1464pub fn built_in_model_profile(id: &str) -> Option<ModelHarnessProfile> {
1465 lookup_model(id).map(model_harness_profile_from_catalog)
1466}
1467
1468pub fn built_in_model_profile_for_provider(
1474 provider: &str,
1475 id: &str,
1476) -> Option<ModelHarnessProfile> {
1477 lookup_model_for_provider(provider, id).map(model_harness_profile_from_catalog)
1478}
1479
1480pub fn built_in_model_profiles() -> Vec<ModelHarnessProfile> {
1481 built_in_models(true)
1482 .into_iter()
1483 .map(model_harness_profile_from_catalog)
1484 .collect()
1485}
1486
1487fn model_harness_profile_from_catalog(model: &ModelCatalogEntry) -> ModelHarnessProfile {
1488 let provider_family = provider_family_for_provider(model.provider);
1489 ModelHarnessProfile {
1490 model: model.id.to_string(),
1491 provider: model.provider.to_string(),
1492 provider_family,
1493 edit_tool: model.edit_tool.map(str::to_string),
1494 schema_policy: schema_policy_for_family(provider_family),
1495 instruction_overlay: instruction_overlay_for_family(provider_family),
1496 reasoning: ModelProfileReasoning {
1497 orientation: Some(model.default_reasoning.to_string()),
1498 execution: Some(default_execution_reasoning(model)),
1499 verification: Some(model.default_reasoning.to_string()),
1500 recovery: Some(model.default_reasoning.to_string()),
1501 },
1502 parallel_tool_calls: Some(
1503 model.supports_tools
1504 && matches!(
1505 provider_family,
1506 ProviderFamily::OpenAi | ProviderFamily::Xai | ProviderFamily::Opencode
1507 ),
1508 ),
1509 auto_compact_token_limit: (model.auto_compact_token_limit > 0)
1510 .then_some(model.auto_compact_token_limit),
1511 }
1512}
1513
1514pub fn provider_family_for_provider(provider: &str) -> ProviderFamily {
1515 match provider {
1516 PROVIDER_OPENAI | PROVIDER_CODEX => ProviderFamily::OpenAi,
1517 PROVIDER_ANTHROPIC | PROVIDER_CLAUDE_CODE => ProviderFamily::Anthropic,
1518 PROVIDER_GEMINI | PROVIDER_VERTEX => ProviderFamily::Gemini,
1519 PROVIDER_XAI | PROVIDER_SUPERGROK => ProviderFamily::Xai,
1520 PROVIDER_OPENCODE | PROVIDER_OPENCODE_GO => ProviderFamily::Opencode,
1521 PROVIDER_OPENROUTER | PROVIDER_FIREWORKS | PROVIDER_RODER_CLOUD => ProviderFamily::OpenAi,
1522 PROVIDER_POOLSIDE => ProviderFamily::Poolside,
1523 PROVIDER_CURSOR => ProviderFamily::Cursor,
1524 PROVIDER_XIAOMI_MIMO | PROVIDER_XIAOMI_MIMO_TOKEN_PLAN => ProviderFamily::OpenAi,
1525 PROVIDER_KIMI_CODE => ProviderFamily::OpenAi,
1526 _ => ProviderFamily::Mock,
1527 }
1528}
1529
1530fn schema_policy_for_family(family: ProviderFamily) -> ModelSchemaPolicy {
1531 match family {
1532 ProviderFamily::OpenAi => ModelSchemaPolicy::RequiredFirstFlat,
1533 _ => ModelSchemaPolicy::StandardRequiredFirst,
1534 }
1535}
1536
1537fn instruction_overlay_for_family(family: ProviderFamily) -> ModelInstructionOverlay {
1538 match family {
1539 ProviderFamily::OpenAi => ModelInstructionOverlay::LiteralToolOutputs,
1540 ProviderFamily::Anthropic | ProviderFamily::Gemini => {
1541 ModelInstructionOverlay::IntuitiveContext
1542 }
1543 _ => ModelInstructionOverlay::Standard,
1544 }
1545}
1546
1547fn default_execution_reasoning(model: &ModelCatalogEntry) -> String {
1548 if model
1549 .supported_reasoning
1550 .iter()
1551 .any(|option| option.effort == REASONING_LOW)
1552 {
1553 REASONING_LOW.to_string()
1554 } else {
1555 model.default_reasoning.to_string()
1556 }
1557}
1558
1559pub fn model_supports_reasoning_effort(model: &str, effort: &str) -> bool {
1560 lookup_model(model)
1561 .map(|entry| {
1562 entry
1563 .supported_reasoning
1564 .iter()
1565 .any(|option| option.effort == effort)
1566 })
1567 .unwrap_or(false)
1568}
1569
1570pub fn normalize_provider_id(provider: &str) -> String {
1571 match provider.trim().to_ascii_lowercase().as_str() {
1572 "grok" | "x-ai" | "x.ai" => PROVIDER_XAI.to_string(),
1573 "grok-oauth" | "xai-oauth" | "x-ai-oauth" | "xai-grok-oauth" => {
1574 PROVIDER_SUPERGROK.to_string()
1575 }
1576 "opencode" => PROVIDER_OPENCODE.to_string(),
1577 "go" | "opencode_go" | "opencode-go" => PROVIDER_OPENCODE_GO.to_string(),
1578 "openrouter" => PROVIDER_OPENROUTER.to_string(),
1579 "fireworks" | "fireworks-ai" | "fireworks_ai" => PROVIDER_FIREWORKS.to_string(),
1580 "roder-cloud" | "roder_cloud" | "rodercloud" | "roder.cloud" => {
1581 PROVIDER_RODER_CLOUD.to_string()
1582 }
1583 "laguna" | "poolside" => PROVIDER_POOLSIDE.to_string(),
1584 "composer" | "cursor-composer" => PROVIDER_CURSOR.to_string(),
1585 "claude_code" | "claudecode" => PROVIDER_CLAUDE_CODE.to_string(),
1586 "kimi" | "kimi-code" | "kimi_code" | "moonshot" => PROVIDER_KIMI_CODE.to_string(),
1587 provider => provider.to_string(),
1588 }
1589}
1590
1591impl From<&ModelCatalogEntry> for ModelDescriptor {
1592 fn from(model: &ModelCatalogEntry) -> Self {
1593 let supported_reasoning = model
1594 .supported_reasoning
1595 .iter()
1596 .map(|option| ReasoningEffortDescriptor {
1597 effort: option.effort.to_string(),
1598 description: option.description.to_string(),
1599 })
1600 .collect::<Vec<_>>();
1601 Self {
1602 id: model.id.to_string(),
1603 name: model.display_name.to_string(),
1604 context_window: (model.context_window > 0).then_some(model.context_window),
1605 default_reasoning: (!supported_reasoning.is_empty())
1606 .then(|| model.default_reasoning.to_string()),
1607 supported_reasoning,
1608 }
1609 }
1610}
1611
1612#[cfg(test)]
1613mod tests {
1614 use super::*;
1615
1616 #[test]
1617 fn catalog_contains_gode_providers() {
1618 let ids = BUILT_IN_PROVIDERS
1619 .iter()
1620 .map(|provider| provider.id)
1621 .collect::<Vec<_>>();
1622 assert_eq!(
1623 ids,
1624 vec![
1625 "mock",
1626 "openai",
1627 "codex",
1628 "anthropic",
1629 "claude-code",
1630 "gemini",
1631 "vertex",
1632 "xai",
1633 "supergrok",
1634 "opencode",
1635 "opencode-go",
1636 "openrouter",
1637 "fireworks",
1638 "roder-cloud",
1639 "poolside",
1640 "cursor",
1641 "xiaomi-mimo",
1642 "xiaomi-mimo-token-plan",
1643 "kimi-code"
1644 ]
1645 );
1646 }
1647
1648 #[test]
1649 fn gemini_provider_defaults_to_stable_35_flash() {
1650 let provider = BUILT_IN_PROVIDERS
1651 .iter()
1652 .find(|provider| provider.id == PROVIDER_GEMINI)
1653 .unwrap();
1654
1655 assert_eq!(provider.default_model, "gemini-3.5-flash");
1656
1657 let model = lookup_model("gemini-3.5-flash").unwrap();
1658 assert_eq!(model.display_name, "Gemini 3.5 Flash");
1659 assert_eq!(model.provider, PROVIDER_GEMINI);
1660 assert_eq!(model.context_window, 1_048_576);
1661 assert_eq!(model.default_reasoning, REASONING_MEDIUM);
1662 assert!(model.supports_tools);
1663 assert!(model.supports_structured);
1664 assert_eq!(
1665 model
1666 .supported_reasoning
1667 .iter()
1668 .map(|option| option.effort)
1669 .collect::<Vec<_>>(),
1670 vec![
1671 REASONING_MINIMAL,
1672 REASONING_LOW,
1673 REASONING_MEDIUM,
1674 REASONING_HIGH
1675 ]
1676 );
1677 }
1678
1679 #[test]
1680 fn vertex_provider_mirrors_gemini_models_under_vertex_id() {
1681 let provider = BUILT_IN_PROVIDERS
1682 .iter()
1683 .find(|provider| provider.id == PROVIDER_VERTEX)
1684 .unwrap();
1685
1686 assert_eq!(provider.default_model, "gemini-3.5-flash");
1687 assert_eq!(provider.env_key, Some("GOOGLE_APPLICATION_CREDENTIALS"));
1688 assert_eq!(provider.env_aliases, &["VERTEX_CREDENTIALS_JSON"]);
1689
1690 let model = lookup_model_for_provider(PROVIDER_VERTEX, "gemini-3.5-flash").unwrap();
1691 assert_eq!(model.provider, PROVIDER_VERTEX);
1692 assert_eq!(model.context_window, 1_048_576);
1693 assert!(model.supports_tools);
1694 assert_eq!(
1695 provider_family_for_provider(PROVIDER_VERTEX),
1696 ProviderFamily::Gemini
1697 );
1698 }
1699
1700 #[test]
1701 fn catalog_contains_gode_visible_models() {
1702 let ids = built_in_models(false)
1703 .into_iter()
1704 .map(|model| model.id)
1705 .collect::<Vec<_>>();
1706 assert_eq!(
1707 ids,
1708 vec![
1709 "gpt-5.5",
1710 "gpt-5.4-mini",
1711 "gpt-5.3-codex-spark",
1712 "claude-fable-5",
1713 "claude-opus-4-8",
1714 "claude-opus-4-7",
1715 "claude-sonnet-4-6",
1716 "claude-haiku-4-5-20251001",
1717 "fable",
1718 "sonnet",
1719 "opus",
1720 "haiku",
1721 "claude-sonnet-4-6",
1722 "claude-opus-4-8",
1723 "claude-fable-5",
1724 "gemini-3.5-flash",
1725 "gemini-3.1-pro-preview",
1726 "gemini-3.1-pro-preview-customtools",
1727 "gemini-3-flash-preview",
1728 "gemini-3.1-flash-lite-preview",
1729 "gemini-3.5-flash",
1730 "gemini-3.1-pro-preview",
1731 "gemini-3-flash-preview",
1732 "gemini-3.1-flash-lite-preview",
1733 "grok-4.3",
1734 "grok-4.20-multi-agent-0309",
1735 "grok-4.20-0309-reasoning",
1736 "grok-4.20-0309-non-reasoning",
1737 "grok-build-0.1",
1738 "grok-composer-2.5-fast",
1739 "gpt-5.5",
1740 "gpt-5.3-codex-spark",
1741 "big-pickle",
1742 "deepseek-v4-flash-free",
1743 "minimax-m2.5-free",
1744 "nemotron-3-super-free",
1745 "kimi-k2.6",
1746 "qwen3.6-plus",
1747 "glm-5.1",
1748 "deepseek-v4-flash",
1749 "kimi-for-coding",
1750 "x-ai/grok-build-0.1",
1751 "accounts/fireworks/models/qwen3-235b-a22b",
1752 "roder.cloud/free",
1753 "roder.cloud/openai/gpt-5.5",
1754 "roder.cloud/anthropic/claude-opus-4-7",
1755 "roder.cloud/google/gemini-3.1-pro-preview",
1756 "poolside/laguna-m.1",
1757 "poolside/laguna-xs.2",
1758 "mimo-v2.5-pro",
1759 "mimo-v2-pro",
1760 "mimo-v2.5",
1761 "mimo-v2-omni",
1762 "mimo-v2-flash",
1763 "mimo-v2.5-pro",
1764 "mimo-v2-pro",
1765 "mimo-v2.5",
1766 "mimo-v2-omni",
1767 "mimo-v2-flash",
1768 "composer-2.5",
1769 "claude-opus-4-8",
1770 "claude-sonnet-4-6",
1771 "gpt-5.5",
1772 "gemini-3.1-pro-preview",
1773 "grok-4.3",
1774 ]
1775 );
1776 }
1777
1778 #[test]
1779 fn provider_model_lists_match_gode_catalog() {
1780 assert_eq!(models_for_provider(PROVIDER_OPENAI, false).len(), 2);
1781 assert_eq!(models_for_codex(false).len(), 3);
1782 assert_eq!(models_for_provider(PROVIDER_ANTHROPIC, false).len(), 5);
1783 assert_eq!(models_for_provider(PROVIDER_CLAUDE_CODE, false).len(), 7);
1784 assert_eq!(models_for_provider(PROVIDER_GEMINI, false).len(), 5);
1785 assert_eq!(models_for_provider(PROVIDER_VERTEX, false).len(), 4);
1786 assert_eq!(models_for_provider(PROVIDER_XAI, false).len(), 4);
1787 assert_eq!(models_for_provider(PROVIDER_SUPERGROK, false).len(), 2);
1788 assert_eq!(models_for_provider(PROVIDER_OPENCODE, false).len(), 6);
1789 assert_eq!(models_for_provider(PROVIDER_OPENCODE_GO, false).len(), 4);
1790 assert_eq!(models_for_provider(PROVIDER_OPENROUTER, false).len(), 1);
1791 assert_eq!(models_for_provider(PROVIDER_FIREWORKS, false).len(), 1);
1792 assert_eq!(models_for_provider(PROVIDER_RODER_CLOUD, false).len(), 4);
1793 assert_eq!(models_for_provider(PROVIDER_POOLSIDE, false).len(), 2);
1794 assert_eq!(models_for_provider(PROVIDER_CURSOR, false).len(), 6);
1795 assert_eq!(models_for_provider(PROVIDER_XIAOMI_MIMO, false).len(), 5);
1796 assert_eq!(
1797 models_for_provider(PROVIDER_XIAOMI_MIMO_TOKEN_PLAN, false).len(),
1798 5
1799 );
1800 assert_eq!(models_for_provider(PROVIDER_KIMI_CODE, false).len(), 1);
1801 assert_eq!(models_for_provider(PROVIDER_MOCK, true).len(), 1);
1802 }
1803
1804 #[test]
1805 fn claude_code_catalog_uses_long_context_windows() {
1806 let direct = lookup_model_for_provider(PROVIDER_ANTHROPIC, "claude-sonnet-4-6").unwrap();
1807 let claude_code =
1808 lookup_model_for_provider(PROVIDER_CLAUDE_CODE, "claude-sonnet-4-6").unwrap();
1809
1810 assert_eq!(direct.context_window, 1_000_000);
1811 assert_eq!(claude_code.context_window, 1_000_000);
1812 assert_eq!(claude_code.auto_compact_token_limit, 900_000);
1813 assert!(!claude_code.supports_compaction);
1816 assert!(direct.supports_compaction);
1820 assert_eq!(direct.auto_compact_token_limit, 900_000);
1821 }
1822
1823 #[test]
1824 fn claude_haiku_does_not_advertise_server_side_compaction() {
1825 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
1826
1827 assert!(!haiku.supports_compaction);
1832 assert_eq!(haiku.auto_compact_token_limit, 180_000);
1833 }
1834
1835 #[test]
1836 fn google_embedding_model_is_hidden_from_chat_lists() {
1837 assert!(lookup_model("gemini-embedding-2").is_some());
1838 assert!(
1839 models_for_provider(PROVIDER_GOOGLE, false)
1840 .iter()
1841 .all(|model| model.id != "gemini-embedding-2")
1842 );
1843 let model = lookup_model("gemini-embedding-2").unwrap();
1844 assert!(model.hidden);
1845 assert!(!model.supports_tools);
1846 }
1847
1848 #[test]
1849 fn zeroentropy_embedding_model_is_hidden_from_chat_lists() {
1850 assert!(lookup_model("zembed-1").is_some());
1851 assert!(
1852 models_for_provider(PROVIDER_ZEROENTROPY, false)
1853 .iter()
1854 .all(|model| model.id != "zembed-1")
1855 );
1856 let model = lookup_model("zembed-1").unwrap();
1857 assert!(model.hidden);
1858 assert!(!model.supports_tools);
1859 }
1860
1861 #[test]
1862 fn catalog_model_profile_derives_openai_defaults() {
1863 let profile = built_in_model_profile("gpt-5.5").unwrap();
1864
1865 assert_eq!(profile.provider_family, ProviderFamily::OpenAi);
1866 assert_eq!(profile.edit_tool.as_deref(), Some(EDIT_TOOL_PATCH));
1867 assert_eq!(profile.schema_policy, ModelSchemaPolicy::RequiredFirstFlat);
1868 assert_eq!(
1869 profile.instruction_overlay,
1870 ModelInstructionOverlay::LiteralToolOutputs
1871 );
1872 assert_eq!(profile.reasoning.execution.as_deref(), Some(REASONING_LOW));
1873 assert_eq!(profile.parallel_tool_calls, Some(true));
1874 }
1875
1876 #[test]
1877 fn poolside_catalog_defaults_to_thinking_enabled() {
1878 let laguna = lookup_model("poolside/laguna-m.1").unwrap();
1879 assert_eq!(laguna.default_reasoning, REASONING_MEDIUM);
1880 assert_eq!(
1881 laguna
1882 .supported_reasoning
1883 .iter()
1884 .map(|option| option.effort)
1885 .collect::<Vec<_>>(),
1886 vec![REASONING_NONE, REASONING_MEDIUM]
1887 );
1888 }
1889
1890 #[test]
1891 fn xiaomi_mimo_catalog_uses_chat_completions_kind_and_exact_model_ids() {
1892 let provider = BUILT_IN_PROVIDERS
1893 .iter()
1894 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO)
1895 .unwrap();
1896 let token_plan = BUILT_IN_PROVIDERS
1897 .iter()
1898 .find(|provider| provider.id == PROVIDER_XIAOMI_MIMO_TOKEN_PLAN)
1899 .unwrap();
1900
1901 assert_eq!(provider.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
1902 assert_eq!(token_plan.kind, PROVIDER_KIND_CHAT_COMPLETIONS);
1903 assert_eq!(provider.env_key, Some("MIMO_API_KEY"));
1904 assert_eq!(token_plan.env_key, Some("MIMO_TOKEN_PLAN_API_KEY"));
1905
1906 let ids = models_for_provider(PROVIDER_XIAOMI_MIMO, false)
1907 .into_iter()
1908 .map(|model| model.id)
1909 .collect::<Vec<_>>();
1910 assert_eq!(
1911 ids,
1912 vec![
1913 "mimo-v2.5-pro",
1914 "mimo-v2-pro",
1915 "mimo-v2.5",
1916 "mimo-v2-omni",
1917 "mimo-v2-flash"
1918 ]
1919 );
1920 assert!(lookup_model("out-of-v2-flash").is_none());
1921 }
1922
1923 #[test]
1924 fn supergrok_catalog_exposes_build_and_composer_with_expected_context_windows() {
1925 let build = lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-build-0.1").unwrap();
1926 assert_eq!(build.display_name, "Grok Build 0.1");
1927 assert_eq!(build.context_window, 500_000);
1928 assert_eq!(build.auto_compact_token_limit, 450_000);
1929
1930 let composer =
1931 lookup_model_for_provider(PROVIDER_SUPERGROK, "grok-composer-2.5-fast").unwrap();
1932 assert_eq!(composer.display_name, "Grok Composer 2.5 Fast");
1933 assert_eq!(composer.context_window, 200_000);
1934 assert_eq!(composer.auto_compact_token_limit, 180_000);
1935 assert!(composer.supported_reasoning.is_empty());
1936
1937 let visible = models_for_provider(PROVIDER_SUPERGROK, false)
1938 .into_iter()
1939 .map(|model| model.id)
1940 .collect::<Vec<_>>();
1941 assert_eq!(
1942 visible,
1943 vec![
1944 "grok-build-0.1".to_string(),
1945 "grok-composer-2.5-fast".to_string()
1946 ]
1947 );
1948 }
1949
1950 #[test]
1951 fn xai_catalog_entries_match_current_grok_contract() {
1952 let grok43 = models_for_provider(PROVIDER_XAI, false)
1953 .into_iter()
1954 .find(|model| model.id == "grok-4.3")
1955 .unwrap();
1956 assert_eq!(grok43.context_window, Some(1_000_000));
1957 assert_eq!(grok43.default_reasoning.as_deref(), Some(REASONING_LOW));
1958 assert_eq!(
1959 grok43
1960 .supported_reasoning
1961 .iter()
1962 .map(|option| option.effort.as_str())
1963 .collect::<Vec<_>>(),
1964 vec![
1965 REASONING_NONE,
1966 REASONING_LOW,
1967 REASONING_MEDIUM,
1968 REASONING_HIGH
1969 ]
1970 );
1971
1972 let grok420 = lookup_model("grok-4.20-multi-agent-0309").unwrap();
1973 assert_eq!(grok420.context_window, 2_000_000);
1974 assert_eq!(grok420.auto_compact_token_limit, 1_800_000);
1975 assert_eq!(grok420.provider, PROVIDER_XAI);
1976 }
1977
1978 #[test]
1979 fn provider_aliases_normalize_xai_and_supergrok() {
1980 assert_eq!(normalize_provider_id("grok"), PROVIDER_XAI);
1981 assert_eq!(normalize_provider_id("x.ai"), PROVIDER_XAI);
1982 assert_eq!(normalize_provider_id("x-ai"), PROVIDER_XAI);
1983 assert_eq!(normalize_provider_id("xai-oauth"), PROVIDER_SUPERGROK);
1984 assert_eq!(normalize_provider_id("grok-oauth"), PROVIDER_SUPERGROK);
1985 assert_eq!(normalize_provider_id("supergrok"), PROVIDER_SUPERGROK);
1986 assert_eq!(normalize_provider_id("laguna"), PROVIDER_POOLSIDE);
1987 assert_eq!(normalize_provider_id("composer"), PROVIDER_CURSOR);
1988 }
1989
1990 #[test]
1991 fn fireworks_catalog_preserves_account_scoped_default_model() {
1992 let provider = BUILT_IN_PROVIDERS
1993 .iter()
1994 .find(|provider| provider.id == PROVIDER_FIREWORKS)
1995 .unwrap();
1996
1997 assert_eq!(
1998 provider.default_model,
1999 "accounts/fireworks/models/qwen3-235b-a22b"
2000 );
2001 assert_eq!(provider.env_key, Some("FIREWORKS_API_KEY"));
2002 assert_eq!(provider.env_aliases, &["RODER_FIREWORKS_API_KEY"]);
2003
2004 let model = lookup_model_for_provider(PROVIDER_FIREWORKS, provider.default_model).unwrap();
2005 assert_eq!(model.provider, PROVIDER_FIREWORKS);
2006 assert!(model.supports_tools);
2007 assert!(model.supports_structured);
2008 assert_eq!(
2009 provider_family_for_provider(PROVIDER_FIREWORKS),
2010 ProviderFamily::OpenAi
2011 );
2012 }
2013
2014 #[test]
2015 fn cursor_catalog_profile_is_text_only_agentservice() {
2016 let composer = lookup_model("composer-2.5").unwrap();
2017 assert_eq!(composer.provider, PROVIDER_CURSOR);
2018 assert!(!composer.supports_tools);
2019 assert!(!composer.supports_structured);
2020
2021 let profile = built_in_model_profile("composer-2.5").unwrap();
2022 assert_eq!(profile.provider_family, ProviderFamily::Cursor);
2023 assert_eq!(profile.parallel_tool_calls, Some(false));
2024 }
2025
2026 #[test]
2027 fn provider_aware_lookup_resolves_cursor_proxied_models_to_cursor_family() {
2028 let id_only = built_in_model_profile("claude-opus-4-8").unwrap();
2030 assert_eq!(id_only.provider_family, ProviderFamily::Anthropic);
2031
2032 let cursor =
2034 built_in_model_profile_for_provider(PROVIDER_CURSOR, "claude-opus-4-8").unwrap();
2035 assert_eq!(cursor.provider_family, ProviderFamily::Cursor);
2036 assert_eq!(cursor.provider, PROVIDER_CURSOR);
2037 assert_eq!(cursor.parallel_tool_calls, Some(false));
2038
2039 let anthropic =
2040 built_in_model_profile_for_provider(PROVIDER_ANTHROPIC, "claude-opus-4-8").unwrap();
2041 assert_eq!(anthropic.provider_family, ProviderFamily::Anthropic);
2042
2043 let fallback =
2045 built_in_model_profile_for_provider("does-not-exist", "claude-opus-4-8").unwrap();
2046 assert_eq!(fallback.provider_family, ProviderFamily::Anthropic);
2047 }
2048
2049 #[test]
2050 fn cursor_opus_advertises_configurable_reasoning_effort() {
2051 let opus = models_for_provider(PROVIDER_CURSOR, false)
2052 .into_iter()
2053 .find(|model| model.id == "claude-opus-4-8")
2054 .expect("cursor catalog should expose claude-opus-4-8");
2055
2056 assert_eq!(opus.default_reasoning.as_deref(), Some(REASONING_HIGH));
2057 assert_eq!(
2058 opus.supported_reasoning
2059 .iter()
2060 .map(|option| option.effort.as_str())
2061 .collect::<Vec<_>>(),
2062 vec![
2063 REASONING_LOW,
2064 REASONING_MEDIUM,
2065 REASONING_HIGH,
2066 REASONING_XHIGH,
2067 REASONING_MAX
2068 ]
2069 );
2070
2071 let sonnet = models_for_provider(PROVIDER_CURSOR, false)
2073 .into_iter()
2074 .find(|model| model.id == "claude-sonnet-4-6")
2075 .expect("cursor catalog should expose claude-sonnet-4-6");
2076 assert_eq!(sonnet.default_reasoning, None);
2077 assert!(sonnet.supported_reasoning.is_empty());
2078 }
2079
2080 #[test]
2081 fn claude_opus_and_sonnet_advertise_max_effort() {
2082 let efforts = |id: &str| {
2083 lookup_model(id)
2084 .unwrap()
2085 .supported_reasoning
2086 .iter()
2087 .map(|option| option.effort)
2088 .collect::<Vec<_>>()
2089 };
2090
2091 for id in ["claude-opus-4-8", "claude-opus-4-7"] {
2093 assert_eq!(
2094 efforts(id),
2095 vec![
2096 REASONING_LOW,
2097 REASONING_MEDIUM,
2098 REASONING_HIGH,
2099 REASONING_XHIGH,
2100 REASONING_MAX
2101 ],
2102 "{id} effort levels"
2103 );
2104 }
2105
2106 assert_eq!(
2108 efforts("claude-sonnet-4-6"),
2109 vec![
2110 REASONING_LOW,
2111 REASONING_MEDIUM,
2112 REASONING_HIGH,
2113 REASONING_MAX
2114 ]
2115 );
2116
2117 assert!(!efforts("gpt-5.5").contains(&REASONING_MAX));
2119 }
2120
2121 #[test]
2122 fn claude_haiku_does_not_advertise_reasoning_effort() {
2123 let haiku = lookup_model("claude-haiku-4-5-20251001").unwrap();
2124
2125 assert_eq!(haiku.default_reasoning, REASONING_NONE);
2126 assert!(haiku.supported_reasoning.is_empty());
2127
2128 let descriptor = ModelDescriptor::from(haiku);
2129 assert_eq!(descriptor.default_reasoning, None);
2130 assert!(descriptor.supported_reasoning.is_empty());
2131 }
2132
2133 #[test]
2134 fn openai_context_windows_match_current_catalog_values() {
2135 let gpt55 = lookup_model("gpt-5.5").unwrap();
2136 assert_eq!(gpt55.context_window, 1_050_000);
2137 assert_eq!(gpt55.max_context_window, 1_050_000);
2138 assert_eq!(gpt55.auto_compact_token_limit, 945_000);
2139
2140 let mini = lookup_model("gpt-5.4-mini").unwrap();
2141 assert_eq!(mini.context_window, 400_000);
2142 assert_eq!(mini.max_context_window, 400_000);
2143 assert_eq!(mini.auto_compact_token_limit, 360_000);
2144
2145 let spark = lookup_model("gpt-5.3-codex-spark").unwrap();
2146 assert_eq!(spark.provider, PROVIDER_CODEX);
2147 assert_eq!(spark.context_window, 128_000);
2148 assert_eq!(spark.max_context_window, 128_000);
2149 assert_eq!(spark.auto_compact_token_limit, 115_200);
2150 }
2151
2152 #[test]
2153 fn auto_compact_defaults_to_ninety_percent_of_context_window() {
2154 for model in BUILT_IN_MODELS {
2155 if model.context_window == 0 || model.auto_compact_token_limit == 0 {
2156 continue;
2157 }
2158 assert_eq!(
2159 model.auto_compact_token_limit,
2160 model.context_window.saturating_mul(9) / 10,
2161 "{} should compact at 90% of its context window",
2162 model.id
2163 );
2164 }
2165 }
2166}