Add Codestral edit predictions provider (#34371)

Release Notes:

- Added Codestral edit predictions provider which can be enabled by adding an API key in the Mistral section of agent settings.

![2025-07-13 11 35
33](https://github.com/user-attachments/assets/8bf599d7-33c7-4556-b878-6c645d69661f)


## Config

Get API key from https://console.mistral.ai/codestral and add it in the Mistral section of the agent settings. 

```
  "features": {
    "edit_prediction_provider": "codestral"
  },
  "edit_predictions": {
    "codestral": {
      "model": "codestral-latest",
      "max_tokens": 150
    }
  },
```

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
This commit is contained in:
David
2025-10-08 12:02:21 -06:00
committed by GitHub
co-authored by Michael Sloan
parent 096930817b
commit 5fd187769d
19 changed files with 918 additions and 124 deletions
@@ -1,6 +1,7 @@
use anyhow::Result;
use client::{UserStore, zed_urls};
use cloud_llm_client::UsageLimit;
use codestral::CodestralCompletionProvider;
use copilot::{Copilot, Status};
use editor::{Editor, SelectionEffects, actions::ShowEditPrediction, scroll::Autoscroll};
use feature_flags::{FeatureFlagAppExt, PredictEditsRateCompletionsFeatureFlag};
@@ -234,6 +235,67 @@ impl Render for EditPredictionButton {
)
}
EditPredictionProvider::Codestral => {
let enabled = self.editor_enabled.unwrap_or(true);
let has_api_key = CodestralCompletionProvider::has_api_key(cx);
let fs = self.fs.clone();
let this = cx.entity();
div().child(
PopoverMenu::new("codestral")
.menu(move |window, cx| {
if has_api_key {
Some(this.update(cx, |this, cx| {
this.build_codestral_context_menu(window, cx)
}))
} else {
Some(ContextMenu::build(window, cx, |menu, _, _| {
let fs = fs.clone();
menu.entry("Use Zed AI instead", None, move |_, cx| {
set_completion_provider(
fs.clone(),
cx,
EditPredictionProvider::Zed,
)
})
.separator()
.entry(
"Configure Codestral API Key",
None,
move |window, cx| {
window.dispatch_action(
zed_actions::agent::OpenSettings.boxed_clone(),
cx,
);
},
)
}))
}
})
.anchor(Corner::BottomRight)
.trigger_with_tooltip(
IconButton::new("codestral-icon", IconName::AiMistral)
.shape(IconButtonShape::Square)
.when(!has_api_key, |this| {
this.indicator(Indicator::dot().color(Color::Error))
.indicator_border_color(Some(
cx.theme().colors().status_bar_background,
))
})
.when(has_api_key && !enabled, |this| {
this.indicator(Indicator::dot().color(Color::Ignored))
.indicator_border_color(Some(
cx.theme().colors().status_bar_background,
))
}),
move |window, cx| {
Tooltip::for_action("Codestral", &ToggleMenu, window, cx)
},
)
.with_handle(self.popover_menu_handle.clone()),
)
}
EditPredictionProvider::Zed => {
let enabled = self.editor_enabled.unwrap_or(true);
@@ -493,6 +555,7 @@ impl EditPredictionButton {
EditPredictionProvider::Zed
| EditPredictionProvider::Copilot
| EditPredictionProvider::Supermaven
| EditPredictionProvider::Codestral
) {
menu = menu
.separator()
@@ -719,6 +782,25 @@ impl EditPredictionButton {
})
}
fn build_codestral_context_menu(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ContextMenu> {
let fs = self.fs.clone();
ContextMenu::build(window, cx, |menu, window, cx| {
self.build_language_settings_menu(menu, window, cx)
.separator()
.entry("Use Zed AI instead", None, move |_, cx| {
set_completion_provider(fs.clone(), cx, EditPredictionProvider::Zed)
})
.separator()
.entry("Configure Codestral API Key", None, move |window, cx| {
window.dispatch_action(zed_actions::agent::OpenSettings.boxed_clone(), cx);
})
})
}
fn build_zeta_context_menu(
&self,
window: &mut Window,