mistral: Add Z.ai GLM (latest version) and show thinking toggle for supported models (#63535)

# Objective

- Add Z.ai GLM, now hosted by Mistral as a third-party open model, to
the
  built-in Mistral model list.
- Fix the thinking selector never appearing for Mistral models that
support
  reasoning (mistral-medium-latest, mistral-small-latest).

## Solution

- Added a `ZaiGlmLatest` variant to `mistral::Model` that uses Mistral's
`zai-glm-latest` model id: 1M token context window, tool calling
enabled,
image input disabled, thinking enabled. The provider builds its model
list
  by iterating this enum, so no further wiring was needed.
- This PR initially targeted GLM 5.2 (`zai-glm-5-2`), but Mistral
released
5.3 while it was open. Switching to the `zai-glm-latest` alias keeps Zed
on
the newest GLM release without requiring a new PR each time Mistral
bumps
the version, mirroring how Mistral's own model ids work for their
flagship
  models.
- Implemented the missing `LanguageModel::supports_thinking()` override
in the
Mistral provider, delegating to `mistral::Model::supports_thinking()`.
The
request-building code already sent `reasoning_effort` and serialized
thinking
parts, but the UI toggle was hidden because the trait method fell back
to the
  default `false`.

## Testing

- `./script/clippy -p mistral` and `cargo check -p language_models`
pass.
- Existing unit tests in the provider cover the `reasoning_effort`
request
mapping; no new behavior beyond the enum variant and the trait
delegation.
- Reviewers can test with a Mistral API key: `zai-glm-latest` appears in
the
  model dropdown, and the thinking toggle now shows for zai-glm-latest,
  mistral-medium-latest, and mistral-small-latest.
- Tested on macOS; changes are platform-independent.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

---

Release Notes:

- Added Z.ai GLM to the Mistral provider and fixed the thinking toggle
for Mistral Small and Medium.

---------

Co-authored-by: dino <dinojoaocosta@gmail.com>
This commit is contained in:
ummon-v 2026-09-22 11:01:26 +00:00 • committed by GitHub
parent 02238cd4b7
commit a4b9dc5f52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 79 additions and 16 deletions

View file

@ -319,6 +319,14 @@ impl LanguageModel for MistralLanguageModel {
self.model.supports_images()
}
fn supports_thinking(&self) -> bool {
self.model.supports_thinking()
}
fn supports_disabling_thinking(&self) -> bool {
self.model.supports_disabling_thinking()
}
fn telemetry_id(&self) -> String {
format!("mistral/{}", self.model.id())
}
@ -583,9 +591,18 @@ pub fn into_mistral(
})
})
.collect::<Result<_>>()?,
reasoning_effort: if model.supports_thinking() && request.thinking_allowed {
reasoning_effort: if !model.supports_thinking() {
None
} else if request.thinking_allowed {
Some(mistral::ReasoningEffort::High)
} else if model.supports_disabling_thinking() {
// Explicitly disable thinking rather than relying on the API's
// default.
Some(mistral::ReasoningEffort::None)
} else {
// Models for which thinking can't be disabled will reject
// "none" as the `reasoning_effort` value. Omitting the field is
// the only way to request the API's default effort.
None
},
},
@ -937,20 +954,52 @@ mod tests {
max_output_tokens: None,
};
let (mistral_request, _) =
into_mistral(request(true), mistral::Model::MistralMediumLatest, None).unwrap();
assert_eq!(
mistral_request.reasoning_effort,
Some(mistral::ReasoningEffort::High)
);
let cases = vec![
(
mistral::Model::MistralSmallLatest,
true,
Some(mistral::ReasoningEffort::High),
),
(
mistral::Model::MistralSmallLatest,
false,
Some(mistral::ReasoningEffort::None),
),
(
mistral::Model::MistralMediumLatest,
true,
Some(mistral::ReasoningEffort::High),
),
(
mistral::Model::MistralMediumLatest,
false,
Some(mistral::ReasoningEffort::None),
),
(
mistral::Model::ZaiGlmLatest,
true,
Some(mistral::ReasoningEffort::High),
),
// Z.ai GLM always thinks and rejects "none", so the field is
// omitted when the toggle is off instead of sending an explicit
// value.
(mistral::Model::ZaiGlmLatest, false, None),
// Ensure that, for non-thinking models, `reasoning_effort` is
// always omitted.
(mistral::Model::CodestralLatest, true, None),
(mistral::Model::CodestralLatest, false, None),
];
let (mistral_request, _) =
into_mistral(request(false), mistral::Model::MistralMediumLatest, None).unwrap();
assert_eq!(mistral_request.reasoning_effort, None);
for (model, thinking_allowed, reasoning_effort) in cases {
let (mistral_request, _) = into_mistral(request(thinking_allowed), model, None)
.expect("should be able to convert request");
let (mistral_request, _) =
into_mistral(request(true), mistral::Model::CodestralLatest, None).unwrap();
assert_eq!(mistral_request.reasoning_effort, None);
assert_eq!(
mistral_request.reasoning_effort, reasoning_effort,
"reasoning_effort should match, expected {:?}, got {:?}",
reasoning_effort, mistral_request.reasoning_effort
)
}
}
#[test]

