llmshim 0.3.2__tar.gz → 0.3.3__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.2 → llmshim-0.3.3}/CLAUDE.md +1 -1
  2. {llmshim-0.3.2 → llmshim-0.3.3}/Cargo.lock +1 -1
  3. {llmshim-0.3.2 → llmshim-0.3.3}/Cargo.toml +1 -1
  4. {llmshim-0.3.2 → llmshim-0.3.3}/PKG-INFO +2 -2
  5. {llmshim-0.3.2 → llmshim-0.3.3}/README.md +1 -1
  6. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/guides/reasoning.md +3 -3
  7. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/reference/models.md +4 -3
  8. {llmshim-0.3.2 → llmshim-0.3.3}/src/main.rs +3 -2
  9. {llmshim-0.3.2 → llmshim-0.3.3}/src/models.rs +19 -10
  10. {llmshim-0.3.2 → llmshim-0.3.3}/src/providers/gemini.rs +8 -4
  11. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_gemini.rs +42 -0
  12. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_gemini.rs +28 -0
  13. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_models.rs +1 -1
  14. {llmshim-0.3.2 → llmshim-0.3.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  15. {llmshim-0.3.2 → llmshim-0.3.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  16. {llmshim-0.3.2 → llmshim-0.3.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {llmshim-0.3.2 → llmshim-0.3.3}/.github/workflows/pages.yml +0 -0
  18. {llmshim-0.3.2 → llmshim-0.3.3}/.github/workflows/release.yml +0 -0
  19. {llmshim-0.3.2 → llmshim-0.3.3}/.gitignore +0 -0
  20. {llmshim-0.3.2 → llmshim-0.3.3}/CODE_OF_CONDUCT.md +0 -0
  21. {llmshim-0.3.2 → llmshim-0.3.3}/CONTRIBUTING.md +0 -0
  22. {llmshim-0.3.2 → llmshim-0.3.3}/LICENSE-APACHE +0 -0
  23. {llmshim-0.3.2 → llmshim-0.3.3}/LICENSE-MIT +0 -0
  24. {llmshim-0.3.2 → llmshim-0.3.3}/NOTICE +0 -0
  25. {llmshim-0.3.2 → llmshim-0.3.3}/SECURITY.md +0 -0
  26. {llmshim-0.3.2 → llmshim-0.3.3}/benchmarks/bench.rs +0 -0
  27. {llmshim-0.3.2 → llmshim-0.3.3}/benchmarks/bench_python.py +0 -0
  28. {llmshim-0.3.2 → llmshim-0.3.3}/benchmarks/loadtest.rs +0 -0
  29. {llmshim-0.3.2 → llmshim-0.3.3}/docs/.gitignore +0 -0
  30. {llmshim-0.3.2 → llmshim-0.3.3}/docs/book.toml +0 -0
  31. {llmshim-0.3.2 → llmshim-0.3.3}/docs/mermaid-init.js +0 -0
  32. {llmshim-0.3.2 → llmshim-0.3.3}/docs/mermaid.min.js +0 -0
  33. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/SUMMARY.md +0 -0
  34. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/concepts/contracts.md +0 -0
  35. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/concepts/conversations.md +0 -0
  36. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/concepts/portability.md +0 -0
  37. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/concepts/routing.md +0 -0
  38. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/concepts/translation-flow.md +0 -0
  39. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/guides/fallbacks.md +0 -0
  40. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/guides/images.md +0 -0
  41. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/guides/native-controls.md +0 -0
  42. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/guides/streaming.md +0 -0
  43. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/guides/tools.md +0 -0
  44. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/introduction.md +0 -0
  45. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/proxy/deployment.md +0 -0
  46. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/proxy/http-api.md +0 -0
  47. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/proxy/scaling.md +0 -0
  48. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/reference/api.md +0 -0
  49. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/reference/cli.md +0 -0
  50. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/reference/configuration.md +0 -0
  51. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/reference/errors.md +0 -0
  52. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/reference/providers.md +0 -0
  53. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/reference/request-fields.md +0 -0
  54. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/reference/surfaces.md +0 -0
  55. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/start/choose.md +0 -0
  56. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/start/cli.md +0 -0
  57. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/start/clients.md +0 -0
  58. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/start/configure.md +0 -0
  59. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/start/proxy.md +0 -0
  60. {llmshim-0.3.2 → llmshim-0.3.3}/docs/src/start/rust.md +0 -0
  61. {llmshim-0.3.2 → llmshim-0.3.3}/examples/chat.rs +0 -0
  62. {llmshim-0.3.2 → llmshim-0.3.3}/examples/stream.rs +0 -0
  63. {llmshim-0.3.2 → llmshim-0.3.3}/llmshim/__init__.py +0 -0
  64. {llmshim-0.3.2 → llmshim-0.3.3}/llmshim/_client.py +0 -0
  65. {llmshim-0.3.2 → llmshim-0.3.3}/llmshim/_server.py +0 -0
  66. {llmshim-0.3.2 → llmshim-0.3.3}/llmshim/types.py +0 -0
  67. {llmshim-0.3.2 → llmshim-0.3.3}/pyproject.toml +0 -0
  68. {llmshim-0.3.2 → llmshim-0.3.3}/src/client.rs +0 -0
  69. {llmshim-0.3.2 → llmshim-0.3.3}/src/config.rs +0 -0
  70. {llmshim-0.3.2 → llmshim-0.3.3}/src/env.rs +0 -0
  71. {llmshim-0.3.2 → llmshim-0.3.3}/src/error.rs +0 -0
  72. {llmshim-0.3.2 → llmshim-0.3.3}/src/fallback.rs +0 -0
  73. {llmshim-0.3.2 → llmshim-0.3.3}/src/lib.rs +0 -0
  74. {llmshim-0.3.2 → llmshim-0.3.3}/src/log.rs +0 -0
  75. {llmshim-0.3.2 → llmshim-0.3.3}/src/provider.rs +0 -0
  76. {llmshim-0.3.2 → llmshim-0.3.3}/src/providers/anthropic.rs +0 -0
  77. {llmshim-0.3.2 → llmshim-0.3.3}/src/providers/mod.rs +0 -0
  78. {llmshim-0.3.2 → llmshim-0.3.3}/src/providers/openai.rs +0 -0
  79. {llmshim-0.3.2 → llmshim-0.3.3}/src/providers/openai_compat.rs +0 -0
  80. {llmshim-0.3.2 → llmshim-0.3.3}/src/providers/openrouter.rs +0 -0
  81. {llmshim-0.3.2 → llmshim-0.3.3}/src/providers/xai.rs +0 -0
  82. {llmshim-0.3.2 → llmshim-0.3.3}/src/proxy/convert.rs +0 -0
  83. {llmshim-0.3.2 → llmshim-0.3.3}/src/proxy/error.rs +0 -0
  84. {llmshim-0.3.2 → llmshim-0.3.3}/src/proxy/handlers.rs +0 -0
  85. {llmshim-0.3.2 → llmshim-0.3.3}/src/proxy/mod.rs +0 -0
  86. {llmshim-0.3.2 → llmshim-0.3.3}/src/proxy/ratelimit.rs +0 -0
  87. {llmshim-0.3.2 → llmshim-0.3.3}/src/proxy/types.rs +0 -0
  88. {llmshim-0.3.2 → llmshim-0.3.3}/src/router.rs +0 -0
  89. {llmshim-0.3.2 → llmshim-0.3.3}/src/vision.rs +0 -0
  90. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration.rs +0 -0
  91. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_fallback.rs +0 -0
  92. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_gemini_tools.rs +0 -0
  93. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_long_context.rs +0 -0
  94. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_multimodel.rs +0 -0
  95. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_openrouter.rs +0 -0
  96. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_proxy.rs +0 -0
  97. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_sglang.rs +0 -0
  98. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_thinking.rs +0 -0
  99. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_tool_roundtrip.rs +0 -0
  100. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_vision.rs +0 -0
  101. {llmshim-0.3.2 → llmshim-0.3.3}/tests/integration_xai.rs +0 -0
  102. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_anthropic.rs +0 -0
  103. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_fallback.rs +0 -0
  104. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_fast_mode.rs +0 -0
  105. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_log.rs +0 -0
  106. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_multimodel.rs +0 -0
  107. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_openai.rs +0 -0
  108. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_openai_compat.rs +0 -0
  109. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_openrouter.rs +0 -0
  110. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_proxy.rs +0 -0
  111. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_proxy_convert.rs +0 -0
  112. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_router.rs +0 -0
  113. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_sse.rs +0 -0
  114. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_tools.rs +0 -0
  115. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_vision.rs +0 -0
  116. {llmshim-0.3.2 → llmshim-0.3.3}/tests/unit_xai.rs +0 -0
@@ -14,7 +14,7 @@ This is a public crate on crates.io. Do NOT make breaking changes to `pub` items
14
14
 
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
- - **Gemini:** `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-flash-preview`
17
+ - **Gemini:** `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.5-flash-lite`
18
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`.
@@ -958,7 +958,7 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
958
958
 
959
959
  [[package]]
960
960
  name = "llmshim"
961
- version = "0.3.2"
961
+ version = "0.3.3"
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.2"
3
+ version = "0.3.3"
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.2
3
+ Version: 0.3.3
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -222,7 +222,7 @@ billed provider calls; run it only when you deliberately want to hit real APIs.
222
222
  |----------|--------|
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
- | Gemini | `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-flash-preview` |
225
+ | Gemini | `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.5-flash-lite` |
226
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.
@@ -364,7 +364,7 @@ Standard library only. Full docs: [`clients/ruby/README.md`](clients/ruby/README
364
364
  |----------|--------|-------------------|
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
- | **Google Gemini** | `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-flash-preview` | Yes (thought summaries) |
367
+ | **Google Gemini** | `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.5-flash-lite` | Yes (thought summaries) |
368
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.
@@ -117,7 +117,7 @@ token budget scaled from `max_tokens` and floored at 1024:
117
117
  Gemini uses the four-rung
118
118
  `generationConfig.thinkingConfig.thinkingLevel` enum:
119
119
 
120
- | unified | gemini-3.5-flash / 3-flash-preview / 3.1-flash-lite-preview | gemini-3.1-pro-preview |
120
+ | unified | gemini-3.5-flash / 3.6-flash / 3.5-flash-lite | gemini-3.7-flash (and gemini-3.1-pro) |
121
121
  |---|---|---|
122
122
  | `none` | `minimal` (zero thinking tokens) | **`low`** (this model cannot disable thinking) |
123
123
  | `low` | `low` | `low` |
@@ -126,8 +126,8 @@ Gemini uses the four-rung
126
126
  | `xhigh` | **`high`** | **`high`** |
127
127
  | `max` | **`high`** | **`high`** |
128
128
 
129
- Gemini 3.1 Pro rejects both `minimal` and `thinkingBudget: 0`, so `none` clamps
130
- to its `low` floor. The legacy integer `thinkingBudget` remains available only
129
+ Gemini 3.7 Flash and Gemini 3.1 Pro reject both `minimal` and `thinkingBudget: 0`, so `none`
130
+ clamps to their `low` floor (verified live). Other flash models accept `minimal` and can disable thinking. The legacy integer `thinkingBudget` remains available only
131
131
  through `x-gemini.thinkingConfig`.
132
132
 
133
133
  ### xAI Responses API
@@ -14,7 +14,7 @@ the ID and display label.
14
14
 
15
15
  ## Registered catalog
16
16
 
17
- The current registry contains 24 entries, newest first within each provider.
17
+ The current registry contains 26 entries, newest first within each provider.
18
18
  This page mirrors `src/models.rs`; use runtime discovery rather than parsing
19
19
  this table in applications.
20
20
 
@@ -48,9 +48,10 @@ this table in applications.
48
48
 
49
49
  | ID | Display name |
50
50
  |---|---|
51
+ | `gemini/gemini-3.7-flash` | Gemini 3.7 Flash |
52
+ | `gemini/gemini-3.6-flash` | Gemini 3.6 Flash |
51
53
  | `gemini/gemini-3.5-flash` | Gemini 3.5 Flash |
52
- | `gemini/gemini-3.1-pro-preview` | Gemini 3.1 Pro |
53
- | `gemini/gemini-3-flash-preview` | Gemini 3 Flash |
54
+ | `gemini/gemini-3.5-flash-lite` | Gemini 3.5 Flash Lite |
54
55
 
55
56
  ### xAI
56
57
 
@@ -22,9 +22,10 @@ const MODELS: &[(&str, &str)] = &[
22
22
  ("anthropic/claude-opus-4-6", "Claude Opus 4.6"),
23
23
  ("anthropic/claude-sonnet-4-6", "Claude Sonnet 4.6"),
24
24
  ("anthropic/claude-haiku-4-5-20251001", "Claude Haiku 4.5"),
25
+ ("gemini/gemini-3.7-flash", "Gemini 3.7 Flash"),
26
+ ("gemini/gemini-3.6-flash", "Gemini 3.6 Flash"),
25
27
  ("gemini/gemini-3.5-flash", "Gemini 3.5 Flash"),
26
- ("gemini/gemini-3.1-pro-preview", "Gemini 3.1 Pro"),
27
- ("gemini/gemini-3-flash-preview", "Gemini 3 Flash"),
28
+ ("gemini/gemini-3.5-flash-lite", "Gemini 3.5 Flash Lite"),
28
29
  ("xai/grok-4.6", "Grok 4.6"),
29
30
  ("xai/grok-4.5", "Grok 4.5"),
30
31
  ("xai/grok-4.3", "Grok 4.3"),
@@ -326,28 +326,37 @@ pub const MODELS: &[ModelInfo] = &[
326
326
  capabilities: CAPS_FULL,
327
327
  },
328
328
  ModelInfo {
329
- id: "gemini/gemini-3.5-flash",
329
+ id: "gemini/gemini-3.7-flash",
330
330
  provider: "gemini",
331
- name: "gemini-3.5-flash",
332
- label: "Gemini 3.5 Flash",
331
+ name: "gemini-3.7-flash",
332
+ label: "Gemini 3.7 Flash",
333
333
  context_window_tokens: Some(1_048_576),
334
334
  max_output_tokens: Some(65_536),
335
- capabilities: CAPS_FULL,
335
+ capabilities: CAPS_STD,
336
336
  },
337
337
  ModelInfo {
338
- id: "gemini/gemini-3.1-pro-preview",
338
+ id: "gemini/gemini-3.6-flash",
339
339
  provider: "gemini",
340
- name: "gemini-3.1-pro-preview",
341
- label: "Gemini 3.1 Pro",
340
+ name: "gemini-3.6-flash",
341
+ label: "Gemini 3.6 Flash",
342
342
  context_window_tokens: Some(1_048_576),
343
343
  max_output_tokens: Some(65_536),
344
344
  capabilities: CAPS_STD,
345
345
  },
346
346
  ModelInfo {
347
- id: "gemini/gemini-3-flash-preview",
347
+ id: "gemini/gemini-3.5-flash",
348
+ provider: "gemini",
349
+ name: "gemini-3.5-flash",
350
+ label: "Gemini 3.5 Flash",
351
+ context_window_tokens: Some(1_048_576),
352
+ max_output_tokens: Some(65_536),
353
+ capabilities: CAPS_FULL,
354
+ },
355
+ ModelInfo {
356
+ id: "gemini/gemini-3.5-flash-lite",
348
357
  provider: "gemini",
349
- name: "gemini-3-flash-preview",
350
- label: "Gemini 3 Flash",
358
+ name: "gemini-3.5-flash-lite",
359
+ label: "Gemini 3.5 Flash Lite",
351
360
  context_window_tokens: Some(1_048_576),
352
361
  max_output_tokens: Some(65_536),
353
362
  capabilities: CAPS_STD,
@@ -581,11 +581,15 @@ fn transform_response_to_openai(model: &str, resp: &Value) -> Result<Value> {
581
581
  }))
582
582
  }
583
583
 
584
- /// Models that cannot turn thinking off: gemini-3.1-pro rejects both
585
- /// thinkingLevel "minimal" and thinkingBudget 0 ("This model only works in
586
- /// thinking mode", verified live). Unified effort "none" clamps to "low".
584
+ /// Models that cannot turn thinking off: gemini-3.1-pro and gemini-3.7-flash
585
+ /// reject thinkingLevel "minimal" (and thinkingBudget 0) verified live.
586
+ /// Unified effort "none" clamps to "low" for them.
587
587
  fn cannot_disable_thinking(model: &str) -> bool {
588
- model.to_lowercase().contains("3.1-pro")
588
+ let m = model.to_lowercase();
589
+ // gemini-3.1-pro and gemini-3.7-flash reject thinkingLevel "minimal" (and
590
+ // thinkingBudget 0), verified live — unified "none" clamps to "low". Other
591
+ // flash models (3.5/3.6/3.5-lite) accept "minimal" and can disable thinking.
592
+ m.contains("3.1-pro") || m.contains("3.7-flash")
589
593
  }
590
594
 
591
595
  impl Provider for Gemini {
@@ -436,3 +436,45 @@ async fn all_three_providers_same_shape() {
436
436
  );
437
437
  }
438
438
  }
439
+
440
+ #[tokio::test]
441
+ #[ignore]
442
+ async fn gemini_3_7_flash_completion() {
443
+ if std::env::var("GEMINI_API_KEY").is_err() {
444
+ return;
445
+ }
446
+ let router = router();
447
+ let req = json!({
448
+ "model": "gemini/gemini-3.7-flash",
449
+ "messages": [{"role": "user", "content": "In one short sentence, what is Rust?"}],
450
+ "max_tokens": 2000,
451
+ });
452
+ let resp = llmshim::completion(&router, &req).await.unwrap();
453
+ let content = resp["choices"][0]["message"]["content"]
454
+ .as_str()
455
+ .unwrap_or("");
456
+ assert!(!content.is_empty(), "expected a response, got: {resp}");
457
+ println!("gemini-3.7-flash said: {content}");
458
+ }
459
+
460
+ #[tokio::test]
461
+ #[ignore]
462
+ async fn gemini_3_7_flash_none_clamps_to_low() {
463
+ // gemini-3.7-flash 400s on thinkingLevel "minimal"; llmshim must clamp
464
+ // reasoning_effort "none" to "low". Asserts success, not a 400.
465
+ if std::env::var("GEMINI_API_KEY").is_err() {
466
+ return;
467
+ }
468
+ let router = router();
469
+ let req = json!({
470
+ "model": "gemini/gemini-3.7-flash",
471
+ "messages": [{"role": "user", "content": "Say hi."}],
472
+ "max_tokens": 2000,
473
+ "reasoning_effort": "none",
474
+ });
475
+ let resp = llmshim::completion(&router, &req)
476
+ .await
477
+ .expect("none must clamp to low for gemini-3.7-flash, not 400");
478
+ assert_eq!(resp["object"], "chat.completion");
479
+ println!("gemini-3.7-flash none->low clamp OK");
480
+ }
@@ -1529,3 +1529,31 @@ fn request_strips_foreign_reasoning_signature() {
1529
1529
  assert!(!body.contains("redacted-should-not-leak"));
1530
1530
  assert!(!body.contains("reasoning_signature"));
1531
1531
  }
1532
+
1533
+ #[test]
1534
+ fn reasoning_none_clamps_per_model_disable_capability() {
1535
+ // gemini-3.7-flash rejects thinkingLevel "minimal" (verified live) -> "none"
1536
+ // clamps to "low". Other flash models accept "minimal" -> "none" stays "minimal".
1537
+ let p = provider();
1538
+ let req = json!({
1539
+ "model": "x",
1540
+ "messages": [{"role": "user", "content": "hi"}],
1541
+ "reasoning_effort": "none",
1542
+ });
1543
+ let r = p.transform_request("gemini-3.7-flash", &req).unwrap();
1544
+ assert_eq!(
1545
+ r.body["generationConfig"]["thinkingConfig"]["thinkingLevel"], "low",
1546
+ "gemini-3.7-flash cannot disable thinking -> none clamps to low"
1547
+ );
1548
+ for m in [
1549
+ "gemini-3.6-flash",
1550
+ "gemini-3.5-flash",
1551
+ "gemini-3.5-flash-lite",
1552
+ ] {
1553
+ let r = p.transform_request(m, &req).unwrap();
1554
+ assert_eq!(
1555
+ r.body["generationConfig"]["thinkingConfig"]["thinkingLevel"], "minimal",
1556
+ "{m} can disable thinking -> none maps to minimal"
1557
+ );
1558
+ }
1559
+ }
@@ -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(), 25);
14
+ assert_eq!(MODELS.len(), 26);
15
15
  }
16
16
 
17
17
  #[test]
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