Skip to main content

agent_engine/runtime/openai/catalog/
xai.rs

1//! Selectable xAI model IDs documented for public text/Responses use.
2//!
3//! This is a conservative allowlist, not an exhaustive xAI model list. Aliases
4//! are first-class selectable IDs when xAI documents them as valid model IDs.
5//! Build/code and image, video, and voice models are excluded because the
6//! available public documentation does not establish public Responses support
7//! for this text runtime.
8
9use super::{
10    CatalogModel, CatalogProviderKind, CatalogSource, Modality, PricingSummary, ReasoningSupport,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct XaiModelDescriptor {
15    pub id: &'static str,
16    pub label: &'static str,
17    pub context_tokens: Option<u64>,
18    pub reasoning: bool,
19}
20
21pub const XAI_TEXT_MODELS: &[XaiModelDescriptor] = &[
22    XaiModelDescriptor {
23        id: "grok-4.3",
24        label: "Grok 4.3",
25        context_tokens: None,
26        reasoning: true,
27    },
28    XaiModelDescriptor {
29        id: "grok-4.3-latest",
30        label: "Grok 4.3 (latest alias)",
31        context_tokens: None,
32        reasoning: true,
33    },
34    XaiModelDescriptor {
35        id: "grok-latest",
36        label: "Grok (latest alias)",
37        context_tokens: None,
38        reasoning: true,
39    },
40    XaiModelDescriptor {
41        id: "grok-4.20-0309-reasoning",
42        label: "Grok 4.20 Reasoning",
43        context_tokens: None,
44        reasoning: true,
45    },
46    XaiModelDescriptor {
47        id: "grok-4.20-0309-non-reasoning",
48        label: "Grok 4.20 Non-Reasoning",
49        context_tokens: None,
50        reasoning: false,
51    },
52    XaiModelDescriptor {
53        id: "grok-4.20-multi-agent-0309",
54        label: "Grok 4.20 Multi-Agent",
55        context_tokens: None,
56        reasoning: true,
57    },
58    XaiModelDescriptor {
59        id: "grok-4.5",
60        label: "Grok 4.5",
61        context_tokens: None,
62        reasoning: true,
63    },
64    XaiModelDescriptor {
65        id: "grok-4.5-latest",
66        label: "Grok 4.5 (latest alias)",
67        context_tokens: None,
68        reasoning: true,
69    },
70    XaiModelDescriptor {
71        id: "grok-4.6",
72        label: "Grok 4.6",
73        context_tokens: None,
74        reasoning: true,
75    },
76];
77
78pub fn xai_model(id: &str) -> Option<&'static XaiModelDescriptor> {
79    XAI_TEXT_MODELS.iter().find(|model| model.id == id)
80}
81
82// ─── Exact-id reasoning capability (spec: anthropic-xai-reasoning-modes) ─────
83
84/// Documented reasoning capability for an exact xAI model id.
85///
86/// Evidence: official xAI docs and a live `GET /v1/models` probe
87/// (2026-08-14). `grok-4.5`/`grok-4.5-latest` and `grok-4.6` support
88/// low/medium/high effort, default high, and reasoning cannot be disabled.
89/// xAI has not published a `grok-4.6-latest` alias (upstream 404), so only
90/// the bare id is cataloged. `grok-4.20-multi-agent-0309` supports
91/// low/medium/high/xhigh where effort controls agent count. No other exact id
92/// has documented effort support.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum XaiReasoningCapability {
95    /// Documented named-effort control (`reasoning:{effort:"..."}` on the
96    /// Responses wire).
97    Effort {
98        supported: &'static [agent_core::reasoning::ReasoningLevel],
99        default_level: Option<agent_core::reasoning::ReasoningLevel>,
100        /// Whether reasoning can be disabled. `false` → `Off` must be
101        /// rejected, never silently omitted/downgraded.
102        can_disable: bool,
103    },
104    /// Reasoning is intrinsic; no documented effort control or disable switch.
105    IntrinsicReasoning,
106    /// Model does not reason; named reasoning levels must be rejected.
107    NonReasoning,
108}
109
110/// Exact-id capability lookup — never substring-based. Unknown ids return
111/// `None` and fail closed at validation time.
112pub fn xai_static_capability(model_id: &str) -> Option<XaiReasoningCapability> {
113    use agent_core::reasoning::ReasoningLevel::*;
114    match model_id {
115        "grok-4.5" | "grok-4.5-latest" | "grok-4.6" => Some(XaiReasoningCapability::Effort {
116            supported: &[Low, Medium, High],
117            default_level: Some(High),
118            can_disable: false,
119        }),
120        "grok-4.20-multi-agent-0309" => Some(XaiReasoningCapability::Effort {
121            supported: &[Low, Medium, High, XHigh],
122            // Effort controls agent count; no documented default level.
123            default_level: None,
124            can_disable: false,
125        }),
126        "grok-4.3" | "grok-4.3-latest" | "grok-latest" | "grok-4.20-0309-reasoning" => {
127            Some(XaiReasoningCapability::IntrinsicReasoning)
128        }
129        "grok-4.20-0309-non-reasoning" => Some(XaiReasoningCapability::NonReasoning),
130        _ => None,
131    }
132}
133
134pub fn xai_static_catalog_models() -> Vec<CatalogModel> {
135    XAI_TEXT_MODELS
136        .iter()
137        .map(|descriptor| CatalogModel {
138            provider_key: "xai-auth".into(),
139            provider_name: "xAI (Grok)".into(),
140            provider_kind: CatalogProviderKind::Generic {
141                key: "xai-auth".into(),
142            },
143            id: descriptor.id.into(),
144            label: Some(descriptor.label.into()),
145            context_tokens: descriptor.context_tokens,
146            max_output_tokens: None,
147            input_modalities: vec![Modality::Text],
148            pricing: PricingSummary::default(),
149            reasoning: if descriptor.reasoning {
150                ReasoningSupport::GenericOpenAi
151            } else {
152                ReasoningSupport::None
153            },
154            source: CatalogSource::StaticFallback,
155        })
156        .collect()
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn documented_selectable_public_responses_text_ids_are_exposed() {
165        assert_eq!(
166            XAI_TEXT_MODELS.iter().map(|m| m.id).collect::<Vec<_>>(),
167            vec![
168                "grok-4.3",
169                "grok-4.3-latest",
170                "grok-latest",
171                "grok-4.20-0309-reasoning",
172                "grok-4.20-0309-non-reasoning",
173                "grok-4.20-multi-agent-0309",
174                "grok-4.5",
175                "grok-4.5-latest",
176                "grok-4.6",
177            ]
178        );
179    }
180
181    #[test]
182    fn reasoning_metadata_is_limited_to_documented_reasoning_models() {
183        let reasoning_ids = xai_static_catalog_models()
184            .into_iter()
185            .filter(|model| model.reasoning != ReasoningSupport::None)
186            .map(|model| model.id)
187            .collect::<Vec<_>>();
188        assert_eq!(
189            reasoning_ids,
190            vec![
191                "grok-4.3",
192                "grok-4.3-latest",
193                "grok-latest",
194                "grok-4.20-0309-reasoning",
195                "grok-4.20-multi-agent-0309",
196                "grok-4.5",
197                "grok-4.5-latest",
198                "grok-4.6",
199            ]
200        );
201    }
202
203    // ── Exact-id reasoning capability table (spec: anthropic-xai-reasoning-modes) ──
204
205    #[test]
206    fn grok_45_and_46_family_effort_capability_is_exact() {
207        use agent_core::reasoning::ReasoningLevel::*;
208        for id in ["grok-4.5", "grok-4.5-latest", "grok-4.6"] {
209            match xai_static_capability(id) {
210                Some(XaiReasoningCapability::Effort {
211                    supported,
212                    default_level,
213                    can_disable,
214                }) => {
215                    assert_eq!(supported, &[Low, Medium, High], "{id}");
216                    assert_eq!(default_level, Some(High), "{id}");
217                    assert!(!can_disable, "{id}: reasoning cannot be disabled");
218                }
219                other => panic!("{id}: expected Effort capability, got {other:?}"),
220            }
221        }
222    }
223
224    #[test]
225    fn multi_agent_supports_xhigh_but_45_does_not() {
226        use agent_core::reasoning::ReasoningLevel::*;
227        match xai_static_capability("grok-4.20-multi-agent-0309") {
228            Some(XaiReasoningCapability::Effort {
229                supported,
230                default_level,
231                can_disable,
232            }) => {
233                assert_eq!(supported, &[Low, Medium, High, XHigh]);
234                assert_eq!(default_level, None, "no documented default");
235                assert!(!can_disable);
236            }
237            other => panic!("expected Effort capability, got {other:?}"),
238        }
239        // 4.5/4.6 have no documented xhigh — must not appear in their sets.
240        for id in ["grok-4.5", "grok-4.6"] {
241            if let Some(XaiReasoningCapability::Effort { supported, .. }) =
242                xai_static_capability(id)
243            {
244                assert!(!supported.contains(&XHigh), "{id}");
245            }
246        }
247    }
248
249    #[test]
250    fn intrinsic_reasoning_models_have_no_named_effort() {
251        for id in [
252            "grok-4.3",
253            "grok-4.3-latest",
254            "grok-latest",
255            "grok-4.20-0309-reasoning",
256        ] {
257            assert_eq!(
258                xai_static_capability(id),
259                Some(XaiReasoningCapability::IntrinsicReasoning),
260                "{id}"
261            );
262        }
263    }
264
265    #[test]
266    fn non_reasoning_model_is_marked_non_reasoning() {
267        assert_eq!(
268            xai_static_capability("grok-4.20-0309-non-reasoning"),
269            Some(XaiReasoningCapability::NonReasoning)
270        );
271    }
272
273    #[test]
274    fn unknown_exact_ids_fail_closed() {
275        for id in ["grok-5", "grok-4.50", "grok-4.5x", "", "gpt-5.5"] {
276            assert_eq!(xai_static_capability(id), None, "{id}");
277        }
278    }
279
280    #[test]
281    fn catalog_is_text_only_and_excludes_unconfirmed_code_model() {
282        let models = xai_static_catalog_models();
283        assert!(models
284            .iter()
285            .all(|m| m.input_modalities == vec![Modality::Text]));
286        assert!(!models.iter().any(|m| m.id == "grok-build-0.1"));
287        assert!(!models.iter().any(|m| m.id == "grok-build-latest"));
288        assert!(!models.iter().any(|m| ["image", "video", "voice"]
289            .iter()
290            .any(|kind| m.id.contains(kind))));
291    }
292}