1use 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum XaiReasoningCapability {
107 Effort {
110 supported: &'static [agent_core::reasoning::ReasoningLevel],
111 default_level: Option<agent_core::reasoning::ReasoningLevel>,
112 can_disable: bool,
115 },
116 IntrinsicReasoning,
118 NonReasoning,
120}
121
122pub 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 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 #[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 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}