View file

@ -66,6 +66,9 @@ pub enum Model {
#[serde(rename = "ministral-14b-latest", alias = "ministral-14b-latest")]
Ministral14bLatest,
#[serde(rename = "zai-glm-latest", alias = "zai-glm-latest")]
ZaiGlmLatest,
#[serde(rename = "custom")]
Custom {
name: String,
@ -94,6 +97,7 @@ impl Model {
"ministral-3b-latest" => Ok(Self::Ministral3bLatest),
"ministral-8b-latest" => Ok(Self::Ministral8bLatest),
"ministral-14b-latest" => Ok(Self::Ministral14bLatest),
"zai-glm-latest" => Ok(Self::ZaiGlmLatest),
invalid_id => anyhow::bail!("invalid model id '{invalid_id}'"),
}
}
@ -107,6 +111,7 @@ impl Model {
Self::Ministral3bLatest => "ministral-3b-latest",
Self::Ministral8bLatest => "ministral-8b-latest",
Self::Ministral14bLatest => "ministral-14b-latest",
Self::ZaiGlmLatest => "zai-glm-latest",
Self::Custom { name, .. } => name,
}
}
@ -120,6 +125,7 @@ impl Model {
Self::Ministral3bLatest => "ministral-3b-latest",
Self::Ministral8bLatest => "ministral-8b-latest",
Self::Ministral14bLatest => "ministral-14b-latest",
Self::ZaiGlmLatest => "zai-glm-latest",
Self::Custom {
name, display_name, ..
} => display_name.as_ref().unwrap_or(name),
@ -135,6 +141,7 @@ impl Model {
Self::Ministral3bLatest => 256000,
Self::Ministral8bLatest => 256000,
Self::Ministral14bLatest => 256000,
Self::ZaiGlmLatest => 1_048_576,
Self::Custom { max_tokens, .. } => *max_tokens,
}
}
@ -156,7 +163,8 @@ impl Model {
| Self::MistralSmallLatest
| Self::Ministral3bLatest
| Self::Ministral8bLatest
| Self::Ministral14bLatest => true,
| Self::Ministral14bLatest
| Self::ZaiGlmLatest => true,
Self::Custom { supports_tools, .. } => supports_tools.unwrap_or(false),
}
}
@ -169,7 +177,7 @@ impl Model {
| Self::Ministral3bLatest
| Self::Ministral8bLatest
| Self::Ministral14bLatest => true,
Self::CodestralLatest => false,
Self::CodestralLatest | Self::ZaiGlmLatest => false,
Self::Custom {
supports_images, ..
} => supports_images.unwrap_or(false),
@ -178,13 +186,19 @@ impl Model {
pub fn supports_thinking(&self) -> bool {
match self {
Self::MistralMediumLatest | Self::MistralSmallLatest => true,
Self::MistralMediumLatest | Self::MistralSmallLatest | Self::ZaiGlmLatest => true,
Self::Custom {
supports_thinking, ..
} => supports_thinking.unwrap_or(false),
_ => false,
}
}
/// Whether thinking can be turned off. Z.ai GLM models always think and
/// reject `reasoning_effort: "none"`.
pub fn supports_disabling_thinking(&self) -> bool {
!matches!(self, Self::ZaiGlmLatest)
}
}
#[derive(Debug, Serialize, Deserialize)]