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    // Official live model/reasoning docs, checked 2026-09-21. No inferred
77    // `-latest` alias, output limit, or vision capability for this route.
78    XaiModelDescriptor {
79        id: "grok-4.7",
80        label: "Grok 4.7",
81        context_tokens: Some(500_000),
82        reasoning: true,
83    },
84];
85
86pub fn xai_model(id: &str) -> Option<&'static XaiModelDescriptor> {
87    XAI_TEXT_MODELS.iter().find(|model| model.id == id)
88}
89
90// ─── Exact-id reasoning capability (spec: anthropic-xai-reasoning-modes) ─────
91
92/// Documented reasoning capability for an exact xAI model id.
93///
94/// Evidence: official xAI docs and a live `GET /v1/models` probe
95/// (2026-08-14). `grok-4.5`/`grok-4.5-latest` and `grok-4.6` support
96/// low/medium/high effort, default high, and reasoning cannot be disabled.
97/// xAI has not published a `grok-4.6-latest` alias (upstream 404), so only
98/// the bare id is cataloged. `grok-4.20-multi-agent-0309` supports
99/// low/medium/high/xhigh where effort controls agent count. No other exact id
100/// has documented effort support in that original snapshot.
101///
102/// Added 2026-09-21: exact `grok-4.7` supports low/medium/high/xhigh,
103/// default high, no off (https://docs.x.ai/developers/model-capabilities/text/reasoning).
104/// The older models' capability contracts are unchanged by this addition.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum XaiReasoningCapability {
107    /// Documented named-effort control (`reasoning:{effort:"..."}` on the
108    /// Responses wire).
109    Effort {
110        supported: &'static [agent_core::reasoning::ReasoningLevel],
111        default_level: Option<agent_core::reasoning::ReasoningLevel>,
112        /// Whether reasoning can be disabled. `false` → `Off` must be
113        /// rejected, never silently omitted/downgraded.
114        can_disable: bool,
115    },
116    /// Reasoning is intrinsic; no documented effort control or disable switch.
117    IntrinsicReasoning,
118    /// Model does not reason; named reasoning levels must be rejected.
119    NonReasoning,
120}
121
122/// Exact-id capability lookup — never substring-based. Unknown ids return
123/// `None` and fail closed at validation time.
124pub fn xai_static_capability(model_id: &str) -> Option<XaiReasoningCapability> {
125    use agent_core::reasoning::ReasoningLevel::*;
126    match model_id {
127        "grok-4.5" | "grok-4.5-latest" | "grok-4.6" => Some(XaiReasoningCapability::Effort {
128            supported: &[Low, Medium, High],
129            default_level: Some(High),
130            can_disable: false,
131        }),
132        "grok-4.7" => Some(XaiReasoningCapability::Effort {
133            supported: &[Low, Medium, High, XHigh],
134            default_level: Some(High),
135            can_disable: false,
136        }),
137        "grok-4.20-multi-agent-0309" => Some(XaiReasoningCapability::Effort {
138            supported: &[Low, Medium, High, XHigh],
139            // Effort controls agent count; no documented default level.
140            default_level: None,
141            can_disable: false,
142        }),
143        "grok-4.3" | "grok-4.3-latest" | "grok-latest" | "grok-4.20-0309-reasoning" => {
144            Some(XaiReasoningCapability::IntrinsicReasoning)
145        }
146        "grok-4.20-0309-non-reasoning" => Some(XaiReasoningCapability::NonReasoning),
147        _ => None,
148    }
149}
150
151pub fn xai_static_catalog_models() -> Vec<CatalogModel> {
152    XAI_TEXT_MODELS
153        .iter()
154        .map(|descriptor| CatalogModel {
155            provider_key: "xai-auth".into(),
156            provider_name: "xAI (Grok)".into(),
157            provider_kind: CatalogProviderKind::Generic {
158                key: "xai-auth".into(),
159            },
160            id: descriptor.id.into(),
161            label: Some(descriptor.label.into()),
162            context_tokens: descriptor.context_tokens,
163            max_output_tokens: None,
164            input_modalities: vec![Modality::Text],
165            pricing: PricingSummary::default(),
166            reasoning: if descriptor.reasoning {
167                ReasoningSupport::GenericOpenAi
168            } else {
169                ReasoningSupport::None
170            },
171            source: CatalogSource::StaticFallback,
172        })
173        .collect()
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn documented_selectable_public_responses_text_ids_are_exposed() {
182        assert_eq!(
183            XAI_TEXT_MODELS.iter().map(|m| m.id).collect::<Vec<_>>(),
184            vec![
185                "grok-4.3",
186                "grok-4.3-latest",
187                "grok-latest",
188                "grok-4.20-0309-reasoning",
189                "grok-4.20-0309-non-reasoning",
190                "grok-4.20-multi-agent-0309",
191                "grok-4.5",
192                "grok-4.5-latest",
193                "grok-4.6",
194                "grok-4.7",
195            ]
196        );
197    }
198
199    #[test]
200    fn reasoning_metadata_is_limited_to_documented_reasoning_models() {
201        let reasoning_ids = xai_static_catalog_models()
202            .into_iter()
203            .filter(|model| model.reasoning != ReasoningSupport::None)
204            .map(|model| model.id)
205            .collect::<Vec<_>>();
206        assert_eq!(
207            reasoning_ids,
208            vec![
209                "grok-4.3",
210                "grok-4.3-latest",
211                "grok-latest",
212                "grok-4.20-0309-reasoning",
213                "grok-4.20-multi-agent-0309",
214                "grok-4.5",
215                "grok-4.5-latest",
216                "grok-4.6",
217                "grok-4.7",
218            ]
219        );
220    }
221
222    // ── Exact-id reasoning capability table (spec: anthropic-xai-reasoning-modes) ──
223
224    #[test]
225    fn grok_45_and_46_family_effort_capability_is_exact() {
226        use agent_core::reasoning::ReasoningLevel::*;
227        for id in ["grok-4.5", "grok-4.5-latest", "grok-4.6"] {
228            match xai_static_capability(id) {
229                Some(XaiReasoningCapability::Effort {
230                    supported,
231                    default_level,
232                    can_disable,
233                }) => {
234                    assert_eq!(supported, &[Low, Medium, High], "{id}");
235                    assert_eq!(default_level, Some(High), "{id}");
236                    assert!(!can_disable, "{id}: reasoning cannot be disabled");
237                }
238                other => panic!("{id}: expected Effort capability, got {other:?}"),
239            }
240        }
241    }
242
243    #[test]
244    fn multi_agent_supports_xhigh_but_45_does_not() {
245        use agent_core::reasoning::ReasoningLevel::*;
246        match xai_static_capability("grok-4.20-multi-agent-0309") {
247            Some(XaiReasoningCapability::Effort {
248                supported,
249                default_level,
250                can_disable,
251            }) => {
252                assert_eq!(supported, &[Low, Medium, High, XHigh]);
253                assert_eq!(default_level, None, "no documented default");
254                assert!(!can_disable);
255            }
256            other => panic!("expected Effort capability, got {other:?}"),
257        }
258        // 4.5/4.6 have no documented xhigh — must not appear in their sets.
259        for id in ["grok-4.5", "grok-4.6"] {
260            if let Some(XaiReasoningCapability::Effort { supported, .. }) =
261                xai_static_capability(id)
262            {
263                assert!(!supported.contains(&XHigh), "{id}");
264            }
265        }
266    }
267
268    #[test]
269    fn intrinsic_reasoning_models_have_no_named_effort() {
270        for id in [
271            "grok-4.3",
272            "grok-4.3-latest",
273            "grok-latest",
274            "grok-4.20-0309-reasoning",
275        ] {
276            assert_eq!(
277                xai_static_capability(id),
278                Some(XaiReasoningCapability::IntrinsicReasoning),
279                "{id}"
280            );
281        }
282    }
283
284    #[test]
285    fn non_reasoning_model_is_marked_non_reasoning() {
286        assert_eq!(
287            xai_static_capability("grok-4.20-0309-non-reasoning"),
288            Some(XaiReasoningCapability::NonReasoning)
289        );
290    }
291
292    #[test]
293    fn unknown_exact_ids_fail_closed() {
294        for id in ["grok-5", "grok-4.50", "grok-4.5x", "", "gpt-5.5"] {
295            assert_eq!(xai_static_capability(id), None, "{id}");
296        }
297    }
298
299    #[test]
300    fn catalog_is_text_only_and_excludes_unconfirmed_code_model() {
301        let models = xai_static_catalog_models();
302        assert!(models
303            .iter()
304            .all(|m| m.input_modalities == vec![Modality::Text]));
305        assert!(!models.iter().any(|m| m.id == "grok-build-0.1"));
306        assert!(!models.iter().any(|m| m.id == "grok-build-latest"));
307        assert!(!models.iter().any(|m| ["image", "video", "voice"]
308            .iter()
309            .any(|kind| m.id.contains(kind))));
310    }
311}