From 23a0b6503c600b17d7ee4204c8484f880f8f4a1e Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Thu, 16 Oct 2025 13:03:15 -0300 Subject: [PATCH] zeta2: Include a single unified diff for the edit history (#40400) Instead of producing multiple code blocks for each edit history event, we now produce a continuous unified diff. Release Notes: - N/A --------- Co-authored-by: Oleksiy Syvokon --- Cargo.lock | 1 + crates/cloud_llm_client/Cargo.toml | 1 + .../cloud_llm_client/src/predict_edits_v3.rs | 112 ++++++++++++++++++ .../src/cloud_zeta2_prompt.rs | 55 ++------- 4 files changed, 123 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f74e688ee2fe89d73ce6445eac5e960321d5c3f..22c5eeccb71451acf1cb7cbd9313d248dabb7a17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3384,6 +3384,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "indoc", "pretty_assertions", "serde", "serde_json", diff --git a/crates/cloud_llm_client/Cargo.toml b/crates/cloud_llm_client/Cargo.toml index 1ef978f0a7d112f4239215d43b2306631bafa64b..df432e79e2d0d900196c6a9205b36252d969fb06 100644 --- a/crates/cloud_llm_client/Cargo.toml +++ b/crates/cloud_llm_client/Cargo.toml @@ -25,3 +25,4 @@ workspace-hack.workspace = true [dev-dependencies] pretty_assertions.workspace = true +indoc.workspace = true diff --git a/crates/cloud_llm_client/src/predict_edits_v3.rs b/crates/cloud_llm_client/src/predict_edits_v3.rs index be665f94a460c170ce446bd14634a283c3255877..e03541e0f7d66bd54d6fbd918debbdc3d6c8d9e7 100644 --- a/crates/cloud_llm_client/src/predict_edits_v3.rs +++ b/crates/cloud_llm_client/src/predict_edits_v3.rs @@ -1,6 +1,7 @@ use chrono::Duration; use serde::{Deserialize, Serialize}; use std::{ + fmt::Display, ops::{Add, Range, Sub}, path::{Path, PathBuf}, sync::Arc, @@ -91,6 +92,38 @@ pub enum Event { }, } +impl Display for Event { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Event::BufferChange { + path, + old_path, + diff, + predicted, + } => { + let new_path = path.as_deref().unwrap_or(Path::new("untitled")); + let old_path = old_path.as_deref().unwrap_or(new_path); + + if *predicted { + write!( + f, + "// User accepted prediction:\n--- a/{}\n+++ b/{}\n{diff}", + old_path.display(), + new_path.display() + ) + } else { + write!( + f, + "--- a/{}\n+++ b/{}\n{diff}", + old_path.display(), + new_path.display() + ) + } + } + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Signature { pub text: String, @@ -204,3 +237,82 @@ impl Sub for Line { Self(self.0 - rhs.0) } } + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + use pretty_assertions::assert_eq; + + #[test] + fn test_event_display() { + let ev = Event::BufferChange { + path: None, + old_path: None, + diff: "@@ -1,2 +1,2 @@\n-a\n-b\n".into(), + predicted: false, + }; + assert_eq!( + ev.to_string(), + indoc! {" + --- a/untitled + +++ b/untitled + @@ -1,2 +1,2 @@ + -a + -b + "} + ); + + let ev = Event::BufferChange { + path: Some(PathBuf::from("foo/bar.txt")), + old_path: Some(PathBuf::from("foo/bar.txt")), + diff: "@@ -1,2 +1,2 @@\n-a\n-b\n".into(), + predicted: false, + }; + assert_eq!( + ev.to_string(), + indoc! {" + --- a/foo/bar.txt + +++ b/foo/bar.txt + @@ -1,2 +1,2 @@ + -a + -b + "} + ); + + let ev = Event::BufferChange { + path: Some(PathBuf::from("abc.txt")), + old_path: Some(PathBuf::from("123.txt")), + diff: "@@ -1,2 +1,2 @@\n-a\n-b\n".into(), + predicted: false, + }; + assert_eq!( + ev.to_string(), + indoc! {" + --- a/123.txt + +++ b/abc.txt + @@ -1,2 +1,2 @@ + -a + -b + "} + ); + + let ev = Event::BufferChange { + path: Some(PathBuf::from("abc.txt")), + old_path: Some(PathBuf::from("123.txt")), + diff: "@@ -1,2 +1,2 @@\n-a\n-b\n".into(), + predicted: true, + }; + assert_eq!( + ev.to_string(), + indoc! {" + // User accepted prediction: + --- a/123.txt + +++ b/abc.txt + @@ -1,2 +1,2 @@ + -a + -b + "} + ); + } +} diff --git a/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs b/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs index b8a9ef5f9a2e9e48c9f4bf6d4aef1cc168ba50df..284b245acf2305350e6a6a5e7c38dfaa9b16c5d4 100644 --- a/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs +++ b/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs @@ -1,9 +1,7 @@ //! Zeta2 prompt planning and generation code shared with cloud. use anyhow::{Context as _, Result, anyhow}; -use cloud_llm_client::predict_edits_v3::{ - self, Event, Line, Point, PromptFormat, ReferencedDeclaration, -}; +use cloud_llm_client::predict_edits_v3::{self, Line, Point, PromptFormat, ReferencedDeclaration}; use indoc::indoc; use ordered_float::OrderedFloat; use rustc_hash::{FxHashMap, FxHashSet}; @@ -419,7 +417,7 @@ impl<'a> PlannedPrompt<'a> { }; if self.request.events.is_empty() { - prompt.push_str("No edits yet.\n\n"); + prompt.push_str("(No edit history)\n\n"); } else { prompt.push_str( "The following are the latest edits made by the user, from earlier to later.\n\n", @@ -465,50 +463,15 @@ impl<'a> PlannedPrompt<'a> { } fn push_events(output: &mut String, events: &[predict_edits_v3::Event]) { - for event in events { - match event { - Event::BufferChange { - path, - old_path, - diff, - predicted, - } => { - if let Some(old_path) = &old_path - && let Some(new_path) = &path - { - if old_path != new_path { - writeln!( - output, - "User renamed {} to {}\n\n", - old_path.display(), - new_path.display() - ) - .unwrap(); - } - } + if events.is_empty() { + return; + }; - let path = path - .as_ref() - .map_or_else(|| "untitled".to_string(), |path| path.display().to_string()); - - if *predicted { - writeln!( - output, - "User accepted prediction {:?}:\n`````diff\n{}\n`````\n", - path, diff - ) - .unwrap(); - } else { - writeln!( - output, - "User edited {:?}:\n`````diff\n{}\n`````\n", - path, diff - ) - .unwrap(); - } - } - } + writeln!(output, "`````diff").unwrap(); + for event in events { + writeln!(output, "{}", event).unwrap(); } + writeln!(output, "`````\n").unwrap(); } fn push_file_snippets(