llmshim 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. {llmshim-0.3.1 → llmshim-0.3.2}/CLAUDE.md +1 -1
  2. {llmshim-0.3.1 → llmshim-0.3.2}/Cargo.lock +1 -1
  3. {llmshim-0.3.1 → llmshim-0.3.2}/Cargo.toml +1 -1
  4. {llmshim-0.3.1 → llmshim-0.3.2}/PKG-INFO +2 -2
  5. {llmshim-0.3.1 → llmshim-0.3.2}/README.md +1 -1
  6. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/guides/reasoning.md +2 -2
  7. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/reference/models.md +1 -0
  8. {llmshim-0.3.1 → llmshim-0.3.2}/src/main.rs +1 -0
  9. {llmshim-0.3.1 → llmshim-0.3.2}/src/models.rs +9 -0
  10. {llmshim-0.3.1 → llmshim-0.3.2}/src/providers/xai.rs +7 -4
  11. llmshim-0.3.2/tests/integration_xai.rs +72 -0
  12. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_models.rs +2 -1
  13. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_xai.rs +23 -0
  14. {llmshim-0.3.1 → llmshim-0.3.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  15. {llmshim-0.3.1 → llmshim-0.3.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  16. {llmshim-0.3.1 → llmshim-0.3.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {llmshim-0.3.1 → llmshim-0.3.2}/.github/workflows/pages.yml +0 -0
  18. {llmshim-0.3.1 → llmshim-0.3.2}/.github/workflows/release.yml +0 -0
  19. {llmshim-0.3.1 → llmshim-0.3.2}/.gitignore +0 -0
  20. {llmshim-0.3.1 → llmshim-0.3.2}/CODE_OF_CONDUCT.md +0 -0
  21. {llmshim-0.3.1 → llmshim-0.3.2}/CONTRIBUTING.md +0 -0
  22. {llmshim-0.3.1 → llmshim-0.3.2}/LICENSE-APACHE +0 -0
  23. {llmshim-0.3.1 → llmshim-0.3.2}/LICENSE-MIT +0 -0
  24. {llmshim-0.3.1 → llmshim-0.3.2}/NOTICE +0 -0
  25. {llmshim-0.3.1 → llmshim-0.3.2}/SECURITY.md +0 -0
  26. {llmshim-0.3.1 → llmshim-0.3.2}/benchmarks/bench.rs +0 -0
  27. {llmshim-0.3.1 → llmshim-0.3.2}/benchmarks/bench_python.py +0 -0
  28. {llmshim-0.3.1 → llmshim-0.3.2}/benchmarks/loadtest.rs +0 -0
  29. {llmshim-0.3.1 → llmshim-0.3.2}/docs/.gitignore +0 -0
  30. {llmshim-0.3.1 → llmshim-0.3.2}/docs/book.toml +0 -0
  31. {llmshim-0.3.1 → llmshim-0.3.2}/docs/mermaid-init.js +0 -0
  32. {llmshim-0.3.1 → llmshim-0.3.2}/docs/mermaid.min.js +0 -0
  33. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/SUMMARY.md +0 -0
  34. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/concepts/contracts.md +0 -0
  35. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/concepts/conversations.md +0 -0
  36. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/concepts/portability.md +0 -0
  37. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/concepts/routing.md +0 -0
  38. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/concepts/translation-flow.md +0 -0
  39. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/guides/fallbacks.md +0 -0
  40. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/guides/images.md +0 -0
  41. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/guides/native-controls.md +0 -0
  42. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/guides/streaming.md +0 -0
  43. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/guides/tools.md +0 -0
  44. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/introduction.md +0 -0
  45. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/proxy/deployment.md +0 -0
  46. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/proxy/http-api.md +0 -0
  47. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/proxy/scaling.md +0 -0
  48. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/reference/api.md +0 -0
  49. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/reference/cli.md +0 -0
  50. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/reference/configuration.md +0 -0
  51. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/reference/errors.md +0 -0
  52. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/reference/providers.md +0 -0
  53. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/reference/request-fields.md +0 -0
  54. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/reference/surfaces.md +0 -0
  55. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/start/choose.md +0 -0
  56. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/start/cli.md +0 -0
  57. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/start/clients.md +0 -0
  58. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/start/configure.md +0 -0
  59. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/start/proxy.md +0 -0
  60. {llmshim-0.3.1 → llmshim-0.3.2}/docs/src/start/rust.md +0 -0
  61. {llmshim-0.3.1 → llmshim-0.3.2}/examples/chat.rs +0 -0
  62. {llmshim-0.3.1 → llmshim-0.3.2}/examples/stream.rs +0 -0
  63. {llmshim-0.3.1 → llmshim-0.3.2}/llmshim/__init__.py +0 -0
  64. {llmshim-0.3.1 → llmshim-0.3.2}/llmshim/_client.py +0 -0
  65. {llmshim-0.3.1 → llmshim-0.3.2}/llmshim/_server.py +0 -0
  66. {llmshim-0.3.1 → llmshim-0.3.2}/llmshim/types.py +0 -0
  67. {llmshim-0.3.1 → llmshim-0.3.2}/pyproject.toml +0 -0
  68. {llmshim-0.3.1 → llmshim-0.3.2}/src/client.rs +0 -0
  69. {llmshim-0.3.1 → llmshim-0.3.2}/src/config.rs +0 -0
  70. {llmshim-0.3.1 → llmshim-0.3.2}/src/env.rs +0 -0
  71. {llmshim-0.3.1 → llmshim-0.3.2}/src/error.rs +0 -0
  72. {llmshim-0.3.1 → llmshim-0.3.2}/src/fallback.rs +0 -0
  73. {llmshim-0.3.1 → llmshim-0.3.2}/src/lib.rs +0 -0
  74. {llmshim-0.3.1 → llmshim-0.3.2}/src/log.rs +0 -0
  75. {llmshim-0.3.1 → llmshim-0.3.2}/src/provider.rs +0 -0
  76. {llmshim-0.3.1 → llmshim-0.3.2}/src/providers/anthropic.rs +0 -0
  77. {llmshim-0.3.1 → llmshim-0.3.2}/src/providers/gemini.rs +0 -0
  78. {llmshim-0.3.1 → llmshim-0.3.2}/src/providers/mod.rs +0 -0
  79. {llmshim-0.3.1 → llmshim-0.3.2}/src/providers/openai.rs +0 -0
  80. {llmshim-0.3.1 → llmshim-0.3.2}/src/providers/openai_compat.rs +0 -0
  81. {llmshim-0.3.1 → llmshim-0.3.2}/src/providers/openrouter.rs +0 -0
  82. {llmshim-0.3.1 → llmshim-0.3.2}/src/proxy/convert.rs +0 -0
  83. {llmshim-0.3.1 → llmshim-0.3.2}/src/proxy/error.rs +0 -0
  84. {llmshim-0.3.1 → llmshim-0.3.2}/src/proxy/handlers.rs +0 -0
  85. {llmshim-0.3.1 → llmshim-0.3.2}/src/proxy/mod.rs +0 -0
  86. {llmshim-0.3.1 → llmshim-0.3.2}/src/proxy/ratelimit.rs +0 -0
  87. {llmshim-0.3.1 → llmshim-0.3.2}/src/proxy/types.rs +0 -0
  88. {llmshim-0.3.1 → llmshim-0.3.2}/src/router.rs +0 -0
  89. {llmshim-0.3.1 → llmshim-0.3.2}/src/vision.rs +0 -0
  90. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration.rs +0 -0
  91. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_fallback.rs +0 -0
  92. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_gemini.rs +0 -0
  93. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_gemini_tools.rs +0 -0
  94. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_long_context.rs +0 -0
  95. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_multimodel.rs +0 -0
  96. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_openrouter.rs +0 -0
  97. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_proxy.rs +0 -0
  98. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_sglang.rs +0 -0
  99. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_thinking.rs +0 -0
  100. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_tool_roundtrip.rs +0 -0
  101. {llmshim-0.3.1 → llmshim-0.3.2}/tests/integration_vision.rs +0 -0
  102. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_anthropic.rs +0 -0
  103. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_fallback.rs +0 -0
  104. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_fast_mode.rs +0 -0
  105. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_gemini.rs +0 -0
  106. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_log.rs +0 -0
  107. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_multimodel.rs +0 -0
  108. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_openai.rs +0 -0
  109. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_openai_compat.rs +0 -0
  110. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_openrouter.rs +0 -0
  111. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_proxy.rs +0 -0
  112. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_proxy_convert.rs +0 -0
  113. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_router.rs +0 -0
  114. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_sse.rs +0 -0
  115. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_tools.rs +0 -0
  116. {llmshim-0.3.1 → llmshim-0.3.2}/tests/unit_vision.rs +0 -0
@@ -15,7 +15,7 @@ This is a public crate on crates.io. Do NOT make breaking changes to `pub` items
15
15
  - **OpenAI:** `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.4-mini`, `gpt-5.4-nano`
16
16
  - **Anthropic:** `claude-opus-5`, `claude-opus-4-8`, `claude-sonnet-5`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001`
17
17
  - **Gemini:** `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-flash-preview`
18
- - **xAI:** `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning`
18
+ - **xAI:** `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning`
19
19
  - **OpenRouter:** not enumerated (huge/dynamic catalog) — any `openrouter/<vendor>/<model>` slug routes through, e.g. `openrouter/anthropic/claude-sonnet-4.5`.
20
20
  - **vLLM / SGLang:** not enumerated (self-hosted) — any `vllm/<served-model>` or `sglang/<served-model>` routes through to the configured server, e.g. `sglang/Qwen/Qwen3.6-35B-A3B-FP8`.
21
21
 
@@ -958,7 +958,7 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
958
958
 
959
959
  [[package]]
960
960
  name = "llmshim"
961
- version = "0.3.1"
961
+ version = "0.3.2"
962
962
  dependencies = [
963
963
  "async-stream",
964
964
  "async-trait",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "llmshim"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  edition = "2021"
5
5
  description = "Blazing fast LLM API translation layer in pure Rust"
6
6
  license = "MIT OR Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llmshim
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -223,7 +223,7 @@ billed provider calls; run it only when you deliberately want to hit real APIs.
223
223
  | OpenAI | `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.4-mini`, `gpt-5.4-nano` |
224
224
  | Anthropic | `claude-opus-5`, `claude-opus-4-8`, `claude-sonnet-5`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001` |
225
225
  | Gemini | `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-flash-preview` |
226
- | xAI | `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning` |
226
+ | xAI | `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning` |
227
227
 
228
228
  Call `llmshim.models()` for the live list filtered to your configured providers.
229
229
 
@@ -365,7 +365,7 @@ Standard library only. Full docs: [`clients/ruby/README.md`](clients/ruby/README
365
365
  | **OpenAI** | `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.4-mini`, `gpt-5.4-nano` | Yes (summaries) |
366
366
  | **Anthropic** | `claude-opus-5`, `claude-opus-4-8`, `claude-sonnet-5`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001` | Yes (full thinking) |
367
367
  | **Google Gemini** | `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-flash-preview` | Yes (thought summaries) |
368
- | **xAI** | `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning` | No (hidden) |
368
+ | **xAI** | `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning` | No (hidden) |
369
369
 
370
370
  Use a bare model name (auto-detected by prefix) or an explicit `provider/model` string.
371
371
 
@@ -134,7 +134,7 @@ through `x-gemini.thinkingConfig`.
134
134
 
135
135
  xAI receives the nested native shape `reasoning: {effort}`:
136
136
 
137
- | unified | grok-4.3 | grok-4.5 | grok-4.20-\*-reasoning / -non-reasoning |
137
+ | unified | grok-4.3 | grok-4.5 / grok-4.6 | grok-4.20-\*-reasoning / -non-reasoning |
138
138
  |---|---|---|---|
139
139
  | `none` | `none` | **`low`** | omitted |
140
140
  | `low` | `low` | `low` | omitted |
@@ -143,7 +143,7 @@ xAI receives the nested native shape `reasoning: {effort}`:
143
143
  | `xhigh` | `xhigh` | `xhigh` | omitted |
144
144
  | `max` | **`xhigh`** | **`xhigh`** | omitted |
145
145
 
146
- grok-4.5 cannot disable reasoning, so `none` clamps to `low`. grok-4.20 models
146
+ grok-4.5 and grok-4.6 cannot disable reasoning, so `none` clamps to `low`. grok-4.20 models
147
147
  are name-locked: reasoning on or off is encoded in the model name, and the API
148
148
  rejects any reasoning parameter. llmshim therefore omits it for that family.
149
149
 
@@ -56,6 +56,7 @@ this table in applications.
56
56
 
57
57
  | ID | Display name |
58
58
  |---|---|
59
+ | `xai/grok-4.6` | Grok 4.6 |
59
60
  | `xai/grok-4.5` | Grok 4.5 |
60
61
  | `xai/grok-4.3` | Grok 4.3 |
61
62
  | `xai/grok-4.20-multi-agent-beta-0309` | Grok 4.20 Multi-Agent |
@@ -25,6 +25,7 @@ const MODELS: &[(&str, &str)] = &[
25
25
  ("gemini/gemini-3.5-flash", "Gemini 3.5 Flash"),
26
26
  ("gemini/gemini-3.1-pro-preview", "Gemini 3.1 Pro"),
27
27
  ("gemini/gemini-3-flash-preview", "Gemini 3 Flash"),
28
+ ("xai/grok-4.6", "Grok 4.6"),
28
29
  ("xai/grok-4.5", "Grok 4.5"),
29
30
  ("xai/grok-4.3", "Grok 4.3"),
30
31
  (
@@ -352,6 +352,15 @@ pub const MODELS: &[ModelInfo] = &[
352
352
  max_output_tokens: Some(65_536),
353
353
  capabilities: CAPS_STD,
354
354
  },
355
+ ModelInfo {
356
+ id: "xai/grok-4.6",
357
+ provider: "xai",
358
+ name: "grok-4.6",
359
+ label: "Grok 4.6",
360
+ context_window_tokens: Some(500_000),
361
+ max_output_tokens: None,
362
+ capabilities: CAPS_XAI,
363
+ },
355
364
  ModelInfo {
356
365
  id: "xai/grok-4.5",
357
366
  provider: "xai",
@@ -189,11 +189,14 @@ fn is_reasoning_name_locked(model: &str) -> bool {
189
189
  model.to_lowercase().contains("4.20")
190
190
  }
191
191
 
192
- /// grok-4.5 cannot disable reasoning: `effort: "none"` -> 400 ("does not
193
- /// support `reasoning_effort` value `none`", verified live). Unified effort
194
- /// "none" clamps to "low". grok-4.3 and the fast pair DO accept "none".
192
+ /// grok-4.5 / grok-4.6 cannot disable reasoning: `effort: "none"` -> 400 ("does
193
+ /// not support `reasoning_effort` value `none`", verified live). Unified effort
194
+ /// "none" clamps to "low". grok-4.3 DOES accept "none".
195
195
  fn reasoning_cannot_disable(model: &str) -> bool {
196
- model.to_lowercase().contains("4.5")
196
+ let m = model.to_lowercase();
197
+ // grok-4.5 and grok-4.6 both 400 on `reasoning_effort: "none"` (verified
198
+ // live); unified "none" clamps to "low" for them. grok-4.3 accepts "none".
199
+ m.contains("4.5") || m.contains("4.6")
197
200
  }
198
201
 
199
202
  impl Provider for Xai {
@@ -0,0 +1,72 @@
1
+ /// Integration tests for xAI (Grok) hitting the real API.
2
+ /// Run with: XAI_API_KEY=... cargo test --test integration_xai -- --ignored --nocapture
3
+ use serde_json::json;
4
+
5
+ fn router() -> llmshim::router::Router {
6
+ llmshim::router::Router::from_env()
7
+ }
8
+
9
+ #[tokio::test]
10
+ #[ignore]
11
+ async fn grok_4_6_completion() {
12
+ if std::env::var("XAI_API_KEY").is_err() {
13
+ return;
14
+ }
15
+ let router = router();
16
+ let req = json!({
17
+ "model": "xai/grok-4.6",
18
+ "messages": [{"role": "user", "content": "In one short sentence, what is Rust?"}],
19
+ "max_tokens": 2000,
20
+ });
21
+ let resp = llmshim::completion(&router, &req).await.unwrap();
22
+ assert_eq!(resp["object"], "chat.completion");
23
+ let content = resp["choices"][0]["message"]["content"]
24
+ .as_str()
25
+ .unwrap_or("");
26
+ assert!(!content.is_empty(), "expected a response, got: {resp}");
27
+ println!("grok-4.6 said: {content}");
28
+ }
29
+
30
+ #[tokio::test]
31
+ #[ignore]
32
+ async fn grok_4_6_reasoning_none_clamps_to_low() {
33
+ // grok-4.6 400s on reasoning_effort "none"; llmshim must clamp it to "low".
34
+ // This asserts the request SUCCEEDS (not a 400), proving the clamp.
35
+ if std::env::var("XAI_API_KEY").is_err() {
36
+ return;
37
+ }
38
+ let router = router();
39
+ let req = json!({
40
+ "model": "xai/grok-4.6",
41
+ "messages": [{"role": "user", "content": "Say hi."}],
42
+ "max_tokens": 2000,
43
+ "reasoning_effort": "none",
44
+ });
45
+ let resp = llmshim::completion(&router, &req)
46
+ .await
47
+ .expect("reasoning_effort=none must be clamped to low, not 400");
48
+ assert_eq!(resp["object"], "chat.completion");
49
+ println!("grok-4.6 none->low clamp OK");
50
+ }
51
+
52
+ #[tokio::test]
53
+ #[ignore]
54
+ async fn grok_4_6_reasoning_high() {
55
+ if std::env::var("XAI_API_KEY").is_err() {
56
+ return;
57
+ }
58
+ let router = router();
59
+ let req = json!({
60
+ "model": "xai/grok-4.6",
61
+ "messages": [{"role": "user", "content": "What is 17 * 24? Reason it through."}],
62
+ "max_tokens": 3000,
63
+ "reasoning_effort": "high",
64
+ });
65
+ let resp = llmshim::completion(&router, &req).await.unwrap();
66
+ let content = resp["choices"][0]["message"]["content"]
67
+ .as_str()
68
+ .unwrap_or("");
69
+ assert!(!content.is_empty(), "expected an answer, got: {resp}");
70
+ assert!(content.contains("408"), "expected 408 in answer: {content}");
71
+ println!("grok-4.6 reasoning OK: {content}");
72
+ }
@@ -11,7 +11,7 @@ fn models_registry_has_all_providers() {
11
11
 
12
12
  #[test]
13
13
  fn models_registry_has_expected_count() {
14
- assert_eq!(MODELS.len(), 24);
14
+ assert_eq!(MODELS.len(), 25);
15
15
  }
16
16
 
17
17
  #[test]
@@ -160,6 +160,7 @@ fn reasoning_support_matches_provider_behavior() {
160
160
  "openai/gpt-5.6-sol",
161
161
  "anthropic/claude-opus-5",
162
162
  "anthropic/claude-opus-4-8",
163
+ "xai/grok-4.6",
163
164
  "xai/grok-4.5",
164
165
  ] {
165
166
  assert_eq!(
@@ -666,6 +666,29 @@ fn name_locked_grok_4_20_omits_reasoning_entirely() {
666
666
  }
667
667
  }
668
668
 
669
+ #[test]
670
+ fn reasoning_none_clamps_to_low_for_grok_4_5_and_4_6() {
671
+ // grok-4.5 and grok-4.6 400 on effort "none" (verified live) — clamp to "low".
672
+ let p = provider();
673
+ for model in ["grok-4.5", "grok-4.6"] {
674
+ let req = json!({
675
+ "model": "x",
676
+ "messages": [{"role": "user", "content": "hi"}],
677
+ "reasoning_effort": "none",
678
+ });
679
+ let r = p.transform_request(model, &req).unwrap();
680
+ assert_eq!(r.body["reasoning"]["effort"], "low", "{model} none->low");
681
+ }
682
+ // grok-4.3 CAN disable — "none" stays "none".
683
+ let req = json!({
684
+ "model": "x",
685
+ "messages": [{"role": "user", "content": "hi"}],
686
+ "reasoning_effort": "none",
687
+ });
688
+ let r = p.transform_request("grok-4.3", &req).unwrap();
689
+ assert_eq!(r.body["reasoning"]["effort"], "none");
690
+ }
691
+
669
692
  #[test]
670
693
  fn mode_pro_bumps_effort() {
671
694
  let p = provider();
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes