edit_prediction: Report patch apply failures (#60637)

# Objective

- Report V4 edit prediction patch apply failures as cloud rejection
events.
- Closes EP-210

## Solution

- Added `PatchApplyFailed` as an edit prediction rejection reason.
- Converted `prediction_edits_for_single_file_diff` errors into rejected
prediction results so the existing rejection pipeline posts them to
`/predict_edits/reject`.
- Kept interpolation failures reported as `InterpolateFailed`.

## Testing

- Ran `cargo fmt --check && cargo check -p edit_prediction`.

## 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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A
This commit is contained in:
Ben Kunkle 2026-07-09 08:28:59 -04:00 committed by GitHub
parent 3e976e89bc
commit 7828463fcb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 68 additions and 21 deletions

View file

@ -198,6 +198,8 @@ pub enum EditPredictionRejectReason {
InterpolatedEmpty,
/// Edits returned, but could not be interpolated after buffer changes
InterpolateFailed,
/// A patch was returned, but could not be applied to the buffer
PatchApplyFailed,
/// The new prediction was preferred over the current one
Replaced,
/// The current prediction was preferred over the new one

View file

@ -104,6 +104,34 @@ impl EditPredictionResult {
e2e_latency,
}
}
pub fn new_rejected(
id: EditPredictionId,
edited_buffer: &Entity<Buffer>,
edited_buffer_snapshot: &BufferSnapshot,
inputs: EditPredictionInputs,
model_version: Option<String>,
trigger: PredictEditsRequestTrigger,
e2e_latency: std::time::Duration,
reject_reason: EditPredictionRejectReason,
) -> Self {
Self {
prediction: EditPrediction {
id,
edits: Arc::default(),
cursor_position: None,
editable_range: None,
snapshot: edited_buffer_snapshot.clone(),
edit_preview: EditPreview::unchanged(edited_buffer_snapshot),
inputs,
buffer: edited_buffer.clone(),
model_version,
trigger,
},
reject_reason: Some(reject_reason),
e2e_latency,
}
}
}
#[derive(Clone)]

View file

@ -9,7 +9,9 @@ use crate::{
udiff::prediction_edits_for_single_file_diff,
};
use anyhow::{Context as _, Result};
use cloud_llm_client::{AcceptEditPredictionBody, predict_edits_v3::RawCompletionRequest};
use cloud_llm_client::{
AcceptEditPredictionBody, EditPredictionRejectReason, predict_edits_v3::RawCompletionRequest,
};
use edit_prediction_types::PredictedCursorPosition;
use gpui::{App, AppContext as _, Entity, Task, TaskExt, WeakEntity, prelude::*};
use language::{
@ -516,26 +518,41 @@ pub(crate) fn request_prediction_with_zeta(
snapshot: fallback_snapshot,
patch,
} => {
let Some((buffer, snapshot, edits, cursor_position)) =
prediction_edits_for_single_file_diff(&patch, &project, cx).await?
else {
return Ok(Some(
EditPredictionResult::new(
id,
&fallback_buffer,
&fallback_snapshot,
Arc::new([]),
None,
None,
inputs,
model_version,
trigger,
request_duration,
cx,
)
.await,
));
};
let (buffer, snapshot, edits, cursor_position) =
match prediction_edits_for_single_file_diff(&patch, &project, cx).await {
Ok(Some(edits)) => edits,
Ok(None) => {
return Ok(Some(
EditPredictionResult::new(
id,
&fallback_buffer,
&fallback_snapshot,
Arc::new([]),
None,
None,
inputs,
model_version,
trigger,
request_duration,
cx,
)
.await,
));
}
Err(error) => {
log::error!("failed to apply edit prediction patch: {error:?}");
return Ok(Some(EditPredictionResult::new_rejected(
id,
&fallback_buffer,
&fallback_snapshot,
inputs,
model_version,
trigger,
request_duration,
EditPredictionRejectReason::PatchApplyFailed,
)));
}
};
let editable_range_in_buffer =
edits
.iter()