Improve workflow suggestion steps and debug info (#16309)

Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
This commit is contained in:
Kirill Bulatov 2024-08-15 22:46:19 +03:00 committed by GitHub
parent 6b7664ef4a
commit ff83e5b55a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 311 additions and 157 deletions

View file

@ -1,22 +1,27 @@
Your task is to map a step from the conversation above to suggestions on symbols inside the provided source files. <overview>
Your task is to map a step from a workflow to locations in source code where code needs to be changed to fulfill that step.
Given a workflow containing background context plus a series of <step> tags, you will resolve *one* of these step tags to resolve to one or more locations in the code.
With each location, you will produce a brief, one-line description of the changes to be made.
Guidelines: <guidelines>
- There's no need to describe *what* to do, just *where* to do it. - There's no need to describe *what* to do, just *where* to do it.
- Only reference locations that actually exist (unless you're creating a file).
- If creating a file, assume any subsequent updates are included at the time of creation. - If creating a file, assume any subsequent updates are included at the time of creation.
- Don't create and then update a file. - Don't create and then update a file. Always create new files in shot.
- We'll create it in one shot.
- Prefer updating symbols lower in the syntax tree if possible. - Prefer updating symbols lower in the syntax tree if possible.
- Never include suggestions on a parent symbol and one of its children in the same suggestions block. - Never include suggestions on a parent symbol and one of its children in the same suggestions block.
- Never nest an operation with another operation or include CDATA or other content. All suggestions are leaf nodes. - Never nest an operation with another operation or include CDATA or other content. All suggestions are leaf nodes.
- Include a description attribute for each operation with a brief, one-line description of the change to perform.
- Descriptions are required for all suggestions except delete. - Descriptions are required for all suggestions except delete.
- When generating multiple suggestions, ensure the descriptions are specific to each individual operation. - When generating multiple suggestions, ensure the descriptions are specific to each individual operation.
- Avoid referring to the location in the description. Focus on the change to be made, not the location where it's made. That's implicit with the symbol you provide. - Avoid referring to the location in the description. Focus on the change to be made, not the location where it's made. That's implicit with the symbol you provide.
- Don't generate multiple suggestions at the same location. Instead, combine them together in a single operation with a succinct combined description. - Don't generate multiple suggestions at the same location. Instead, combine them together in a single operation with a succinct combined description.
</guidelines>
</overview>
Example 1: <examples>
<example>
User: <workflow_context>
<message role="user">
```rs src/rectangle.rs ```rs src/rectangle.rs
struct Rectangle { struct Rectangle {
width: f64, width: f64,
@ -30,12 +35,21 @@ impl Rectangle {
} }
``` ```
We need to add methods to calculate the area and perimeter of the rectangle. Can you help with that?
</message>
<message role="assistant">
Sure, I can help with that!
<step>Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct</step> <step>Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct</step>
<step>Implement the 'Display' trait for the Rectangle struct</step> <step>Implement the 'Display' trait for the Rectangle struct</step>
</message>
</workflow_context>
What are the suggestions for the step: <step>Add a new method 'calculate_area' to the Rectangle struct</step> <step_to_resolve>
Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct
</step_to_resolve>
A (wrong): <incorrect_output reason="NEVER append multiple children at the same location.">
{ {
"title": "Add Rectangle methods", "title": "Add Rectangle methods",
"suggestions": [ "suggestions": [
@ -53,10 +67,9 @@ A (wrong):
} }
] ]
} }
</incorrect_output>
This demonstrates what NOT to do. NEVER append multiple children at the same location. <correct_output>
A (corrected):
{ {
"title": "Add Rectangle methods", "title": "Add Rectangle methods",
"suggestions": [ "suggestions": [
@ -68,11 +81,13 @@ A (corrected):
} }
] ]
} }
</correct_output>
User: <step_to_resolve>
What are the suggestions for the step: <step>Implement the 'Display' trait for the Rectangle struct</step> Implement the 'Display' trait for the Rectangle struct
</step_to_resolve>
A: <output>
{ {
"title": "Implement Display for Rectangle", "title": "Implement Display for Rectangle",
"suggestions": [ "suggestions": [
@ -84,10 +99,11 @@ A:
} }
] ]
} }
</output>
Example 2: <example>
<workflow_context>
User: <message role="user">
```rs src/user.rs ```rs src/user.rs
struct User { struct User {
pub name: String, pub name: String,
@ -105,13 +121,19 @@ impl User {
} }
} }
``` ```
</message>
<message role="assistant">
Certainly!
<step>Update the 'print_info' method to use formatted output</step> <step>Update the 'print_info' method to use formatted output</step>
<step>Remove the 'email' field from the User struct</step> <step>Remove the 'email' field from the User struct</step>
</message>
</workflow_context>
What are the suggestions for the step: <step>Update the 'print_info' method to use formatted output</step> <step_to_resolve>
Update the 'print_info' method to use formatted output
</step_to_resolve>
A: <output>
{ {
"title": "Use formatted output", "title": "Use formatted output",
"suggestions": [ "suggestions": [
@ -123,11 +145,13 @@ A:
} }
] ]
} }
</output>
User: <step_to_resolve>
What are the suggestions for the step: <step>Remove the 'email' field from the User struct</step> Remove the 'email' field from the User struct
</step_to_resolve>
A: <output>
{ {
"title": "Remove email field", "title": "Remove email field",
"suggestions": [ "suggestions": [
@ -138,10 +162,12 @@ A:
} }
] ]
} }
</output>
</example>
Example 3: <example>
<workflow_context>
User: <message role="user">
```rs src/vehicle.rs ```rs src/vehicle.rs
struct Vehicle { struct Vehicle {
make: String, make: String,
@ -159,13 +185,18 @@ impl Vehicle {
} }
} }
``` ```
</message>
<message role="assistant">
<step>Add a 'use std::fmt;' statement at the beginning of the file</step> <step>Add a 'use std::fmt;' statement at the beginning of the file</step>
<step>Add a new method 'start_engine' in the Vehicle impl block</step> <step>Add a new method 'start_engine' in the Vehicle impl block</step>
</message>
</workflow_context>
What are the suggestions for the step: <step>Add a 'use std::fmt;' statement at the beginning of the file</step> <step_to_resolve>
Add a 'use std::fmt;' statement at the beginning of the file
</step_to_resolve>
A: <output>
{ {
"title": "Add use std::fmt statement", "title": "Add use std::fmt statement",
"suggestions": [ "suggestions": [
@ -176,11 +207,13 @@ A:
} }
] ]
} }
</output>
User: <step_to_resolve>
What are the suggestions for the step: <step>Add a new method 'start_engine' in the Vehicle impl block</step> Add a new method 'start_engine' in the Vehicle impl block
</step_to_resolve>
A: <output>
{ {
"title": "Add start_engine method", "title": "Add start_engine method",
"suggestions": [ "suggestions": [
@ -192,10 +225,12 @@ A:
} }
] ]
} }
</output>
</example>
Example 4: <example>
<workflow_context>
User: <message role="user">
```rs src/employee.rs ```rs src/employee.rs
struct Employee { struct Employee {
name: String, name: String,
@ -219,12 +254,18 @@ impl Employee {
} }
} }
``` ```
</message>
<message role="assistant">
<step>Make salary an f32</step> <step>Make salary an f32</step>
<step>Remove the 'department' field and update the 'print_details' method</step>
</message>
</workflow_context>
What are the suggestions for the step: <step>Make salary an f32</step> <step_to_resolve>
Make salary an f32
</step_to_resolve>
A (wrong): <incorrect_output reason="NEVER include suggestions on a parent symbol and one of its children in the same suggestions block.">
{ {
"title": "Change salary to f32", "title": "Change salary to f32",
"suggestions": [ "suggestions": [
@ -242,10 +283,9 @@ A (wrong):
} }
] ]
} }
</incorrect_output>
This example demonstrates what not to do. `struct Employee salary` is a child of `struct Employee`. <correct_output>
A (corrected):
{ {
"title": "Change salary to f32", "title": "Change salary to f32",
"suggestions": [ "suggestions": [
@ -257,11 +297,13 @@ A (corrected):
} }
] ]
} }
</correct_output>
User: <step_to_resolve>
What are the correct suggestions for the step: <step>Remove the 'department' field and update the 'print_details' method</step> Remove the 'department' field and update the 'print_details' method
</step_to_resolve>
A: <output>
{ {
"title": "Remove department", "title": "Remove department",
"suggestions": [ "suggestions": [
@ -278,10 +320,12 @@ A:
} }
] ]
} }
</output>
</example>
Example 5: <example>
<workflow_context>
User: <message role="user">
```rs src/game.rs ```rs src/game.rs
struct Player { struct Player {
name: String, name: String,
@ -305,10 +349,17 @@ impl Game {
} }
} }
``` ```
</message>
<message role="assistant">
<step>Add a 'level' field to Player and update the 'new' method</step> <step>Add a 'level' field to Player and update the 'new' method</step>
</message>
</workflow_context>
A: <step_to_resolve>
Add a 'level' field to Player and update the 'new' method
</step_to_resolve>
<output>
{ {
"title": "Add level field to Player", "title": "Add level field to Player",
"suggestions": [ "suggestions": [
@ -326,10 +377,12 @@ A:
} }
] ]
} }
</output>
</example>
Example 6: <example>
<workflow_context>
User: <message role="user">
```rs src/config.rs ```rs src/config.rs
use std::collections::HashMap; use std::collections::HashMap;
@ -343,10 +396,17 @@ impl Config {
} }
} }
``` ```
</message>
<message role="assistant">
<step>Add a 'load_from_file' method to Config and import necessary modules</step> <step>Add a 'load_from_file' method to Config and import necessary modules</step>
</message>
</workflow_context>
A: <step_to_resolve>
Add a 'load_from_file' method to Config and import necessary modules
</step_to_resolve>
<output>
{ {
"title": "Add load_from_file method", "title": "Add load_from_file method",
"suggestions": [ "suggestions": [
@ -363,10 +423,12 @@ A:
} }
] ]
} }
</output>
</example>
Example 7: <example>
<workflow_context>
User: <message role="user">
```rs src/database.rs ```rs src/database.rs
pub(crate) struct Database { pub(crate) struct Database {
connection: Connection, connection: Connection,
@ -383,10 +445,17 @@ impl Database {
} }
} }
``` ```
</message>
<message role="assistant">
<step>Add error handling to the 'query' method and create a custom error type</step> <step>Add error handling to the 'query' method and create a custom error type</step>
</message>
</workflow_context>
A: <step_to_resolve>
Add error handling to the 'query' method and create a custom error type
</step_to_resolve>
<output>
{ {
"title": "Add error handling to query", "title": "Add error handling to query",
"suggestions": [ "suggestions": [
@ -409,5 +478,16 @@ A:
} }
] ]
} }
</output>
</example>
</examples>
Now generate the suggestions for the following step: Now generate the suggestions for the following step:
<workflow_context>
{{{workflow_context}}}
</workflow_context>
<step_to_resolve>
{{{step_to_resolve}}}
</step_to_resolve>

View file

@ -2911,7 +2911,7 @@ impl ContextEditor {
let mut assist_ids = Vec::new(); let mut assist_ids = Vec::new();
for (excerpt_id, suggestion_group) in suggestion_groups { for (excerpt_id, suggestion_group) in suggestion_groups {
for suggestion in &suggestion_group.suggestions { for suggestion in &suggestion_group.suggestions {
assist_ids.extend(suggestion.show( assist_ids.extend(suggestion.kind.show(
&editor, &editor,
excerpt_id, excerpt_id,
workspace, workspace,

View file

@ -2974,12 +2974,12 @@ mod tests {
model model
.as_fake() .as_fake()
.respond_to_last_tool_use(tool::WorkflowStepResolution { .respond_to_last_tool_use(tool::WorkflowStepResolutionTool {
step_title: "Title".into(), step_title: "Title".into(),
suggestions: vec![tool::WorkflowSuggestion { suggestions: vec![tool::WorkflowSuggestionTool {
path: "/root/hello.rs".into(), path: "/root/hello.rs".into(),
// Simulate a symbol name that's slightly different than our outline query // Simulate a symbol name that's slightly different than our outline query
kind: tool::WorkflowSuggestionKind::Update { kind: tool::WorkflowSuggestionToolKind::Update {
symbol: "fn main()".into(), symbol: "fn main()".into(),
description: "Extract a greeting function".into(), description: "Extract a greeting function".into(),
}, },

View file

@ -11,7 +11,7 @@ use ui::{
div, h_flex, px, Color, Element as _, ParentElement as _, Styled, ViewContext, WindowContext, div, h_flex, px, Color, Element as _, ParentElement as _, Styled, ViewContext, WindowContext,
}; };
use crate::{Context, ResolvedWorkflowStep, WorkflowSuggestion}; use crate::{Context, ResolvedWorkflowStep, WorkflowSuggestion, WorkflowSuggestionKind};
type StepRange = Range<language::Anchor>; type StepRange = Range<language::Anchor>;
@ -68,7 +68,7 @@ impl ContextInspector {
.and_then(|file| file.path().to_str()) .and_then(|file| file.path().to_str())
.unwrap_or("untitled"); .unwrap_or("untitled");
let snapshot = buffer.text_snapshot(); let snapshot = buffer.text_snapshot();
writeln!(output, " {buffer_path}:").ok()?; writeln!(output, "Path: {buffer_path}:").ok()?;
for group in suggestion_groups { for group in suggestion_groups {
for suggestion in &group.suggestions { for suggestion in &group.suggestions {
pretty_print_workflow_suggestion(&mut output, suggestion, &snapshot); pretty_print_workflow_suggestion(&mut output, suggestion, &snapshot);
@ -163,7 +163,7 @@ impl ContextInspector {
} }
fn pretty_print_anchor( fn pretty_print_anchor(
out: &mut String, out: &mut String,
anchor: &language::Anchor, anchor: language::Anchor,
snapshot: &text::BufferSnapshot, snapshot: &text::BufferSnapshot,
) { ) {
use std::fmt::Write; use std::fmt::Write;
@ -177,9 +177,9 @@ fn pretty_print_range(
) { ) {
use std::fmt::Write; use std::fmt::Write;
write!(out, " Range: ").ok(); write!(out, " Range: ").ok();
pretty_print_anchor(out, &range.start, snapshot); pretty_print_anchor(out, range.start, snapshot);
write!(out, "..").ok(); write!(out, "..").ok();
pretty_print_anchor(out, &range.end, snapshot); pretty_print_anchor(out, range.end, snapshot);
} }
fn pretty_print_workflow_suggestion( fn pretty_print_workflow_suggestion(
@ -188,37 +188,46 @@ fn pretty_print_workflow_suggestion(
snapshot: &text::BufferSnapshot, snapshot: &text::BufferSnapshot,
) { ) {
use std::fmt::Write; use std::fmt::Write;
let (range, description, position) = match suggestion { let (position, description, range) = match &suggestion.kind {
WorkflowSuggestion::Update { range, description } => (Some(range), Some(description), None), WorkflowSuggestionKind::Update { range, description } => {
WorkflowSuggestion::CreateFile { description } => (None, Some(description), None), (None, Some(description), Some(range))
WorkflowSuggestion::AppendChild {
position,
description,
} }
| WorkflowSuggestion::InsertSiblingBefore { WorkflowSuggestionKind::CreateFile { description } => (None, Some(description), None),
WorkflowSuggestionKind::AppendChild {
position, position,
description, description,
} } => (Some(position), Some(description), None),
| WorkflowSuggestion::InsertSiblingAfter { WorkflowSuggestionKind::InsertSiblingBefore {
position, position,
description, description,
} } => (Some(position), Some(description), None),
| WorkflowSuggestion::PrependChild { WorkflowSuggestionKind::InsertSiblingAfter {
position, position,
description, description,
} => (None, Some(description), Some(position)), } => (Some(position), Some(description), None),
WorkflowSuggestionKind::PrependChild {
WorkflowSuggestion::Delete { range } => (Some(range), None, None), position,
description,
} => (Some(position), Some(description), None),
WorkflowSuggestionKind::Delete { range } => (None, None, Some(range)),
}; };
writeln!(out, " Tool input: {}", suggestion.tool_input).ok();
writeln!(
out,
" Tool output: {}",
serde_json::to_string_pretty(&suggestion.tool_output)
.expect("Should not fail on valid struct serialization")
)
.ok();
if let Some(description) = description { if let Some(description) = description {
writeln!(out, " Description: {description}").ok(); writeln!(out, " Description: {description}").ok();
} }
if let Some(range) = range { if let Some(range) = range {
pretty_print_range(out, range, snapshot); pretty_print_range(out, &range, snapshot);
} }
if let Some(position) = position { if let Some(position) = position {
write!(out, " Position: ").ok(); write!(out, " Position: ").ok();
pretty_print_anchor(out, position, snapshot); pretty_print_anchor(out, *position, snapshot);
write!(out, "\n").ok(); write!(out, "\n").ok();
} }
write!(out, "\n").ok(); write!(out, "\n").ok();

View file

@ -31,6 +31,15 @@ pub struct TerminalAssistantPromptContext {
pub user_prompt: String, pub user_prompt: String,
} }
/// Context required to generate a workflow step resolution prompt.
#[derive(Debug, Serialize)]
pub struct StepResolutionContext {
/// The full context, including <step>...</step> tags
pub workflow_context: String,
/// The text of the specific step from the context to resolve
pub step_to_resolve: String,
}
pub struct PromptBuilder { pub struct PromptBuilder {
handlebars: Arc<Mutex<Handlebars<'static>>>, handlebars: Arc<Mutex<Handlebars<'static>>>,
} }
@ -278,7 +287,10 @@ impl PromptBuilder {
self.handlebars.lock().render("edit_workflow", &()) self.handlebars.lock().render("edit_workflow", &())
} }
pub fn generate_step_resolution_prompt(&self) -> Result<String, RenderError> { pub fn generate_step_resolution_prompt(
self.handlebars.lock().render("step_resolution", &()) &self,
context: &StepResolutionContext,
) -> Result<String, RenderError> {
self.handlebars.lock().render("step_resolution", context)
} }
} }

View file

@ -1,4 +1,6 @@
use crate::{AssistantPanel, Context, InlineAssistId, InlineAssistant}; use crate::{
prompts::StepResolutionContext, AssistantPanel, Context, InlineAssistId, InlineAssistant,
};
use anyhow::{anyhow, Error, Result}; use anyhow::{anyhow, Error, Result};
use collections::HashMap; use collections::HashMap;
use editor::Editor; use editor::Editor;
@ -10,7 +12,7 @@ use project::Project;
use rope::Point; use rope::Point;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smol::stream::StreamExt; use smol::stream::StreamExt;
use std::{cmp, ops::Range, sync::Arc}; use std::{cmp, fmt::Write, ops::Range, sync::Arc};
use text::{AnchorRangeExt as _, OffsetRangeExt as _}; use text::{AnchorRangeExt as _, OffsetRangeExt as _};
use util::ResultExt as _; use util::ResultExt as _;
use workspace::Workspace; use workspace::Workspace;
@ -34,7 +36,36 @@ pub struct WorkflowSuggestionGroup {
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum WorkflowSuggestion { pub struct WorkflowSuggestion {
pub kind: WorkflowSuggestionKind,
pub tool_input: String,
pub tool_output: tool::WorkflowSuggestionTool,
}
impl WorkflowSuggestion {
pub fn range(&self) -> Range<Anchor> {
self.kind.range()
}
pub fn show(
&self,
editor: &View<Editor>,
excerpt_id: editor::ExcerptId,
workspace: &WeakView<Workspace>,
assistant_panel: &View<AssistantPanel>,
cx: &mut ui::ViewContext<crate::assistant_panel::ContextEditor>,
) -> Option<InlineAssistId> {
self.kind
.show(editor, excerpt_id, workspace, assistant_panel, cx)
}
fn try_merge(&mut self, other: &mut WorkflowSuggestion, snapshot: &BufferSnapshot) -> bool {
self.kind.try_merge(&other.kind, snapshot)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum WorkflowSuggestionKind {
Update { Update {
range: Range<language::Anchor>, range: Range<language::Anchor>,
description: String, description: String,
@ -87,6 +118,15 @@ impl WorkflowStepResolution {
.text_for_range(self.tagged_range.clone()) .text_for_range(self.tagged_range.clone())
.collect::<String>(); .collect::<String>();
let mut workflow_context = String::new();
for message in context.messages(cx) {
write!(&mut workflow_context, "<message role={}>", message.role).unwrap();
for chunk in context_buffer.read(cx).text_for_range(message.offset_range) {
write!(&mut workflow_context, "{chunk}").unwrap();
}
write!(&mut workflow_context, "</message>").unwrap();
}
Some(cx.spawn(|this, mut cx| async move { Some(cx.spawn(|this, mut cx| async move {
let result = async { let result = async {
let Some(model) = model else { let Some(model) = model else {
@ -99,7 +139,12 @@ impl WorkflowStepResolution {
cx.notify(); cx.notify();
})?; })?;
let mut prompt = prompt_builder.generate_step_resolution_prompt()?; let resolution_context = StepResolutionContext {
workflow_context,
step_to_resolve: step_text.clone(),
};
let mut prompt =
prompt_builder.generate_step_resolution_prompt(&resolution_context)?;
prompt.push_str(&step_text); prompt.push_str(&step_text);
request.messages.push(LanguageModelRequestMessage { request.messages.push(LanguageModelRequestMessage {
role: Role::User, role: Role::User,
@ -108,7 +153,7 @@ impl WorkflowStepResolution {
// Invoke the model to get its edit suggestions for this workflow step. // Invoke the model to get its edit suggestions for this workflow step.
let mut stream = model let mut stream = model
.use_tool_stream::<tool::WorkflowStepResolution>(request, &cx) .use_tool_stream::<tool::WorkflowStepResolutionTool>(request, &cx)
.await?; .await?;
while let Some(chunk) = stream.next().await { while let Some(chunk) = stream.next().await {
let chunk = chunk?; let chunk = chunk?;
@ -119,14 +164,16 @@ impl WorkflowStepResolution {
} }
let resolution = this.update(&mut cx, |this, _| { let resolution = this.update(&mut cx, |this, _| {
serde_json::from_str::<tool::WorkflowStepResolution>(&this.output) serde_json::from_str::<tool::WorkflowStepResolutionTool>(&this.output)
})??; })??;
// Translate the parsed suggestions to our internal types, which anchor the suggestions to locations in the code. // Translate the parsed suggestions to our internal types, which anchor the suggestions to locations in the code.
let suggestion_tasks: Vec<_> = resolution let suggestion_tasks: Vec<_> = resolution
.suggestions .suggestions
.iter() .iter()
.map(|suggestion| suggestion.resolve(project.clone(), cx.clone())) .map(|suggestion| {
suggestion.resolve(step_text.clone(), project.clone(), cx.clone())
})
.collect(); .collect();
// Expand the context ranges of each suggestion and group suggestions with overlapping context ranges. // Expand the context ranges of each suggestion and group suggestions with overlapping context ranges.
@ -152,7 +199,7 @@ impl WorkflowStepResolution {
suggestions.sort_by(|a, b| a.range().cmp(&b.range(), &snapshot)); suggestions.sort_by(|a, b| a.range().cmp(&b.range(), &snapshot));
// Merge overlapping suggestions // Merge overlapping suggestions
suggestions.dedup_by(|a, b| b.try_merge(&a, &snapshot)); suggestions.dedup_by(|a, b| b.try_merge(a, &snapshot));
// Create context ranges for each suggestion // Create context ranges for each suggestion
for suggestion in suggestions { for suggestion in suggestions {
@ -214,40 +261,40 @@ impl WorkflowStepResolution {
} }
} }
impl WorkflowSuggestion { impl WorkflowSuggestionKind {
pub fn range(&self) -> Range<language::Anchor> { pub fn range(&self) -> Range<language::Anchor> {
match self { match self {
WorkflowSuggestion::Update { range, .. } => range.clone(), Self::Update { range, .. } => range.clone(),
WorkflowSuggestion::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX, Self::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
WorkflowSuggestion::InsertSiblingBefore { position, .. } Self::InsertSiblingBefore { position, .. }
| WorkflowSuggestion::InsertSiblingAfter { position, .. } | Self::InsertSiblingAfter { position, .. }
| WorkflowSuggestion::PrependChild { position, .. } | Self::PrependChild { position, .. }
| WorkflowSuggestion::AppendChild { position, .. } => *position..*position, | Self::AppendChild { position, .. } => *position..*position,
WorkflowSuggestion::Delete { range } => range.clone(), Self::Delete { range } => range.clone(),
} }
} }
pub fn description(&self) -> Option<&str> { pub fn description(&self) -> Option<&str> {
match self { match self {
WorkflowSuggestion::Update { description, .. } Self::Update { description, .. }
| WorkflowSuggestion::CreateFile { description } | Self::CreateFile { description }
| WorkflowSuggestion::InsertSiblingBefore { description, .. } | Self::InsertSiblingBefore { description, .. }
| WorkflowSuggestion::InsertSiblingAfter { description, .. } | Self::InsertSiblingAfter { description, .. }
| WorkflowSuggestion::PrependChild { description, .. } | Self::PrependChild { description, .. }
| WorkflowSuggestion::AppendChild { description, .. } => Some(description), | Self::AppendChild { description, .. } => Some(description),
WorkflowSuggestion::Delete { .. } => None, Self::Delete { .. } => None,
} }
} }
fn description_mut(&mut self) -> Option<&mut String> { fn description_mut(&mut self) -> Option<&mut String> {
match self { match self {
WorkflowSuggestion::Update { description, .. } Self::Update { description, .. }
| WorkflowSuggestion::CreateFile { description } | Self::CreateFile { description }
| WorkflowSuggestion::InsertSiblingBefore { description, .. } | Self::InsertSiblingBefore { description, .. }
| WorkflowSuggestion::InsertSiblingAfter { description, .. } | Self::InsertSiblingAfter { description, .. }
| WorkflowSuggestion::PrependChild { description, .. } | Self::PrependChild { description, .. }
| WorkflowSuggestion::AppendChild { description, .. } => Some(description), | Self::AppendChild { description, .. } => Some(description),
WorkflowSuggestion::Delete { .. } => None, Self::Delete { .. } => None,
} }
} }
@ -286,16 +333,16 @@ impl WorkflowSuggestion {
let snapshot = buffer.read(cx).snapshot(cx); let snapshot = buffer.read(cx).snapshot(cx);
match self { match self {
WorkflowSuggestion::Update { range, description } => { Self::Update { range, description } => {
initial_prompt = description.clone(); initial_prompt = description.clone();
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)? suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?; ..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
} }
WorkflowSuggestion::CreateFile { description } => { Self::CreateFile { description } => {
initial_prompt = description.clone(); initial_prompt = description.clone();
suggestion_range = editor::Anchor::min()..editor::Anchor::min(); suggestion_range = editor::Anchor::min()..editor::Anchor::min();
} }
WorkflowSuggestion::InsertSiblingBefore { Self::InsertSiblingBefore {
position, position,
description, description,
} => { } => {
@ -311,7 +358,7 @@ impl WorkflowSuggestion {
line_start..line_start line_start..line_start
}); });
} }
WorkflowSuggestion::InsertSiblingAfter { Self::InsertSiblingAfter {
position, position,
description, description,
} => { } => {
@ -327,7 +374,7 @@ impl WorkflowSuggestion {
line_start..line_start line_start..line_start
}); });
} }
WorkflowSuggestion::PrependChild { Self::PrependChild {
position, position,
description, description,
} => { } => {
@ -343,7 +390,7 @@ impl WorkflowSuggestion {
line_start..line_start line_start..line_start
}); });
} }
WorkflowSuggestion::AppendChild { Self::AppendChild {
position, position,
description, description,
} => { } => {
@ -359,7 +406,7 @@ impl WorkflowSuggestion {
line_start..line_start line_start..line_start
}); });
} }
WorkflowSuggestion::Delete { range } => { Self::Delete { range } => {
initial_prompt = "Delete".to_string(); initial_prompt = "Delete".to_string();
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)? suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?; ..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
@ -392,15 +439,15 @@ pub mod tool {
use schemars::JsonSchema; use schemars::JsonSchema;
#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct WorkflowStepResolution { pub struct WorkflowStepResolutionTool {
/// An extremely short title for the edit step represented by these operations. /// An extremely short title for the edit step represented by these operations.
pub step_title: String, pub step_title: String,
/// A sequence of operations to apply to the codebase. /// A sequence of operations to apply to the codebase.
/// When multiple operations are required for a step, be sure to include multiple operations in this list. /// When multiple operations are required for a step, be sure to include multiple operations in this list.
pub suggestions: Vec<WorkflowSuggestion>, pub suggestions: Vec<WorkflowSuggestionTool>,
} }
impl LanguageModelTool for WorkflowStepResolution { impl LanguageModelTool for WorkflowStepResolutionTool {
fn name() -> String { fn name() -> String {
"edit".into() "edit".into()
} }
@ -429,16 +476,17 @@ pub mod tool {
/// programmatic changes to source code. It provides a structured way to describe /// programmatic changes to source code. It provides a structured way to describe
/// edits for features like refactoring tools or AI-assisted coding suggestions. /// edits for features like refactoring tools or AI-assisted coding suggestions.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct WorkflowSuggestion { pub struct WorkflowSuggestionTool {
/// The path to the file containing the relevant operation /// The path to the file containing the relevant operation
pub path: String, pub path: String,
#[serde(flatten)] #[serde(flatten)]
pub kind: WorkflowSuggestionKind, pub kind: WorkflowSuggestionToolKind,
} }
impl WorkflowSuggestion { impl WorkflowSuggestionTool {
pub(super) async fn resolve( pub(super) async fn resolve(
&self, &self,
tool_input: String,
project: Model<Project>, project: Model<Project>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<(Model<Buffer>, super::WorkflowSuggestion)> { ) -> Result<(Model<Buffer>, super::WorkflowSuggestion)> {
@ -475,9 +523,8 @@ pub mod tool {
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?; let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
let outline = snapshot.outline(None).context("no outline for buffer")?; let outline = snapshot.outline(None).context("no outline for buffer")?;
let suggestion; let kind = match kind {
match kind { WorkflowSuggestionToolKind::Update {
WorkflowSuggestionKind::Update {
symbol, symbol,
description, description,
} => { } => {
@ -494,12 +541,12 @@ pub mod tool {
snapshot.line_len(symbol.range.end.row), snapshot.line_len(symbol.range.end.row),
); );
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end); let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
suggestion = super::WorkflowSuggestion::Update { range, description }; WorkflowSuggestionKind::Update { range, description }
} }
WorkflowSuggestionKind::Create { description } => { WorkflowSuggestionToolKind::Create { description } => {
suggestion = super::WorkflowSuggestion::CreateFile { description }; WorkflowSuggestionKind::CreateFile { description }
} }
WorkflowSuggestionKind::InsertSiblingBefore { WorkflowSuggestionToolKind::InsertSiblingBefore {
symbol, symbol,
description, description,
} => { } => {
@ -514,12 +561,12 @@ pub mod tool {
annotation_range.start annotation_range.start
}), }),
); );
suggestion = super::WorkflowSuggestion::InsertSiblingBefore { WorkflowSuggestionKind::InsertSiblingBefore {
position, position,
description, description,
}; }
} }
WorkflowSuggestionKind::InsertSiblingAfter { WorkflowSuggestionToolKind::InsertSiblingAfter {
symbol, symbol,
description, description,
} => { } => {
@ -528,12 +575,12 @@ pub mod tool {
.with_context(|| format!("symbol not found: {:?}", symbol))? .with_context(|| format!("symbol not found: {:?}", symbol))?
.to_point(&snapshot); .to_point(&snapshot);
let position = snapshot.anchor_after(symbol.range.end); let position = snapshot.anchor_after(symbol.range.end);
suggestion = super::WorkflowSuggestion::InsertSiblingAfter { WorkflowSuggestionKind::InsertSiblingAfter {
position, position,
description, description,
}; }
} }
WorkflowSuggestionKind::PrependChild { WorkflowSuggestionToolKind::PrependChild {
symbol, symbol,
description, description,
} => { } => {
@ -548,18 +595,18 @@ pub mod tool {
.body_range .body_range
.map_or(symbol.range.start, |body_range| body_range.start), .map_or(symbol.range.start, |body_range| body_range.start),
); );
suggestion = super::WorkflowSuggestion::PrependChild { WorkflowSuggestionKind::PrependChild {
position, position,
description, description,
}; }
} else { } else {
suggestion = super::WorkflowSuggestion::PrependChild { WorkflowSuggestionKind::PrependChild {
position: language::Anchor::MIN, position: language::Anchor::MIN,
description, description,
}; }
} }
} }
WorkflowSuggestionKind::AppendChild { WorkflowSuggestionToolKind::AppendChild {
symbol, symbol,
description, description,
} => { } => {
@ -574,18 +621,18 @@ pub mod tool {
.body_range .body_range
.map_or(symbol.range.end, |body_range| body_range.end), .map_or(symbol.range.end, |body_range| body_range.end),
); );
suggestion = super::WorkflowSuggestion::AppendChild { WorkflowSuggestionKind::AppendChild {
position, position,
description, description,
}; }
} else { } else {
suggestion = super::WorkflowSuggestion::PrependChild { WorkflowSuggestionKind::PrependChild {
position: language::Anchor::MAX, position: language::Anchor::MAX,
description, description,
}; }
} }
} }
WorkflowSuggestionKind::Delete { symbol } => { WorkflowSuggestionToolKind::Delete { symbol } => {
let symbol = outline let symbol = outline
.find_most_similar(&symbol) .find_most_similar(&symbol)
.with_context(|| format!("symbol not found: {:?}", symbol))? .with_context(|| format!("symbol not found: {:?}", symbol))?
@ -599,9 +646,15 @@ pub mod tool {
snapshot.line_len(symbol.range.end.row), snapshot.line_len(symbol.range.end.row),
); );
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end); let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
suggestion = super::WorkflowSuggestion::Delete { range }; WorkflowSuggestionKind::Delete { range }
} }
} };
let suggestion = WorkflowSuggestion {
kind,
tool_output: self.clone(),
tool_input,
};
Ok((buffer, suggestion)) Ok((buffer, suggestion))
} }
@ -609,7 +662,7 @@ pub mod tool {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind")] #[serde(tag = "kind")]
pub enum WorkflowSuggestionKind { pub enum WorkflowSuggestionToolKind {
/// Rewrites the specified symbol entirely based on the given description. /// Rewrites the specified symbol entirely based on the given description.
/// This operation completely replaces the existing symbol with new content. /// This operation completely replaces the existing symbol with new content.
Update { Update {